membrane.h 14 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
// Copyright (c) 2015 Sandstorm Development Group, Inc. and contributors
// Licensed under the MIT License:
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

22
#pragma once
23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
// In capability theory, a "membrane" is a wrapper around a capability which (usually) forwards
// calls but recursively wraps capabilities in those calls in the same membrane. The purpose of a
// membrane is to enforce a barrier between two capabilities that cannot be bypassed by merely
// introducing new objects.
//
// The most common use case for a membrane is revocation: Say Alice wants to give Bob a capability
// to access Carol, but wants to be able to revoke this capability later. Alice can accomplish this
// by wrapping Carol in a revokable wrapper which passes through calls until such a time as Alice
// indicates it should be revoked, after which all calls through the wrapper will throw exceptions.
// However, a naive wrapper approach has a problem: if Bob makes a call to Carol and sends a new
// capability in that call, or if Carol returns a capability to Bob in the response to a call, then
// the two are now able to communicate using this new capability, which Alice cannot revoke. In
// order to avoid this problem, Alice must use not just a wrapper but a "membrane", which
// recursively wraps all objects that pass through it in either direction. Thus, all connections
// formed between Bob and Carol (originating from Alice's original introduction) can be revoked
// together by revoking the membrane.
//
// Note that when a capability is passed into a membrane and then passed back out, the result is
// the original capability, not a double-membraned capability. This means that in our revocation
// example, if Bob uses his capability to Carol to obtain another capability from her, then send
// it back to her, the capability Carol receives back will NOT be revoked when Bob's access to
// Carol is revoked. Thus Bob can create long-term irrevocable connections. In most practical use
// cases, this is what you want. APIs commonly rely on the fact that a capability obtained and then
// passed back can be recognized as the original capability.
//
// Mark Miller on membranes: http://www.eros-os.org/pipermail/e-lang/2003-January/008434.html

#include "capability.h"

namespace capnp {

class MembranePolicy {
  // Applications may implement this interface to define a membrane policy, which allows some
  // calls crossing the membrane to be blocked or redirected.

public:
59 60
  virtual kj::Maybe<Capability::Client> inboundCall(
      uint64_t interfaceId, uint16_t methodId, Capability::Client target) = 0;
61 62 63 64 65 66 67 68 69 70
  // Given an inbound call (a call originating "outside" the membrane destined for an object
  // "inside" the membrane), decides what to do with it. The policy may:
  //
  // - Return null to indicate that the call should proceed to the destination. All capabilities
  //   in the parameters or result will be properly wrapped in the same membrane.
  // - Return a capability to have the call redirected to that capability. Note that the redirect
  //   capability will be treated as outside the membrane, so the params and results will not be
  //   auto-wrapped; however, the callee can easily wrap the returned capability in the membrane
  //   itself before returning to achieve this effect.
  // - 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 75 76 77 78 79 80 81
  // return as a redirect. The redirect capability may modify the call in some way and send it to
  // `target`. Be careful to use `copyIntoMembrane()` and `copyOutOfMembrane()` as appropriate when
  // copying parameters or results across the membrane.
  //
  // Note that since `target` is inside the capability, if you were to directly return it (rather
  // than return null), the effect would be that the membrane would be broken: the call would
  // proceed directly and any new capabilities introduced through it would not be membraned. You
  // generally should not do that.
82

83 84
  virtual kj::Maybe<Capability::Client> outboundCall(
      uint64_t interfaceId, uint16_t methodId, Capability::Client target) = 0;
85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105
  // Like `inboundCall()`, but applies to calls originating *inside* the membrane and terminating
  // outside.
  //
  // Note: It is strongly recommended that `outboundCall()` returns null in exactly the same cases
  //   that `inboundCall()` return null. Conversely, for any case where `inboundCall()` would
  //   redirect or throw, `outboundCall()` should also redirect or throw. Otherwise, you can run
  //   into inconsistent behavion when a promise is returned across a membrane, and that promise
  //   later resolves to a capability on the other side of the membrane: calls on the promise
  //   will enter and then exit the membrane, but calls on the eventual resolution will not cross
  //   the membrane at all, so it is important that these two cases behave the same.

  virtual kj::Own<MembranePolicy> addRef() = 0;
  // Return a new owned pointer to the same policy.
  //
  // Typically an implementation of MembranePolicy should also inherit kj::Refcounted and implement
  // `addRef()` as `return kj::addRef(*this);`.
  //
  // Note that the membraning system considers two membranes created with the same MembranePolicy
  // object actually to be the *same* membrane. This is relevant when an object passes into the
  // membrane and then back out (or out and then back in): instead of double-wrapping the object,
  // the wrapping will be removed.
106 107 108 109 110 111 112 113 114 115

  virtual kj::Maybe<kj::Promise<void>> onRevoked() { return nullptr; }
  // If this returns non-null, then it is a promise that will reject (throw an exception) when the
  // membrane should be revoked. On revocation, all capabilities pointing across the membrane will
  // be dropped and all outstanding calls canceled. The exception thrown by the promise will be
  // propagated to all these calls. It is an error for the promise to resolve without throwing.
  //
  // After the revocation promise has rejected, inboundCall() and outboundCall() will still be
  // invoked for new calls, but the `target` passed to them will be a capability that always
  // rethrows the revocation exception.
116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145

  // ---------------------------------------------------------------------------
  // Control over importing and exporting.
  //
  // Most membranes should not override these methods. The default behavior is that a capability
  // that crosses the membrane is wrapped in it, and if the wrapped version crosses back the other
  // way, it is unwrapped.

  virtual Capability::Client importExternal(Capability::Client external);
  // An external capability is crossing into the membrane. Returns the capability that should
  // substitute for it when called from the inside.
  //
  // The default implementation creates a capability that invokes this MembranePolicy. E.g. all
  // calls will invoke outboundCall().
  //
  // Note that reverseMembrane(cap, policy) normally calls policy->importExternal(cap), unless
  // `cap` itself was originally returned by the default implementation of exportInternal(), in
  // which case importInternal() is called instead.

  virtual Capability::Client exportInternal(Capability::Client internal);
  // An internal capability is crossing out of the membrane. Returns the capability that should
  // substitute for it when called from the outside.
  //
  // The default implementation creates a capability that invokes this MembranePolicy. E.g. all
  // calls will invoke inboundCall().
  //
  // Note that membrane(cap, policy) normally calls policy->exportInternal(cap), unless `cap`
  // itself was originally returned by the default implementation of exportInternal(), in which
  // case importInternal() is called instead.

146 147 148 149 150 151 152 153 154
  virtual MembranePolicy& rootPolicy() { return *this; }
  // If two policies return the same value for rootPolicy(), then a capability imported through
  // one can be exported through the other, and vice versa. `importInternal()` and
  // `exportExternal()` will always be called on the root policy, passing the two child policies
  // as parameters. If you don't override rootPolicy(), then the policy references passed to
  // importInternal() and exportExternal() will always be references to *this.

  virtual Capability::Client importInternal(
      Capability::Client internal, MembranePolicy& exportPolicy, MembranePolicy& importPolicy);
155 156 157 158 159
  // An internal capability which was previously exported is now being re-imported, i.e. a
  // capability passed out of the membrane and then back in.
  //
  // The default implementation simply returns `internal`.

160 161
  virtual Capability::Client exportExternal(
      Capability::Client external, MembranePolicy& importPolicy, MembranePolicy& exportPolicy);
162 163 164 165
  // An external capability which was previously imported is now being re-exported, i.e. a
  // capability passed into the membrane and then back out.
  //
  // The default implementation simply returns `external`.
166 167 168
};

Capability::Client membrane(Capability::Client inner, kj::Own<MembranePolicy> policy);
169
// Wrap `inner` in a membrane specified by `policy`. `inner` is considered "inside" the membrane,
170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193
// while the returned capability should only be called from outside the membrane.

Capability::Client reverseMembrane(Capability::Client outer, kj::Own<MembranePolicy> policy);
// Like `membrane` but treat the input capability as "outside" the membrane, and return a
// capability appropriate for use inside.
//
// Applications typically won't use this directly; the membraning code automatically sets up
// reverse membranes where needed.

template <typename ClientType>
ClientType membrane(ClientType inner, kj::Own<MembranePolicy> policy);
template <typename ClientType>
ClientType reverseMembrane(ClientType inner, kj::Own<MembranePolicy> policy);
// Convenience templates which return the same interface type as the input.

template <typename ServerType>
typename ServerType::Serves::Client membrane(
    kj::Own<ServerType> inner, kj::Own<MembranePolicy> policy);
template <typename ServerType>
typename ServerType::Serves::Client reverseMembrane(
    kj::Own<ServerType> inner, kj::Own<MembranePolicy> policy);
// Convenience templates which input a capability server type and return the appropriate client
// type.

194 195 196 197 198 199 200 201 202 203 204
template <typename Reader>
Orphan<typename kj::Decay<Reader>::Reads> copyIntoMembrane(
    Reader&& from, Orphanage to, kj::Own<MembranePolicy> policy);
// Copy a Cap'n Proto object (e.g. struct or list), adding the given membrane to any capabilities
// found within it. `from` is interpreted as "outside" the membrane while `to` is "inside".

template <typename Reader>
Orphan<typename kj::Decay<Reader>::Reads> copyOutOfMembrane(
    Reader&& from, Orphanage to, kj::Own<MembranePolicy> policy);
// Like copyIntoMembrane() except that `from` is "inside" the membrane and `to` is "outside".

205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222
// =======================================================================================
// inline implementation details

template <typename ClientType>
ClientType membrane(ClientType inner, kj::Own<MembranePolicy> policy) {
  return membrane(Capability::Client(kj::mv(inner)), kj::mv(policy))
      .castAs<typename ClientType::Calls>();
}
template <typename ClientType>
ClientType reverseMembrane(ClientType inner, kj::Own<MembranePolicy> policy) {
  return reverseMembrane(Capability::Client(kj::mv(inner)), kj::mv(policy))
      .castAs<typename ClientType::Calls>();
}

template <typename ServerType>
typename ServerType::Serves::Client membrane(
    kj::Own<ServerType> inner, kj::Own<MembranePolicy> policy) {
  return membrane(Capability::Client(kj::mv(inner)), kj::mv(policy))
223
      .castAs<typename ServerType::Serves>();
224 225 226 227 228
}
template <typename ServerType>
typename ServerType::Serves::Client reverseMembrane(
    kj::Own<ServerType> inner, kj::Own<MembranePolicy> policy) {
  return reverseMembrane(Capability::Client(kj::mv(inner)), kj::mv(policy))
229
      .castAs<typename ServerType::Serves>();
230 231
}

232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258
namespace _ {  // private

OrphanBuilder copyOutOfMembrane(PointerReader from, Orphanage to,
                                kj::Own<MembranePolicy> policy, bool reverse);
OrphanBuilder copyOutOfMembrane(StructReader from, Orphanage to,
                                kj::Own<MembranePolicy> policy, bool reverse);
OrphanBuilder copyOutOfMembrane(ListReader from, Orphanage to,
                                kj::Own<MembranePolicy> policy, bool reverse);

}  // namespace _ (private)

template <typename Reader>
Orphan<typename kj::Decay<Reader>::Reads> copyIntoMembrane(
    Reader&& from, Orphanage to, kj::Own<MembranePolicy> policy) {
  return _::copyOutOfMembrane(
      _::PointerHelpers<typename kj::Decay<Reader>::Reads>::getInternalReader(from),
      to, kj::mv(policy), true);
}

template <typename Reader>
Orphan<typename kj::Decay<Reader>::Reads> copyOutOfMembrane(
    Reader&& from, Orphanage to, kj::Own<MembranePolicy> policy) {
  return _::copyOutOfMembrane(
      _::PointerHelpers<typename kj::Decay<Reader>::Reads>::getInternalReader(from),
      to, kj::mv(policy), false);
}

259
} // namespace capnp