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.
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:
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 | tailThe 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 rotXNow try removing your module. Re-examine the ring buffer to see the message generated during module exit.
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 permittedBecause
rotX_write()
returns -EPERM
,
trying to write to the device is supposed to result in permission
denied.
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
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.)
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.
PAGE_SIZE
amount of characters. This does not mean PAGE_SIZE
- 1 + trailing \0
, but exactly
one PAGE_SIZE
.
Sorry, there is no extra credit available for this assignment.