CSE 522S: Studio 2

Linux System Calls


`What does it mean by speak, friend, and enter?' asked Merry.

'That is plain enough,' said Gimli. `If you are a friend, speak the password, and the doors will open, and you can enter.'

The Fellowship of the Ring, Book 2, Chapter 4

System calls are the fundamental, most stable interface that is provided by the operating system. They are how user programs request the vast majority of kernel actions: creating, reading, and destroying files; allocating and freeing dynamic memory; executing new programs, etc.

In this studio, you will:

  1. Make system calls with the libc wrapper
  2. Make system calls with the native Linux interface
  3. Write your own system call

Please complete the required exercises below, as well as any optional enrichment exercises that you wish to complete.

As you work through these exercises, please record your answers, and when finished email your results to dferry@email.wustl.edu with the phrase System Calls Studio in the subject line.

Make sure that the name of each person who worked on these exercises is listed in the first answer, and make sure you number each of your responses so it is easy to match your responses with each exercise.


Required Exercises

  1. As the answer to the first exercise, list the names of the people who worked together on this studio.

  2. First we're going to make a system call through the libc wrapper. This is the standard way that user programs make calls to the kernel. You can find a complete list of system calls in the manual pages with the command man syscalls.

    Navigate outside of the linux source code directory and make a new directory for your userspace programs, and a new file called lib_call.c. In Linux, all users have an associated user ID number. Write a short program that reads your user ID, then attempts to set it to zero (the root uid), and then reads it again with the functions getuid and setuid. Use the man pages at man getuid and man setuid to understand what header files you need to include and how to call these functions.

    Since the call to setuid may fail, we need to do proper unix style error checking. Store the return value from setuid into a variable, and follow it up with something like:

    if( return_val != 0 ) printf("Error to setuid failed! Reason: %s\n", strerror(errno));

    Again, you can use the man pages to determine which header files to include with the commands man printf, man strerror, and man errno.

    At the end of your program, write out the before and after values of the user ID. As the answer to this question, copy and paste your program output.

  3. Now we're going to modify this program to use the native Linux syscall interface as documented in man syscall (this is a different page than we accessed before, which was man syscalls). Copy your program to a new file called native_call.c and replace the calls to getuid and setuid with calls to syscall. To do so, you will have to determine their ARM architecture specific system call numbers by looking at the linux source files arch/arm/include/uapi/asm/unistd.h

    As the answer to this exercise, copy and paste your program output.

  4. Now we will begin the process of writing two new system calls into the Linux kernel. The first system call will override the protections built into the code for setuid and give any process the root user ID (this is a bad thing in general, but it is illustrative). You will write the second system call, which will print a message into the kernel log.

    Note:When making changes to the linux source code, add the comment //AOS before each section. This, along with generating file diffs is how we will keep track of the changes made to the kernel.

    There are five discrete tasks we need to accomplish to do so:

    1. Declare a C function prototype inside the kernel
    2. Write a C function implementation
    3. Define a new system call number
    4. Update the ARM architecture system call table
    5. Update the total number of syscalls value in the kernel

    First, we declare two new function prototypes that take no arguments at the bottom of include/linux/syscalls.h. Before doing so, make a backup with the command cp syscalls.h syscalls.h.orig

    You can use the prototype for sys_getuid as a template for this. Make sure that you use void in the argument list to indicate no arguments, since in C programming a function declared f(void) is not the same as a function declared f() (which inidicates that the function may take any number of parameters of unknown type). Our new system call is going to do something relatively bad (in operating system terms), so call it sys_badcall. Make a second prototype that accepts one integer parameter and call it what you would like.

    As the answer to this exercise, give the two function prototypes you've made.

  5. Second, create a new file for the implementation under arch/arm/kernel/. The naming convention for a file that only implements a system call is to call the file by the syscall name, so since our prototype is called sys_badcall, you would create the file sys_badcall.c. (Additionally, there are other places we could have put this file, but since we're only adding this call for the ARM architecture, this is the most appropriate place.) Make a second file for your second system call in the same place and with the same naming convention.

    For the implementation of sys_badcall, copy and paste the contents of the file found here. Take a moment to look through this file. Notice that the function declaration isn't a normal declaration, but actually made through the macro SYSCALL_DEFINE0 (the zero comes from that this syscall takes zero arguments, and is defined in include/linux/syscalls.h).

    For the implementation of your second syscall, use sys_badcall.c as a template. You'll need to change the SYSCALL_DEFINE0 macro to reference the name you came up with, as well as the fact that this function accepts an integer parameter. You do this by passing the type and the name of the parameter to the macro as such:

    SYSCALL_DEFINE1( your_name, int, param_name )

    In the body of this syscall, use the kernel function printk to print a message along with the value of the parameter. You can use printk as you would use printf. Be sure to return a proper return value from this function.

    Leave the answer to this exercise blank.

  6. We want to make sure that our two new files are included in the Linux build process. The kernel has a robust build system, so this is easy. Open up the makefile in the same directory as your source code files (which should be arch/arm/kernel/) and add our two new files to the end of the object file list, which starts on the line with obj-y (make sure you do not add your files after a \ character, as this specifies the start of a new line.

    Leave the answer to this exercise blank.

  7. The implementation of our system calls is done, now we need to add them into the kernel's system call interface. We will define two new system call numbers in arch/arm/include/uapi/asm/unistd.h.

    Before modifying this file, make a copy into unistd.h.orig. Then, open the file and write system call numbers 388 and 389 for sys_badcall and your own system call near the bottom of the file. Use the other system calls as a template for how to do so.

    Next we will modify arch/arm/kernel/calls.S. This is actually an ARM assembly language file that defines the system call table. Similar to before, first make a backup of calls.S and then add two new invocations of the CALL macro near the end of the file for sys_badcall and your function.

    Finally, we need to update the symbol that tells the kernel how many system calls that it has. Go to arch/arm/include/asm/unistd.h, make a backup copy, and update the value of __NR_syscalls to 392. This seems strange, but on the ARM architecture the system call table can only be certain sizes. See the enrichment exercises for more info.

    Leave this exercise blank.

  8. At this point the kernel is fully modified and can be recompiled. It's not wise to try and rebuild only part of the kernel, so in the base linux source directory (the one with arch, init, drivers, etc.) you should issue the command make clean to remove previous build products.

    Then, to differentiate your next kernel from your current, we'll modify the kernel LOCALVERSION. Rather than using the menuconfig like last time, it's faster and easier to modify the configuration directly. In the base directory, open the file called .config (the leading period means that this is a hidden file that is not normally displayed) and modify the CONFIG_LOCALVERSION string to reflect the fact that this new kernel implements studio 2.

    You can now build your new kernel following the procedure outlined in studio 1.

  9. Once your new kernel is booted and running (remember you can confirm this with uname -a), you're now ready invoke your new system calls. First, make a copy of lib_call.c and call it badcall_call.c. Then, replace the call to setuid with a native system call to sys_badcall.

    As the answer to this exercise, copy and paste the output from badcall_call.c.

  10. Next, we can test the system call you have written. Write a program that invokes it with the native system call interface, remembering to pass an additional argument. However, this system call writes to the system message log. After executing your program, you can check the system log with the command dmesg.

    As the answer to this exercise, copy and paste the last few lines of the system message log.

Turning in this studio

To submit this studio, include the answers to exercises given above, attach the userspace programs you wrote, as well as diffs of the kernel source code files you modified. To create a diff, use the diff program with the backup versions of the source code you modified.

For example: diff -up unistd.h unistd.h.orig > unistd.h.diff

For this studio, you will submit:

If you don't have an original copy for any of these files, you can find them in the original source code package.


Optional Enrichment Exercises

  1. Look at arch/arm/kernel/entry-common.S lines 99-109 and the end of arch/arm/kernel/calls.S and parse out exactly how large the system call table is. A few questions you should be able to answer if you really understand the process: What does the CALL macro do (defined in entry-common.S)? What are the different roles of the symbols NR_syscall and __NR_syscall? Calculate the value of the symbol syscalls_padding at the end of arch/arm/kernel/calls.S for a few different values of NR_syscalls.

  2. If you want to know more about the machine-level actions that occur during a syscall, you can start with the detailed explanation given in the manual to the native system call interface, man syscall.

    The kernel code that contains the entry point for system calls is found in arch/arm/kernel/entry-common.S.