membrane.h (15500B)
1 // Copyright (c) 2015 Sandstorm Development Group, Inc. and contributors 2 // Licensed under the MIT License: 3 // 4 // Permission is hereby granted, free of charge, to any person obtaining a copy 5 // of this software and associated documentation files (the "Software"), to deal 6 // in the Software without restriction, including without limitation the rights 7 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 // copies of the Software, and to permit persons to whom the Software is 9 // furnished to do so, subject to the following conditions: 10 // 11 // The above copyright notice and this permission notice shall be included in 12 // all copies or substantial portions of the Software. 13 // 14 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 // THE SOFTWARE. 21 22 #pragma once 23 // In capability theory, a "membrane" is a wrapper around a capability which (usually) forwards 24 // calls but recursively wraps capabilities in those calls in the same membrane. The purpose of a 25 // membrane is to enforce a barrier between two capabilities that cannot be bypassed by merely 26 // introducing new objects. 27 // 28 // The most common use case for a membrane is revocation: Say Alice wants to give Bob a capability 29 // to access Carol, but wants to be able to revoke this capability later. Alice can accomplish this 30 // by wrapping Carol in a revokable wrapper which passes through calls until such a time as Alice 31 // indicates it should be revoked, after which all calls through the wrapper will throw exceptions. 32 // However, a naive wrapper approach has a problem: if Bob makes a call to Carol and sends a new 33 // capability in that call, or if Carol returns a capability to Bob in the response to a call, then 34 // the two are now able to communicate using this new capability, which Alice cannot revoke. In 35 // order to avoid this problem, Alice must use not just a wrapper but a "membrane", which 36 // recursively wraps all objects that pass through it in either direction. Thus, all connections 37 // formed between Bob and Carol (originating from Alice's original introduction) can be revoked 38 // together by revoking the membrane. 39 // 40 // Note that when a capability is passed into a membrane and then passed back out, the result is 41 // the original capability, not a double-membraned capability. This means that in our revocation 42 // example, if Bob uses his capability to Carol to obtain another capability from her, then send 43 // it back to her, the capability Carol receives back will NOT be revoked when Bob's access to 44 // Carol is revoked. Thus Bob can create long-term irrevocable connections. In most practical use 45 // cases, this is what you want. APIs commonly rely on the fact that a capability obtained and then 46 // passed back can be recognized as the original capability. 47 // 48 // Mark Miller on membranes: http://www.eros-os.org/pipermail/e-lang/2003-January/008434.html 49 50 #include "capability.h" 51 52 namespace capnp { 53 54 class MembranePolicy { 55 // Applications may implement this interface to define a membrane policy, which allows some 56 // calls crossing the membrane to be blocked or redirected. 57 58 public: 59 virtual kj::Maybe<Capability::Client> inboundCall( 60 uint64_t interfaceId, uint16_t methodId, Capability::Client target) = 0; 61 // Given an inbound call (a call originating "outside" the membrane destined for an object 62 // "inside" the membrane), decides what to do with it. The policy may: 63 // 64 // - Return null to indicate that the call should proceed to the destination. All capabilities 65 // in the parameters or result will be properly wrapped in the same membrane. 66 // - Return a capability to have the call redirected to that capability. Note that the redirect 67 // capability will be treated as outside the membrane, so the params and results will not be 68 // auto-wrapped; however, the callee can easily wrap the returned capability in the membrane 69 // itself before returning to achieve this effect. 70 // - Throw an exception to cause the call to fail with that exception. 71 // 72 // `target` is the underlying capability (*inside* the membrane) for which the call is destined. 73 // Generally, the only way you should use `target` is to wrap it in some capability which you 74 // return as a redirect. The redirect capability may modify the call in some way and send it to 75 // `target`. Be careful to use `copyIntoMembrane()` and `copyOutOfMembrane()` as appropriate when 76 // copying parameters or results across the membrane. 77 // 78 // Note that since `target` is inside the capability, if you were to directly return it (rather 79 // than return null), the effect would be that the membrane would be broken: the call would 80 // proceed directly and any new capabilities introduced through it would not be membraned. You 81 // generally should not do that. 82 83 virtual kj::Maybe<Capability::Client> outboundCall( 84 uint64_t interfaceId, uint16_t methodId, Capability::Client target) = 0; 85 // Like `inboundCall()`, but applies to calls originating *inside* the membrane and terminating 86 // outside. 87 // 88 // Note: It is strongly recommended that `outboundCall()` returns null in exactly the same cases 89 // that `inboundCall()` return null. Conversely, for any case where `inboundCall()` would 90 // redirect or throw, `outboundCall()` should also redirect or throw. Otherwise, you can run 91 // into inconsistent behavion when a promise is returned across a membrane, and that promise 92 // later resolves to a capability on the other side of the membrane: calls on the promise 93 // will enter and then exit the membrane, but calls on the eventual resolution will not cross 94 // the membrane at all, so it is important that these two cases behave the same. 95 96 virtual kj::Own<MembranePolicy> addRef() = 0; 97 // Return a new owned pointer to the same policy. 98 // 99 // Typically an implementation of MembranePolicy should also inherit kj::Refcounted and implement 100 // `addRef()` as `return kj::addRef(*this);`. 101 // 102 // Note that the membraning system considers two membranes created with the same MembranePolicy 103 // object actually to be the *same* membrane. This is relevant when an object passes into the 104 // membrane and then back out (or out and then back in): instead of double-wrapping the object, 105 // the wrapping will be removed. 106 107 virtual kj::Maybe<kj::Promise<void>> onRevoked() { return nullptr; } 108 // If this returns non-null, then it is a promise that will reject (throw an exception) when the 109 // membrane should be revoked. On revocation, all capabilities pointing across the membrane will 110 // be dropped and all outstanding calls canceled. The exception thrown by the promise will be 111 // propagated to all these calls. It is an error for the promise to resolve without throwing. 112 // 113 // After the revocation promise has rejected, inboundCall() and outboundCall() will still be 114 // invoked for new calls, but the `target` passed to them will be a capability that always 115 // rethrows the revocation exception. 116 117 virtual bool shouldResolveBeforeRedirecting() { return true; } 118 // If this returns true, then when inboundCall() or outboundCall() returns a redirect, but the 119 // original target is a promise, then the membrane will discard the redirect and instead wait 120 // for the promise to become more resolved and try again. 121 // 122 // This behavior is important in particular when implementing a membrane that wants to intercept 123 // calls that would otherwise terminate inside the membrane, but needs to be careful not to 124 // intercept calls that might be reflected back out of the membrane. If the promise eventually 125 // resolves to a capability outside the membrane, then the call will be forwarded to that 126 // capability without applying the policy at all. 127 // 128 // However, some membranes don't need this behavior, and may be negatively impacted by the 129 // unnecessary waiting. Such membranes should override this to return false. 130 // 131 // TODO(cleanup): Consider a backwards-incompatible revamp of the MembranePolicy API with a 132 // better design here. Maybe we should more carefully distinguish between MembranePolicies 133 // which are reversible vs. those which are one-way? 134 135 // --------------------------------------------------------------------------- 136 // Control over importing and exporting. 137 // 138 // Most membranes should not override these methods. The default behavior is that a capability 139 // that crosses the membrane is wrapped in it, and if the wrapped version crosses back the other 140 // way, it is unwrapped. 141 142 virtual Capability::Client importExternal(Capability::Client external); 143 // An external capability is crossing into the membrane. Returns the capability that should 144 // substitute for it when called from the inside. 145 // 146 // The default implementation creates a capability that invokes this MembranePolicy. E.g. all 147 // calls will invoke outboundCall(). 148 // 149 // Note that reverseMembrane(cap, policy) normally calls policy->importExternal(cap), unless 150 // `cap` itself was originally returned by the default implementation of exportInternal(), in 151 // which case importInternal() is called instead. 152 153 virtual Capability::Client exportInternal(Capability::Client internal); 154 // An internal capability is crossing out of the membrane. Returns the capability that should 155 // substitute for it when called from the outside. 156 // 157 // The default implementation creates a capability that invokes this MembranePolicy. E.g. all 158 // calls will invoke inboundCall(). 159 // 160 // Note that membrane(cap, policy) normally calls policy->exportInternal(cap), unless `cap` 161 // itself was originally returned by the default implementation of exportInternal(), in which 162 // case importInternal() is called instead. 163 164 virtual MembranePolicy& rootPolicy() { return *this; } 165 // If two policies return the same value for rootPolicy(), then a capability imported through 166 // one can be exported through the other, and vice versa. `importInternal()` and 167 // `exportExternal()` will always be called on the root policy, passing the two child policies 168 // as parameters. If you don't override rootPolicy(), then the policy references passed to 169 // importInternal() and exportExternal() will always be references to *this. 170 171 virtual Capability::Client importInternal( 172 Capability::Client internal, MembranePolicy& exportPolicy, MembranePolicy& importPolicy); 173 // An internal capability which was previously exported is now being re-imported, i.e. a 174 // capability passed out of the membrane and then back in. 175 // 176 // The default implementation simply returns `internal`. 177 178 virtual Capability::Client exportExternal( 179 Capability::Client external, MembranePolicy& importPolicy, MembranePolicy& exportPolicy); 180 // An external capability which was previously imported is now being re-exported, i.e. a 181 // capability passed into the membrane and then back out. 182 // 183 // The default implementation simply returns `external`. 184 }; 185 186 Capability::Client membrane(Capability::Client inner, kj::Own<MembranePolicy> policy); 187 // Wrap `inner` in a membrane specified by `policy`. `inner` is considered "inside" the membrane, 188 // while the returned capability should only be called from outside the membrane. 189 190 Capability::Client reverseMembrane(Capability::Client outer, kj::Own<MembranePolicy> policy); 191 // Like `membrane` but treat the input capability as "outside" the membrane, and return a 192 // capability appropriate for use inside. 193 // 194 // Applications typically won't use this directly; the membraning code automatically sets up 195 // reverse membranes where needed. 196 197 template <typename ClientType> 198 ClientType membrane(ClientType inner, kj::Own<MembranePolicy> policy); 199 template <typename ClientType> 200 ClientType reverseMembrane(ClientType inner, kj::Own<MembranePolicy> policy); 201 // Convenience templates which return the same interface type as the input. 202 203 template <typename ServerType> 204 typename ServerType::Serves::Client membrane( 205 kj::Own<ServerType> inner, kj::Own<MembranePolicy> policy); 206 template <typename ServerType> 207 typename ServerType::Serves::Client reverseMembrane( 208 kj::Own<ServerType> inner, kj::Own<MembranePolicy> policy); 209 // Convenience templates which input a capability server type and return the appropriate client 210 // type. 211 212 template <typename Reader> 213 Orphan<typename kj::Decay<Reader>::Reads> copyIntoMembrane( 214 Reader&& from, Orphanage to, kj::Own<MembranePolicy> policy); 215 // Copy a Cap'n Proto object (e.g. struct or list), adding the given membrane to any capabilities 216 // found within it. `from` is interpreted as "outside" the membrane while `to` is "inside". 217 218 template <typename Reader> 219 Orphan<typename kj::Decay<Reader>::Reads> copyOutOfMembrane( 220 Reader&& from, Orphanage to, kj::Own<MembranePolicy> policy); 221 // Like copyIntoMembrane() except that `from` is "inside" the membrane and `to` is "outside". 222 223 // ======================================================================================= 224 // inline implementation details 225 226 template <typename ClientType> 227 ClientType membrane(ClientType inner, kj::Own<MembranePolicy> policy) { 228 return membrane(Capability::Client(kj::mv(inner)), kj::mv(policy)) 229 .castAs<typename ClientType::Calls>(); 230 } 231 template <typename ClientType> 232 ClientType reverseMembrane(ClientType inner, kj::Own<MembranePolicy> policy) { 233 return reverseMembrane(Capability::Client(kj::mv(inner)), kj::mv(policy)) 234 .castAs<typename ClientType::Calls>(); 235 } 236 237 template <typename ServerType> 238 typename ServerType::Serves::Client membrane( 239 kj::Own<ServerType> inner, kj::Own<MembranePolicy> policy) { 240 return membrane(Capability::Client(kj::mv(inner)), kj::mv(policy)) 241 .castAs<typename ServerType::Serves>(); 242 } 243 template <typename ServerType> 244 typename ServerType::Serves::Client reverseMembrane( 245 kj::Own<ServerType> inner, kj::Own<MembranePolicy> policy) { 246 return reverseMembrane(Capability::Client(kj::mv(inner)), kj::mv(policy)) 247 .castAs<typename ServerType::Serves>(); 248 } 249 250 namespace _ { // private 251 252 OrphanBuilder copyOutOfMembrane(PointerReader from, Orphanage to, 253 kj::Own<MembranePolicy> policy, bool reverse); 254 OrphanBuilder copyOutOfMembrane(StructReader from, Orphanage to, 255 kj::Own<MembranePolicy> policy, bool reverse); 256 OrphanBuilder copyOutOfMembrane(ListReader from, Orphanage to, 257 kj::Own<MembranePolicy> policy, bool reverse); 258 259 } // namespace _ (private) 260 261 template <typename Reader> 262 Orphan<typename kj::Decay<Reader>::Reads> copyIntoMembrane( 263 Reader&& from, Orphanage to, kj::Own<MembranePolicy> policy) { 264 return _::copyOutOfMembrane( 265 _::PointerHelpers<typename kj::Decay<Reader>::Reads>::getInternalReader(from), 266 to, kj::mv(policy), true); 267 } 268 269 template <typename Reader> 270 Orphan<typename kj::Decay<Reader>::Reads> copyOutOfMembrane( 271 Reader&& from, Orphanage to, kj::Own<MembranePolicy> policy) { 272 return _::copyOutOfMembrane( 273 _::PointerHelpers<typename kj::Decay<Reader>::Reads>::getInternalReader(from), 274 to, kj::mv(policy), false); 275 } 276 277 } // namespace capnp