Towards Capability Policy Specification and Verification

0 downloads 0 Views 387KB Size Report
Mar 26, 2014 - We use the term capability policy to describe how capabili- ties are ... it needs in order to function correctly: even a trusted object should not ...
Towards Capability Policy Specification and Verification Sophia Drossopoulou

James Noble

Imperial College, London [email protected]

Victoria University of Wellington [email protected]

Abstract

1.

The object-capability model is a de-facto industry standard widely adopted for the implementation of security policies for web based software. Unfortunately, code written using capabilities tends to concentrate on the low-level mechanism rather than the high-level policy, and the parts implementing the policy tend to be tangled with the parts implementing the functionality. In this paper we argue that the policies followed by programs using object capabilities should be made explicit and written separately from the code implementing them. We also argue that the specification of such capability policies requires concepts that go beyond the features of current specification languages. Moreover, we argue that we need methodologies with which to prove that programs adhere to their capability policies as specified. To write policy specifications, we propose execution abstractions, which talk about various properties of a program’s execution. We use execution abstractions to write the formal specification of five out of the six informal policies in the mint example, famous in the object capability literature. In these specifications, the conclusions but also the premises may relate to the state before as well as after execution, the code may be existentially or universally quantified, and interpretation quantifies over all modules extending the current module. In the process of writing these specifications, we uncovered several different and plausible alternative meanings for the policies of the mint example, and also discovered some new policies not mentioned in the original papers. Finally, we demonstrate how we can prove that the example implemented in Java satisfies the capability policies. These proofs make extensive use of the guarantees provided by type system features such as final and private.

Capabilities — unforgeable authentication tokens — have been used to provide security and task separation on multiuser machines since the 60s [7], e.g. PDP-1, operating systems e.g. CAL-TSS [17], and the CAP computer and operating system [47]. In capability-based security, resources can only be accessed via capabilities: possessing a capability gives the right to access the resource represented by that capability. Object capabilities [26] apply capabilities to objectoriented programming. In an object capability system, an object is a capability for the services the object provides: any part of a program that has a reference to an object can always use all the services of that object. To restrict authority over an object, programmers must create an intermediate proxy object which offers only restricted services, delegating them back to the original object. Object capabilities afford simpler and more fine-grained protection than privilege levels (as in Unix), static types, adhoc dynamic security managers (as in Java or JSand [1]), or state-machine-based event monitoring [2]. On the other hand, object capability systems are only secure as long as trusted capabilities (that is, trusted objects) are never leaked to untrusted code. Object capabilities have been adopted in several programming languages [23, 28, 45] and are increasingly used for the provision of security in web programming in industry [29, 40, 46]. On a different development strand to object capabilities, and with the aim to restrict access across code, programming languages adopted features like packages and opaque types, const or final fields, private and protected members, or final classes [41, 48]. More advanced features, such as ownership types [6], restrict access to different parts of the heap. Such features do not introduce new behaviour into the language, but restrict the set of legal programs, hence we call them restrictive features. The key problem with object capability programming as practiced today is that — because capabilities are just objects — code manipulating capabilities is tangled together with code supporting the functional behaviour of the program. The actual security policies enforced by a program are implicit, scattered throughout the program’s code. Any part of a program that uses an object may (by oversight, error, or

[Copyright notice will appear here once ’preprint’ option is removed.]

1

Introduction

2014/3/26

fraud) hand that object to an untrusted part of the program, giving the untrusted code access to all the services provided by that object. This makes it difficult to determine what security properties are guaranteed by a given program, and as a result, programs are difficult to understand, validate, and maintain. We argue that capability policies should be specified separately from the code program implementing them. We also argue that the specification of capability policies requires features that go beyond what is available in current specification languages. Namely, capability policies are program centred, fine grained, open in the sense that they specify aspects of the behaviour of all possible extensions of a program, and have deny elements, which require that certain effects may only take place if the originating code or the runtime context satisfy some conditions. In an earlier position paper, we anticipated expressing such policies through extensions of temporal logics [10]. In this paper we propose that capability policies can be specified through execution abstractions, which are, essentially observations relating to program execution, accessibility, reachability and tracing. For example, execution abstractions can say things like ”execution of a given code snippet in a given runtime context will access a certain field”, or “it is possible to reach certain code through execution of some initial code”. We define a toy Java-like language with some restrictive features, and use it to give precise meaning to execution abstractions. We follow the Mint example [28] to illustrate our ideas; using these abstractions we give precise specifications to five out of the six policies proposed informally in that paper. In these specifications, the conclusions but also the premises may relate to the state before as well as after execution, the code may be existentially or universally quantified, and interpretation quantifies over all modules extending the current module. In the process of developing the mint specifications, we were surprised by the many different, and plausible interpretations we found for the policies. We then sketch proofs showing that the Mint code written in JoE/Java adheres to the capability policies. In doing so, we make heavy use of restrictive language features; this was surprising for us, since in traditional program verification, but also in the verification of refinement properties, restrictive features have played only a small role. Finally, we discuss five further policies, which we discovered in the course of this work, and which we think should have been proposed along with the original six [28].

formalise a number of alternative specifications in our language of execution abstractions. • We give a set of lemmas which support reasoning about

adherence to such policies, apply these lemmas informally, and prove adherence to some of the Mint policies. The rest of the paper is organised as follows: Section 2 presents the Mint [23] as an example of object-capability programming, implemented in Joe-E/Java. Based on that example, Section 3 distills the characteristics of capability policies. Section 4 then outlines executions abstractions, uses them to express those policies, and discusses alternative interpretations. Section 5 explores reasoning about capability policies expressed in our specification language. Section 6 discusses further useful policies not listed in [23], Section 7 surveys related work, and Section 8 concludes.

2.

Object-Capability Example

We use as running example a system for electronic money proposed in [28]. This example allows for mints with electronic money, purses held within mints, and transfers of funds between purses. The currency of a mint is the sum of the balances of all purses created by that mint. Purses trust the mint to which they belong, and programs using the money system trust their purses (and thus the mint). Crucially, separate users of the money system do not trust each other. The standard presentation of the mint example defines six capability policies, which we repeat here, as they were described in [28]: Pol 1 With two purses of the same mint, one can transfer money between them. Pol 2 Only someone with the mint of a given currency can violate conservation of that currency. Pol 3 The mint can only inflate its own currency. Pol 4 No one can affect the balance of a purse they don’t have. Pol 5 Balances are always non-negative integers. Pol 6 A reported successful deposit can be trusted as much as one trusts the purse one is depositing into. An immediate consequence of these policies is that the mint capability gives its holder the ability to subvert the currency system by “printing money”. This means that while purse capabilities may safely be passed around the system, the mint capability must be carefully protected. There is also an implicit assumption that no purses are destroyed. This assumption is necessary because destruction of a purse would decrease the currency of a mint, in opposition to Pol 3. The implication of this assumption is that there will be no explicit destruction of purses, and also no garbage collection of purses — or at least, not of purses with a nonzero balance.

The contributions of our work are as follows: • We argue that the specification of capability policies re-

quires concepts that go beyond the features of current specification languages. • We propose execution abstractions, and use them to spec-

ify five of the six policies given in the Mint example. We 2

2014/3/26

Several different implementations have been proposed for the mint. Fig.1 contains an implementation in Joe-E [23], a capability-oriented subset of Java, which restricts static variables and reflection. In the Joe-E version, the policies are adhered to through the interplay of appropriate actions in the method bodies (e.g. the check in line 17), with the use of Java’s restrictive language features (private members are visible to the same class only; final fields cannot be changed after initialisation; and final classes cannot be extended). The code concerned with the functional behaviour is tangled with the code implementing the policy (e.g. in deposit, line 19 is concerned with the functionality, while line 17 is concerned with Pol 2). The implementation of one policy is scattered throughout the code, and may use explicit runtime tests, as well as restrictive elements (e.g. Pol 2 is implemented through a check in line 17, the private and final annotations, and the initialisations in lines 9 and 13). Note that an apparently innocuous change to this code — such as a public getMint accessor that returned a purse’s mint — would be enough to leak the mint to untrusted code, destroying the security of the whole system.

3.

ments promise that if execution of a code snippet reaches a certain state, or changes state in a certain way, or accesses some program entity, then the code snippet must satisfy some given properties. In other words, rely policies are about sufficient conditions, while deny policies are about necessary conditions. None of the terms above are standard; we coined them to delineate our ideas. The mint’s policies are capability policies, because: • They are program centred, since they refer to the actual

mint program. • They are fine grained, as they refer to individual purse

and mint objects; • Even though not explicitly stated there, the policy in [28]

is expected to be open; any extension of the code, e.g. by inheritance, should satisfy the requirements. • They contain rely as well as deny elements:

Pol 1 is a rely requirement, expressible through classic pre- and post- conditions: namely, execution of deposit in a state satisfying the pre-condition that the the two purses belong to the same mint leads to a state satisfying the post-condition where the money has been transferred.

Capability Policies

We use the term capability policy to describe how capabilities are intended to be used: which objects are trusted, which are untrusted, and precisely which capabilities can be accessed by which object. A key feature of capability systems is the principle of least authority — an object should only be able to access the capabilities (i.e. the other objects) that it needs in order to function correctly: even a trusted object should not have access to all the capabilities (objects) in the system [31, 38, 47]. A range of object capability policies are discernible from the literature [26–28]. Capability policies generally have the following characteristics:

Pol 2 is a deny requirement; it says that a mint’s currency may be changed by some code snippet only if that code snippet makes a function call to the mint object owning the currency. Pol 3 is another deny requirement; it says that if a mint’s currency should change, it increases. Pol 4 is also a deny requirement, preventing objects that cannot access a purse from modifying the purse’s balance. Pol 5 can be understood as an object invariant, requiring purses’ balances to always be positive, but also as a deny requirement which requires that all the code in the system preserves this property.

• They are program centred: they talk about properties

of programs rather than properties of specifications or protocols.

Deny policies are related to deny-guarantee specifications [9] which can forbid particular memory locations from being modified either by the current thread, or by any other threads. Deny policies typically apply throughout program execution, rather than during specific functions, and refer to any properties of the program (e.g. the currency of a mint), rather than just specific locations. Deny policies are also related to correspondence assertions [12, 49], which require principals reaching a certain point in a protocol to be preceded by other principals reaching corresponding points. Recently, correspondence assertions have been adapted to refer to program state, and thus can prove that the code adheres to security, authentication, and privacy policies [3]: functions are annotated by refinement types that require that the function is only called if its arguments satisfy the type’s conditions.

• They are fine-grained: they can talk about individual

objects, while coarse-grained policies only talk about large components such as file servers or the DOM. • They are open. Open requirements must be satisfied for

any use of the code extended in any possible manner — e.g. through dynamic loading, inheritance, subclassing, mashups, mixins, reflection, intercession, or any other extension mechanism supported by the programming language. This is in contrast to closed specifications that need only be satisfied for the actual code snippet itself. • They have rely as well as deny elements. Rely elements

essentially promise that execution of a code snippet in a state satisfying a given pre-condition will reach another state which satisfies some post-condition [13]. Deny ele3

2014/3/26

1

public final class Mint {

}

//

the Mint capability

2 3 4 5

public final class Purse { private final Mint mint; private long balance;

6

public Purse(Mint mint, long balance) { if (balanceprs.balance || amnt+balance