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.
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.
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.
In this part, you will reduce your kernel build time drastically.
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:
First, backup your .config
to something like .config.<UNI>-from-lts
.
5.10.57-cs4118
.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
.
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.
supermom()
System CallNote: 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.
LKD Chapter 5: you must read this to understand how adding a system call works in general. Unfortunately, some of the steps described in the LKD book have changed since the book was written.
What’s Available to Your Module, What Isn’t: In part 4, you will convert your syscall implementation into a kernel module. In order to minimize the amount of modifications you’ll need to make, you should ensure that your syscall implementation only uses symbols that will also be available in a kernel module.
The following document describes how to add a new system call:
/Documentation/process/adding-syscalls.rst
.
CONFIG
option and a fallback stub for your new system call.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:
If the given pid
is NOT the PID of the calling process’s parent process, it
will return -1 and set errno
to EINVAL
. In addition, it should print Not
Yo Mama
to the kernel log.
man syscalls
for syscall return value conventions.If the given pid
matches the PID of the calling process’s parent:
If uid
is not NULL
, the effective user ID (EUID) of the calling
process’s parent process is written to uid
.
If the effective user of the parent is root, it returns 0.
If the effective user of the parent is not root, it returns -1 and sets
errno
to EACCES
.
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:
Code for other system calls that do similar things. For instance, check out
getppid()
and geteuid()
.
Kernel documentation is located in the Documentation
subdirectory of the
kernel source.
To learn more about user credentials, check out man 7 credentials
.
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()
.
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).
Deliverables:
The supermom()
system call should be implemented in
linux/kernel/supermom.c
.
Any other modifications to kernel source code.
Optional: a test program that makes the syscall from userspace, in
user/test/
.
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
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.
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:
The system call will be activated when you call sudo insmod supermom.ko
.
When it is activated, user programs should be able to make the syscall as
usual.
The system call will be deactivated when you call sudo rmmod supermom
. When
the supermom()
syscall is not activated, (before you call insmod
or after
you call rmmod
), it will return -1 and set errno
to ENOSYS
, indicating
that the function is not implemented.
Big hint: use function pointers.
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 errno
s are set and checked for).
Deliverables:
The supermom()
system call should be stubbed out in
linux/kernel/supermom.c
, as described above.
The functionality of the supermom()
system call should be implemented in
user/module/supermom/supermom.c
.
Any other modifications to kernel source code.
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
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.
/Documentation/filesystems/debugfs.rst
).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.
Deliverables:
The supermom()
system call should be stubbed out in
linux/kernel/supermom.c
, as described above.
The functionality of the supermom()
system call with the debugfs additions
should be implemented in user/module/supermom/supermom.c
.
Any other modifications to kernel source code.
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