Build Security Abstractions Into Capabilities

What kind of security abstractions over capabilities are sufficiently orthogonal and pervasive as to fall under BuildInPervasiveAndOrthogonalAttributes??


From PermissionFlags

How many legitimate variations on permission flags can we expect?

Building an additional layer of security abstraction (proxies) above capabilities is pointless because there aren't any legitimate alternative permission flag schemes. There's only one scheme which is powerful, secure, simple, elegant, coherent and self-consistent. So it might as well be sunk into the lowest layer and forgotten.

In most cases, there are several variations of the abstraction that make sense. The point of inventing an additional layer is to avoid baking in a particular policy at a low level, i.e. to separate PolicyAndMechanism. I've also been thinking about this intensively for years on end, and I am absolutely convinced that there are several useful variations of each of the fundamental abstractions at the level we're talking about (revocation, versioning, permissions, etc.)

Then you don't have a very good imagination. I have difficulty coming up with one policy that's completely and totally self-consistent in all circumstances. The problem is that I can imagine a lot of circumstances.

A system does not need to handle all circumstances. It does need to handle a range of circumstances that is too wide to be handled by a fixed set of policies specified in advance.

Again, you simply don't have a very good imagination. While I can't come up with alternate policies to fit all the circumstances that I can imagine, I can come up with one.


On the necessity of standard permission flags

If you don't build permission flags into capabilities, you can end up putting them within a higher layer, mixed in with stuff that's very different in nature. This is dangerous because the permission flags will invariably be unused because of laziness, misused because of stupidity and abused because of sheer perversity.

If you don't build permission flags into capabilities, you can end up making very serious mistakes. The consequences depend on which of the many wrong ways you've chosen to do things you happen to pick. But they include:

Careful design of the layering is certainly necessary. It's not a good idea to mix abstractions that will be required for the security of all objects with unrelated things, agreed.

Standard permission flags are required for the security of all objects. There's no legitimate reason to avoid them.

The question is not whether permission flags are needed; we agree on that. The question is simply whether they should be built into the lowest layer.

[It is useful to have a 'standard' set of permission flags with common semantics and lower-layer implementation. With that much I agree. Standardization helps programmers know what to expect. However, I also think it useful that programmers or system designers be able to include their own ad-hoc set of permissions and check permissions at higher layers. This might allow the 'byzantine' insane permissions systems that RK fears, but it also allows for flexible policies to be developed as needed. Usefully, programmer-defined 'flags' effectively serve in place of environment variables that are implicitly handed down the communications pipeline (unless explicitly removed) to the objects that eventually consume them (see ExplicitManagementOfImplicitContext).]


From GarbageCollectionUnderVersioning

Since refs are fairly high level objects themselves (they keep track of which versions of an object they point to) instead of just pointers, it makes sense that this is a CapabilitySecurityScheme?. And not just any cap scheme but one with high level capabilities like GrandUnifiedCapabilities. And in that case, only 'override' (ownership) permission allows one to change a ref's reprieval condition.

See BuildSecurityAbstractionsIntoCapabilities for a discussion relevant to whether GrandUnifiedCapabilities are actually needed for this. The range of options for versioning further reinforces my opinion that this kind of thing should be implemented in a separate abstraction layer by use of proxies. --dh

Proxies bite. Especially so if you don't have true delegation in your language.

Why do proxies bite? I agree that languages should have true delegation.

Apparently you've never had to forego using the debugger because it blows up due to your redefining DoesNotUnderstand. And even when it works correctly, the fact that you've fucked with DNU leaves traces in the system. And that's not even mentioning the abstraction inversion that is using a high level mechanism like DNU to implement a very basic mechanism like delegation.

That would be a broken debugger. I'm assuming a language with an object model specifically designed to support proxying (for example OzLanguage, EeLanguage, or JouleLanguage), and development tools to match. DoesNotUnderstand is intended as an error reporting mechanism, not a proxying mechanism.

Do you realize how stupid it makes you sound to me when you slam Smalltalk?

I didn't "slam Smalltalk"; I simply pointed out that DoesNotUnderstand is intended for error reporting, not proxying. Also, that any debugger that "blows up" is broken. The latter is an implementation issue, not a language issue.

Oh, and for fuck's sake, stop inserting your comments into my paragraphs!

No.

In that case, this will have to be our last discussion.


SyntaxVsSemantics

The FacetPattern is meant for special cases where the semantics of particular objects are concerned. It isn't meant for the generic case where the syntax of objects (the object graph that underlies them) is concerned.

By semantics of particular objects I mean the particular factoring of objects in a component and the distribution of powers between them. If my hard_disk component does not export a HD_block object, there can be no security based on that abstraction.

How to create an HD_block capability with the powers you want, that's all syntax. What the HD_block (as opposed to log_segment, or storage_object) does for users with a particular capability, that's semantics.

In a capability system, connectivity constraints on the object graph can be enforced by static program analysis (e.g. auditors in EeLanguage), or confinement mechanisms such as the KeyKos/ErosOs Factory design, or dynamically using the MembranePattern?, for example. Is that the kind of thing you mean?

I hold code verification in contempt. It's an optimization detail that should be of no concern to OS designers. Instead, it's treated as if it were some fundamental security concept. Bleh!

I do not refer to the membrane pattern because membranes are also fundamental syntactic objects little different from primitive capabilities. And to preemptively answer "what kind of syntax are membranes", they're NestedProcesses' syntax. Because processes are syntax and only components, the object graphs which processes export, are semantics.

I don't see that NestedProcesses (similar to HierarchicalScheduling? or the process model that was used by CapOs), have much to do with membranes.

If you think processes have anything to do with scheduling then no you wouldn't. Because you wouldn't know what processes are.

The term "process" has been used by many different systems to mean different things, but most of them had a lot to do with scheduling. If you mean something else ("protection domain", maybe?), then I suggest not using the term "process".

The term process no longer means anything related to scheduling, except insofar as it refers to a protection domain plus a thread. Since I do not mean just protection domains, I choose to use the term process and choose it to mean a protection domain with relevant subprocess management machinery and zero or more threads. IOW, something that has nothing to do with scheduling. Get with it, the terminology's heading in this direction anyways.

I never liked that usage of "process", since it conflicts with process calculi. To get back to the point, CapOs had NestedProcesses in the sense you mean (way ahead of its time, and probably the hardware wasn't powerful enough to make significant use of this facility). From LevyBookCapabilityBasedComputerSystems:

 CAP's designers chose to use the process tree mechanism to eliminate the need for a privileged mode of operation.
 Each CAP process can control the addressing environment and execution of its subprocesses without special privilege
 or operating systems intervention.

This is certainly a good idea. In a language-based system, it corresponds to "refraction" -- the ability to efficiently create recursive virtual machines to execute and reflect on subcomputations. However, I still don't see what it has to do with membranes. Membranes are an entirely different mechanism/pattern.

(EditHint: move to NestedProcesses.)

Process calculi is something only language designers, not OS designers, care about. It's becoming obvious you're the former.

They are not mutually exclusive. How can someone be competent to design a language-based OS without being a language designer? And designers of operating systems of all kinds should care about process calculi; they could learn a lot from them.

Membranes have to do with processes. They're ALL about process boundaries. Meanwhile, non-programmer end-users don't give a good goddamn about processes. And you know, I don't think nested processes (as opposed to just processes) have anything to do with membranes. They were just something I was thinking about at the time. Pretty much the same reason you brought up membranes in the first place.


ExoKernel

That permission flags are universal abstractions necessary for the security of all objects everywhere and orthogonal to all objects might not be obvious in broken systems where capabilities are centralized in the kernel, which is probably any system with a kernel. Eros' centralized capabilities makes it uninteresting.

In any cap system there has to be a security kernel: the component that is responsible for enforcing the capability model. There are disadvantages to memory-protection-based cap systems, but I don't see that this is one of them.

It's false that there has to be a central process table. NestedProcesses is based on the idea that each process can maintain a table of its own children.

It's false that there has to be a central capability store. My cap scheme is based on the idea that each process implements and exports its own set of caps.

It's false that there has to be a kernel of anything. ExoKernel puts the lie to that. Eros is a kernel system, therefore Eros is bad and its cap scheme with it.

Anyway, if you think that exokernels are a good idea, why do you want to hard-wire so much policy into your system at a low level? The exokernel approach is more consistent with my position than with yours.

Because none of it's policy. It's all mechanism. It's just that I require my mechanisms to be clean, elegant and self-consistent. In addition, exokernels provide secure multiplexing of underlying abstractions. And how do you suppose security should be done if not by capabilities? Virtualizing can only go so far before you have to produce abstractions to allocate and distribute the virtualized resources. Once you do that, you need caps.

Well, the distinction between mechanism and policy is not entirely well-defined. However, the point is that implementations of the security abstractions we're talking about cannot avoid building in some aspects of policy. Some of the design decisions on GarbageCollectionUnderVersioning and PermissionFlags are certainly policy, for instance.

This page defines the difference between mechanism and policy. If you don't see that then you're really missing the point. The point being that these things you claim "are certainly policy" are certainly not policy as far as I'm concerned.

--

"It's false that there has to be a kernel of anything. ExoKernel puts the lie to that. Eros is a kernel system, therefore Eros is bad and its cap scheme with it."

This is simply fuzzy thinking. EROS is a MicroKernel system, so it goes at least part-way toward accepting the reasoning behind exokernels. You could argue that its kernel is still too big, but that doesn't have much to do with its cap scheme. It's perfectly possible to implement an EROS-like, memory-protection-based cap scheme on top of an exokernel.

There are much better arguments against memory-protection-based implementations of capabilities. The strongest one IMHO is that to write secure applications you need to use a high-level language anyway, and so you want to minimize the ImpedanceMismatch between the OS interface and the language interface. The easiest and most effective way to do that is for the OS interface to be the language interface. Provided that the language is expressive enough (and can therefore support multiple ProgrammingParadigms and surface syntaxes), there's no justification for separating them.

You don't seem to understand the essential difference between an operating system and a language. Or perhaps you simply don't understand languages. An OS must do much more than simply run an expressive language. It has to be able to run other languages. It has to be able to run assembly even.

The fact that you're even thinking in terms of "memory protection vs in the language protection" tells me how narrow-minded and warped your thinking on caps is. My caps scheme transcends such petty distinctions. Unlike you, I'm not concerned whether EROS made the right or wrong choice regarding its caps, the fact that you can even distinguish between memory-protection and language protection is sufficient for me to condemn it.

What makes you think that assembly isn't a language? Or that it is not possible to translate several languages into one? Or that it is not possible to view several languages as one simply by tagging programs? Or that it is not possible to view any virtual machine as a language?

More legalisms.

They are not just legalisms. Think about them more carefully, you might learn something.

As already explained, this was the last discussion I'm having. That's too bad since I very much enjoy talking about my field of expertise, for once.

Is the hardware memory protection used to enforce protection domains, or is it not used? That's a pretty clear distinction. Of course it's strictly speaking a property of the implementation, but in practice it affects the design so much that systems that expect different answers to this question look very different in other respects.

In practice, operating systems writers are morons incapable of any design. In practice, operating systems researchers don't do any research. In practice, operating systems design is a dead field. What do YOU know about OS design? And where the hell would you have ever learned it? And don't bother claiming that you taught yourself because it's clear that you're not even a designer, let alone an OS designer.


Simulation vs Reality

Unix's permissions are absolutely horrible. Execute is meaningless for almost every file, then there are the unused and useless permissions in ext2fs (append-only, compress). But at least Unix has permissions. In most implementations of the CapabilitySecurityModel, permissions seem to be absent.

Permissions are supported primitively in most ObjectCapabilityOperatingSystems based on memory protection (HydraOs, etc.). They are not supported primitively in most language-based cap systems. That's because these systems typically try to look as much like conventional ObjectOrientedLanguages as possible, and because it is easy to simulate permission flags using a variation of FacetPattern (in which the facet interface can be controlled dynamically).

It seems that language designers are almost as retarded as systems programmers. LanguagesAreOperatingSystems, and if you don't include the lessons learned in OS research then your language is worthless. You have to include permissions at a low level.

And the kicker is that you don't have to change the user's experience of the language to do so, except in a good way like garbage collection does. The user's (programmer's) experience of your program is far more important than the irrelevant implementation details which programmers (language designers) are so obsessed with.

Finally, memory protection is worthless as a capability system. It's coarse-grained, inflexible, and insufficiently general.

There's something to that argument; I'm also much more interested in language-based systems, although I wouldn't call memory-protection-based systems worthless.


From TwoKindsOfCapabilities

The clean implementation treats unlimited caps as exceptions. Message dispatch occurs as usual except that at every reference, the VM performs an OR between the 'unlimited bit' on the message and that on the reference. When the message gets to its destination, only then does the VM decide whether to check the permission bits (if the unlimited bit is unset).

I don't see why this needs to interact with primitive message dispatch. Another way to do it is to use a membrane between user domains (again, using an abstraction layer rather than trying to cram every aspect of policy into the primitive mechanisms).

That's because you don't want every object reference to be a useful security abstraction, a high level capability.

In my scheme you have a fundamental object (capability) which you can set in any of various pre-defined modes. In other schemes, you have a "secure" reference (a "raw" or unlimited capability) which you shield by building a proxy to access it. You can simulate my modal caps scheme by having a standard set of proxies which you can instantiate at will on any object. But my imagination is not so strong that I can ignore what's actually happening.

What I call capabilities mostly refers to the proxies which are at the same level of abstraction. Since the raw capabilities are subcomponents of the proxies, I can't think of them as being at the same level of abstraction as the proxies themselves. This would be more obvious if there existed null proxies which permitted all operations. Kinda pointless except that it would make the system uniform.

There is no loss of security in using proxies, and they have some important advantages:

I need to redefine message dispatch to handle these permission interactions because this construct is really too basic and much too universal to bother dealing with. I also don't think you appreciate just how distributed and decentralized my cap scheme is, how dynamic while remaining completely uniform and elegant it all is.

Oh, and I do have something resembling membranes called portals. They're for interprocess caps. But hey, same goes for membrane pattern, doesn't it? And let me emphasize here: user domains have NOTHING to do with processes. A user domain can encompass multiple nested components (the object graphs exported by processes), or only a fraction of a single one, or fractions of multiple ones. They simply have nothing to do with each other.

Actually, I do want to thank you for raising the issue of what I'll have to put into the VM.


CategorySecurityPatterns CategoryAbstraction


EditText of this page (last edited December 4, 2008) or FindPage with title or text search