Practical Fine-grained Privilege Separation in Multithreaded Applications

by   Jun Wang, et al.
Penn State University

An inherent security limitation with the classic multithreaded programming model is that all the threads share the same address space and, therefore, are implicitly assumed to be mutually trusted. This assumption, however, does not take into consideration of many modern multithreaded applications that involve multiple principals which do not fully trust each other. It remains challenging to retrofit the classic multithreaded programming model so that the security and privilege separation in multi-principal applications can be resolved. This paper proposes ARBITER, a run-time system and a set of security primitives, aimed at fine-grained and data-centric privilege separation in multithreaded applications. While enforcing effective isolation among principals, ARBITER still allows flexible sharing and communication between threads so that the multithreaded programming paradigm can be preserved. To realize controlled sharing in a fine-grained manner, we created a novel abstraction named ARBITER Secure Memory Segment (ASMS) and corresponding OS support. Programmers express security policies by labeling data and principals via ARBITER's API following a unified model. We ported a widely-used, in-memory database application (memcached) to ARBITER system, changing only around 100 LOC. Experiments indicate that only an average runtime overhead of 5.6 induced to this security enhanced version of application.


page 1

page 2

page 3

page 4


Salus: Fine-Grained GPU Sharing Primitives for Deep Learning Applications

GPU computing is becoming increasingly more popular with the proliferati...

μTiles: Efficient Intra-Process Privilege Enforcement of Memory Regions

With the alarming rate of security advisories and privacy concerns on co...

Hardware-assisted Trusted Memory Disaggregation for Secure Far Memory

Memory disaggregation provides efficient memory utilization across netwo...

Domain Page-Table Isolation

Modern applications often consist of different security domains that req...

Fine-Grained, Language-Based Access Control for Database-Backed Applications

Context: Database-backed applications often run queries with more author...

Prediction of Compression Index of Fine-Grained Soils Using a Gene Expression Programming Model

In construction projects, estimation of the settlement of fine-grained s...

CAP-VMs: Capability-Based Isolation and Sharing for Microservices

Cloud stacks must isolate application components, while permitting effic...

1 Introduction

While multithreaded programming paradigm has clear advantages over multiprocessed programming, it implicitly assumes that all the threads inside a process are mutually trusted. This is reflected on the fact that all the threads inside a process run in the same address space and thus share the same privilege to access resource, especially data. This assumption does not take into consideration of the sharply increasing number of multi-principal applications in recent years, wherein principals usually do not fully trust each other and therefore should not have the same privilege for data access.

When two threads are not supposed to share any data, the problem can be resolved by “moving back” to the multiprocessed programming. However, as more and more applications are by-design expecting their principal threads to share data and to collaborate, a fundamental security problem emerges; that is, how to retrofit the classic multithreaded programming model so that “thread-are-mutually-trusted” assumption can be properly relaxed?

This security problem, once solved, can even substantially benefit single-principal applications: when two threads X and Y are providing different though related functionalities and when X is compromised, the attacker can no longer corrupt Y’s data objects that are not shared with X. We view this as a second motivation of this work.

A couple of existing solutions could be potentially utilized to approach this problem. First, program partitioning, such as OpenSSH [29] and Privtrans [10], can realize privilege separation by splitting privileged code and unprivileged code into separate compartments. However, the privileged vs. unprivileged code separation policy in many cases is very different from application-logic-based (data access) privilege separation policies. For example, the policy used in our motivating example in Section 2 simply cannot be “expressed” by the privileged vs. unprivileged code “language”. Neither [29] nor [10] can handle the privilege separation needs of a set of threads that do not contain any privileged code. Second, multiprocessed programming is another resort. However, it requires the shift of programming paradigm and thus programmers can no longer enjoy the convenience of multithreaded programming. Specifically, complex inter-process communication (IPC) protocols (e.g., pipes and sockets in Chrome [1]) are needed and, thus, a considerable amount of effort is required to design, implement, and verify these protocols. In addition, message passing is considered as less efficient than direct memory sharing [17]. Third, language-based solutions, such as Jif [28] and Joe-E [26], can realize information flow control and least privilege at the granularity of program data object. However, they need to rely on type-safe languages like Java. As stated in [28], type checking and label checking “cannot be entirely disentangled, since labels are type constructors and appear in the rules for subtyping.” As the result, programmers have to rewrite legacy applications not originally developed in a type-safe language.

This paper proposes Arbiter, a run-time system and a set of security primitives, aimed at data-centric and fine-grained privilege separation in multithreaded applications. Arbiter has three main features. First, Arbiter not only provides effective isolation among principals, but also allows flexible sharing and communication between threads. The classic multithreaded programming paradigm is preserved so that programmers no longer need to conduct manual program behavior controlling, design inter-compartment communication protocols, or adopt a new language. Second, application developers only need to express security properties of data in a lightweight manner and then Arbiter will infer, configure, and enforce a complete set of policies based on those properties. Compared to the program partitioning approach which requires a per-application security protocol, Arbiter provides programmers with a unified security model and interface. Finally, Arbiter supports controlled sharing with fine-grained access control policy at data-object level. It focuses on data-centric privileges and targets at explicit control of data flow inside applications, which can be complementary to OS-level privilege separation mechanisms.

We designed a novel framework, including a run-time system and OS support, to achieve these features. To effectively separate data-centric privileges, we augment traditional multithreaded programming paradigm with programmer-transparent address space isolation between threads (In contrast, address space isolation is not transparent to programmers in multiprocessed programming). To realize controlled data sharing, we created a new OS memory segment abstraction named Arbiter Secure Memory Segment (ASMS), which maps shared application data in multiple address spaces to the same set of physical pages but with different permissions. We designed a memory allocation mechanism to achieve fine-grained privilege separation for data objects on ASMS. We also provide a security label model and a set of APIs for programmers to make lightweight annotations to express security policy. We implemented an Arbiter prototype based on Linux, including a run-time library, a reference monitor, and a set of kernel primitives.

To evaluate retrofitting complexity and performance overhead, we ported a widely used application, memcached [2], to Arbiter system. Memcached is a general-purpose, in-memory object cache. We analyzed the security requirements among the threads in memcached and customized the corresponding security policies. The changes to the program, mostly annotations, are only around 100 lines of code out of 20k total lines of memcached code. Experimental results indicate that only an average runtime overhead of 5.6% is induced. We also tested Arbiter with a group of microbenchmarks to further examine the contributing factors of the overhead.

The contributions of this work include:

  • A practical framework for data-centric privilege separation in multithreaded applications, which is mostly-transparent to programmers. Not only is privilege separation ensured, the convenience and efficiency in conventional multithreaded programming paradigm are also preserved.

  • A new memory segment abstraction and memory allocation mechanism for controlled sharing among multiple threads with fine-grained access control policy.

  • A unified security model and interface with simple rules for secure cross-thread sharing.

  • A full-featured memcached built on top of Arbiter with enhanced security and slight performance penalty.

The rest of this paper is organized as follows. Section 2 provides a high-level overview of Arbiter’s approach. Section 3 and 4 discuss the design and implementation. Section 5 describes the retrofit of memcached. Section 6 shows the performance evaluation. Related work is described in Section 7. Section 8 discusses pros and cons of our approach and Section 9 concludes.

2 Overview

This section begins with an example to show how programs can benefit from Arbiter system. Then Section 2.2 introduces Arbiter API and explains its usage. Finally, Section 2.3 presents the security model that programmers can utilize to specify security policies.

2.1 Motivating Example

To demonstrate how data-centric privilege separation can protect program internal data, consider a calendar server application which has a feature of scheduling activities for multiple users. Suppose that the server is a multithreaded program written in C using Pthreads library and includes three types of threads. First, worker threads are used to handle the connection and communication with each user client. Second, a scheduler thread is responsible for figuring out time slots available for all the participating users based on their calendars. Third, a dispatcher thread is in charge of dispatching and synchronization.

Now suppose that Alice and Bob are going to schedule a meeting using this scheduling server. However, security concerns (SC) arise when Alice and Bob want to protect the confidentiality of their calendar information against each other (SC1). This is simply because, in a traditional multithreaded process, threads could have direct access to the data belonging to other threads. If some vulnerabilities exist in worker threads and are exploited by Bob, he might be able to take control of the entire process and therefore access sensitive information of Alice. Moreover, Alice and Bob may also want to protect the integrity of their calendar information against the scheduler thread, which might have design flaws or programming errors (SC2). This is usually the case if the scheduler employs a complex algorithm and some complicated data structures. Further more, the scheduler thread, together with Alice and Bob, may also need to protect the secrecy of the results against the worker thread serving another user, say, Charlie (SC3). Table 1 provides a complete summary of access rights rooted from SC1, SC2, and SC3 (along with the labels used to specify such policies, which will be formally introduced in Section 2.3).

Alice Bob Charlie scheduler
Data L:{dr} L:{dr} L:{} L:{ar,br}
O:{ar,aw} O:{br,bw} O:{cr,cw} O:{ar,br,dr,dw}
Alice’s Cal. RW R
Bob’s Cal. RW R
Result R R RW
Table 1: Security concerns in motivating example

These complex permissions are hard to achieve in traditional multithreaded programming model. With Arbiter, however, programmers can easily tackle such problems. In order to make programs run on Arbiter, programmers simply need to write or port programs in mostly the same way as writing a traditional multithreaded program, except for some lightweight annotations to denote the desired security policy. The following code fragments, implementing the above idea of secure activity scheduling, illustrates the ease of adoption of Arbiter system.

First, worker thread accepts user’s input of calendar and stores it as a dynamic data object struct cal. The protection of user’s calendar is through Arbiter’s secure memory allocation ab_malloc. Its second argument L_user is a label representing how the data is shared or protected. Here, the value ur,uw and dr can help to resolve SC1 and SC2, that is, the calender is read-writable only to the corresponding user and is read-only to the scheduler. Other than that, nobody can access that data (see Section 2.3 for how the label works). Note that ur,uw will be instantiated to ar,aw for Alice and br,bw for Bob at run time.

#define CAL_SIZE sizeof(struct cal)
thr_worker(argW_t *arg) {
   // receive input of user’s calendar
  label_t L_user = {dr, ur, uw};
  calendar = (struct cal*)ab_malloc( CAL_SIZE, L_user);
   // send out result

Next, the scheduler thread receives the calendar of Alice and Bob from the worker threads and computes the available time slots. Similarly, scheduler thread returns the results allocated using ab_malloc. The label L_ret can help to ensure SC3, that is, the results are only readable to worker threads serving Alice and Bob.

thr_scheduler(argS_t *arg) {
   // compute available time slots using certain algorithm
  L_ret = {dr, dw};
  ret = (struct cal*)ab_malloc(CAL_SIZE, L_ret);

Finally, regarding the dispatcher thread, it first dispatches two worker threads with label {dr} and corresponding ownership O_user. Here, ownership means the possession of certain types of information being protected (see Section 2.3 for details). After user inputs are collected, the dispatcher thread dispatches the scheduler thread to do the computation. The scheduler thread is assigned with label L_sched and ownership O_sched. Note that the labels/ownerships are typically programmed as variables, not constants. These variables are then instantiated at run time to specific values (e.g., according to a config file). In sum, these annotations will let Arbiter enforce the separation of privileges shown in Table 1. It should be pointed out that Arbiter is focused on data-centric privilege. Thus, OS-level mechanisms like mandatory access control (e.g. SELinux [24]), capability (e.g. Capsicum [36]), or DIFC (e.g. Flume [22]) can be utilized to provide complementary privilege separation.

dispatcher() {
  // dispatch worker to serve request
  O_user[0] = {ar, aw};
  O_user[1] = {br, bw};
  ab_pthread_create(thrW[i], NULL, thr_worker, argW[i],{dr},O_user[i]);
  // dispatch scheduler to compute
  L_sched = {ar, br};
  O_sched = {ar, br, dr, dw};
  ab_pthread_create(thrS, NULL, thr_scheduler,argS,L_sched,O_sched);

As shown above, fine-grained access control inside multithreaded applications is achieved in a simple manner. Programmers’ effort is thus minimized. They can explicitly express the desired security policy while still preserving the multithreaded programming paradigm. This property is primarily enabled by the Arbiter API.

2.2 Arbiter Api

Arbiter API has three subcategories which are used for labeling, threading, and sharing. Figure 1 lists a representative part of them. With these API calls, programmers can create threads that will be granted with certain labels/ownerships at run time. They can also have dynamic data allocated with particular labels.

cat_t create_category(cat_type t); Create a new category of type t, which can be either secrecy category CAT_S or integrity category CAT_I. void get_label(label_t L); Get the label of a thread itself into . void get_ownership(own_t O); Get the ownership of a thread itself into . void get_mem_label(void *ptr, label_t L); Get the label of a data object into . int ab_pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void *),void *arg, label_t L, own_t O); Create a new thread with label and ownership . int ab_pthread_join(pthread_t thread, void **value_ptr); Wait for thread termination. pthread_t ab_pthread_self(void); Get the calling thread ID. void *ab_malloc(size_t size, label_t L); Allocate dynamic memory on ASMS with label . void ab_free(void *ptr); Free dynamic memory on channel heap. void *ab_calloc(size_t nmemb, size_t size, label_t L); Allocate an array of memory on channel heap with label . void *ab_realloc(void *ptr, size_t size); Change the size of the memory on channel heap. void *ab_mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset, label_t L); Map files on ASMS with label .

Figure 1: A partial list of ARBITER API

To ease the programmers’ work of either porting existing programs or writing new ones, Arbiter’s memory allocation and thread library are fully compatible with the C Standard Library and the Pthreads Library. First, their syntax are in exact accordance. For example, glibc malloc also takes a size argument of size_t type and returns a void pointer pointed to the allocated memory chunk. Second, if programmers use ab_malloc without assigning any label (L = NULL;), it will behave in the same way as glibc malloc, i.e., allocating a memory chunk read-writable to every thread. This makes it possible that programmers can incrementally modify their programs to run on Arbiter.

2.3 Security Model

To realize data-level privilege separation, we need a set of labels for programmers to properly express security policy. We adopted the notion used in HiStar’s DIFC label model [39]. DIFC’s label is a better fit for Arbiter than per-data ACLs in that it is also data-centric and can set privileges for principals in a decentralized manner. However, it should be noted that although Arbiter uses DIFC label notions, itself is not a DIFC system (see Section 8 for more discussion). Therefore, Arbiter does not perform information flow tracking or taint operations.

In light of the HiStar’s notation, we use label to describe the security property of principals (i.e. threads) and ASMS data objects. A label is a set that consists of secrecy and/or integrity categories. For a data object, secrecy categories and integrity categories help to protect its secrecy and integrity, respectively. For a thread, the possession of a secrecy category (, where represents the name of a category) denotes its read permission to data objects protected by that category; likewise, an integrity category () grants the thread with the corresponding write permission. We also use the notion ownership to mark a thread’s privilege to bypass security check on specific categories. A thread that creates a category also owns that category. Different from threads, data objects do not have ownership. Note that categories can be dynamically created by threads; but labels, once assigned, are not allowed to be changed.

The security in Arbiter is guaranteed by the following three rules:

RULE 1 - Data Flow We use , to denote that data can flow from to ( and represent threads or data objects). This means: 1) every secrecy category in is present in ; and 2) every integrity category in is present in . If the bypassing power of ownership is considered, a thread can read object iff

which can be written as

Similarly, thread can write object iff

In Table 1, for example, Alice’s calender has label . Bob has label and ownership . The relation between them is that and . Therefore, Bob does not have either read or write access to Alice’s calender. However, since the scheduler has ownership and thus the relation , the scheduler can read Alice’s calender. Still, the scheduler cannot corrupt Alice’s calender because . In the same way, the whole table of access rights can be figured out and, thus, the security concerns SC1, SC2, and SC3 can be addressed. Meanwhile, the labeling semantics are easy to understand. For example, the label of Alice’s calendar simply implies that it is readable by the scheduler and read-writable by Alice.

RULE 2 - Thread Creation Thread creation is another way of data flow and privilege inheritance. Therefore, a thread is allowed to create a new thread with label and ownership iff

Of course, the new thread is not allowed to modify its own labels.

RULE 3 - Memory Allocation Memory allocation also implies that data flows from a thread to the allocated memory. As the result, a thread can get a memory object allocated on ASMS with label iff

With the above rules, security critical operations can be mediated and regulated. The Arbiter will, based upon the assigned labels/ownerships and the above rules, infer, configure, and enforce the corresponding security policy.

3 Design

3.1 Design Goals

The following bullets summarize the three major goals that guided our design.

  • Data-centric (orthogonal to OS-level) privilege separation in multithreaded applications (A1).

  • Secure inter-thread communication (B1) with fine-grained access control at data-object level (B2).

  • To preserve multithreaded programming paradigm (C1) with a unified model (rather than per-application security protocols) (C2) for programmers to explicitly control data flow inside applications (C3).

To realize A1, we designed a novel framework for multithreaded applications (Section 3.2). To address B1 and C1, we created a new abstraction named Arbiter Secure Memory Segment (Section 3.3). To tackle B2, C2, and C3, we designed Arbiter library (Section 3.4). Figure 2 sketches the architecture of Arbiter system.


Figure 2: System architecture

3.2 ARBITER Framework

In Arbiter framework, a multithreaded program runs inside a special thread group, called Arbiter group. In addition to the application threads or what we call member threads, there is a privileged thread: arbiter thread. Arbiter thread is essentially a reference monitor, isolated from member threads. Its privilege does not refer to root privilege or privilege of OS-level operations. Instead, it means the privilege to manage the Arbiter group, for example, to manage the label/ownership information for the member threads. The member threads, on the other hand, are the application threads being protected. They run on top of the Arbiter API. For those common processes and threads running on the same OS, they are not influenced by the existence of Arbiter group. Meanwhile, multiple Arbiter groups can co-exist on the same OS without disturbing each other. This means that many Arbiter protected applications can concurrently execute.

Arbiter achieves privilege separation by applying address space isolation to the threads. However, it is significantly different from the multiprocessed programming: the member threads still have the luxury of direct sharing of resources. For example, they share code, global data, open files, signal handlers, etc. This is achieved by mapping the corresponding virtual memory segments to the same set of physical memory. In order to ensure security, the privilege of member threads to access shared resource is different from that in a traditional multithreaded program. For example, stack segment is made private to each member thread and inaccessible to others so that stack integrity can be preserved. For similar reasons, the heap is also made private. Now the remaining challenge is how to allow legitimate data objects to be shared efficiently and securely between member threads. To tackle this problem, we created a new memory segment called Arbiter Secure Memory Segment.

3.3 ARBITER Secure Memory Segment (ASMS)

To preserve the merits of multithreaded programming paradigm, such as reference passing and data sharing, Arbiter needs to support legitimate communication between member threads. What makes this even more challenging is that the communication should be secure.

Existing approaches seem to have difficulties to achieve this goal. First, message passing scheme is a preferable solution for program partitioning and multi-process architecture. However, message passing is considered as less efficient than direct memory sharing [17], especially when security is concerned so that a complex message passing protocol is needed. Anyway, it dramatically changes the multithreaded programming paradigm which unfortunately increases the burden of the program developer. Second, some OS utilities can help to realize direct memory sharing, for example, Unix system call mmap and System V IPC shmget. However, none of them can satisfy the complex security needs in multi-principal situations, such as those in our motivating example. The reason is that the owner cannot directly assign different access rights for different principals. Third, file system access control mechanisms, such as Unix world/group/owner model, can achieve the security requirements in multi-principal situations. Nevertheless, it has to assign a unique UID for each thread and, even worse, change the programming paradigm by relying on files to share data.

We created a novel memory segment called Arbiter Secure Memory Segment (ASMS) to achieve secure and efficient sharing. ASMS is a special memory segment compared with other memory segments like code, data, stack, heap, etc. In kernel, the corresponding memory regions of ASMS is of a special type and thus cannot be merged with other types of memory regions. Regarding virtual memory mapping style, it is also different from both anonymous mapping and file mapping. It has a synchronized virtual-to-physical memory mapping for all the member threads inside an Arbiter group, yet the access permissions could be different. Figure 3 illustrates the ASMS setup for the calendar example described in Section 2.1. We leverage the paging scheme to achieve this property. Furthermore, only the arbiter thread has the privilege to control ASMS. This means that the member threads delegate the memory allocation/deallocation requests as well as access rights configurations of ASMS to the arbiter thread. Member threads, on the other hand, cannot directly allocate/deallocate memory on ASMS. Neither can they modify their access rights to ASMS on their own.

Figure 3: ASMS setup in the motivation example

The page fault exception handling routine for ASMS is also different from common page fault handling, such as copy-on-write and demand paging. Legal page faults on ASMS will trigger a particular handler similar to demand paging. The shared page frame rather than a free frame will be mapped. For illegal page faults, which are mostly security violations in our case, either a segmentation fault by default or a programmer-specified handling routine will be triggered.

Since ASMS is a kernel abstraction, it exports primitives to the arbiter thread to conduct operations on it. These operations include memory allocation and deallocation, access rights configuration, and so on.

3.4 ARBITER Library

Arbiter library is a user-space run-time library built on top of Arbiter’s kernel primitives. It strives to provide a fine-grained secure sharing mechanism based on ASMS as well as a unified model for programmers to explicitly control data sharing. In terms of component, it has a back end (i.e. the arbiter thread), a front end (i.e. the Arbiter API), and a remote procedure call (RPC) connection in between.

The real difficulty here is how to achieve fine-grained access control for program data objects on ASMS. According to the paging scheme and page table construct, access rights of a page are described by a corresponding page table entry. Therefore, traditional memory allocation mechanisms, such as dlmalloc [23], are not suitable because all the data objects on a page can only have one kind of access rights for a certain thread. So an intuitive idea is to allocate one page per data object (or multiple pages if the data object is large enough). However, this is not practical because of two reasons. First, page allocation and permission configuration usually requires a system call to the kernel. One data object per system call will undoubtedly induce too much overhead. Second, a huge amount of internal fragmentation will be incurred if the size of data objects is much smaller than the page size, which is usually the case.

To solve this problem, we devised a special memory allocation mechanism for ASMS: permission-oriented allocation. The key idea is to put data objects with identical labels (thus the same access rights for a thread) on the same page. Allocator allocates from the system one memory block per time so as to save the number of system calls. Here a memory block is a continuous memory area containing multiple pages. Figure 4 demonstrates this idea (more details in Section 4.2). In this way, both the internal fragmentation and performance overhead can be reduced to a large extent.

At the same time of memory allocation, the arbiter thread also needs to correctly determine and configure the access rights of each ASMS block for different member threads. To do this, the arbiter thread maintains a real-time registry of the label/ownership information for member threads and ASMS memory blocks. For example, if there is a request to allocate a new block with a certain label, the arbiter thread first looks up the label/ownership information for every thread. Then it determines the access permissions for every member thread according to the data flow rule (RULE 1). After that, it calls the kernel primitives to construct memory regions and configure access permissions.


Figure 4: A typical memory layout of ASMS

4 Implementation

We implemented a prototype of Arbiter based on Linux. This section highlights the major components of the implementation, including Arbiter group management (Section 4.1), ASMS memory management (Section 4.2), and page fault handling (Section 4.3).

4.1 Management of ARBITER Group

Application Startup Each Arbiter group has the preliminary environment settings that need to be done before the application runs. Thus, the arbiter thread executes first and acts as the bootstrap of the protected application. Arbiter first registers its identity to the kernel in order to get privileges to perform subsequent operations on the Arbiter group and ASMS management. We implemented a system call ab_register for this purpose. Next, the arbiter thread starts the application by using the combination of Unix operations fork and exec with provided arguments, and blocks until a request coming from member threads.

Once the application is started, it can create child member thread by calling ab_pthread_create and ab_pthread_join to wait for threads’ termination. We implemented it using system call clone. Registration to kernel, also via ab_register, is conducted before the new thread starts to execute. The label and ownership of the new thread, if not specified, default to its parent’s. ab_pthread_join is implemented using waitpid. Although implemented differently from Pthreads library, we have managed to offer a compatible syntax and functionality.

ARBITER Group Identification In order to determine privileges for the threads in an Arbiter group, OS kernel needs to have an identification mechanism to distinguish: (1) member threads from the arbiter thread, (2) one Arbiter group from another, and (3) threads in Arbiter groups from non-member threads, i.e., normal processes and threads. We utilized the process descriptor structure task_struct to store the identity information. Specifically, we add two fields to the process descriptor. One is ab_identity, a 32-bit identification whose upper 31 bits stores the Arbiter group ID and the last 1 bit indicates the role of either arbiter or member threads. All the threads in an Arbiter group share the same group ID. This means that a total number of Arbiter groups can concurrently exist, which is sufficient for a general purpose OS. Note that ab_identity for a normal process is 0. The other item we added is ab_task_list, a doubly-linked list connecting every member in an Arbiter group, including the arbiter thread. Therefore, given a member thread, kernel procedures are able to find its arbiter thread and peer threads.

Label Registry Another important aspect of Arbiter group management is to keep track of labels/ownerships of member threads and ASMS data objects. Whenever a new thread is created or a new data object is allocated, arbiter thread should store their label/ownership information. Meanwhile, when to make a decision based on RULE 1, 2, and 3, arbiter thread must also lookup the label/ownership efficiently. As a result, we implemented a registry and a set of storing/retrieving facilities. The registry are essentially two hash tables, one is for member threads and other for data objects. For the sake of efficiency and simplicity, the latter one is combined with the hash table for memory allocation.

RPC The arbiter thread plays a major role in Arbiter group management by performing privileged operations, such as allocating ASMS data objects for member threads. Therefore, a reliable and efficient RPC connection between member threads and the arbiter thread is quite necessary. We selected Unix socket to implement the RPC. The major advantage of Unix socket in our context is about security. Unix sockets allow a receiver to get the Unix credentials, such as PID, of the sender. Therefore, the arbiter thread is able to verify the identity of the sender thread. This is especially meaningful in case that the sender thread is compromised and manipulated by the attacker to send illegal requests or forged information to the arbiter thread.

Authentication and Authorization Every time the arbiter thread is about to handle a RPC, it first performs security verifications, including authentication and authorization. Authentication helps to make sure the caller is a valid member thread in that group. The arbiter thread authenticates a caller by verifying the validity of its PID acquired from the socket. Authorization ensures that the caller has the privilege for the requested operation, for example, to create a child thread or to allocate an ASMS data object. This is achieved by label/ownership comparison according to the RULE 2 and RULE 3. If either one of the above verifications fails, the arbiter thread simply returns the RPC with an indication of security violation. Otherwise, arbiter thread continues to perform the rest of the handling procedure.

4.2 ASMS Memory Management

ASMS Memory Region Handling ASMS is a special memory segment compared with other segments like code, data, stack, and heap. Therefore, the kernel-level memory region descriptors for ASMS, i.e., structure vm_area_struct in Linux, should also be different from those of others. To do this, we added a special flag AB_VMA to the vm_flags field of the vm_area_struct descriptor. We also want to make sure that only the arbiter thread can do allocation, deallocation, and protection modification of ASMS memory regions. So we modified related system calls, such as mmap and mprotect, to prevent them from manipulating ASMS.

To properly create or destroy ASMS memory regions so as to enlarge or shrink ASMS, we implemented a set of kernel procedures similar to their Linux equivalents such as do_mmap and do_munmap. The difference is that when creating or destroying ASMS memory regions for a calling thread, the operation will also be propagated to all the other member threads in that Arbiter group. The doubly-linked list ab_task_list helps to guarantee that every member threads can be traversed. Once these batched operations are complete, each member thread will have a memory region, with the same virtual address range, created or deleted. Nevertheless, the page access rights described in each memory region could be different, based on the decision made by the arbiter thread. In order to know the arbiter thread’s decision, kernel procedures export several system calls to the arbiter thread. They include absys_sbrk, absys_mmap, and absys_mprotect, which have similar semantics as their Linux equivalents.

In our current implementation, ASMS has a default size limit of 500MB, ranging from the virtual address of 0x800000 00 to 0x9fffffff. In practice, however, both the location and the size of ASMS is adjustable by the programmer.

User-space Memory Allocation For arbiter thread to handle ab_malloc/ab_free requests from member threads, we implemented a user-space memory allocation mechanism built on top of the system calls for ASMS management. To realize fine-grained access control of data objects on ASMS, this allocation mechanism is permission-oriented: to put data objects with identical labels on the same page. To reduce the frequency of system calls, a block containing multiple pages instead of a single page is allocated per time.

Blocks are sequentially allocated from the start of ASMS. Of course, some data objects might have larger size and cannot fit in a normal block. In this case, large blocks will be allocated backward starting at the end of ASMS. The patten of this memory layout is shown in the top half of Figure 4. Inside each block, we took advantage of the dlmalloc strategy [23] to allocate memory chunks for each data object. The bottom half of Figure 4 depicts the memory chunks on pages inside a block.

Algorithms A high-level presentation of the allocation/deallocation algorithm is shown in Figure 5. For clarity, we omit the discussion on the strategy of memory chunk management, which is adopted from dlmalloc.

  • Allocation If the size of the data is larger than a normal block size (i.e. threshold), a large block will be allocated using absys_mmap (line 5). Otherwise, the arbiter thread will search for free chunks inside blocks with that label (line 7). If there is an available free chunk, the arbiter thread simply returns it. If not, the allocator will allocate a new block using absys_sbrk (line 12).

  • Deallocation For a large block, the arbiter thread simply frees it using absys_munmap (line 3) so that it can be reused later on. Otherwise, the arbiter thread puts the chunk back to the free list (line 5). Next, the arbiter thread checks if all the chunks on this block is free. If so, this block will be recycled for later use (line 7).

1ablib_malloc(sz, L)
2    if sz > threshold
3        for every member thread
4            Compute permission
5            Allocate a large block
6        return
7    Search free chunks in blocks with label L
8    if there is an available free chunk
9        return
10    for every member thread
11        Compute permission
12        Allocate a normal block
13    return
2    if it is a large block
3        Free the block
4        return
5    Free the chunk pointed by ptr
6    if the whole block is free now
7        Free the block
8    return
Figure 5: ASMS memory allocation algorithm

ASMS Initialization for New Thread In addition to memory allocation/deallocation for existing member threads, another important responsibility of the arbiter thread is to initialize the ASMS for newly created threads. When handling ab_pthread_create, the arbiter thread walks through every existing block. For each block, it determines the access permission for the new thread by comparing the labels/ownerships. Then it calls absys_mprotect to finish the access rights configuration. Therefore, when the new thread starts to execute, its ASMS has already been correctly set up.

4.3 Page Fault Handling

In our implementation of Arbiter, two types of page faults need special treatment. One is the page fault caused by accessing ASMS. The other is the page fault caused by accessing data segment, including both initialized data (.data) and uninitialized data (.bss). Figure 6 depicts the flow diagram of the page fault handler.


Figure 6: Page fault handling diagram in ARBITER

ASMS Fault A page fault caused by accessing ASMS normally leads to two possible results: ASMS demand paging and segmentation fault. ASMS demand paging happens when a member thread legally accesses an ASMS page for the first time. In this case, the page fault handler should find the shared physical page frame and create and configure the corresponding page table entry for the member thread. The protection bits of the page table entry are determined according to the associated memory region descriptor. In this way, subsequent accesses to this page will be automatically checked by MMU and trapped if illegal, for example, trying to write with read-only permission. This hardware enforced security check significantly contributes to the satisfying run-time performance of Arbiter. Illegal access to an ASMS page will result in a SIGSEGV signal sent to the faulting thread. Then it is up to the programmer to either, by default, let the thread terminate or customize a handling routine. We implemented a kernel procedure do_ab_page and made corresponding modifications to the Linux default page fault handler to realize the above idea.

Data Segment Fault Most multithreaded programs also use global data to share information. In order to minimize programmers’ porting effort, we decide to make the data segment accessible to all member threads and programmers only need to move security-critical global data on to ASMS instead of all of them. If data segment is made private for each thread, programmers will be forced to declare all the global data on ASMS. Though technically feasible, it inevitably increases programmers’ porting effort. Meanwhile, our observation indicates that global data is usually not security sensitive because a large portion of it is metadata rather than user data. Therefore, we decided to make the data segment accessible to all member threads. In this way, programmers only needs to move the security sensitive global data into ASMS instead of all of them. We implemented another page fault handling subroutine to do this. Since data segment is originally shared after clone, this subroutine mainly helps to avoid the copy-on-write operations.

A minor yet critical issue with data segment page fault handling is about the futex (i.e. fast userspace mutex) mechanism. Multithreaded programs often rely on mutexes and condition variables, which are often declared on data segment, for mutual exclusion and synchronization. In Pthreads, both of them are implemented using futex. For retrieving purpose, kernel regards the key of each futex as either the address of mm_struct if the futex is on an anonymous page or the address of inode if the futex is on a file mapped page. In Arbiter, since data segment is anonymous mapping but the mm_struct for each member thread are different, kernel will treat the same mutex or condition variable as different ones. Of course, we can force programmers to declare them on ASMS, which is file mapping and does not have this issue. However, we again decided to reduce programmers’ effort by modifying the corresponding kernel routine get_futex_key and set the key to a same value.

4.4 Implementation Complexity

We implemented Arbiter prototype using C. We add about 2,000 LOC to the Linux kernel. The arbiter thread including memory allocation, part of which is based on uClibc [3], contains around 3,500 LOC. The Arbiter API contains roughly 600 LOC. Arbiter’s trusted computing base (TCB) consists of the arbiter thread and the kernel, as shaded in Figure 2. The Arbiter API is not part of the TCB.

5 Application

To explore Arbiter’s practicality on ease of adoption and to examine its effectiveness to enhance application security, we ported memcached [2] to Arbiter system. Section 5.1 introduces memcached. Section 5.2 presents the effort we applied to retrofit memcached. Section 5.3 discusses how memcached could benefit from Arbiter in terms of security.

5.1 Memcached

Memcached is a distributed, in-memory object caching system, intended for speeding up dynamic web applications by alleviating database load. It caches data and objects from results of database queries, API calls, or page rendering into memory (i.e. RAM) so that the average response time can be reduced to a large extent. It is widely-used for data-intensive websites including LiveJournal, Wikipedia, Facebook, YouTube, Twitter, etc.

Memcached uses a client-server architecture. The servers maintain a key/value associative array. There can be multiple clients talking to the same server. Clients use client libraries to contact the servers to make caches or queries.

Security issues arise considering the fact that memcached servers are multithreaded. Two worker threads in the same server might work on behalf of different applications or users, which may not be mutually trusted. Although memcached has SASL (Simple Authentication and Security Layer) authentication support, it cannot prevent authenticated clients from querying or updating others’ data, either accidentally or maliciously. What might be even worse is that the vulnerabilities in memcached [4, 5] could be exploited by an adversary (e.g., through a buffer overflow attack) so that the compromised worker thread can arbitrarily access any data on that server. Approaches like SELinux can help to restrict OS-level operations of the compromised thread, but cannot deal with program data objects.

5.2 Retrofitting Effort

To mitigate the security problems stated above, we retrofitted a memcached server on top of Arbiter system. There are mainly three types of threads in a memcached process: main thread, worker thread, and maintenance thread. Upon arrival of each client request, the main thread dispatches a worker thread to serve that request. Periodically, maintenance threads wake up to maintain some important assets such as the hash table. For demonstration clarity, we only focus on the main thread and worker threads and regard the maintenance threads with the same privilege as the main thread.

To separate the data-centric privileges for each thread, we replace the original thread creation function pthread_create with ab_pthread_create. Regarding dynamic data that needs to be shared, we replace the malloc/free function families with ab_malloc/ab_free to allocate them on ASMS. For those dynamic data of private use for each thread, we do not make any change so that they will be allocated on their own heap that is private. Since global data are shared in Arbiter, we only need to declare those which contain sensitive information onto ASMS. In case of memcached, however, we do not find any type of this global data.

We assign each thread and data with different labels and ownerships. For example, consider a memcached server which is supposed to serve two applications App A and App B, or two users User A and User B. To make the scenario more complex, we assume that A has a higher privilege to read (but not write) B’s data. Meanwhile, some metadata created by the main thread in memcached are shared with worker threads for dispatching purpose. For instance, the shared CQ_ITEM contains information about the connection with the client and some request details. We want to protect the integrity of these critical metadata so that they are read-only to worker threads. In light of these security needs, we made the corresponding configurations shown in Table 2.

Main/Maintenance A B
Data L:{} L:{mr,br} L:{mr}
O:{mr,mw} O:{ar,aw} O:{br,bw}
A’s Data RW
B’s Data R RW
Table 2: Sample labeling configuration for memcached

We modified the source code of memcached based on the above scenario. We slightly changed the original thread dispatching scheme so that requests from different principals can be delivered to the associated worker thread. This modification does not affect other features of memcached. The changes to the program are only around 100 lines of code out of 20k in total. Among these changes, label/ownership declaration takes 31 and function replacement for label annotation takes 46.

5.3 Protection Effectiveness

To demonstrate Arbiter’s protection effectiveness, we assume that an adversary has exploited a program flaw or vulnerability in memcached and thus take control of a worker thread. We have simulated various attempts to access other threads’ data. Some typical scenarios are as below.

  • First, the adversary may try to read or write the data directly. A segmentation fault will then be triggered.

  • The adversary may want to call mprorect to change the permission of ASMS. However, Arbiter forbids mprotect to operate ASMS memory region.

  • The adversary may attempt to call ab_munmap first and then call ab_mmap to indirectly modify the permission to ASMS. However, since it does not have permission to access the data, arbiter basically will deny the ab_munmap request during authorization.

  • The adversary may also want to fork a child process outside of that Arbiter group. Still, the child process cannot modify the permission of ASMS. If the child process calls ab_register to set itself as a new arbiter thread, it will have the privilege to manage ASMS. However, it is in a new Arbiter group and its ASMS can no longer have the same physical mapping as the old ASMS does.

  • A more sophisticated approach is to forge a reference and fool an innocent worker thread to access data on behalf of the adversary. However, Arbiter provides an API get_privilege, that allows the innocent thread to verify if the requesting thread has the necessary privileges.

Nevertheless, the adversary can starve resource by creating member threads or allocating ASMS memory. This can be properly addressed by enforcing a resource limit.

6 Performance Evaluation

In this section, we describe the experimental evaluation of Arbiter prototype. Our goal is to examine the system overhead (Section 6.1) and to test the application level performance (Section 6.2).

All of our experiments were run on a server with Intel quad-core Xeon X3440 2.53GHz CPU and 4GB memory. We used 32bit Ubuntu Linux (10.04.3) with kernel version 2.6.32. Since we implemented the ASMS memory chunk allocation mechanism based on uClibc 0.9.32, we used the same version as microbenchmarks for comparison. We employed glibc 2.11.1 with the Pthreads library version 2.5 to compare with Arbiter’s thread library. In addition, we built a security-enhanced memcached based on its version 1.4.13 and we chose libMemcached 1.0.5 as the client library.

Operation Linux (s) ARBITER (s) Overhead
(ab_)malloc 4.14 9.09 2.20
(ab_)free 2.06 8.36 4.06
(ab_)calloc 4.14 8.41 2.03
(ab_)realloc 3.39 8.27 2.43
(ab_)pthread_create 91.45 145.33 1.59
(ab_)pthread_join 36.22 41.00 1.13
(ab_)pthread_self 2.99 1.98 0.66
create_category 7.17
get_mem_label 7.66
get_label 7.65
get_ownership 7.55
ab_register 2.74
ab_null 5.84
(RPC round trip)
(absys_)sbrk 0.65 0.76 1.36
(absys_)mmap 0.60 0.83 1.38
(absys_)mprotect 0.83 0.92 1.11
Table 3: Microbenchmark results in Linux and ARBITER

6.1 Microbenchmarks

The runtime overhead induced by Arbiter mainly comes from the Arbiter API. Therefore, we selected these API calls as microbenchmarks to quantify the performance overhead. We ran each microbenchmark on Linux and Arbiter, and recorded the average time consumption of 1,000 times of repeat. In order to make a fair comparison, we ran the test program as a single-threaded application on Arbiter. Table 3 shows the results.

The major contributor of the latency of Arbiter API is the RPC round trip between member threads and the arbiter thread. The RPC round trip contains RPC marshaling, socket latency, etc. To measure the overhead from RPC, we implemented a null API named ab_null to test the RPC time usage. As shown in Table 3, a RPC round trip takes 5.84 s on average. This result can help to explain most of the API overhead. One exception is ab_register, which is a direct system call and does not involve any RPC round trip. Meanwhile, ab_pthread_self runs even faster than its Linux equivalent. This is due to our own design of thread management and thus a simpler implementation using getpid to return the thread ID.

In addition to the latency of RPC round trip, the system calls made by the arbiter also contribute to the API overhead. To further examine the system call overhead, we selected sbrk, mmap, and mprotect to compare between Linux and Arbiter. On average, Arbiter’s version of these system calls have a 28% overhead.

The above experiment does not take into account of the number of member threads. Theoretically, since the memory allocation on ASMS for one member thread is also propagated to others, the time usage for memory allocation should be proportional to the number of concurrent member threads. Meanwhile, for those API calls dealing with label information such as get_label, operations are only conducted on one thread, i.e., the calling thread. Thus, in theory, the time usage of these API calls should remain constant despite the change of thread number. The left subfigure in Figure 7 shows the time usage of ab_malloc and get_label as the number of member threads increases. The result conforms to our theoretical prediction very well. The time increasing rate of ab_malloc is roughly 5.7% per additional thread. And the time variation of get_label is almost 0.

In addition, the creation of member threads also involves the access rights reconfiguration for ASMS. The thread creation time should be proportional to the size of allocated pages on ASMS. The right subfigure in Figure 7 shows the time consumption of ab_pthread_create with regard to the allocated ASMS size. It is also in line with our expectation.

Figure 7: ARBITER API performance regarding number of threads and allocated ASMS size

6.2 Application Performance

We conducted experiments on memcached to further evaluate the application level performance of Arbiter. According to the results of the above experiments, the overhead induced by Arbiter is related to the number of member threads. Hence, we stick to the scenario introduced in Section 5.2, in which four threads are involved We built a client as benchmark which constantly contacts memcached on behalf of User X and User Y. We measured the throughput of two basic memcached operations, SET and GET, with various value size and key size. The results are compared with that of unmodified memcached. In the upper two sub of Figure 8, we anchored the key size to 32 bytes and changed the value size. Note that we selected a non-linear distribution of value size in order to cover as much range as possible. In the lower two sub of Figure 8, we fixed the value size to 256 bytes and adjusted the key size. Each point in the figure is an average of 100,000 times of repeat. All together, the average performance decrease incurred by Arbiter is about 5.6%, with the worst case of 19.0%.

Compared to the microbenchmarks, the application level performance overhead is relatively small. The major reason is that the Arbiter specific operations (i.e. Arbiter API calls) are invoked infrequently. For most of the time, memcached does the real work rather than calling Arbiter API. In fact, memcached is a memory intensive application. For those applications which are not as memory intensive, we believe they could achieve better overall performance.

Figure 8: Performance comparison for memcached

7 Related Work

Privilege separation has long been the primary principle for secure system construction. It has been applied to system software dated from Multics to micro-kernels [21] and Xen [27, 14] to build secure and resilient operating systems and virtual machine monitors. For application software, it has also been widely used to securely construct severs [29, 6, 9], web applications [13] and browsers [1, 35, 20].

A typical way to achieve privilege separation is to leverage OS-level run-time access control techniques. For example, Apache [6] and OpenSSH [29] split their functionality into different processes with different UID/GID, and leverage OS-level access control mechanism to enforce privilege separation. Traditionally, UNIX systems employs discretionary access control (DAC) mechanism in which users can specify access permissions for their own files. To further provide system-wide policies and centralized control, mandatory access control (MAC) mechanisms like SELinux [24] and Apparmor [7] are proposed for commodity OSes. In MAC systems, the system administrator is responsible for all the type assignment and policy configuration, which could be inflexible and error-prone. Thus, other research approaches are proposed that allow users to create protection domains on their own. Capsicum [36] provides commodity UNIX systems with practical capability primitives to support privilege separation. Flume [22] implements DIFC in Linux. Besides research effort on commodity OSes, EROS [32], Asbestos [15], and HiStar [39] are clean-slate designed operating systems that take least-of-privilege as the major design principle and offer native security primitives (capabilities or information flow labeling) bound with OS abstractions. The primary difference between OS-level access control systems and Arbiter is the type of privilege concerned: OS-level approaches focus on managing privileges to OS resources (e.g, files, networking, and devices) while Arbiter is primarily used for controlling access privileges of data object within a program. Thus, these approaches and Arbiter are complementary to each other to build secure programs.

Sandboxing techniques such as software fault isolation (SFI) [34, 16, 38] and binary translation [18] are developed to restrict the privilege of certain portion of untrusted code inside a trusted program. The major goal of sandbox approaches is isolation and confinement, which is to prevent untrusted code from manipulating control and data of the main program illegally. In contrast, Arbiter not only enforces isolation, but also provides primitives for secure sharing and communication in multi-principal programs with mutually untrusted relationships. Wedge [9], though taking heap data access into consideration, still targets at splitting monolithic programs into functional compartments with just-enough privileges. Arbiter, however, aims to resolve the complex data sharing relationships in multi-principal applications, which is difficult to achieve in the traditional multithreaded programming.

Language-based approaches can help programmers enforce fine-grained access control in regard to program internal data and semantics. Typically these approaches integrate security notions into the type systems of the programming language, and/or use verification methods and compiler-inserted checks to enforce security policies and access control. Jif [28] and Joe-E [26] are Java language extensions that implements DIFC and capability security primitives, respectively. Laminar [30] is a Java language-based DIFC system with the OS support to handle OS abstractions. Singularity [17] is a research operating system which leverages language (C# extension) support and static verification to achieve isolation and controlled communication. Aeolus [12] is a DIFC platform for building secure distributed applications using practical abstractions relying on memory-safe language. Compared to these language-based approaches, Arbiter does not require a strongly typed language such as Java and C#. Therefore, it takes much less effort to build or enhance the security for multithreaded programs written in C/C++ and Pthreads library using Arbiter.

Virtual machine offers another layer of indirection for security mediation and enforcement [33], with advantages of transparency and tamper-proof. In Arbiter, we choose not to employ virtual machine for building our security mechanisms, primarily because of the semantic gap (e.g., managing process memory regions at the VMM-level) and performance (e.g., system call vs. hypercall) reasons. However, Arbiter could incorporate existing virtual machine security techniques to reduce kernel TCB and improve the security of OS kernel. These approaches include: reducing the trust of kernel for applications [11, 25], kernel integrity protection [31], and sandboxing untrusted kernel code [19, 37].

8 Discussion

Arbiter is a generic approach and applicable to a variety of applications. In this paper, we demonstrate its application in calender server and memcached. With a fully compatible memory allocation and thread library, we believe that most, if not all, types of multi-principal multithreaded applications can benefit from Arbiter. Potential candidates include cloud-based collaborative applications, database servers, Web servers, printing servers, etc. In the future, we plan to port more legacy software onto Arbiter and further demonstrate its applicability.

Nevertheless, some limitations still exist in our current design and implementation of Arbiter. One limitation is that the user-space ASMS memory allocator uses a single lock for allocation/deallocation. Therefore, the processing of allocation and deallocation requests has to be serialized. This partly contributes to the performance overhead. A finer lock granularity can help to improve parallelism and scalability, such as the per-processor heap lock in Hoard [8]. In fact, Arbiter’s memory allocation mechanism inherently has the potential to employ a per-label lock. This per-label lock makes Arbiter ready to embrace a much more efficient allocator. We are looking at ways to implement such a parallelized allocator.

Another limitation with Arbiter is about the security policy specification. First, in complex and dynamic deployment scenarios, getting all the labels/ownerships correct is not an easy job for system admins or programmers. Second, in cases where programmers want to build secure-by-design applications and only need to hard code policies, the annotations may still spread across multiple locations, which increases the possibility of human error. One potential solution is to have a policy debugging or model checking tool that can verify the correctness of label/ownership assignments. Another direction is to let human express security requirements in a high-level policy description language and automatically generate annotations. We plan to investigate both directions in the future.

Arbiter’s security model, including notions and rules, are inspired by DIFC. However, it should be noticed that Arbiter does not perform information flow tracking inside a program, mainly due to two observations. (1) For a run-time system approach, tracking fine-grained data flow (e.g. moving a 4-byte integer from memory to CPU register) could incur tremendous overhead, making Arbiter impractical to use. (2) The fact that information flow tracking can enhance security does not logically exclude the possibility of solving real security problems without information flow tracking. The main contribution of Arbiter is that it provides effective access control and practical fine-grained privilege separation while preserving the advantages of traditional multithreaded programming paradigm. Once practical information flow tracking mechanisms emerge, we will investigate this issue and explore the solution space of integrating Arbiter with tracking.

9 Conclusion

Arbiter is a run-time system and a set of security primitives which can realize fine-grained and data-centric privilege separation for multi-principal multithreaded applications. Arbiter is a practical framework that not only provides privilege separation and access control, but also preserves the convenience and efficiency of traditional multithreaded programming paradigm. Our experiments demonstrate Arbiter’s ease-of-adoption as well as its satisfying application performance.


  • [1] The Chromium Projects: Inter-process Communication (IPC).
  • [2] Memcached.
  • [3] uClibc.
  • [4] Security Vulnerabilities in Memcached.
  • [5] SA-CONTRIB-2010-098 - Memcache - Multiple vulnerabilities.
  • [6] Apache httpd Privilege Separation.
  • [7] Apparmor.
  • [8] E. D. Berger, K. S. McKinley, R. D. Blumofe, and P. R. Wilson. Hoard: A scalable memory allocator for multithreaded applications. In ASPLOS, 2000.
  • [9] A. Bittau, P. Marchenko, M. Handley, and B. Karp. Wedge: splitting applications into reduced-privilege compartments. NSDI’08.
  • [10] D. Brumley and D. Song. Privtrans: automatically partitioning programs for privilege separation. In Proceedings of the 13th conference on USENIX Security Symposium - Volume 13, SSYM’04, pages 5–5, Berkeley, CA, USA, 2004. USENIX Association.
  • [11] X. Chen, T. Garfinkel, E. C. Lewis, P. Subrahmanyam, C. A. Waldspurger, D. Boneh, J. S. Dwoskin, and D. R. K. Ports. Overshadow: a virtualization-based approach to retrofitting protection in commodity operating systems. In ASPLOS, 2008.
  • [12] W. Cheng, D. R. K. Ports, D. Schultz, V. Popic, A. Blankstein, J. Cowling, D. Curtis, L. Shrira, and B. Liskov. Abstractions for Usable Information Flow Control in Aeolus. In USENIX ATC ’12.
  • [13] S. Chong, J. Liu, A. C. Myers, X. Qi, K. Vikram, L. Zheng, and X. Zheng. Secure web application via automatic partitioning. In In SOSP, 2007.
  • [14] P. Colp, M. Nanavati, J. Zhu, W. Aiello, G. Coker, T. Deegan, P. Loscocco, and A. Warfield. Breaking up is hard to do: security and functionality in a commodity hypervisor. In SOSP, 2011.
  • [15] P. Efstathopoulos, M. Krohn, S. VanDeBogart, C. Frey, D. Ziegler, E. Kohler, D. Mazières, F. Kaashoek, and R. Morris. Labels and event processes in the asbestos operating system. SOSP ’05.
  • [16] U. Erlingsson, M. Abadi, M. Vrable, M. Budiu, and G. C. Necula. XFI: Software Guards for System Address Spaces. In OSDI, 2006.
  • [17] M. Fähndrich, M. Aiken, C. Hawblitzel, O. Hodson, G. Hunt, J. R. Larus, and S. Levi. Language support for fast and reliable message-based communication in singularity OS. EuroSys ’06.
  • [18] B. Ford and R. Cox. Vx32: Lightweight User-level Sandboxing on the x86. In USENIX ATC, 2008.
  • [19] K. Fraser, S. Hand, R. Neugebauer, I. Pratt, A. WArfield, and M. Williamson. Safe hardware access with the xen virtual machine monitor. In OASIS ’04.
  • [20] C. Grier, S. Tang, and S. T. King. Secure web browsing with the op web browser. In IEEE S&P(Oakland), 2008.
  • [21] G. Klein, K. Elphinstone, G. Heiser, J. Andronick, D. Cock, P. Derrin, D. Elkaduwe, K. Engelhardt, R. Kolanski, M. Norrish, T. Sewell, H. Tuch, and S. Winwood. seL4: Formal Verification of an OS Kernel. In SOSP ’09.
  • [22] M. Krohn, A. Yip, M. Brodsky, N. Cliffer, M. F. Kaashoek, E. Kohler, and R. Morris. Information flow control for standard OS abstractions. SOSP ’07.
  • [23] D. Lea. A Memory Allocator.
  • [24] P. Loscocco and S. Smalley. Integrating flexible support for security policies into the linux operating system. In USENIX ATC, 2001.
  • [25] J. M. McCune, Y. Li, N. Qu, Z. Zhou, A. Datta, V. Gligor, and A. Perrig. TrustVisor: Efficient TCB Reduction and Attestation. In IEEE S&P (Oakland) ’10.
  • [26] A. Mettler, D. Wagner, and T. Close. Joe-E: A Security-Oriented Subset of Java. In NDSS ’10.
  • [27] D. G. Murray, G. Milos, and S. Hand. Improving xen security through disaggregation. In VEE, 2008.
  • [28] A. C. Myers. JFlow: practical mostly-static information flow control. POPL ’99.
  • [29] N. Provos, M. Friedl, and P. Honeyman. Preventing privilege escalation. SSYM’03.
  • [30] I. Roy, D. E. Porter, M. D. Bond, K. S. McKinley, and E. Witchel. Laminar: practical fine-grained decentralized information flow control. PLDI ’09.
  • [31] A. Seshadri, M. Luk, N. Qu, and A. Perrig. SecVisor: A Tiny Hypervisor to Provide Lifetime Kernel Code Integrity for Commodity OSes. In SOSP, 2007.
  • [32] J. S. Shapiro, J. M. Smith, and D. J. Farber. EROS: a fast capability system. SOSP ’99.
  • [33] R. Ta-Min, L. Litty, and D. Lie. Splitting interfaces: making trust between applications and operating systems configurable. In OSDI, 2006.
  • [34] R. Wahbe, S. Lucco, T. E. Anderson, and S. L. Graham. Efficient software-based fault isolation. SOSP ’93.
  • [35] H. J. Wang, C. Grier, A. Moshchuk, S. T. King, P. Choudhury, and H. Venter. The multi-principal os construction of the gazelle web browser. In USENIX Security Symposium, 2009.
  • [36] R. N. M. Watson, J. Anderson, B. Laurie, and K. Kennaway. Capsicum: Practical Capabilities for UNIX. In USENIX Security, 2010.
  • [37] X. Xiong, D. Tian, and P. Liu. Practical protection of kernel integrity for commodity os from untrusted extensions. In NDSS, 2011.
  • [38] B. Yee, D. Sehr, G. Dardyk, J. B. Chen, R. Muth, T. Ormandy, S. Okasaka, N. Narula, and N. Fullagar. Native client: A sandbox for portable, untrusted x86 native code. In IEEE S&P (Oakland), 2009.
  • [39] N. Zeldovich, S. Boyd-Wickizer, E. Kohler, and D. Mazières. Making information flow explicit in HiStar. Commun. ACM, 54(11):93–101, Nov. 2011.