View on GitHub

CheckLT

Taint Checking for Mere Mortals

Download this project as a .zip file Download this project as a tar.gz file
      

What is CheckLT?

CheckLT is a program verification tool for Java which can help you use taint tracking to find defects in your software. CheckLT provides an easy to install verification toolset, a simple, non-invasive syntax for annotating programs, and a dynamically configurable security lattice.

CheckLT was born out of a research project at the University of Central Florida where we are researching creating tools for secure information flow analysis for Android applications. CheckLT does not implement full information flow security, but instead aims at being a lightweight security analysis toolkit for Java.

Though CheckLT relies on Checker Framework for its base functionality (in fact, it is a plugin for the checker framework) CheckLT is being distributed independently of Checker Framework as a part of an experiment by this author to make formal verification tools more easily accessible outside of the research community. You can think of it as a "Click, Run, Verify!" approach to formal methods software!

Install

Before you install CheckLT, make sure you have a recent Java 7 (1.7) installed. You can use either the Oracle JDK or OpenJDK.

To install, download the archive, and run the included jar file by double clicking on it. If for some reason your system does not suport running executable jar files by double clicking, you can install it from a command prompt by running:

# java -jar checklt-installer.jar

The CheckLT installer automatically updates your path (OSX, Windows, and Linux) and after you install CheckLT you can run it from the command prompt by running:

# checklt MyProgram.java

Note that checklt supports all the commands available to a traditional Java compiler. To get a complete list of options, you can run the following command:

# checklt -help

Setting Up Classpaths in Your Environment

Use of the @Level annotations requires that you have the CheckLT jar in your classpath. The jar you need is called checker-lattice-tainting-plugin.jar, and it is located in the lib directory of your CheckLT installation.

Once you have added this jar to your classpath, you can import the necessary classes by adding the following import statement to the header of files that utilize CheckLT.

import checkers.latticetainting.quals.*;

Platform Support

CheckLT is regularly tested against OSX and Linux; lesser so for Windows-based platforms. Any help getting testing on more platforms is greatly needed!

Date Tested/Updated OSX 10.8.3 Ubuntu Linux 13.04 Windows 7 Windows 8
06/06/2013 OK OK N/A OK

Building CheckLT

To build CheckLT, you will need to have the build tool Gradle (version 1.4 or higher) and IzPack installed (version 4 or higher). CheckLT's build scripts assume that the IzPack install directory is on your path. To build, simply use one of the gradle targets or simply:

# gradle

To build the entire distribution. All required dependencies will be fetched at build time. Note that you will need some recent Java 7 compiler (OpenJDK or Oracle) to compile CheckLT.

Setting Up for Development

CheckLT is currently primarily developed on Emacs using eclim. If you wish to use Eclipse (or eclim) for hacking on CheckLT, there is a special Gradle target for generating a Eclipse workspace for the CheckLT plugin.

To do this, execute the following commands from the base of the source distribution:

# cd checker-lattice-tainting-plugin
# gradle -q eclipse

The directory checker-lattice-tainting-plugin will now be an Eclipse project directory which may be imported.

Note that you must make a minor modification to the generated .classpath file.

In your .classpath file, make sure the following line is LAST.

<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>

Poor Man's Information Flow in Two Steps

To configure CheckLT for your application you must do two things. First, you must configure security rules for your particular application. In this process you will configure a security lattice, as we shall see in the next section. Once your security lattice is set up you can then annotate your program to reflect your concerns. The next two sections cover this process in detail.

Configuring The Lattice

The first thing you must do is configure a security lattice for use with CheckLT. This file should be called security.xml, and it should be in the root directory from which you execute CheckLT. Note that CheckLT can (and will) check your proposed lattice for soundness before executing. As of the current version, CheckLT checks to make sure that no insecure flows are specified (via graph cycle checking) and that all specified trusts are reachable and properly declared.

To override the location of the default security.xml file, run CheckLT like this:

# checklt -Alattice=yourxmlfile.xml YourClass.java

The following example represents a slightly more complicated lattice than you will need for most projects. It is presented simply to showcase some of the flexibility of CheckLT.

<linear-lattice>

    <!-- all levels must first be declared -->
    <levels>
        <level>Private</level>
        <level>UserTrusts</level>
        <level>User</level>
        <level>Public</level>
    </levels>


    <!-- 
         'level-specs' is a collection defining the relations
         of the various levels 
    -->
    <level-specs>

        <!-- level spec for Public -->
        <!-- (trusts everyone)     -->
        <level-spec>
            <name>Public</name>

            <trusts>
                <level>Private</level>
                <level>UserTrusts</level>
                <level>User</level>
            </trusts>
        </level-spec>

        <!-- level spec for User -->
        <level-spec>
            <name>User</name>

            <trusts>
                <level>UserTrusts</level>
                <level>Private</level>
            </trusts>
        </level-spec>


        <!-- level spec for UserTrusts -->
        <level-spec>
            <name>UserTrusts</name>

            <trusts>
                <level>Private</level>
            </trusts>
        </level-spec>


        <!-- level spec for UserTrusts -->
        <level-spec>
            <name>Private</name>

            <trusts>
                <!-- trusts no one (other than self) -->
            </trusts>
        </level-spec>

    </level-specs>

</linear-lattice>

The labels in the preceding listing are explained as follows:

  1. Public (⊥): Any information that may freely move through the application and conversely, the least trusted information level. To think about this in terms of types, you can think of it as all other types are a subtype of Public.
  2. User: Information that is private and should only be known to the user.
  3. UserTrusts: Information that is private, but may be shared with special endpoints that the user highly trusts. Endpoints may be specific instance variables, methods, or entire classes.
  4. Private (⊤): The highest level of privacy. Information may not even be known to the user. An example of such a piece of information could be an internal result used for the verification of a serial number. Nothing is a subtype of this type.
Note: The DEFAULT level will always be ⊤. 
      It is determined by finding the element 
      of the lattice with the LEAST outgoing edges.

While more complex lattice structures are possible to represent in CheckLT, as of the current release, CheckLT recognizes a linear lattice at the moment.

Graphically represented, the lattice described above looks like a traditional role hierarchy. As you can see, the reason we call this a linear lattice is that the structure is strongly linear; that is, information may travel upwards in the lattice, but not back down (without declassification).

Label Syntax

Labels in CheckLT are specified using Java Annotations. The BNF of the @Level annotation is given in the following listing.

<level> ::= ‘"’ <string> ‘"’

<level-spec> ::= <level>

<security-annotation> ::= ‘@Level(’ <level-spec> ‘)’

Program statements are annotated by specifying the @Level annotation on them. The argument to the annotation must be a level as defined in your security.xml. In the snippet, below, we define two annotated variables.

@Level("Public")
int publicValue = 36;

@Level("Private")
int privateValue = 1337;

Declassification

Declassification is essentially a controlled release of information. Depending on your application of CheckLT, you may or may not want explicit declassification. For example, if you are using CheckLT to do taint tracking against within SQL queries, you might want to specify that no declassification may be performed. However, in the same scenario you might have some routine that "untaints" a tainted query. In such a case you will need declassification.

Below is an example of explicit declassification.

import checkers.latticetainting.quals.Level;


public class Simple {

    public static void main(String args[]) {

        // This assiangment is from Private -> Public
        // which is not allowed in our lattice

        //@Level("Public")
        //int e = Simple.testVal();

        // To allow such an assignment, we can "declassify" the value
        // like so:

        @Level("Private")
        int secretValue = Simple.testVal();

        @Level("Public")
        int publicValue = declassify(secretValue);

    }

    public static int testVal() {
        @Level("Private")
        int notSecret = 1000;
        return notSecret;
    }

    public static @Level("Public") int declassify(@Level("Private") int privateVal){
        // Specify "safe" here to override the lattice
        @SuppressWarnings("safe")
        @Level("Public")
        int publicVal = privateVal;

        return publicVal;
    }  
}

Note in the above example the need to specify @SuppressWarnings("safe"). This is required so that the flow against the lattice will be allowed.

Example: Keeping SQL Queries Clean

One very common application of Taint Tracking is to indicate which inputs are "Safe" and to indicate which inputs need to be sanitized before being used.

A common example of where this technique is useful is in the problem of accepting input from a user that forms SQL queries. In many applications this problem is mitigated by simply sanitizing all inputs (ie, PreparedStatements), but our example takes the approach of using raw queries and showing that it is indeed safe to do so with the proper constraints.

In the application below, we break our queries up into two categories: Internal and TouchedByUser. Queries that are Internal are considered safe for execution. Queries that are TouchedByUser are inherently untrustworthy and need to be sanitized before use.

The Lattice

In the listing below we give the structure of the lattice for this example. Note that we have two levels: Internal and TouchedByUser.

<linear-lattice>


    <levels>
        <level>Internal</level>
        <level>TouchedByUser</level>
    </levels>

    <level-specs>

        <level-spec>
            <name>Internal</name>

            <trusts>
                <level>TouchedByUser</level>
            </trusts>
        </level-spec>

        <!-- level spec for User -->
        <level-spec>
            <name>TouchedByUser</name>

            <trusts>
              <!-- 
                   this sort of information is "quarantined" and my not 
                   go anywhere
              -->
            </trusts>
        </level-spec>

    </level-specs>

</linear-lattice>

The Annotated Program

With the annotated lattice in hand we may now proceed to our example. In this example we execute two types of queries: internal, trusted queries, and queries that have been influenced by user input.

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.io.*;
import checkers.latticetainting.quals.*;


public class SqlExample {

    public Connection getConnection() throws Exception {
        Class.forName("com.mysql.jdbc.Driver");
        return DriverManager.getConnection("jdbc:mysql://localhost/examp");
    }

    // require that all of the executed queries are "Safe"
    private void executeQuery(@Level("Internal") String q) throws Exception {

        Connection c = getConnection();

        try {
            Statement stmt = getConnection().createStatement();
            ResultSet rs =   stmt.executeQuery(q);
        }finally {
            c.close();
        }
    }

    // internal queries are "Safe" by definition

    public @Level("Internal") String getBaseQuery()
    {
        @Level("Internal")
        String query = "SELECT * from test_table";

        return query;
    }

    // running an internal query is no problem, since the types 
    // @Level("Internal") will match throughout.
    public void runInternalQuery() throws Exception {
        executeQuery(getBaseQuery());
    }

    public void getAndRunQueryFromUser() throws Exception {

        // baseQuery is implicitly @Level("Internal")
        String baseQuery = getBaseQuery();

        // newQuery is now @Level("TouchedByUser");
        String newQuery = baseQuery + getQueryFromUser();


        // This call will fail
        //executeQuery(newQuery);

        // It must be called like:
        executeQuery(SqlExample.sanitizeUserInput(newQuery));
    }



    // Because this has not been explicitly marked with 
    // @Level("Internal"), it is "TouchedByUser" by default
    public String getQueryFromUser(){

      System.out.print("Enter Query Filter [NONE]: ");

      BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

      String query = null;

      try {
          query =  br.readLine();
      } catch (IOException ioe) {
          System.out.println("IO Error?");
          System.exit(1);
      }
      return query;
    }



    public static @Level("Internal") String sanitizeUserInput(
         @Level("TouchedByUser") String dirty
    ){

        //
        // do some cleanup, remove SQL injections, etc
        //

        // Mark this as "clean" input
        @SuppressWarnings("safe")
        @Level("Internal")
        String clean = dirty;

        return clean;
    } 

    public static void main(String args[]) throws Exception {

        System.out.println("====Executing Database Test===");

        SqlExample ex = new SqlExample();

        // first, try to run an internal query.
        ex.runInternalQuery();
        // next, try to execute an internal query that has 
        // been modified by the user
        ex.getAndRunQueryFromUser();
    }

}

Perhaps the most interesting portion of the preceding listing is in the getAndRunQueryFromUser() function. In this function, we see how the value of newQuery is influenced by combining baseQuery and the value returned from getQueryFromUser(). All of this is possible because values that are Internal may lower their security level to TouchedByUser, but not the other way around.

public void getAndRunQueryFromUser() throws Exception {

    // baseQuery is implicitly @Level("Internal")
        String baseQuery = getBaseQuery();

        // newQuery is now @Level("TouchedByUser");
        String newQuery = baseQuery + getQueryFromUser();


        // This call will fail
        //executeQuery(newQuery);

        // It must be called like:
        executeQuery(SqlExample.sanitizeUserInput(newQuery));
}

What's With the Name?

CheckLT is a plugin for the Checker Framework that checks for taint using a lattice. The "LT" in CheckLT is for "Lattice Tainting." It is pronounced to rhyme with "Chicklet" (hence the graphic in the banner).

Acknowledgements and Thanks

CheckLT is a direct byproduct of my work with Gary Leavens at the University of Central Florida and David Naumann the Stevens Insitute of Technology, to whom I am deeply grateful. Without their constant guidance (and correction!) and encouragement CheckLT wouldn't have been possible!

Contact