Lab: system calls

In the last lab you used system calls to write a few utilities. In this lab you will add a new system call to xv6, which will help you understand how they work and will expose you to some of the internals of the xv6 kernel. You will add more system calls in later labs.

Before you start coding, read Chapter 2 of the xv6 book, and Sections 4.3 and 4.4 of Chapter 4, and related source files:

To start the lab, switch to the syscall branch:

  $ git fetch
  $ git checkout syscall
  $ make clean
  

Using gdb

In many cases, print statements will be sufficient to debug your kernel, but sometimes it is useful to single step through code or get a stack backtrace. The GDB debugger can help.

To help you become familiar with gdb, run make qemu-gdb and then fire up gdb in another window (see the gdb material on the guidance page). Once you have two windows open, type in the gdb window:

(gdb) b syscall
Breakpoint 1 at 0x80002142: file kernel/syscall.c, line 243.
(gdb) c
Continuing.
[Switching to Thread 1.2]

Thread 2 hit Breakpoint 1, syscall () at kernel/syscall.c:243
243     {
(gdb) layout src
(gdb) backtrace

The layout command splits the window in two, showing where gdb is in the source code. backtrace prints a stack backtrace.

Answer the following questions in answers-syscall.txt.

Looking at the backtrace output, which function called syscall?

Type n a few times to step past struct proc *p = myproc(); Once past this statement, type p /x *p, which prints the current process's proc struct (see kernel/proc.h>) in hex.

What is the value of p->trapframe->a7 and what does that value represent? (Hint: look at user/init.c, the first user program xv6 starts, and its compiled assembly user/init.asm.)

The processor is running in supervisor mode, and we can print privileged registers such as sstatus (see RISC-V privileged instructions for a description):

    (gdb) p /x $sstatus
  
What was the previous mode that the CPU was in?

The xv6 kernel code contains consistency checks whose failure causes the kernel to panic; you may find that your kernel modifications cause panics. For example, replace the statement num = p->trapframe->a7; with num = * (int *) 0; at the beginning of syscall, run make qemu, and you will see something similar to:

xv6 kernel is booting

hart 2 starting
hart 1 starting
scause=0xd sepc=0x80001bfe stval=0x0
panic: kerneltrap
  
Quit out of qemu.

To track down the source of a kernel page-fault panic, search for the sepc value printed for the panic you just saw in the file kernel/kernel.asm, which contains the assembly for the compiled kernel.

Write down the assembly instruction the kernel is panicing at. Which register corresponds to the variable num?

To inspect the state of the processor and the kernel at the faulting instruction, fire up gdb, and set a breakpoint at the faulting epc, like this:

(gdb) b *0x80001bfe
Breakpoint 1 at 0x80001bfe: file kernel/syscall.c, line 138.
(gdb) layout asm
(gdb) c
Continuing.
[Switching to Thread 1.3]

Thread 3 hit Breakpoint 1, syscall () at kernel/syscall.c:138

Confirm that the faulting assembly instruction is the same as the one you found above.

Why does the kernel crash? Hint: look at figure 3-3 in the text; is address 0 mapped in the kernel address space? Is that confirmed by the value in scause above? (See description of scause in RISC-V privileged instructions)

Note that scause was printed by the kernel panic above, but often you need to look at additional info to track down the problem that caused the panic. For example, to find out which user process was running when the kernel paniced, you can print the process's name:

    (gdb) p p->name
  
What is the name of the process that was running when the kernel paniced? What is its process id (pid)?

You may want to revisit Using the GNU Debugger as needed. The guidance page also has debugging tips.

Sandbox a command

In this assignment you will "sandbox" a process to restrict the system calls it can make. For example, one might sandbox a process to disallow opening files. You'll create a new interpose system call that will specify which system calls the kernel should reject from the calling process. interpose should take two arguments: an integer mask and a path. The mask's bits specify which system calls to reject. The second argument you will use in the next assignment and in this assignment it is always "-". For example, in order for a process to prevent itself from using the open system call, it should call interpose(1 << SYS_open, "-"), where SYS_open is a syscall number from kernel/syscall.h. Your implementation should cause the mask to be inherited by children of fork, so that children inherit the parent's restrictions.
We've provided you with a user/sandbox.c user program that forks, calls interpose() in the child, and then execs a program in the child. When you're done implementing interpose(), you should see output like this:
$ sandbox 32768 - cat README
cat: cannot open README
$  

In this example, cat README is the command being sandboxed. 32768 is the mask of system calls to reject; in this example it is 1<<SYS_open. If your solution is correct, you should see "cat: cannot open README".

Your solution gets full points, if you pass the tests in grade-lab-syscall sandbox_mask:

$ ./grade-lab-syscall sandbox_mask
== Test sandbox_mask == sandbox_mask: OK (1.5s) 
  

Some hints:

Sandbox with allowed pathnames

In this assignment you will extend the sandbox to allow masked open and exec system calls based on the pathname they use. The second argument of sys_interpose is the pathname allowed. If open or exec is masked but the pathname matches the allowed pathname, then these system calls should be allowed.
When you're done, you should see output like this:
$ sandbox 32768 README grep xv6 README
xv6 is a re-implementation of Dennis Ritchie's and Ken Thompson's Unix
Version 6 (v6).  xv6 loosely follows the structure and style of v6,
xv6 is inspired by John Lions's Commentary on UNIX 6th Edition (Peer
(kaashoek,rtm@mit.edu).  The main purpose of xv6 is as a teaching
$ sandbox 32768 README grep xv6 x
grep: cannot open x

In this example, the sandbox allows open calls that access README, but not any other file (e.g., x).

Your solution is correct if it passes the sandbox tests in make grade:

$ make grade
...
== Test sandbox_mask == 
$ make qemu-gdb
sandbox_mask: OK (9.5s) 
== Test sandbox_fork == 
$ make qemu-gdb
sandbox_fork: OK (0.3s) 
== Test sandbox_path == 
$ make qemu-gdb
sandbox_path: OK (1.1s) 
== Test sandbox_most == 
$ make qemu-gdb
sandbox_most: OK (0.8s) 
== Test sandbox_minus == 
$ make qemu-gdb
sandbox_minus: OK (1.1s) 
...
  

Some hints:

Attack xv6

The xv6 kernel isolates user programs from each other and isolates the kernel from user programs. As you saw in the above assignments, an application cannot directly call a function in the kernel or in another user program; instead, interactions occur only through system calls. However, if there is a bug in the kernel's implementation of a system call, an attacker may be able to exploit that bug to break the isolation boundaries. To get a sense for how bugs can be exploited, we have introduced a bug into xv6 and your goal is to exploit that bug to steal a secret from another process.

The bug is that the call to memset(mem, 0, sz) in uvmalloc() in kernel/vm.c to clear a newly-allocated page is omitted when compiling this lab. Similarly, when compiling kernel/kalloc.c for this lab the two lines that use memset to put garbage into free pages are omitted. The net effect of omitting these 3 lines (all marked by ifndef LAB_SYSCALL) is that newly allocated memory retains the contents from its previous use. Thus an application that calls sbrk() to allocate memory may receive pages that have data in them from previous uses. Despite the 3 deleted lines, xv6 mostly works correctly; it even passes most of usertests.

user/secret.c writes a secret string in its memory and then exits (which frees its memory). Your goal is to add a few lines of code to user/attack.c to find the secret that a previous execution of secret.c wrote to memory, and to print the secret on a line by itself.

Your attack.c must work with unmodified xv6 and unmodified secret.c. You can change anything to help you experiment and debug, but must revert those changes before final testing and submitting.

The secret program takes the secret as an argument. You can test your attack program by running secret with some argument, then runing attack, and seeing whether attack prints exactly the argument passed to secret. Here's a successful run:
$ secret xyzzy
$ attack
xyzzy
$

Depending on exactly how you implement your attack, you may need to run attack a second time in order for it to find the secret. The grader runs attack twice, just in case, and is satisfied if either produces the secret.

From outside xv6, you can use ./grade-lab-syscall attack, or make grade, to see if your attack passes our tests. The secret strings that the tests generate are guaranteed to contain only digits and upper and lower case letters.

As with this example, bugs that do not directly affect correctness can sometimes be exploited to break security. Careful programming and extensive testing can reduce the number of bugs but can't guarantee their absence. xv6 has had bugs in the past, and probably has yet more undiscovered errors. Real kernels, which have much more code than xv6, have a long history of such bugs. For example, see the public Linux vulnerabilities and how to report vulnerabilities.

Submit the lab

Time spent

Create a new file, time.txt, and put in a single integer, the number of hours you spent on the lab. git add and git commit the file.

Answers

If this lab had questions, write up your answers in answers-*.txt. git add and git commit these files.

Submit

Assignment submissions are handled by Gradescope. You will need an MIT gradescope account. See Piazza for the entry code to join the class. Use this link if you need more help joining.

When you're ready to submit, run make zipball, which will generate lab.zip. Upload this zip file to the corresponding Gradescope assignment.

If you run make zipball and you have either uncomitted changes or untracked files, you will see output similar to the following:

 M hello.c
?? bar.c
?? foo.pyc
Untracked files will not be handed in.  Continue? [y/N]
Inspect the above lines and make sure all files that your lab solution needs are tracked, i.e., not listed in a line that begins with ??. You can cause git to track a new file that you create using git add {filename}.

Optional challenge exercises


Questions or comments regarding 6.1810? Send e-mail to the course staff at 61810-staff@lists.csail.mit.edu.

Creative Commons License