HiStar ====== What's the goal of this work? Proposes a new OS that might offer better security ... by helping reduce the amount of security-critical code in apps ... by allowing users to control how information flows through the system Why information flow control? Last time already discussed why the military might want this Paper claims it's sometimes easier to express security policy this way Includes a few examples: ClamAV, VPN isolation, untrusted login More generally information flow = causality Say process A can only affect process B's behavior through process C Reduces code you must understand to reason about system's behavior to C What's the point of the Asbestos labels HiStar uses? Uses concept of *taint* to track and control information flow 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 HiStar Idea: - Small number of types of kernel object - Limited ways for information to flow between objects - Enforce restrictions specified by labels when information flows What is an Asbestos label? A label L is a function L : Categories -> Taint levels Handles: 61-bit opaque identifiers Levels: { *, 0, 1, 2, 3, HS [special HiStar symbol] } Written with special compact syntax: { category-1 level-1, category-2 level-2, ..., default-level } We say L1 [= L2 if L2 is at least as tainted as L1 in *every* category meaning For all categories c, L1(c) <= L2(c) Roughly speaking, information can only flow "up", not "down" I.e., can flow from O1 to O2 only if L_O1 [= L_O2 Some examples: {1} [= {1} {1} [= {2} {1} [= {a 2, b3, 1} L1={a 2, 1} NOT [= L2={b 2, 1} (because L1(a)=1 NOT <= L2(a)=1) L2={b 2, 1} NOT [= L1={a 2, 1} (because L2(b)=1 NOT <= L2(b)=1) (I.e., L1 and L2 not ordered, either way) Notice that: 1. [= is a partial order 2. Any 2 labels have unique least upper bound written w. LUB (square cup) L = (L1 LUB L2) is given by L(c) = max (L1(c), L2(c)) Properties 1 & 2 mean labels form a lattice 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 If process P labeled L_P reads object labeled L_O P has to raise its label so that L_O [= L_P 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 If thread T's label L_T contains *, T is impervious to taint in T Represented by shifting between low * and high HS When deciding if information can flow *to* T => treat T's stars as high using ^{HS} operator (L_T^{HS}) When deciding if information can flow *from* T => treat T's stars as low (L_T) New information flow rules (p. 4): * T observes O requires: L_O [= L_T^{HS} * T modifies O requires: L_T [= L_O [= L_T^{HS} Notice that this affects how much T must raise label to observe object O Before said lowest possible value was L_O LUB L_P Now lowest value is (L_O LUB L_T^{HS})* - i.e., "T can keep its stars" Allows precise statement of system's security goals (p. 4): 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 *? Whatever thread allocates a category initially gets * If you have *, you can grant it to other threads What is clearance and why? Clearance bounds how tainted a thread may make itself Why do you care? Covert channels would let data slowly be untainted Clearance also bounds the taint on objects created by a thread (The first is actually just a special case of the second... If a thread taints itself, it's creating a more tainted thread) Why do we care about bounding allocation? Prevents unauthorized threads from copying tainted 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 3 Only tenured faculty should have g_tenure * Delete letters before promoting anyone But if I get tenure and get g_tenure *, I can read any copies I made 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 0, 1 } Now default process with label { 1 } 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 3, u_w 0, 1 } Note user u must be in group g to read file (different from Unix) How would you implement chroot- or jail-like confinement? Allocate a "jail" categories j_in, j_out Set jailed objects including threads to have label { j_in 0, j_out 2, 1 } Lower clearance of jailed threads to { j_in 0, 2 } j_out prevents jailed threads from modifying rest of system By default labels of non-jailed objects have L(j_out)=1 So no information can flow out of the jail j_in prevents jailed threads from observing rest of system Most outside objects have L(j_in)=1 So information cannot flow to jailed threads with L(j_in)=0 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 their *s 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 to possess the * 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 3, 1} and thread T_lo, with L_{T_lo} = {1} and segment S, with L_S = {1} 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 on 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 How do signals work? thread_alert allows you to send an exception to a thread When can T1 send an alert to T2? Normal "writing" rule is L_T1 [= L_T2 [= L_T2^{HS} but this is very restrictive--fails if T2 allocates a new category Instead, permitted if T1 can change T2's address space and T2 can talk to T1 - which is sufficient to disrupt T2 anyway How does IPC work (p. 9)? Example: Timestamped digital signature daemon - Daemon knows secret signature key labeled {d_r 3, d_w 0, 1} - Daemon creates a service gate G with {d_r *, d_w *, 1} - Client running with process categories p_r *, p_w * - Client allocates a new category r - Client allocates a "return gate" G_r with label {p_r *, p_w *, 1}, clearance {r 0, 2} - Client jumps through gate G, starts server code with d_r *, d_w * - Server code now has access to secret key, computes signature - Returns to client by jumping through G_r; resets thread's label from {d_r *, d_w *, r *, 1} -> {p_r *, d_w *, r *, 1} Can you prevent the signature daemon from looking at data it is signing? Yes, can invoke signature daemon tainted in some new category t 3 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 How trusted is the TCP/IP network stack? How does VPN isolation work? How does user authentication work? 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?