4 ms·
> If a library needs access to a remote service, you have to open the socket yourself and pass that in, and the library then needs to plumb it through the whole
by nmadden 6y ago
> If a library needs access to a remote service, you have to open the socket yourself and pass that in, and the library then needs to plumb it through the whole stack manually to the point where it's needed.
You don't need to do this. There are a variety of ways to handle this, just as you would any other kind of dependency injection:
1. Design libraries to actually be modular so that dependencies (including capabilities) can be injected just where they are needed.
2. Pass in a factory object that lets the library construct sockets as and when it needs them. You can then enforce any arbitrary checks at the point of creating the socket. (This is much more flexible than a Java policy file).
3. Use a powerbox pattern [1] to allow the user to be directly asked each time the library attempts to open a socket. This is not always good UX, but sometimes it is the right solution.
> If the library develops a need for a new permission then the API must change and again, the whole thing has to be manually plumbed through.
Capturing permission requirements in the API is a good thing! With the stack walking/policy based approach I won't know the library needs a new permission until some library call suddenly fails at runtime.
[1]: http://wiki.erights.org/wiki/Powerbox http://wiki.erights.org/wiki/Powerbox
- mike_hearn 6y agoThe policy file isn't required, by the way. That's just a default implementation. My PDF viewer had a hard-coded policy and didn't use the file. OK, so in a pure capability language how would you implement this: program A depends on dynamically loaded/installed plugin B written by some third party, that in turn depends on library C. One day library C gets a native implementation of some algorithm to speed it up. To load that native library requires a capability, as native code can break the sandbox. However: 1. You can't change the API of C because plugin B depends on it and would break. 2. You can't pass in a "load native library" capability to plugin B because you don't know in advance that B wants to use C, and if you did, B could just grab the capability before it gets passed to C and abuse it. So you need to pass the capability directly from A to C. But now A has to have a direct dependency on C and initialise it even if it's not otherwise being used by A or B. Stack walking solves both these problems. You can increase the set of permissions required by library C without changing its callers, and you don't have the problem of needing to short-circuit everything and create a screwed up dependency graph. With the stack walking/policy based approach I won't know the library needs a new permission until some library call suddenly fails at runtime You often wouldn't need to. What permissions a module has is dependent on its implementation. It's legitimate for a library to be upgraded such that it needs newer permissions but that fact is encapsulated and abstracted away - just like if it needed a newer Java or a newer transitive dependency.
- nmadden 6y agoTypically libraries don’t directly load one another. The language runtime does this.
- mike_hearn 6y agoYes, but authorisation to do so must come from somewhere. In Java it's ambient. In a pure caps system, I'm not sure how it'd work.
- nmadden 6y agoThe beauty of object-capability security is that it completely aligns with normal object-oriented design. So you can always recast these discussions to not be about security: how would I inject any other new dependency I needed without changing the API of all intermediaries? And there is a whole literature of design patterns for doing this.
- mike_hearn 6y agoAll you'd do there is make the injector a semantic equivalent of the AccessController. The injector must have some sort of security policy after all, to decide whether a component is allowed to request injection of a capability. Whether you structure it as a single subsystem is responsible for intercepting object construction and applying policy based on the home module of what's being constructed, or whether you determine that module via stack walks, the end result is very similar: some central engine decides what components can do and then applies that policy. The Java approach is nice because it avoids any need for DI. DI is not a widely accepted pattern. There are no DI engines that would have any support for this kind of policy-driven injection. And whilst popular in some areas of software like Java web servers, it hardly features in most other languages and areas, there are no programming languages with built in support for it and that includes modern languages like Kotlin. DI engines meanwhile have changed how they work pretty radically over time - compare something like the original Spring XML DI to Guice to Dagger3. Plus, DI is awkward when the dependency you need isn't a singleton. How would I express for example "I need a capability injected that gives me access to the $HOME/.local/cache/app-name directory"? Annotation based DI struggles with this, but with the AccessController it's natural: the component just requests what it needs, and that's checked against a policy, which can be dynamically loaded from a file, or built by code.