Ancient CS 61 Content Warning!!!!!1!!!
This is not the current version of the class.
This site was automatically translated from a wiki. The translation may have introduced mistakes (and the content might have been wrong to begin with).

Process Isolation and Weensy OS

The process abstraction is among the most powerful abstractions with which we've dealt this semester. It is one you have used from the very first program you wrote -- in this unit, we've begun identifying the key mechanisms that provide this abstraction -- today we will try to uncover any remaining mechanisms.

Learning Objectives

Get the Code

We'll be working today with code that is practically identical to that from which you'll start Assignment 6 (we've made a couple of minor changes to facilitate the exercises, but nothing worth noticing). Today's code is in the cs61-exercises repository under l24, but understand that it's really the same code from which you'll start assignment 6.

NOTE: This will run on the appliance; this may run on other Linux boxes, assuming they have QEMU installed; this will not run on other platforms.

What can I do with Weensy OS?

Weensy is a real live operating system that has been designed for you! It's small enough that you can master it and functional enough that you can learn some really good stuff.

So, let's see what Weensy can do. (Try these things.)

Note: Sometimes you can end up with QEMU running even though you don't have any foreground processes running in your terminal window. In this case, use ps to find the pid of QEMU and the use kill PID to kill the QEMU process.

1. You can run Weensy's default workload: 4 processes that simply try to allocate memory.

   make run

2. You can stop Weensy from running: simply type q in the console window. (The console is the pretty screen displaying the multi-colored picture). If you want to know what the picture is telling you, check out the assignment.

3. You can try to make your system fork processes. After you've done make run, type an f in the console. Unfortunately, if you tried that, you will have discovered that Weensy doesn't yet support fork -- you'll add fork as part of Assignment 6. In fact, the following letters typed in the console do the following:

4. You can debug Weensy! If you type make run-gdb you'll see that the console window pops up, but the terminal in which you typed the command will have you at that familiar GDB prompt! You can break anywhere you want. We suggest putting a breakpoint in kernel and stepping around to see how your tiny operating system works. You'll notice that you get both the line of C displayed as well as the set of assembly instructions. Aren't you glad you spent all that time learning to read assembly?

While you're in GDB, step and next around to see if you can answer the following questions:

4.1 There is a secret command and process that this version of Weensy can start up. Can you find it? What is its name and on what line does it get started?

4.2 current is a global variable that stores the currently running process's information. Where does it get set?

4.3 What does it have to do to run a process?

4.4 Why must the kernel to an iret instead of a ret?

Providing process isolation

5. Once the kernel runs a process, how does it ever gain control again? Let's take a look at the canonical mischievous process -- we've added a program to the Weensy distribution, called p-badguy.c. Take a look at it. What does it do? You can run badguy by typing a b in the console window. Try that.

6. Notice that the way Weensy starts that process is by rebooting the entire machine! You'll find that you're back at the breakpoint you set at kernel. This time, you get different behavior than before; why?

7. OK, once you've seen badguy write all over your console, type q to quit. Take a look at log.txt. Given what you see there, how would you answer the question, "How does the OS gain control again once it starts running a process?" Set a breakpoint in Weensy that you think will let you regain control while programs are running.

The timer and timer interrupts are another one of those kernel mechanisms that helps the OS provide process isolation. No matter what a process does, if the OS regains control every time the timer goes off, then the OS can ensure that all processes eventually get to run.

So, what else could a mischievous process try to do? What if a process could turn off interrupt? That would be problematic. Let's see what happens if we try that. We now know how to sneak assembly code into our C programs -- let's try that! The instruction to turn interrupts off is cli. Edit p-badguy.c and instead of (or inanition to) printing a message, try inserting the following code:

    asm volatile ("cli\n");

8. What happened?

9. Use the x86.h file to help you figure out what that means. What does it mean?

Maintaining process isolation requires that unprivileged processes not be allowed to issue dangerous instructions.

10. Can you think of any other examples of dangerous instructions? Try them and see what happens.

The VM System and Process Isolation

We've also discussed how virtual memory provides process isolation, preventing processes from stepping on the operating system and preventing processes from accessing each other's memory. Let's see how Weensy responds to such attempts.

Overwriting the Dispatch Table

While we were unable to explicitly turn off interrupts, what if we could get the operating system to invoke our unprivileged code on a timer interrupt? Wouldn't that be cool! OK, in order to that, we need to overwrite the operating system's dispatch table entry for the timer interrupt. To do that, we'll have to find the address of the dispatch table.

The file obj/kernel.sym contains the operating system's symbol table. Try the following:

   grep INT_TIMER *
   grep INT_HARDWARE *

Note: Grep is a pretty handy tool for various things, not the least of which is finding where things are in code. If you are not familiar with this command, read the man page and play with it.

So, here are the code snippets to add to p-badguy

    // Put this before main
    void
    evil_interrupt_handler(void)
    {
        for (int i = 1; ; i++) {
            if (i % 1000000 == 0)
                app_printf(10, "I own your machine! %d\n", i);
        }
    }
    // Put this at the beginning of main
    x86_gatedescriptor *p = (void *)0x50180;
    SETGATE(*p, 0, 8, evil_interrupt_handler, 3);

OK -- now, if the OS is properly protected, we should get a protection fault when we run badguy. Try it!

If you made all the right changes, when you type b in the console, you'll suddenly see a bunch of messages indicating that our evil program has taken over your machine! Furthermore, you'll notice that typing q in the console window doesn't help! That's because we've overwritten the hardware interrupt handler. Oops! A Control-C from the window in which you typed make run should kill your evil, evil process. One of the first things you'll do in A6 is prevent the nefarious badguy from taking over your machine.

Overwriting the Interrupt Handler

Let's try another way that badguy might take over your machine. While the timer interrupts prevent badguy's infinite loop from locking out the operating system, what if we could make the operating system itself loop infinitely!? That might be really cool, because the operating system will have already turned off interrupts, so there will be nothing one can do! How might we go about doing this?

 cc -c x.S
 objdump -D x.o

You should see a single 2-byte instruction. Now, make that into a 2-byte hex number (remember that the display shows you the low-order byte first, so construct this number correctly!

 unsigned short *sp = (void *)SYS49_INT_HANDLER_ADDRESS;
 *sp = INFINITE_LOOP_INSTRUCTION;

If you did this correctly, then when you run Weensy and type b into the console window, your entire Weensy instance will hang! You can should be able to kill QEMU and Weensy by typing a control-C into the window from which you ran it.

At this point, we have firmly established the fact that Weensy is not protecting itself (the operating system) from naught unprivileged programs. Fortunately, you know how to do this and will need to do so the first part of Assignment 6. The secret is all about setting the proper permissions in PTE entries to prohibit unprivileged processes from writing into kernel memory. This requires only a single line of code -- see if you can figure out where to insert the proper call to virtual_memory_map to prevent the kinds of attacks we've just demonstrated.

Clobbering Other Processes Address Spaces

We'll play with the allocator process since we can already run multiple instances of it. Add a call to app_printf to have each process print its pid (p) in p-allocator.c each time it allocates a page. (This will destroy your display, but that's OK for now.)

Make sure everything still builds.

Ok, now for the fun! Let's have allocator1 clobber the pid for each of the other allocators!

If you've done this correctly, when you run Weensy, it will appear that only process 1 ever allocates pages!

Clearly Weensy is not protecting user processes from one another! This is a problem you will solve in part 2 of the assignment.

Wrapping Up

By now, we hope that you are comfortable playing with Weensy. The beauty of running your own operating system inside a virtual machine (which happens to be inside another virtual machine) is that you can tweak things and try things out without breaking your whole system! Go ahead and experiment. This assignment brings together all the skills you've developed this semester: understanding how memory is allocated, being able to read assembly, using GDB. And mostly, it requires thought, not writing a large number of lines of code.

Please complete this short survey and then have a wonderful break