supermom

HW4: Linux System Calls

Submission

As with previous assignments, we will be using GitHub to distribute skeleton code and collect submissions. Please refer to our Git Workflow guide for a more details. Note that we will be using multiple tags for this assignment, for each deliverable part.

For students on ARM Mac computers (e.g. with M1 chip): if you want your submission to be built/tested for ARM, you must create and submit a file called .armpls in the top-level directory of your repo; feel free to use the following one-liner:

cd "$(git rev-parse --show-toplevel)" && touch .armpls && git add .armpls && git commit -m "ARM pls"

You should do this first so that this file is present in all parts.

Code Style

There is a script in the skeleton code named run_checkpatch.sh. It is a wrapper over linux/scripts/checkpatch.pl, which is a Perl script that comes with the linux kernel that checks if your code conforms to the kernel coding style.

Execute run_checkpatch.sh to see if your code conforms to the kernel style – it’ll let you know what changes you should make. We recommend you make those changes.

Passing run_checkpatch.sh with no warnings and no errors is NOT required for this assignment, but will be for the next one. We recommend you get familiar with this workflow now: run the script and fix what it suggests before pushing a tag.

Part 1: Building a Kernel in Debian Linux

Reading

Task

Kernel Compilation in Debian Linux

First, follow the above guide and compile yourself a pristine (unmodified) kernel from the 5.10.57 Linux source provided in the skeleton repo. You should name it 5.10.57-cs4118, and keep it around as your fallback kernel for all future assignments (including this one), in case you run into any trouble booting into the kernel you’re working on.

Additionally, make sure that the CONFIG_BLK_DEV_LOOP option is set to y in your .config file before you build and install your pristine kernel. This will come in handy in later assignments.

Submission

Part 2: Reducing Kernel Build Time (optional)

In this part, you will reduce your kernel build time drastically.

Tasks

localmodconfig

A large amount of time is spent compiling and installing kernel modules you never use. You can regenerate .config so that it contains only those modules you are currently using. This will drastically cut down the number of modules. This is how:

  1. First, backup your .config to something like .config.<UNI>-from-lts.

    • Make sure to keep your local version the same as what it was in part 1; that is, your kernel should still be named 5.10.57-cs4118.
  2. Run make localmodconfig in your Linux kernel source tree.

    • This will take your current .config and turn off all modules that you are not using.

    • It will ask you a few questions. You can hit ENTER to accept the defaults, or just have yes do so for you:

        $ yes '' | make localmodconfig
      
    • Make sure that CONFIG_BLK_DEV_LOOP is still set to y before building and installing this kernel.

Now you have a much smaller .config. You can follow the rest of the steps starting from make.

Don’t Reinstall Modules

When you are hacking kernel code, you’ll often make simple changes to only a handful of .c files. If you didn’t touch any header files, the modules will not be rebuilt when you run make; thus there is no reason to reinstall all modules every time you rebuild your kernel.

Submission

Part 3: The supermom() System Call

Note: You’re about to make changes to the pristine kernel source. This means that what you build from it might not even boot. In order to make sure that you always have the -cs4118 pristine kernel as a fallback to boot into, you should avoid overwriting it by setting the local version in your .config file to something else, like your UNI (for example, -abc1234). Make sure to verify your changes:

$ hw4-<username>/linux/scripts/diffconfig .config.old .config
LOCALVERSION "-cs4118" -> "-abc1234"

You should use your UNI as the local version for all modified kernels you build for this course from now on.

You are now ready to add some system calls to your kernel.

Reading

Task

Implement a new system call, supermom(), which checks if the parent process of the calling process has superuser privileges. This syscall should work on both x86 and arm64 architectures.

At this time of writing, the Linux kernel has about 400 system calls, though new system calls are constantly being added to the Linux kernel. Let’s leave some room and use 500 as the syscall number for supermom().

The system call should have the following interface:

long supermom(pid_t pid, uid_t *uid);

This is what it should do:

LKD Chapter 5 tells you what you need to do to copy values between kernel and user space.

A few things that you might find helpful:

The purpose of this part is to learn how to add a system call, and get a little bit of practice navigating through kernel code and documentation. As such, we are going to ignore a big issue in correctly implementing a system call – race conditions. You don’t have to worry about synchronization in implementing supermom().

Testing

In order to test your syscall, you should write a test program that performs the syscall from userspace. To learn how to make a syscall, read:

man 2 syscall

You may optionally submit your test program under the user/test/ (you will have to create this directory yourself).

Submission

Deliverables:

To submit this part, push the hw4p3handin tag with the following:

$ git tag -a -m "Completed hw4 part3." hw4p3handin
$ git push origin master
$ git push origin hw4p3handin

Part 4: Moving a system call into a kernel module

By now, you must be tired of rebooting your VM every time you make a small change in system call code. In this part, we will move the code for supermom() into a dynamically loadable kernel module. Our goal is to be able to make modifications to the system call code without having to reboot the machine.

Reading

Task

There are two ways to implement a system call using a module. You can try to modify the system call table from the module initialization code. Changes in recent kernels on the mechanics of setting up system calls make this method more cumbersome than it used to be, so we are not going to do this.

Another way is to leave the system call definition in the static kernel code, but have it call another function defined in a module. This is our approach.

Move your implementation of the supermom() syscall to the provided module skeleton code:

Big hint: use function pointers.

Testing

Make sure you’re still able to run your test program you wrote in Part 3 before you load your module (deactivated), after you load your module (activated), and after you unload your module (deactivated again). You should check that errors are gracefully handled (i.e. the appropriate errnos are set and checked for).

Submission

Deliverables:

To submit this part, push the hw4p4handin tag with the following:

$ git tag -a -m "Completed hw4 part4." hw4p4handin
$ git push origin master
$ git push origin hw4p4handin

Part 5: Adding Superlog

Lastly, instead of simply printing to the kernel log buffer, we will add a means to output to an in-memory filesystem, debugfs. Augment your kernel module to include a counter implemented as a debugfs file to count the number of successful calls to supermom(). A call to supermom is successful if it returns 0.

Reading

Task

Add to your supermom kernel module so that when the module is loaded you create a directory called /sys/kernel/debug/superlog. Within this directory there should be a single file called success to count the number of supermoms. The directory should be created when the module is loaded and destroyed when the module is removed.

Submission

Deliverables:

To submit this part, push the hw4p5handin tag with the following:

$ git tag -a -m "Completed hw4 part5." hw4p5handin
$ git push origin master
$ git push origin hw4p5handin

Good luck!


Last updated: 2022-02-03