Required reading: HiStar.
why security in the OS? managing resources for different applications must protect different users from one another file system memory processes Unix model designed for specific purpose: multiple users time-sharing a Unix system users identified by a user id (uid, usually 32-bit today) uid 0, called root, treated specially by the kernel as administrator each process has a uid, which is used in many security checks file system: each inode (file, dir) has an owner uid (and group; ignore for now) basic operations: read, write, execute [rwx] inode has 3-bit permissions for owner, group, and everyone else directory operations also map to rwx perm checks uid 0 has extra privileges can change uid, create device inodes, chown, reboot, etc some security mechanisms, however, aren't based on uid's file descriptor access chroot mechanism (only root can chroot, to not confuse privileged code) wait(), getting the exit status of a process how do these uid's get set? setuid() call uid=0 can change to any other uid other uid's cannot invoke setuid(), to a first approximation Unix login runs as root checks username, password against /etc/shadow calls setuid(user), runs user's shell not a great model for login: way too much privileged code rlogind runs "login [-f] username", -f if already authenticated user asks to log in as user "-froot", avoids password check, gets root neither should have been running as root in the first place? how else could you do login? KeyKOS receptionist. or HiStar, later. setuid programs what's missing in our model? no way to get back to higher privilege. how would we implement the "su" command? setuid programs fix that, by coupling privileges to a binary or exec() special "setuid" bit in the permissions of a file on exec() of binary, kernel sets process uid = file uid often, file owned by root, so process will run as root how to tell who called you? kernel keeps a real + effective UID for each process setuid binaries change effective UID, keep real UID effective UID used by most syscalls some syscalls explicitly use real UID (e.g. access) allows explicitly checking permissions "as if" it was the caller why might this be a bad idea? from earlier lectures: confused deputy, TOCTTOU (with symlinks) binary inherits process from "malicious" user environment variables: LD_PRELOAD, PATH fix: libraries ignore LD_PRELOAD in setuid programs; use full path secure setuid shell scripts hard to write; bash explicitly prohibits file descriptors: what if stdout was closed? overwrite some file.. fix: kernel will open dummy fds for 0, 1, 2 if not already open sending signals: user wants to kill setuid programs they ran kernel allows signals if uid matches not quite right, so filter signums (e.g., no SIGALRM) ptrace: can debug your own procs, but not others how does this interact with setuid programs? used to be a bug in linux unfortunately, no other way to pass privileges in Unix, so setuid is used limitations of Unix security can't pass privileges to other processes can't have multiple privileges at once not a very general mechanism (cannot create a user unless root) subjective policy (no objective underlying principle): unexpected results (used to be) easy to escape from chroot with uid=0 fd=open("/"), chroot("/tmp"), fchdir(fd), chroot("./../../../..") can create hard link to any file, even if we can't read or write it buggy code remains setuid even if root rm's it, installs fixed version keep a copy of sensitive data for later when you can obtain root access
historically, military was interested in a different security model what if the enemy gains access to secret data? could email it somewhere. mandatory access control vs discretionary Unix is discretionary, because process can specify security policy change permissions on files it writes to, give its privileges to others military goal was to disallow such discretion mandatory policy set by security officer, processes have no choice typical model: Bell-LaPadula, concerned with secrecy system has subjects (e.g. processes) and objects (e.g. files) each subject and object is assigned a security level (c,s) c = classification (unclassified, secret, top-secret, ...) s = category set (nuclear, crypto, NATO, ...) "dominates" relation: (c1, s1) dominates (c2, s2) iff c2 <= c1 and s2 \subseteq s1 use the dominates relation for access control S does an operation on O if operation will allow S to observe O, check if L(S) dominates L(O) if operation will allow S to modify O, check if L(O) dominates L(S) the dominates relation is transitive, so we get nice properties malicious process cannot copy top-secret file to unclassified file can we implement this on Unix? suppose we associate a security level with each file, directory and process when user logs in, shell's process level starts low check "dominates" on all file access, pipe messages when reading a file, raise process level as needed (but never lower) does this work? turns out that it's not enough: lots of covert channels spawn a child with shared fd, child reads secret data, stores fd offset lots of other examples: proc table, memory use, fs link counts timing channels: secret proc affects timing of unclassified procs requires lots of work to retrofit this into Unix flip side: Biba model, protects integrity want to ensure that network attackers can't modify system binaries turns out to be quite similar to Bell-LaPadula secrecy: no reading more secret files, writing less secret files integrity: no writing higher-integrity files, reading lesser-integrity files add an extra integrity value to the security level why didn't these ideas get widely used? 1) very specialized security level scheme, fits military but not many others 2) hard to figure out when to declassify data -- everything becomes secret 3) unclear interaction with traditional access control (Unix users, groups) still need both mechanisms SELinux is getting some interest, maybe even some use
goal: OS abstractions to build more secure applications (1) make protection mechanism general, available to every process. (2) improve security by making parts of the application untrusted, by specifying security policies in terms of information flow. why information flow? paper argues that some security policies better phrased this way not just military, but also virus scanners, VPNs, ... more generally, controlling information flow controls causality can potentially control how different untrusted components interact how to track information flow? tracking fine-grained dependencies along with every bit is hard instead, label objects that contain data (threads, files) be conservative, assume the "worst" for data coming from an object what's the representation of information flow? lots of categories of data (my email, my ssh key, another user's mail) anyone can create one of these categories roughly, subject and object security labels are a set of categories label model captures both Bell-LaPadula and Biba object and subject labels actually map categories to (0,1,2,3,*) "1" is effectively "no restriction" "3" is secrecy-protection in a category "0" is integrity-protection in a category label syntax: { c1 l1, c2 l2, ldef }, ldef usually 1 equivalent of the dominates relation: L1 can-flow-to L2 iff, forall c, L1(c) >= L2(c) examples of labels that can-flow-to or cannot-flow-to { mymail 3, 1 } cannot flow to { 1 } { mymail 3, 1 } can flow to { mymail 3, mykey 3, 1 } { mymail 3, 1 } cannot flow to { otheruser 3, 1 } { 1 } cannot flow to { systembin 0, 1 } ownership: how to declassify? centralized security officer doing declassification isn't going to work decentralize privileges whoever creates a category gets to "own" it (special level *) owner can ignore restrictions in that category, thereby declassifying can grant ownership to other threads allows precise statement of system's security goal (paper, p4): The contents of object A can only affect object B if, for every category c in which A is more tainted than B, a thread owning c takes part in the process. example: virus scanner security goal translates into wrap intercepting all output still mandatory security, except that we've decentralized "policy-setting" how do unix fs permissions map to labels? world-readable, world-writable: { 1 } world-readable, owner-writable: { u_w 0, 1 } (u_w for user owner) owner-readable, owner-writable: { u_r 3, u_w 0, 1 } (u_r for user_owner) can we do groups? sort-of, except user owner has to be in group fs similar to keykos: separate kernel-protected object for each inode how is this better than the military Unix systems from the 80s? don't need separate Unix security in addition to inf. flow kernel has few object types & syscalls clear when information flows, and it's the only security mechanism objects: threads, segments, containers, address spaces, gates, devices
what does the kernel interface look like? objects named by 61-bit IDs each object has a label for protection segments: sequence of pages threads: register set and the object ID of an address space address space: table of VA -> segment object ID .. and some others that aren't important for now basic plan: few system calls, always clear where information is flowing label recap: objects have a label that tracks secrecy, integrity in different categories data can generally flow to higher secrecy and lesser integrity threads can own categories and bypass these restrictions threads also have a clearance: upper limit for labels of allocated objects raising thread's own label is a degenerate case of object allocation also prevents malicious tainted thread from stashing copies of secrets what does a process look like? need a thread, a segment, and an address space simple protection: segment, AS only accessible to process threads we'll build up as we go along how do we implement a FS? goal: minimal trusted code similar trick to KeyKOS: full persistence in the kernel allows a lot of FS code to run in user-space, not in trusted kernel each file is a segment where does mtime go? 64 bytes of metadata with every object. each directory has a segment that maps names to object IDs either files (segments) or sub-directories access control in terms of labels on files, directories how would a process access this FS? need a FS library that translates open, read, write into segment ops how to implement rename in a directory? change the name in dir segment need to ensure atomicity: how to make sure two threads don't collide? spinlock in shared memory (in directory segment itself) in reality, spinlocks are bad. how to go to sleep? kernel provides an addr-sleep mech, much like inside xv6 kern Linux calls this a futex slightly non-trivial: segment can be mapped at diff. VAs labels control who can sleep or wakeup an address what about read-only directories? generation# trick. how does this compare to the KeyKOS FS? can't change the file implementation quite so easily still one protection domain per file or directory cool feature of labels: no inherent superuser privileges can run ssh-agent without giving root access to your keys but this seems potentially bad -- what to do with buggy ssh-agent? esp. with single-level store, this is a bit of a problem solution: need a resource allocation mechanism keykos had space banks and sub-banks histar has containers new kernel object: container (back to diagram!) what does this look like? basically, rmdir non-empty directories. file descriptors? segment for each FD, very JOS-like (keeps offset) mapped in address space of every process with access to it but anyone can map segment into AS, so use a per-fd category in labels what container does the FD live in? want to make sure FD is accessible even if other process exits kernel allows object hard links object naming -- why <container ID, object ID>? what could go wrong if we just named objects by object-ID? secret proc could create hard links to different objects unclassified proc removes objs from container, tries to access success or failure indicates hard links in other containers problem: object's existence itself conveys information naming an object through a container requires observing container as a result, caller allowed to receive data from any container writer explicitly allowed to see whether object exists or not cannot name objects gone from our container (even if exists elsewhere) process exit need a separate segment object to keep exit status value and actually a higher-level process container to expose it only the process should be able to write it, anyone can read what happens if you ran a process that looked at some secret files? in theory, shell should never hear from tainted process but really want to get back to shell prompt two possibilities: - process reports it's about to become tainted, then taints itself. - owner of tainted category allows process to leak one bit of data; very explicit in user-space, rather than weakening kernel mechanism how do signals work? e.g. ^C base kernel operation: trap a thread, like utraps in JOS who can trap a thread? can trap a thread if can read/write thread object itself that's often too restrictive, so can trap with write access to AS but a different process can't access either thread or AS of another proc fundamental goal: need privileges of another process, for specific op new kernel object: gate, associates privileges with some code (diagram!) so how do gates work? gate call: thread changes AS (page table) & registers to those in gate can also change label, using privileges (stars) from gate's label [actually need more precise label manipulation, for tainted gates] not quite IPC because there doesn't need to be anyone waiting for call what's "left" of the thread? resource allocation (container) signal gate for killing process: per-user category limits callers how to name a process that we want to kill? unix: global proc_table keynix: still a global proc_table, to map PIDs to capability keys histar: use the container ID of a process container as the PID given a PID, always clear how to find proc, signal gate, exit segment gates example app: timestamping service send secret msg to server, server can only reply to you do we need this gate thing? can this work with pure IPC? need to make sure server can talk to other procs afterwards fork server for each message? resource problem alternative 1: infinite resource use for server forks. bad. alternative 2: bound the number of server forks. covert channel. how are we going to use gates? invoke service with taint, what can the thread do? initially, can't write anywhere (except a special thread-local page) caller provides a writable, tainted container thread copies AS, segments (text, stack, data), etc into container switches to cloned (now writable) address space, uses key to sign cannot leak private data outside of its tainted container sending response back: invoke a return gate keykos analogy: key call, but that created a return key automatically why does histar gate call not create a return gate? if it's identical to a regular gate, no need to do it in kernel keykos return keys special (can only return once), can we do that? return-once semantics leak information, so histar deals in userspace timestamping service could play same trick on the caller! return at different taint levels, see how it behaves then return for real at a low taint level not great, what can we do? refuse tainted return. for untainted return, we can write to original AS, remove gate. what applications would benefit from histar labels? wrap virus scanner user experience: wrap scan ~ unix: hard to implement keykos: need to understand all capabilities wrap is giving to scan histar: just label scan with new category {v3} main thing: protection and naming separated VPN isolation what's the problem? single machine, connected to internet and corporate VPN viruses go into corporate network, secret documents leak out should be able to use taint to prevent this how does the network stack work in histar? very much like jos (lab6 code came from histar) TCP stack not trusted, nothing particularly special about it can easily run a new one for VPN how does the application find the TCP stack? lab6: special env_id histar: /netd (object id for "netd" in root directory segment) figure 11 from paper could we do this in keykos? hard: need to know ahead of time if process should be {i2} or {v2} what happens if the TCP stack is malicious? what happens if the VPN code is malicious? how does login work in HiStar? one agent per user, checks password, grants user privileges (stars) so far, similar to KeyKOS. but one diff: agent cannot leak pw. why is this hard? also want agent to log authentication attempts. idea: instead of checking pw, create another agent to check it later no password involved -- can log that we created this second agent same trick to log a successful auth: grant an "intermediate" star new problem: second agent invoked many times to guess pw, cannot log! idea: retry counter, limits how many times second agent will work how to create? turns out, neither party can create it on their own login process should not be trusted -- can create many of them auth agent should not be trusted either -- not as obvious allowing it would involve login granting clearance to auth agent auth agent could place retry counter out of reach of login process have second agent stash pw there, leak it through covert channels login process loses control over copies of password solution: simple mechanism to combine privileges both parties know what a system call instruction looks like login process creates a gate pointing to create-segment syscall code auth agent checks that gate points to create-segment syscall code invokes gate with its own privileges only needed mechanism: read-only objects evaluation security cool applications, could not do this in Unix or KeyKOS inf. flow seems useful: don't have to trust even password checker! kernel seems smaller, maybe fewer bugs, but nothing formal performance seems comparable to Linux with high-level benchmarks why is fork slow? why is spawn better?