SafeDrive is a research prototype that detects and recovers memory-safety errors in Linux drivers, with the help of a custom source-to-source compiler called Deputy and a small kernel patch. This file covers the first steps to try the SafeDrive prototype for OSDI '06.
If you have any questions, please contact Feng Zhou
You need the following files to setup SafeDrive.
cd $BASE/deputy patch -p1 < ../ctwifi/ctswifi-deputy.patch # make a link to ctswifi code in deputy dir ln -s ../ctswifi ctswifi
cd $BASE/deputy # configure Deputy with linux kernel support # note that the path after 'with-linux' has to be a full absolute path ./configure --with-linux=$BASE/linux make ( now add $BASE/deputy/bin to your path )
cd $BASE/linux for f in `cat ../safedrive/patches/series`; do patch -p1 < ../safedrive/patches/$f; done
make menuconfig ( now choose the config for your machine turn on "Device Drivers -> Generic Driver Options -> Failure Recovery for Deputy-enabled drivers" (default on) optionally turn on "Kernel hacking -> KGDB: ..." if you want to debug your kernel using KGDB.) makeNow install the new kernel.
cd $BASE/safedrive/e1000 export KERNELDIR=$BASE/linux make
If everything goes well, the compiled module will be e1000_kr.ko. Now with the machine running the newly-compiled kernel, you can use the module by (assuming a Redhat/Fedora Core system),
# Remove existing driver module rmmod e1000 insmod e1000_kr.ko /etc/init.d/network startNow the driver should work normally. There are a couple of ways to trigger failures in the driver so that you can test the recovery funcitonality of SafeDrive. First this driver is already modified such that when you set its debug message level to 1000, it will trigger a fault (by calling kr_trigger_fault()). Thus doing the following to try it,
echo "r 1" > /proc/krecover # turn on safedrive recovery ethtool -s eth0 msglvl 1000 # triggers recovery (now the driver will trigger failure and be unloaded by safedrive) tail /var/log/message # shows log messages related to this recoveryThe current recovery implementation completely unloads the driver after a failure. So you need to re-insmod the driver once a failure happens. Note also that recovery is automatically turned off after a driver fails. So you need to execute echo "r 1" > /proc/krecover again before triggering another failure. cat /proc/krecover will show some stats about safedrive.
The method above do not actually do bad things in the driver. For more realistic experiments (as done in the paper), you can recompile the driver with injected faults, e.g. with certain out-of-bound accesses.
cd $BASE/safedrive/e1000 make clean make FAULT=scanoverrun SEED=1Then you can load the recompiled driver and exercise it to see if any recovery is triggered. It may or may not fail, depending on whether the injected failure actually causes any memory-safety problem. You can see a list of possible failures to inject with deputy --help (in help for --faultscan.
Another way to trigger failures is to specify the driver to fail after a fixed number of Deputy checks,
echo 'r 1' > /proc/krecover # turn on recovery due to Deputy assertion failures echo 'q 10000' > /proc/krecover # trigger fault after 10000 checks cat /proc/krecover # will show how many checks have been counted
The last fault injection method is to fault on a specific code address. This is done through binary modification (using kprobe).
echo 'r 1' > /proc/krecover echo 'f _e1000_set_msglevel 0' > /proc/krecover # fault on entrance to _e1000_set_msglevel() ethtool -s eth0 msglvl 10 # This should trigger the fault
Sometimes kernel headers need to be changed. These are handled by first copying the corresponding header file to $BASE/safedrive/include/... if the file is not already there, and then modifying the header there.
Add update tracking wrappers. Kernel updates that should be undone at recovery time should be tracked. There can often be done by wrapping the kernel API calls in the headers. There are already many updates tracked. If your need more (knowing what is needed requires understanding of how the kernel works!), you can do so by copying over the related header file to $BASE/safedrive/include/.. and do the modification there,
inline static int kr_pci_enable_device(struct pci_dev *dev) { int r = pci_enable_device(dev); if (!r) kr_add_object(KR_PCI_ENABLE_DEVICE, dev, 0); return r; } #undef pci_enable_device #define pci_enable_device(x) kr_pci_enable_device(x)This way, the code calling pci_enable_device() is actually calling kr_pci_enable_device(), which tracks the update when it is successful.
Add entrance wrapper to the driver. Add kr_enter_driver(failure_return_value), kr_exit_driver() to the beginning and end of each interface function of the driver, i.e. functions directly called by the kernel. Admittedly this is tedious. Hopefully we will have a tool to generate these automatically. The template to use is (note the TRUSTEDBLOCK annotation on the wrapper function):
/* original function */ static int _e1000_probe(struct pci_dev *pdev, const struct pci_device_id *ent) { ... } /* entrance wrapper function */ static int e1000_probe(struct pci_dev *pdev, const struct pci_device_id *ent) { TRUSTEDBLOCK int r; kr_enter_driver_rec(-EIO); r = _e1000_probe(pdev, ent); kr_exit_driver(); return r; }