CMSC 421: Principles of Operating Systems

Project 1: ROT-13 Kernel Module

This project is due on Thursday, April 23, at 11:59:59 PM (Eastern daylight time). You must use the submit to turn in your homework like so: submit cs421_jtang proj1 rot13.c

Your module code must be named rot13.c. It will be compiled against a 3.14 Linux kernel source tree, via the Kbuild supplied below. It must not have any compilation warnings; warnings will result in grading penalties.

In addition, your code must be properly indented and have a file header comment, as described on the coding conventions page. Prior to submission, use the kernel's indentation script to reformat your code, like so:

    /usr/src/linux/scripts/Lindent rot13.c
  

In this project, you will write a Linux kernel module that will "encrypt" strings using the ROT-13 algorithm. Your module will create the character device /dev/rot13. Your module will store anything written to this device from user space, appending the input to an internal buffer. When something reads from the device, your module will return a ROT-13 encoding of the internal buffer, and then flush that buffer.

Part 1: Compiling rot13 Module

To begin, create a directory for your project and download the following three files into that directory via wget:

Kbuild
Read by Linux kernel's build system, defines what is being built.
Makefile
Hooks your module into kernel source tree. Build your module by simply running make. Also included is a clean target to remove all built objects.
rot13.c
Source file containing your ROT-13 kernel module.

Now run make to compile the module. Upon success, you should now have the kernel module rot13.ko. Load that module like so:

    sudo insmod rot13.ko (enter your password as necessary)
  
The module was inserted if the following returns a non-empty string: lsmod | grep rot13.

So far, all this module does is write a message to the kernel's ring buffer. View the module messages like so:

    dmesg | tail
  
The number at the beginning is the time stamp of when the message was written.

When the rot13 module is loaded, the kernel calls its init function (similar to a C program's main() function) where execution begins. The only thing this module's init function does is call pr_info(). The pr_info() function is an easy way to generate messages within kernel code. It accepts a formatting string like printf().

Every time you make a change and recompile rot13.c, don't forget to first unload the module before reinserting it. Execute this command to unload the module:

    sudo rmmod rot13
  
Re-examine the ring buffer to see the message generated during module exit.

Part 2: Creating a Character Device

Creating a custom character device can be daunting. Fortunately, the Linux kernel has the miscellaneous devices subsystem to simplify this task. For this project, the rot13 module will use a miscdevice to control /dev/rot13.

Start off by adding the following stub functions prior to rot13_init():

#include <linux/fs.h>
#include <linux/miscdevice.h>

/**
 * rot13_open() - callback invoked when a process tries to open this
 * character device
 * @inode: inode of character device (ignored)
 * @filp: process's file object that is opening the device (ignored)
 * Return: 0 if open is permitted, negative on error
 */
static int rot13_open(struct inode *inode, struct file *filp)
{
	return -EPERM;
}

/**
 * rot13_release() - callback invoked when a process closes this
 * character device
 * @inode: inode of character device (ignored)
 * @filp: process's file object that is closing the device (ignored)
 * Return: 0 on success, negative on error
 */
static int rot13_release(struct inode *inode, struct file *filp)
{
	return -EPERM;
}

/**
 * rot13_read() - callback invoked when a process reads from this
 * character device
 * @filp: process's file object that is reading this device (ignored)
 * @ubuf: location to write ROT-13 of internal buffer
 * @count: number of bytes requested by process
 * @ppos: file offset (ignored)
 *
 * Write to @ubuf up to @count bytes from internal buffer, performing
 * a ROT-13 during write. If @count is greater than number of bytes
 * actually stored in internal buffer, truncate to internal buffer's
 * size.
 *
 * Regardless of number of bytes requested, clear entire internal
 * buffer afterwards.
 *
 * Return: number of bytes written to @ubuf, or negative on error
 */
static ssize_t rot13_read(struct file *filp, char __user * ubuf, size_t count,
			  loff_t * ppos)
{
	return -EPERM;
}

/**
 * rot13_read() - callback invoked when a process writes to this
 * character device
 * @filp: process's file object that is writing this device (ignored)
 * @ubuf: source buffer of bytes to copy into internal buffer
 * @count: number of bytes in @ubuf
 * @ppos: file offset (ignored)
 *
 * Read from @ubuf up to @count bytes and append to internal
 * buffer. If there is insufficient space to store @count bytes, store
 * as many as possible and ignore the rest.
 *
 * Return: @count, or negative on error
 */
static ssize_t rot13_write(struct file *filp, const char __user * ubuf,
			   size_t count, loff_t * ppos)
{
	return -EPERM;
}

Next, you need to create the miscellaneous device and register these callbacks against the device. Declare a static const struct file_operations with fields pointing to the above four callbacks. Then declare a static struct miscdevice referencing the above struct file_operations. In rot13_init(), call misc_register() to create the character device, and then in rot13_exit() call misc_deregister() to undo the operation.

If all of the above works, you will now have character device /dev/rot13 when the module is loaded:

    $ sudo insmod rot13.ko
    $ ls -l /dev/rot13
    crw-rw-rw- 1 root root 10, 55 Mar 14 12:34 /dev/rot13
    $ cat /dev/rot13
    cat: /dev/rot13: Operation not permitted
  
Because rot13_open() returns -EPERM, trying to open the device is supposed to result in permission denied.

Part 3: Implementing Internal Buffer

At the top of rot13.c, declare a global static array that can hold up to 1024 characters (not including any trailing null characters). Next, implement rot13_open(), rot13_release(), rot13_read(), and rot13_write() as per their comments. Note that in rot13_read() and rot13_write(), the module cannot simply access the buf pointer. Instead, the code must use copy_to_user() and copy_from_user() to safely copy data to and from user space into kernel space.

After implementing the four required callbacks, recompile and reinsert the module. Test it like so:

    $ echo "I <3 CS421!" > /dev/rot13
    $ echo "I am a 31337 programmer." > /dev/rot13
    $ cat /dev/rot13
    V <3 PF421!
    V nz n 31337 cebtenzzre.
  

Part 4: Locking

The final step is to ensure your driver runs thread-safe. In theory, multiple processes could read/write to the driver simultaneously. The easiest way to solve this is to have a single static spinlock. Use that spinlock to lock the entire rot13_read() and rot13_write() functions.

(The above spinlock blog post is out of date, as that SPIN_LOCK_UNLOCKED no longer exists in the 3.14 kernel. It has been replaced by the DEFINE_SPINLOCK macro.)

Other Hints and Notes

Extra Credit

Sorry, there is no extra credit available for this assignment.