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?