principle of least authority and programming languages

Sorry for the long post. This is really a pretty simple idea, but I want to make sure I’m explaining it correctly. I don’t have a background in programming language theory, so I find I have to describe my motivation to communicate this idea to people who do. Skip everything except the idea and the questions if you’d like.

Idea

The idea is to allow trusted code to restrict what global variables untrusted code can access. Every top-level module and procedure would be a global variable, and every side effect would require access to some global variable, so we could effectively sandbox code and control side effects this way. It seems to me that this could be enforced with a statically checkable type system. Could it not?

Motivation

I’m interested in languages that help programmers enforce the principle of least authority. One such language, E (www.erights.org), is basically Java without global variables. That means an object cannot import anything from the Java API; everything has to be passed to it as a reference. One parent object starts out with access to everything and delegates authority to objects as it needs to by passing references.

Some other languages take an approach similar to E’s, allowing the programmer to sandbox code by restricting but not eliminating the ability to access global variables. Lua and Python sandboxes can be built this way:

Lua Sandboxes

And Racket does something similar:

Racket Sandboxes

I think the approach that Racket and Lua take is more natural, since it is a little cumbersome to pass a reference whenever you want to do something. (E also tries to allow for secure distributed computing, so that approach might not make sense for E.) All of these approaches share two properties: they are strong enough to run untrusted code, and they work at runtime. Python and Lua modify dictionaries that code uses to look up global variables, and the Racket sandbox restricts the evaluator’s access to read certain files, which prevents it from importing certain modules.

Monads and effect types are also strong enough to allow running untrusted code, but they are statically checkable. Code operating in a particular monad will never be able to break out of that monad and do something that monad cannot do. Both monads and effect types, however, seem difficult for programmers to manage.

The idea above essentially combines the natural approach of Lua and Racket with a static type-checking system.

Response so Far

I sent a message to bitc-dev, basically asking about applying this idea to BitC. Jonathan Shapiro was kind enough to respond. Here is the thread:

BitC thread

I didn’t want to take up too much space on the BitC list, but I’m not sure why effects couldn’t be scoped the way I’ve mentioned. I’m interested in a fuller answer from anyone who is interested.

He mentioned that a higher-order language would present some complications. A procedure f that is passed another procedure g as a parameter and calls it would have to add all of the variables referenced by g to its effect type. That would be tricky because the inferred type of f would depend on what g a caller was using. Everything is still decidable, but I don’t have any experience with compiler design, so I don’t know how practically difficult it would be to implement this type system efficiently. It seems like a solvable problem.

He also pointed out that my idea would pose a problem for separate compilation of modules: if we linked in a compiled object file containing method definitions, we would lose information about the global variables that those methods had accessed. I personally don’t mind the idea of requiring everyone to include source code (or a compiler-generated proof?) along with their modules, but I wonder if this would slow down compilation. Again, I don’t have the expertise to say.

Questions

I'm wondering,

  1. Has something like this been done before? In particular, I'm very interested to know if a language exists that has implemented the idea above with a type system.
  2. Could this idea be enforced with a type system in a fast compiler?
  3. How difficult would it be to extend a compiler for Racket, OCaml, or some other language to incorporate this idea into its type-checking system?
  4. Suggestions for further reading would be appreciated.

Thanks for your thoughts!

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.

Despite that I don't really like Monads

I don't see that there's any other way of doing what you want except for the monadic approach (where you can think of linear/uniqueness types as a manner of dealing with what you want as a essentially-the-same-but-explicit manner.)

You want to split (certain) side-effects and encapsulate those from (mostly) effect-free programs. Monads should be your choice as a (beginning of a) formal model - you could lift type expressions to live at the module level, not sure it'll buy you much.

To answer your questions:

1. Dunno, it should be easy to diversify the IO monad in monads for DB, network, etc. Probably has been tried before.

2. Yes, in theory Haskell supports that. But define 'fast' and how many years you want to spend arriving at that.

3. Start in Haskell, derive DB/network/GUI monads etc, and you're set. Ocaml could do too, but at least Haskell will give you syntactic support.

4. I don't have suggestions for reading. But, to be honest, I expect it to be tried in Haskell already.

Every top-level module and

Every top-level module and procedure would be a global variable, and every side effect would require access to some global variable, so we could effectively sandbox code and control side effects this way. It seems to me that this could be enforced with a statically checkable type system. Could it not?

I do not believe you can profitably do so. Type systems traditionally check input and output types. If you want to control 'uses this particular global variable' through a type system, you'll need some unusually fine-grained effect typing. This fine granularity will require much syntactic precision to achieve the desired POLA restrictions. You found it syntactically cumbersome to pass references explicitly; I doubt you'll find it any less syntactically cumbersome to detail the effect types.

More relevantly, what would be the type of an object or procedure that accesses a global variable in a restricted manner? Could you pass such objects or procedure references to untrusted code? How will your type-system be detailed enough to distinguish between procedurally restricted, indirect access to a variable or effect, vs. full access? The ability to pass such controlled authority to other objects is the basis for any number of valuable POLA patterns, such as revocable powers.

But your motivation is to avoid some syntactic inconveniences. Certainly this can be achieved by some other means. One possibility is supporting implicit parameters (e.g. special variables) - effectively, a developer-controlled environment. Another possibility is to extend the syntax to access a particular object implicitly... i.e. to use 'import' or 'with' on an object in order to implicitly access its methods.

You found it syntactically

You found it syntactically cumbersome to pass references explicitly; I doubt you'll find it any less syntactically cumbersome to detail the effect types.

It should be easy if we restrict imports, along the lines of Lua and Racket. For example, you could restrict access to global variables using something like an "import only" statement. A module could have an "import only" statement, which would restrict the imports of its submodules and procedures. Each procedure could have something similar, which would restrict the procedures it called, and code blocks could restrict the imports of procedures in them. Furthermore, at any point in which it would be valid to declare the type of a procedure, that procedure's imports could be restricted using an "imports only" declaration. You may be thinking along the same lines as me toward the end of your post.

More relevantly, what would be the type of an object or procedure that accesses a global variable in a restricted manner? Could you pass such objects or procedure references to untrusted code? How will your type-system be detailed enough to distinguish between procedurally restricted, indirect access to a variable or effect, vs. full access? The ability to pass such controlled authority to other objects is the basis for any number of valuable POLA patterns, such as revocable powers.

If I understand you correctly, that would have to be accomplished using traditional OO data hiding, just as it is in an object capability model. So, for example, let's say we want to allow procedure Foo restricted access to A, which we have imported or constructed ourselves. We could create or import an object B that has a reference to A and then pass B to Foo. Foo would then have access to A only through B's interface. I'm trying to provide a superset of the functionality of an object capability model.

The same thing could be accomplished by having multiple interfaces to the same object. Instead of "interfaces," let's call them "permissions." If we define a partial ordering over permissions and let method m have permissions P1,...,Pn and require permissions R1,...,Rn corresponding to n parameters, then we should not allow any method m' without permission P' >= Pi corresponding to object o to pass o as the ith parameter to m. Furthermore, if Pi > Ri for some i, then m possesses a greater level of permission than it requires from its caller. Such an m should be included in a class definition corresponding to the type for parameter i. Methods might have to be included in more than one class definition.

We could then use "import only" statements to restrict access by granting various levels of permissions.

Language Sandboxing

I've seen something similar before.

[... much digging ...]

Ah, yes. Matej Kosik did a lot of work on Taming of Pict related and did something similar with regards to import restrictions. You might find it interesting.

The same thing could be accomplished by having multiple interfaces to the same object. Instead of "interfaces," let's call them "permissions."

Supporting multiple interfaces is handled effectively via 'facet pattern'. It is unclear to me what you are attempting with partially-ordered permissions.

Personally, I prefer to save myself the trouble and represent interfaces to objects as a record containing a distinct capability per 'method'. This is much more flexible, allowing one to mix and match without raising indirection. 'Objects' become logical entities, evolving graphs of agents and services potentially distributed across multiple hosts, with partial replication for the less synchronization or security sensitive subsets.

Anyhow, I'm still not seeing how you're winning anything with regards to syntactic convenience. You've now listed use of ocap mechanisms anyway, massive extra annotations for 'permissions' for methods, and detailed 'import only' statements - to escape the challenge of occasionally passing an extra parameter around or performing constructor injection. Other solutions, such as implicit parameters, would seem to solve the 'problem' simply and effectively.

I've recently grown interested in Gilad Bracha's Newspeak, which forbids 'imports' entirely. Instead, modules are parameterized. You inject the dependencies of a module upon construction of a module instance.

good stuff. Thank you for

good stuff. Thank you for digging.

Anyhow, I'm still not seeing how you're winning anything with regards to syntactic convenience.

The contortions are mainly to have a statically checkable system, which might be nice in a systems programming language. The idea is to do whatever we can at compile time in order to avoid contortions at runtime.

Staging

I'm personally more interested in 'merging' the compile-time and run-time. This can go two directions: Live programming, persistent programming languages, and JIT move compile-time into the run-time. Partial-evaluation and staged languages move run-time into the compile-time.

Either technique reduces the dichotomy, and reduces the number of distinct 'concepts' users must understand to grok the language.

Staging with an object-capability language would (ideally) allow a developer to securely provide authority to a set of objects that will exist at runtime. The set of authorities provided by the developer could be distinct from those provided by the user. The code could then coordinate authorities of the developer and the user to useful effect.

Live programming with object-capability languages has pretty much the same story, excepting that we grant a live reference to the end-user rather than granting an executable.

RE: Permissions

I've spent a bit more time figuring out what you meant with the permissions. I've seen something similar before with Richard Kulisz's "Grand Unified Capabilities" model - which never obtained credibility in the ocap community. RK's GUC can be modeled with plain 'object' capabilities via facet-pattern.

Whereas RK had a bitfield of specific permissions for read/write/create/watch and so on, you described a set of permissions as granting access to each method. If you represent the set of methods as a bitfield, then each 'permission' value P1..Pn (and R1..Rn) would correspond to a mask of bits. Partial ordering is easily computable: (Px >= Py) is computed by (0 == (Py & ~Px)).

RK applied these masks on the references between objects. Every reference effectively holds a pointer and a bitfield. You suggest making this bitfield 'statically typed'. It is an interesting idea. The notion of 'public vs. private' methods is subsumed by the more flexible permissions you are suggesting.

OTOH, I've long considered the monolithic 'class' structures problematic for other reasons (relating to modularity, dependency management, garbage collection, security, and distribution), so I consider breaking 'objects' down into composable graphs of components with exposed facets to be a significant victory all its own.

RE: RE: Permissions

Whereas RK had a bitfield of specific permissions for read/write/create/watch and so on, you described a set of permissions as granting access to each method. If you represent the set of methods as a bitfield, then each 'permission' value P1..Pn (and R1..Rn) would correspond to a mask of bits. Partial ordering is easily computable: (Px >= Py) is computed by (0 == (Py & ~Px)).

RK applied these masks on the references between objects. Every reference effectively holds a pointer and a bitfield. You suggest making this bitfield 'statically typed'. It is an interesting idea. The notion of 'public vs. private' methods is subsumed by the more flexible permissions you are suggesting.

exactly! We're on the same page now.

OTOH, I've long considered the monolithic 'class' structures problematic for other reasons (relating to modularity, dependency management, garbage collection, security, and distribution), so I consider breaking 'objects' down into composable graphs of components with exposed facets to be a significant victory all its own.

I'm not quite sure what you mean here. Maybe you're talking about inheritance? It looks to me like E basically uses delegation instead, along with some syntactic sugar.

You got me thinking, though!

I like the way Haskell separates type classes from datatypes, and the permissions above are very much like Haskell type classes. Parametric polymorphism is used at runtime to distinguish between datatypes, but the class of a datatype in a function is always known at compile time.

Let's throw out the word 'class' above and just talk about permissions and datatypes. Permissions are basically like interfaces or Haskell type classes. We're left with this from above:

If we define a partial ordering over permissions and let method m have permissions P1,...,Pn and require permissions R1,...,Rn corresponding to n parameters, then we should not allow any method m' without permission P' >= Pi corresponding to object o to pass o as the ith parameter to m. Furthermore, if Pi > Ri for some i, then m possesses a greater level of permission than it requires from its caller.

Okay, now since we are basically considering a permission an interface or type class, we ought to be able to simply refer to the permission a method has for a particular parameter. A permission doubles as a type class. Here's some pseudocode:

permission write
permission read

data A = ...
data B = ...

f :: read -> write -> Void
f (A a) (B b) = ...

g :: write -> read -> Void
g (A a) (B b) = ...

This says that f both possesses and requires read access for its first parameter. g both possesses and requires write access for its first parameter. and similarly for the second parameters.

This decouples datatypes from type classes. It's a little bit confusing, though. In this case, f does not have the permission to call g a b, and g is not defined on g b a. So f cannot call g on a and b in any way. It has the right permissions and the right types, but they're swapped. Even though it's confusing, we should know whether or not a method is defined for a datatype at compile time.

I just realized that it is confusing because, unlike Haskell, the type of a method does not at all constrain its definition; it only constrains the other methods it is allowed to call.

The tricky thing is handling methods with more permissions than they require. Those are special from a security and engineering perspective, since they allow untrusted code to call more trusted code. Let's call them "taming methods" for whatever parameter they have escalated permissions for. If a method m is a method is defined for a datatype D passed as a parameter that it is a taming method for, then m must be declared in an access control list for D. An access control list is something like a class definition, but it doesn't really matter where exactly it is; it could even be spread out over the source. It only matters that it is managed by trusted code. Taming methods would also need special annotation, unless they all had unrestricted access to datatypes.

We can add polymorphism (parametric or subtyping) independently by creating a separate union or hierarchy among the datatypes.

Oh, and we don't need modules. Instead, just let methods import and export permissions of their own. A method m cannot call another method m' unless it exports a permission at least as great as the one m' requires. As usual, methods with greater permissions than they required would have to be managed by trusted code somehow. But we can have namespaces if we want them.

[ot] class vs. component

isn't it all a matter of fractal zoom? might not the little components be classes?

Re: class vs. component

Fractal zoom? There's glory for you!

The issues I have with 'classes' is that they combine too darn many responsibilities: function, namespace, containment, ownership, object-graph construction, unit of identity (and thus state, synchronization, communication, and capability), maintenance of helper code.

Breaking these responsibilities apart doesn't necessarily result in 'little' components, but does result in more composable, reusable, independently GC-able, independently distributable, and securable components.

Combine or //serve// too darn many reponsibilities?

In object-oriented analysis methodologies used in real-time engineering, a class just represents a problem domain entity.

It would be nice to go back to this simpler way of thinking, rather than have classes serve too many responsibilities. However, it does not make sense to me to come up with individual concepts for all these things, and split them up into knobs you tune. Classes are the right concept, the problem is methodology.

The issue with splitting these responsibilities up, so far, has been explored in pluggable type systems, but usually at a cost to REPL speed.

Both.

In popular programming languages, a 'class' creates state, defines functionality that uses state, combines functionality - a particular group of methods, creates objects, hooks objects together, maintains a namespace, and describes a 'type'. In languages without GC, classes also manage memory of the 'child' objects. These are programming-language responsibilities, not 'domain' responsibilities. Combining all these responsibilities into one language structure hinders development of reusable components.

Beyond that, they tend also //serve// too many domain responsibilities.

I'm using the word 'class' with the connotations from popular OO programming languages, not domain analysis. I would expect that to have been be clear from context. But I understand that your tendency to put a UML spin on everything isn't so different than my tendency to put a distributed programming spin on everything.

I do not agree that classes are the 'right' concept, not at all, at least not for programming languages.

Rather than looking to pluggable type systems, look to languages and programming models that never made mistake of introducing the OO 'class' in the first place...

The ability to pass such

The ability to pass such controlled authority to other objects is the basis for any number of valuable POLA patterns, such as revocable powers.

Oh, and is there a book or a paper with some of those patterns?

Papers and Websites and Mailing Lists...

Most of them found through erights. But that site is painful to navigate, so I'll point you to a few subsections:

Secure Distributed Computing of the E wiki has a section dedicated to POLA patterns.

Cap-talk mailing list archives available for search and perusal.

Capability documentation from e-lib is very approachable.

Robust Composition (thesis) is of intimidating size and depth.

Horton's Who Dunnit [ltu] achieves user responsibility tracking via capabilities.

There is plenty more out there. Capabilities go back to the 70s and were part of the secure design for a major operating system of the time, key-kos.

Monads vs. Capabilities for Reasoning about Effects

We've discussed [1] [2] monadic vs. capability approaches for security before.

Information Flow

This all sounds related to Flow Caml, JIF, and Generalizing Parametricity Using Information Flow (PDF).

Information flow seems

Information flow seems significantly stricter (in a good even if unsound-in-a-production-setting way).

I think something like Fine is more in the direction of what's going on (and, like E, in a more modular way than shared globals).

Both of these look

Both of these look promising, thanks!