Capabilities and KeyKOS ======================= Administrivia: Lab 2 part 2 due this Friday. Ben Livshits will give several lectures on web security, starting this Wednesday. What's the problem the authors of "confused deputy" encountered? Their system had a Fortran compiler, /sysx/fort (in Unix filename syntax) They wanted the Fortran compiler to record usage statistics, but where? Created a special statistics file, /sysx/stat. Gave /sysx/fort "home files license" (kind-of like setuid w.r.t. /sysx) What goes wrong? User can invoke the compiler asking it to write output to /sysx/stat. e.g. /sysx/fort /my/code.f -o /sysx/stat Compiler opens supplied path name, and succeeds, because of its license. User alone couldn't have written to that /sysx/stat file. Why isn't the /sysx/fort thing just a bug in the compiler? Could, in principle, solve this by adding checks all over the place. Problem: need to add checks virtually everywhere files are opened. Perfectly correct code becomes buggy once it's part of a setuid binary. So what's the "confused deputy"? The compiler is running on behalf of two principals: - the user principal (to open user's files) - the compiler principal (to open compiler's files) Not clear what principal's privileges should be used at any given time Can we solve this confused deputy problem in Unix? Suppose gcc wants to keep statistics in /etc/gcc.stats Could have a special setuid program that only writes to that file Not so convenient: can't just open the file like any other. What if we make gcc setuid to some non-root user (owner of stats file)? Hard to access user's original files. What if gcc is setuid-root? (Bad idea, but let's figure out why..) Lots of potential for buffer overflows leading to root access. Need to instrument every place where gcc might open a file. What check should we perform when gcc is opening a file? If it's an "internal" file (e.g. /etc/gcc.stats), maybe no check. If it's a user-supplied file, need to make sure user can access it. Can look at the permissions for the file in question. Need to also check permissions on directories leading up to this file. Potential problem: race conditions. What if the file changes between the time we check it and use it? Common vulnerability: attacker replaces legit file with symlink Symlink could point to, say, /etc/gcc.stats, or /etc/passwd, or ... Known as "time-of-check to time-of-use" bugs (TOCTTOU). What problems are capabilities trying to solve? Someone (e.g. malicious user) can name an object without having the privileges to access it, and trick a privileged application into accessing it nonetheless. Several possible ways of thinking of this problem: 1. Ambient authority: privileges that are automatically used by process are the problem here. No privileges should ever be used automatically. Name of an object should be also the privileges for accessing it. 2. Complex permission checks: hard for privileged app to replicate. With simpler checks, privileged apps might be able to correctly check if another user should have access to some object. What are examples of ambient authority? Unix UIDs, GIDs. Firewalls (IP address vs. privileges for accessing it) HTTP cookies (e.g. going to a URL like http://gmail.com) What's the proposed solution? "Capabilities": unify the notion of naming an object with privileges. Cannot name an object without having privileges to access it (in some way). If you have a capability to an object, it's unambiguous that you can access. Much simpler access control plan than Unix. How would capabilities help improve security? Makes it difficult to unintentionally mis-use privileges when writing code. Not any easier to avoid other programming mistakes (memory errors, etc). Doesn't help existing applications. Must change applications to pass around capabilities instead of names. Can delegate privileges to a process without giving up all your privileges. What are examples of capabilities in today's systems? Object references in a type-safe language (e.g. Java). File descriptors in Unix. Could we use file descriptors to solve our problem with a setuid gcc? Sort-of: could make the compiler only accept files via FD passing. Or, could create a setuid helper that opens the /etc/gcc.stats file, passes an open file descriptor back to our compiler process. Then, can continue using this open file much like any other file. How to ensure only gcc can run this helper? Make gcc setgid to some special group. Make the helper only executable to that special group. Make sure that group has no other privileges given to it. What's the motivation behind KeyKOS? 1. Much easier to get security right if you have the right primitives. E.g. TOCTTOU bugs: hard to implement security with a poor interface. 2. Microkernel design: smaller kernel, hopefully fewer bugs? How big is the Linux kernel? About 1MLOC, on any single machine. Periodically, security vulnerabilities are found in the Linux kernel. Recall: a good way to improve security is to reduce trusted code. Goal: small fully-trusted kernel, everything else is in user-space. Pitfall: end up with a single trusted Unix server (e.g., Mach). KeyKOS actually did something interesting in this regard (KeyNIX). What does the KeyKOS kernel interface look like? Contrast with Unix kernel: files, processes, UIDs/GIDs, exec, fork, .. KeyKOS has three basic objects Device: provides access to underlying hardware (disk, network, ..) Page: 4KB of memory. Node: a block of 16 capabilities ("keys"). Every object must be named by a capability (think file descriptor). Other things are built up using nodes Segment (a file): node containing keys to pages (or other nodes) Meter: explicit CPU time allocation Domain: sort-of like a Unix process What does a domain look like? 16 general-purpose key slots (think capability registers). Address slot: key to a segment describing the process memory space. Meter slot: key to a meter providing CPU time to run. Keeper slot: key to another domain (process) to handle exceptions. What does a key look like? Inside the kernel, it's a 12 byte blob. Processes (domains) cannot directly modify those 12 bytes. 1 "data byte" reserved for app-level information (e.g. "read-only", ..) Where is the KeyKOS "root user" equivalent? No such thing: all checks are uniform, no exceptions. Domains/processes may be "special" if they hold keys (e.g. device key). No inherent superuser that has privileges over everything. Flip side: any user can use any kernel functionality (will see later). How does a process name a capability? Has to use one of the 16 capability slots in its domain (process). Works much like a "capability register". How can you use a capability? void FORK(key k, msg m): send m to k's domain, continue running msg *CALL(key k, msg m): send m (+ resume key) to k's domain, suspend msg *RETURN(key k, msg m): send m to k's domain, wait for next msg The way you specify a key is the number of a slot in your domain (0-15). Everything else is done via these three system calls: Device I/O: CALL the device driver. Memory mapping: add a key to your address space segment. Accessing a file: CALL the file system. To access capabilities stored in a node, need to call that node, ask it to send back a particular key (Node_Swap), and store it in one of the 16 key slots in the domain. But if you name your capability by a number, isn't that ambient authority? Yes, but hopefully programmers aren't passing around domain slot IDs. Other things in KeyKOS are also ambient authority: e.g. memory addrs. Again, probably assuming programs do not pass around memory addrs. (Some computer systems were built around the idea of cap at hw level.) What does a capability system look like from a user point of view? Quite different from Unix. Let's look at GNOSIS (predecessor of KeyKOS). No single file system name space (that would require ambient authority). Every user has their own home directory (named by a key). Directory's job is to map names to keys (similar to Unix). One user cannot access another user's home directory. How do you get the home directory key in the first place? Each user has a continuously-running shell process. Shell process holds a key to the user's home directory. How does a user get to the shell process? Login ("receptionist") holds keys for every user's shell. If users provides correct password, login sends terminal key to shell. Shell's job is to talk to the user's terminal from that point onward. When invoking commands at the shell, must explicitly pass capabilities. The command "cat f.txt" (in Unix shell syntax) is ambiguous. Need to run differently if f.txt is a string (passed as bytes) or a capability name in user's directory (passed as a key). How does the receptionist get the keys to all user shells in the first place? Full system-wide persistence. All objects live on disk, preserved across reboots. Even processes (domain objects) are preserved. When a user is created, receptionist gets a key for that user. Receptionist lives forever, holds these keys. KeyNIX: emulating Unix on top of KeyKOS Why? Run legacy applications, side-by-side with KeyKOS. What's the design of KeyNIX? Separate keeper for every process. Keeper gets faults when Unix app makes system call. Keeper has access to a global segment storing shared Unix state. Process table. Open file descriptors. Everything is implemented on top of KeyKOS objects. Cool: any user can decide to run another KeyNIX universe. Wouldn't be able to do this on Unix: many things require root (uid 0), and there's only one "root" user. What's the design of the KeyNIX file system? Each file is a separate segment. To read/write a file, process can add it to its address space. Some small files (<4KB) might be stored in shared segments. Each directory is a node (containing file keys) + segment (file names). Directory has a domain, can implement its own scheme for lookup, etc. What's the access control model? KeyNIX emulates Unix: the Unix keeper tracks UIDs, checks them as Unix. Somewhat of a bummer: Unix apps can get tricked like a confused deputy. Function of the API rather than of the kernel. What are the security properties of KeyNIX and its file system? What happens if one Unix keeper domain is compromised? Can corrupt shared state segment. Can access anyone's open file descriptors (from shared FD table). Could we do better? Get rid of shared FD table, use real capabilities for FDs. Split shared process table into a per-process segment. Each process keeper keeps its own process state. Can query another domain via messages. Slight problem: process table required by Unix to look up proc by PID Hard to fit existing naming conventions into capabilities. Global namespace is often a sign of ambient authority. Additional references: http://www.cap-lore.com/CapTheory/KK/m/100.html http://www.cis.upenn.edu/~KeyKOS/agorics/KeyKos/Gnosis/166.html