HiStar ====== What is goal? Separate security critical code from bulk of applications Why do we need a new OS for this? E.g., Linux kernel is huge and entirely security critical System call API does not promote privilege separation (E.g., Most of the motivation for capsicum applies) Walk through virus scanner example What goes wrong with chroot jail? What goes wrong with ptrace syscall interposition? What if you wanted to secure virus scanner on capsicum? Spawns lots of helper programs, all would need to work in capability mode E.g., might run into problems with lots of programs that use /tmp Can you fit virus scanner onto a lattice? Key ideas lower level interface simplifies reasoning about information flow use labels exclusively for protection--everything else in terms of labels make the mechanism *egalitarian* Six object types Container Segment Address space Thread Gate Device Each object has a LABEL, 64-bit object ID, and 64 bytes of metadata ~75 system calls, but organized in groups targeting each object type System calls (and page faults) may require reading and writing objects For each case, compare label of object (L_O) with label of thread (L_T) - When reading object, must have: L_O [= L_T - When writing, must have L_T [= L_O, but... no such thing as write-only so always check: L_O [= L_T [= L_O Label format Label is a set of secrecy and integrity categories What does the paper mean by the term "taint"? When label is high in category c, you are "tainted" in c If B observes A, then B must be at least as tainted as A Analogy: Hazmat diamond symbols (health, fire, reactivity hazard) If container A's contents is a level 3 fire hazard can't move it into container B only rated for level 1 fire hazard Always conservatively assume contents is as hazardous as label But note that secrecy and integrity categories work inversely Say c_r is a secrecy category and c_w an integrity category Label {c_r} is more tainted than {} -- fewer threads write {} But {} is more tainted than {c_w} -- fewer threads can write {c_w} Do HiStar's labels form a lattice? Yes Let Sec(L) be secrecy categories in L, Int(L) be integrity categories 1. [= is a partial order (reflexive, antisymmetric, and transitive) L1 [= L2 iff Sec(L1) \subseteq Sec (L2) AND Int(L2) \subseteq Int(L1) 2. Any two labels have least upper bound (square cup). What is it? L1 LUB L2 = Sec(L1) UNION Sec(L2) UNION (Int(L1) INTERSECT Int(L2)) 3. (Similar for greatest lower bound) Why do we care that labels form a lattice? LUB used to determine what happens when you combine taints Analogy: If you mix fire hazard 3 and health hazard 3, get both hazards For thread T labeled L_T to read object O labeled L_O T has to raise its label so that L_O [= L_T Lowest value it can chose is L_O LUB L_P Why don't labels just keep creeping up until system becomes useless? Because threads may *own* categories and *untaint* data in those categories Kernel keeps set O_T of categories owned by each thread T T is impervious to taint in categories O_T New notation L1 [=_O L2 iff (L1 - O) [= (L2 - O) "L1 can flow using privileges O to L2" So re-writing access checks to consider ownership: T reads O requires: L_O [=_{O_T} L_T T writes O requires: L_O [=_{O_T} L_T [=_{O_T} L_O Notice that this affects how much T must raise label to observe object O Before said lowest possible value was L_O LUB L_T New lowest value is (L_O - O_T) LUB L_T Allows precise statement of system's security goals: 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. Who gets to own a category? Whatever thread allocates a category owns it A thread that owns a category can grant it to other threads Digression: HiStar also supports *clearance* Clearance is another label value associated with a thread For thread T, write T's label as L_T and T's clearance as C_T System enforces that L_T [= C_T -- so C_T is upper bound on label Why do we care? Covert channels would let data slowly be leaked C_T also bound the label L_O of any object T creates (The first is actually just a special case of the second... A thread that raises its label creates a more secret thread) Effectively *two* kinds of secrecy categories - Categories that are already in default clearance when created - Categories that are not in default clearance when created Latter categories implement traditional DAC read protection [Really two ways of adding secrecy category to labels, but let's simplify] Why might we care about bounding allocation? Prevents unauthorized threads from copying secret data Copying would allow threads to exploit covert channels more effectively E.g., have all eternity to leak users password instead of a few seconds Copying could also lead to information disclosure Example: suppose my tenure letters are stored on HiStar Taint all letters with g_tenure Only tenured faculty's threads should own g_tenure Delete letters before promoting anyone But if I get tenure and own g_tenure *, I can read any copies I made E.g., allocate segment labeled g_tenure that no one knows about Get people to use trojaned PDF viewer that copies letters into segment Does Jif have a notion of clearance? No. Does it matter? Yes for covert channels Since Jif doesn't model persistent state, maybe copy issue is less big deal but have to kill and re-start application after promoting someone How would labels map to various Unix permission bits? World-readable file, only writable by owner? Each user u owns two categories, u_r and u_w, gets stars on login Set file's label to { u_w } Now default process with label { } can't write file, but can read Group-readable file, only writable by owner? Have to introduce categories g_r, g_w for group, give g_r, g_w on login Set file label to { g_r, u_w } Note user u must be in group g to read file (different from Unix) How would you implement chroot- or jail-like confinement? Allocate two "jail" categories j_r, j_w Set jailed objects including threads to have label { j_r, j_w } Lower clearance of jailed threads to { j_w } j_r prevents jailed threads from modifying rest of system By default labels of non-jailed objects don't contain j_r So no information can flow out of the jail j_w prevents jailed threads from observing rest of system Most outside objects don't have j_w Clearance prevents jailed process from removing j_w from label So information cannot flow to jailed threads from rest of system What is a single-level store and why? Single-level means no distinction between memory and secondary storage View memory as just a cache for disk--everything persists across reboots Solves the system initialization problem w/o a superuser Otherwise, how would users get category ownership back after a reboot? What does no superuser mean, and how can you administer such a system? No superuser = no inherent read/write/untaint access to objects The thread that creates a category is the only one that owns it But then can you create a process you can't kill? No--because allocation uses container hierarchy Idea: no implicit resource allocation Any object you create is "charged" against a container's space quota Anyone who can write the container can deallocate the object (unless other containers are also paying for the object's existence) Container hierarchy very much like Unix file namespace Except all objects (threads, gates, etc.) are in the hierarchy Most system calls specify objects by pair. Why? Suppose not--then prolonging and deleting objects might be a covert channel Say you have thread T_hi, with L_{T_hi} = { t_r } and thread T_lo, with L_{T_lo} = {} and segment S, with L_S = {} Thread T_hi can link S into its own "high" container Thread T_lo can unlink S from its "low" container If T_lo can still access segment S, means not deallocated so T_lo observed T_hi's actions Basically you must know an object exists to use it L_D used to check you are allowed to know if the object exists L_O used to check you have access to the object Related question--how does a thread know how tainted an object is? Most objects have immutable labels, specified at creation If you can know the object exists (because you can read the container) then you can also know the label, specified when object created Want to be able to access containers without access to parent Solution: a container always contains itself What are the implications? - Say container D is in container D' - Say T1 can write D' but not D - Say T can read D but not D', and can't receive messages from T1 - T1 Can still delete D, which T will notice by trying to access Why is this not fatal for HiStar? Consider thread T' that created D So T' owns all categories in which D' is higher than D Could have downgraded information from D' to D HiStar views creating D as "pre-authorizing" one bit to be downgraded How does IPC work (2.6, 3.5)? Gate objects allow a thread to cross into another address space. Specify: G_G - a guard set restricts who can call into gate paper says O_T \subseteq G_G, but actually L_T [=_{O_T} G_G O_G - extra categories you can own once through the gate Address space - the address space to switch to Entry point - where to start executing in the address space A gate call looks like: enter(gate_ce, new_ownership, new_label, new_clearance, thread_ce) gate_ce - the container entry of gate new_ownership - new O_T must be subset of T_G UNION (old O_T) new_label - new L_T, must satisfy (old L_T) [=_{O_T} (new L_T) new_clearance - new C_T, must satisfy (new C_T) [=_{O_T} (old C_T) thread_ce - container entry of thread, for thread to know it exists Example: Timestamped digital signature daemon Daemon knows secret signature key labeled {d_r, d_w} Daemon creates a service gate G with O_G = {d_r} Client running with process categories O_T = {p_r, p_w} Client allocates a new category ret_w Client allocates a "return gate" G' with O_G' = {p_r, p_w}, G_G = {ret_w} Client enters gate G, starts server code with L_T = {d_r, d_w, ret_w} Server code now has access to secret key, computes signature Returns to client by jumping through G_r; resets thread's label/privs from {d_r, ret_w} -> {p_r, p_w, ret_w} Can you prevent the signature daemon from looking at data it is signing? Yes, can invoke signature daemon tainted in some new category t_r But then need to jump through some hoops for space allocation Daemon can't know about gate invocation, so can't use it's own space Instead, client must give it a container with sufficient quota Why not use more traditional message-based client-server IPC? Server needs to dedicate resources to receiving messages Hence, resource consumption could leak information about signed message E.g., could run the server out of threads to leak secret bit Applications: How are line counts so high in Figure 7 given limited manpower? Project incorporated tons of third-party code (e.g., ghostscript, OpenSSL) Remember goal is to separate security-critical and other code Import tons of dubious-quality code, just don't make it security-critical Look at Figure 6/7. What is security critical and what is big? How would you do red-green VPNs? Software interlocks? Evaluation: What questions should we be asking? - Does HiStar impose unacceptable performance penalty? - Would HiStar be usable for tasks we currently do on Unix/Linux? - Is HiStar more secure than existing, widely-used operating systems? - Does HiStar permit better division of functionality in applications?