CMSC 421: Principles of Operating Systems

Project 1: ROT-X Kernel Module

This project is due on Monday, November 23, at 11:59:59 PM (Eastern standard time). You must use the submit to turn in your homework like so: submit cs421_jtang proj1 rotX.c rotX-test.c

Your module code must be named rotX.c. It will be compiled against a 4.1 Linux kernel source tree, via the Kbuild supplied below. It must not have any compilation warnings; warnings will result in grading penalties. This module 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:

    ~/linux/scripts/Lindent rotX.c
  

In addition, you will write a unit test program, rotX-test.c. This code must also have a file header comment and be properly indented. You will submit this test code along with your module code.

In this project, you will write a Linux kernel module that will "encrypt" data using a Caesar Cipher. The encryption key is the number of letters to shift. (When the encryption key is 13, the algorithm is commonly known as the ROT-13, hence the name for this project.)

Your module will create two miscellaneous devices, /dev/rotX and /dev/rotXctl. The user writes to /dev/rotX the encryption key. Next, the user creates a memory map to /dev/rotX, via mmap(), to store the data to encrypt. Next, the user writes the magic string go to /dev/rotXctl to actually perform the encryption; the resulting ciphertext is fetched from the memory mapping.

Part 1: Compiling rotX Module

All instructions hereforth assume you successfully completed the first homework. If you have not done so, go back and finish the homework before proceeding. You have been warned.

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

http://www.csee.umbc.edu/~jtang/cs421.f15/homework/proj1/Kbuild
Read by Linux kernel's build system, defines what is being built.
http://www.csee.umbc.edu/~jtang/cs421.f15/homework/proj1/Makefile
Builds the kernel module and unit test program, by simply running make. Also included is a clean target to remove all built objects.
http://www.csee.umbc.edu/~jtang/cs421.f15/homework/proj1/rotX.c
Skeleton code for your ROT-X kernel module.
http://www.csee.umbc.edu/~jtang/cs421.f15/homework/proj1/rotX-test.c
Skeleton code for your unit test code.

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

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

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 rotX module is loaded, the kernel calls its init function (similar to a C program's main() function) where execution begins. Currently, all this module's init function does is call pr_info(). The pr_info() function is an easy way to generate logging messages within kernel code. It accepts a format string like printf().

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

    sudo rmmod rotX
  
Now try removing your module. Re-examine the ring buffer to see the message generated during module exit.

Part 2: Creating Character Devices

Creating a custom character device can be daunting, and in this project, you get to create two of them. Fortunately, the Linux kernel has the miscellaneous devices subsystem to simplify this task. For this project, the rotX module will use miscdevice to control /dev/rotX and /dev/rotXctl.

Start off by adding the following stub code prior to rotX_init():

#include <linux/fs.h>
#include <linux/miscdevice.h>
#include <linux/mm.h>
#include <asm/uaccess.h>

static char *rotX_buffer;

/**
 * rotX_read() - callback invoked when a process reads from /dev/rotX
 * @filp: process's file object that is reading from this device (ignored)
 * @ubuf: destination buffer to store key
 * @count: number of bytes in @ubuf
 * @ppos: file offset (in/out parameter)
 *
 * Write to @ubuf the ASCII string representation of the key (via
 * snprintf()), incrementing @ppos by the number of bytes written. If
 * @ppos already points to the end of the string, then this user has
 * reached the end of file.
 *
 * Return: number of bytes written to @ubuf, 0 on end of file, or
 * negative on error
 */
static ssize_t rotX_read(struct file *filp, char __user *ubuf,
			  size_t count, loff_t *ppos)
{
	return -EPERM;
}

/**
 * rotX_write() - callback invoked when a process writes to /dev/rotX
 * @filp: process's file object that is writing to this device (ignored)
 * @ubuf: source buffer of bytes from user
 * @count: number of bytes in @ubuf
 * @ppos: file offset (ignored)
 *
 * Interpreting the string at @ubuf as an ASCII value, convert it to
 * an unsigned value via kstrtouint_from_user. Store the resulting
 * value as the encryption key.
 *
 * Return: @count, or negative on error
 */
static ssize_t rotX_write(struct file *filp, const char __user * ubuf,
			  size_t count, loff_t * ppos)
{
	return -EPERM;
}

/**
 * rotX_mmap() - callback invoked when a process mmap()s to /dev/rotX
 * @filp: process's file object that is writing to this device (ignored)
 * @vma: virtual memory allocation object containing mmap() request
 *
 * Create a mapping from kernel memory (specifically, rotX_buffer)
 * into user space. Code based upon
 * http://bloggar.combitech.se/ldc/2015/01/21/mmap-memory-between-kernel-and-userspace/
 *
 * You do not need to do modify this function.
 *
 * Return: 0 on success, negative on error.
 */
static int rotX_mmap(struct file *filp, struct vm_area_struct *vma)
{
	unsigned long size = (unsigned long)(vma->vm_end - vma->vm_start);
	unsigned long page = vmalloc_to_pfn(rotX_buffer);
	if (size > PAGE_SIZE)
		return -EIO;
	vma->vm_pgoff = 0;
	vma->vm_page_prot = PAGE_SHARED;
	if (remap_pfn_range(vma, vma->vm_start, page, size, vma->vm_page_prot))
		return -EAGAIN;
	return 0;
}

/**
 * rotXctl_write() - callback invoked when a process writes to
 * /dev/rotXctl
 * @filp: process's file object that is writing to this device (ignored)
 * @ubuf: source buffer from user
 * @count: number of bytes in @ubuf
 * @ppos: file offset (ignored)
 *
 * If @count is at least 2 and @ubuf begins with the string "go", then
 * encrypt the data buffer with the current encryption key. Otherwise,
 * consume @ubuf and do nothing; this is not an error.
 *
 * Return: @count, or negative on error
 */
static ssize_t rotXctl_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 two variables of type static const struct file_operations, one for /dev/rotX and the other for /dev/rotXctl. In the former, set its read, write, and mmap fields to the first three stub functions. In the latter, set its write field to the last stub function.

Then declare two variables of type static struct miscdevice. For both struct miscdevice, set minor field to MISC_DYNAMIC_MINOR, and mode field to 0666. For one struct miscdevice, set its name to "rotX" and its fops to the first struct file_operations. In the other one, set its name to "rotXctl" and fops to second struct file_operations.

In rotX_init(), call misc_register() twice to create the character devices. In rotX_exit() call misc_deregister() twice to undo the registrations.

If all of the above works, when the module is loaded, you will now have character devices /dev/rotX and /dev/rotXctl:

    $ sudo insmod rotX.ko
    $ ls -l /dev/rotX*
    crw-rw-rw- 1 root root 10, 56 Oct 25 12:34 /dev/rotX
    crw-rw-rw- 1 root root 10, 55 Oct 25 12:34 /dev/rotXctl
    $ echo '13' > /dev/rotX
    cat: /dev/rotX: Operation not permitted
  
Because rotX_write() returns -EPERM, trying to write to the device is supposed to result in permission denied.

Part 3: Implementing File Operations

Note the global static pointer rotX_buffer. In rotX_init(), call vmalloc() to allocate one PAGE_SIZE for the pointer. Then initialize the buffer to zeroes. In rotX_exit(), call vfree() to deallocate the buffer.

Next, implement the functions rotX_read(), rotX_write(), and rotXctl_write() as per their comments. For these functions, 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 these callbacks, recompile and reinsert the module. Test it like so:

    $ cat /dev/rotX
    0
    $ echo "13" > /dev/rotX
    $ cat /dev/rotX
    13
    $ echo "go" > /dev/rotXctl
  

Part 4: Locking

The final piece to your module is to make it thread-safe. In theory, multiple processes could read/write to the device nodes simultaneously. The easiest way to solve this is to have a single static spinlock. Use that spinlock to lock the entire read and write functions.

(The above spinlock blog post is out of date. The SPIN_LOCK_UNLOCKED macro no longer exists in modern kernels. It has been replaced by the DEFINE_SPINLOCK macro.)

Part 5: Testing and Documentation

Now that you have (in theory) a working module, you must then write your own unit tests. Modify rotX-test.c to open /dev/rotX and /dev/rotXctl. Create a memory mapping to /dev/rotX. This program is to exercise all of the ROT-X functionality as described aboved. This includes a mix of inputs when writing to the device nodes, writing things to the memory map, and verifying that the encryptor works correctly.

The unit tests must have comments that explain what things are being tested. You will be graded based upon the thoroughness of the tests.

Other Hints and Notes

Extra Credit

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