CPROVER Manual TOC

## SATABS – Predicate Abstraction with SAT

### Tutorials

#### Example: Reference Counting in Linux Device Drivers

Microsoft's SLAM toolkit has been successfully used to find bugs in Windows device drivers. SLAM automatically verifies device driver whether a device driver adheres to a set of specifications. SLAM provides a test harness for device drivers that calls the device driver dispatch routines in a non-deterministic order. Therefore, the Model Checker examines all combinations of calls. Motivated by the success this approach, we provide a toy example based on Linux device drivers. For a more complete approach to the verification of Linux device drivers, consider DDVerify.

Dynamically loadable modules enable the Linux Kernel to load device drivers on demand and to release them when they are not needed anymore. When a device driver is registered, the kernel provides a major number that is used to uniquely identify the device driver. The corresponding device can be accessed through special files in the filesystem; by convention, they are located in the `/dev` directory. If a process accesses a device file the kernel calls the corresponding `open`, `read` and `write` functions of the device driver. Since a driver must not be released by the kernel as long as it is used by at least one process, the device driver must maintain a usage counter (in more recent Linux kernels, this is done automatically, however, drivers that must maintain backward compatibility have to adjust this counter).

We provide a skeleton of such a driver. Download the files spec.c, driver.c, driver.h, kdev_t.h, and modules.h.

The driver contains following functions:

1. `register_chrdev`: (in spec.c) Registers a character device. In our implementation, the function sets the variable `usecount` to zero and returns a major number for this device (a constant, if the user provides 0 as argument for the major number, and the value specified by the user otherwise).

       int usecount;

       int register_chrdev (unsigned int major, const char* name)
       {
         usecount = 0;
         if (major == 0)
           return MAJOR_NUMBER;
         return major;
       }

2. `unregister_chrdev`: (in spec.c) Unregisters a character device. This function asserts that the device is not used by any process anymore (we use the macro `MOD_IN_USE` to check this).

       int unregister_chrdev (unsigned int major, const char* name)
       {
         if (MOD_IN_USE)
           {
           ERROR: assert (0);
           }
         else
           return 0;
       }

3. `dummy_open`: (in driver.c) This function increases the `usecount`. If the device is locked by some other process `dummy_open` returns -1. Otherwise it locks the device for the caller.

4. `dummy_read`: (in driver.c) This function "simulates" a read access to the device. In fact it does nothing, since we are currently not interested in the potential buffer overflow that may result from a call to this function. Note the usage of the function `nondet_int`: This is an internal SATABS-function that nondeterministically returns an arbitrary integer value. The function `__CPROVER_assume` tells SATABS to ignore all traces that do not adhere to the given assumption. Therefore, whenever the lock is held, `dummy_read` will return a value between 0 and `max`. If the lock is not held, then `dummy_read` returns -1.

5. `dummy_release`: (in driver.c) If the lock is held, then `dummy_release` decreases the `usecount`, releases the lock, and returns 0. Otherwise, the function returns -1.

We now want to check if any valid sequence of calls of the dispatch functions (in driver.c) can lead to the violation of the assertion (in spec.c). Obviously, a call to `dummy_open` that is immediately followed by a call to `unregister_chrdev` violates the assertion.

The function `main` in spec.c gives an example of how these functions are called. First, a character device "`dummy`" is registered. The major number is stored in the `inode` structure of the device. The values for the file structure are assigned non-deterministically. We rule out invalid sequences of calls by ensuring that no device is unregistered while it is still locked. We use the following model checking harness for calling the dispatching functions:

          random = nondet_uchar ();
          __CPROVER_assume (0 <= random && random <= 3);

          switch (random)
          {
          case 1:
            rval = dummy_open (&inode, &my_file);
            if (rval == 0)
              lock_held = TRUE;
            break;
          case 2:
            __CPROVER_assume (lock_held);
            count = dummy_read (&my_file, buffer, BUF_SIZE);
            break;
          case 3:
            dummy_release (&inode, &my_file);
            lock_held = FALSE;
            break;
          default:
            break;
          }

The variable `random` is assigned non-deterministically. Subsequently, the value of `random` is restricted to be 0 &le `random` ≤ 3 by a call to `__CPROVER_assume`. Whenever the value of `random` is not in this interval, the corresponding execution trace is simply discarded by SATABS. Depending on the value of `random`, the harness calls either `dummy_open`, `dummy_read` or `dummy_close`. Therefore, if there is a sequence of calls to these three functions that leads to a violation of the assertion in `unregister_chrdev`, then SATABS will eventually consider it.

If we ask SATABS to show us the properties it verifies with

`satabs driver.c spec.c --show-properties`

for our example, we obtain

1. Claim unregister_chrdev.1:
   file spec.c line 18 function unregister_chrdev
   MOD_IN_USE in unregister_chrdev
   FALSE

2. Claim dummy_open.1:
   file driver.c line 15 function dummy_open
   i_rdev mismatch
   (unsigned int)inode->i_rdev >> 8 == (unsigned int)dummy_major

It seems obvious that the property dummy_open.1 can never be violated. SATABS confirms this assumption: We call

`satabs driver.c spec.c --property dummy_open.1`

and SATABS reports `VERIFICATION SUCCESSFUL` after a few iterations.

If we try to verify property unregister_chrdev.1, SATABS reports that the property in line 18 in file spec.c is violated (i.e., the assertion does not hold, therefore the `VERIFICATION FAILED`). Furthermore, SATABS provides a detailed description of the problem in the form of a counterexample (i.e., an execution trace that violates the property). On this trace, `dummy_open` is called twice, leading to a `usecount` of 2. The second call of course fails with `rval=-1`, but the counter is increased nevertheless:

    int dummy_open (struct inode *inode, struct file *filp)
    {
      __CPROVER_assert(MAJOR (inode->i_rdev) == dummy_major,
          "i_rdev mismatch");
      MOD_INC_USE_COUNT;

      if (locked)
        return -1;
      locked = TRUE;

      return 0; /* success */
    }

Then, `dummy_release` is called to release the lock on the device. Finally, the loop is left and the call to `unregister_chrdev` results in a violation of the assertion (since `usecount` is still 1, even though `locked=0`).