Wednesday, October 12, 2011

Hijack Linux System Calls: Part II. Miscellaneous Character Drivers

We all know what device drivers are - the hands of the operating system that make it possible for the kernel to handle hardware.  We also know that there are two types of devices  - character and block, depending on the way they handle data transmissions, but what does "miscellaneous" device mean? To put it simple - it means what it means. On one hand, this may be a driver that handles simple hardware, on the other hand, it is the way Linux allows us to create virtual devices, as one of the ways to communicate with kernel modules, which is exactly what we need in order to hijack Linux System Calls. 

In this section, we will create a simple virtual character device, which will be used by a user space process to instruct our kernel module whether it should hijack or restore certain system call. This virtual device will be controlled with ioctl function. For simplicity, I decided not to add read/write handlers to this device as it is not really required for what we are about to do. Although, it is a "nice to have" feature.

Miscellaneous devices are represented with the struct miscdevice which is declared in /include/linux/miscdevice.h as

   struct miscdevice
   {
      int minor;
      const char *name;
      const struct file_operations *fops;
      struct list_head list;
      struct device *parent;
      struct device *this_device;
      const char *nodename;
      mode_t mode;
   };

Quite a big one, ha? However, we only should take care of the first three members of the structure:

minor stands for the minor number of the device. It is preferred to set it to
              MISC_DYNAMIC_MINOR (at least in for our module) unless you need some specific
              number to be used.
name  this is the name of our device as it should appear in the /dev filesystem
struct file_operations is a set of pointers to corresponding implementations of IO
              functions and a pointer to the owner module. 
              This structure is too big to be presented here, but you may find it in 
              /include/linux/fs.h

So, first of all, add another include file to your code (which we've written in previous article) with

   #include <linux/miscdevice.h>

Then, we should add custom handlers for functions and global variables we are interested in, namely - open, release, ioctl and variable in_use.

   /* We will set this variable to 1 in our open handler and erset it
       back to zero in release handler*/
   int in_use = 0;

   /* This function will be invoked each time a user process attempts
       to open our device. You should keep in mind that the prototype
      of this function may change along different kernel versions. */
   static int our_open(struct inode *inode, struct file *file)
   {
      /* We would not like to allow multiple processes to open this device */
      if(in_use)
         return -EBUSY;
      in_use++;
      printk("device has been opened\n");
      return 0;
   }

   /* This function, in turn, will be called when a process closes our device */
   static int our_release(struct inode *inode, struct file *file)
   {
      in_use--;
      printk("device has been closed\n");
      return 0;
   }

   /* This function will handle ioctl calls performed on our device */
   static int our_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
   {
      int retval = 0;
      
      /* We will fill this function in the Part III of this series */

      return retval;
   }

Now it's time to create the struct file_operations and struct miscdevice and populate the relevant fields:

   static const struct file_operations our_fops =\
   {
      .owner = THIS_MODULE,
      .open = &our_open,
      .release = &our_release,
      .unlocked_ioctl = (void*)&our_ioctl,
      .compat_ioctl = (void*)&our_ioctl
   }

A reasonable question would be "Why do we set unlocked_ioctl and compat_ioctl with the same value and where the heck is the regular ioctl?". There is nothing special about this. unlocked_ioctl is used on 64 bit platforms and compat_ioctl in 32 bit or in compatibility mode and it is totally normal to make them point at the same location as long as you handler function does not mess the types up. As to ioctl, it is simply not there any more...

   static struct miscdevice our_device = \
   {
      MISC_DYNAMIC_MINOR,
      "interceptor",
      &our_fops
   };

After all this, we should make a small adjustment to our init_module function by inserting the following code:

   int retval = misc_register(&our_device);

You should also change "return 0;" to "return retval;". The code above tells the system to register a miscellaneous device described by the miscdevice structure with the kernel. In case the minor field is assigned MISC_DYNAMIC_MINOR (our case exactly), kernel fills it with random (from our point of view) number, otherwise, the requested minor number is used.

Our cleanup_module function should have this line added:

   misc_deregister(&our_device);

in order to unregister our device and remove it from the system.

Important note: functions exported by kernel return 0 upon success or negative error code in case of failure.

By now we have a working kernel module which registers a miscellaneous device when loaded and unregisters it when unloaded. Build it now with the make command. Load it with insmod and check the content of the /dev file system. You will see that there is a new device called "interceptor" with number 10 as major number and what ever has been assigned as minor number. You may unload it now. 

If you wish, you may try to open and close the /dev/interceptor device from a user process and check the log with dmesg | tail. You will see the lines "device has been opened" and "device has been closed" respectively. You may also try to open the device from two user processes simultaneously, then you will see that only one process may successfully open it.

In the next section we are going to add some code to our module, which would make it possible to actually patch the sys_call_table and replace original calls with custom wrappers.

Hope this post was helpful. See you at the next one!



8 comments:

  1. linux/fs.h needs to be directly included, because none of the other includes reference it.

    ReplyDelete
  2. Please, read part III - we use "linux/highmem.h" there which includes "linux/fs.h". Don't consider each part as a standalone article.

    But thanks for participation :)

    ReplyDelete
  3. Any special reason for in_use++ and in_use--? As coded, it looks like in_use = 1 and in_use = 0 would also work.

    ReplyDelete
    Replies
    1. There may be more than one process or thread manipulating your module. Using ++/-- promises that stops logging once the last process closes the file.

      Delete
  4. Hey hi, I am getting error as

    sudo make -C /lib/modules/2.6.38/build M=/root/kernel modules
    make: Entering directory `/usr/src/linux-source-2.6.38'

    WARNING: Symbol version dump /usr/src/linux-source-2.6.38/Module.symvers
    is missing; modules will have no dependencies and modversions.

    CC [M] /root/kernel/kern_module.o
    /root/kernel/kern_module.c: In function ‘cleanup_module’:
    /root/kernel/kern_module.c:60: error: implicit declaration of function ‘mics_deregister’
    make[1]: *** [/root/kernel/kern_module.o] Error 1
    make: *** [_module_/root/kernel] Error 2
    make: Leaving directory `/usr/src/linux-source-2.6.38'
    make: *** [all] Error 2


    and I am not able to predict what is problem..

    ReplyDelete
    Replies
    1. Hey,

      first, I guess, your kernel sources are not configured properly. Let me refer you to kernel docs to look for a solution.

      Second - this one is simple. Just check function's name spelling - it should be "misc_" not "mics"

      Delete
  5. Hi Alexey,

    Sorry man, now it works fine after I correct the spelling.

    Great Blog.

    Thank you very much.

    ReplyDelete
  6. got following error ..using ubuntu 12.04,linux:3.8.0-29-generic
    please help me out to understand the prob:
    make -C /lib/modules/3.8.0-29-generic/build M=/home/alt/Desktop/KineticProg modules
    make[1]: Entering directory `/usr/src/linux-headers-3.8.0-29-generic'
    CC [M] /home/alt/Desktop/KineticProg/myDev.o
    /home/alt/Desktop/KineticProg/myDev.c:50:1: error: initializer element is not constant
    /home/alt/Desktop/KineticProg/myDev.c:50:1: error: (near initialization for ‘our_fops.unlocked_ioctl’)
    /home/alt/Desktop/KineticProg/myDev.c:52:1: error: initializer element is not constant
    /home/alt/Desktop/KineticProg/myDev.c:52:1: error: (near initialization for ‘our_fops.compat_ioctl’)
    make[2]: *** [/home/alt/Desktop/KineticProg/myDev.o] Error 1
    make[1]: *** [_module_/home/alt/Desktop/KineticProg] Error 2
    make[1]: Leaving directory `/usr/src/linux-headers-3.8.0-29-generic'
    make: *** [all] Error 2

    ReplyDelete

Note: Only a member of this blog may post a comment.