Commit c3b381ee authored by Kenton Varda's avatar Kenton Varda

Add framework for capability membranes.

parent cbd18b28
......@@ -161,6 +161,7 @@ includecapnp_HEADERS = \
src/capnp/any.h \
src/capnp/message.h \
src/capnp/capability.h \
src/capnp/membrane.h \
src/capnp/schema.capnp.h \
src/capnp/schema-lite.h \
src/capnp/schema.h \
......@@ -255,6 +256,7 @@ libcapnp_rpc_la_LDFLAGS = -release $(SO_VERSION) -no-undefined
libcapnp_rpc_la_SOURCES= \
src/capnp/serialize-async.c++ \
src/capnp/capability.c++ \
src/capnp/membrane.c++ \
src/capnp/dynamic-capability.c++ \
src/capnp/rpc.c++ \
src/capnp/rpc.capnp.c++ \
......@@ -373,6 +375,7 @@ heavy_tests = \
src/kj/parse/char-test.c++ \
src/kj/std/iostream-test.c++ \
src/capnp/capability-test.c++ \
src/capnp/membrane-test.c++ \
src/capnp/schema-test.c++ \
src/capnp/schema-loader-test.c++ \
src/capnp/schema-parser-test.c++ \
......
......@@ -36,6 +36,7 @@ set(capnp_headers
any.h
message.h
capability.h
membrane.h
dynamic.h
schema.h
schema.capnp.h
......@@ -62,6 +63,7 @@ install(FILES ${capnp_headers} ${capnp_schemas} DESTINATION "${INCLUDE_INSTALL_D
set(capnp-rpc_sources
serialize-async.c++
capability.c++
membrane.c++
dynamic-capability.c++
rpc.c++
rpc.capnp.c++
......@@ -209,6 +211,7 @@ if(BUILD_TESTING)
add_executable(capnp-heavy-tests
endian-reverse-test.c++
capability-test.c++
membrane-test.c++
schema-test.c++
schema-loader-test.c++
schema-parser-test.c++
......
......@@ -139,6 +139,7 @@ private:
template <typename, typename>
friend class Request;
friend class ResponseHook;
};
class Capability::Client {
......@@ -488,6 +489,11 @@ class ResponseHook {
public:
virtual ~ResponseHook() noexcept(false);
// Just here to make sure the type is dynamic.
template <typename T>
inline static kj::Own<ResponseHook> from(Response<T>&& response) {
return kj::mv(response.hook);
}
};
// class PipelineHook is declared in any.h because it is needed there.
......
......@@ -2440,6 +2440,10 @@ kj::Maybe<Arena&> PointerReader::getArena() const {
return segment == nullptr ? nullptr : segment->getArena();
}
CapTableReader* PointerReader::getCapTable() {
return capTable;
}
PointerReader PointerReader::imbue(CapTableReader* capTable) const {
auto result = *this;
result.capTable = capTable;
......
......@@ -411,6 +411,9 @@ public:
kj::Maybe<Arena&> getArena() const;
// Get the arena containing this pointer.
CapTableReader* getCapTable();
// Gets the capability context in which this object is operating.
PointerReader imbue(CapTableReader* capTable) const;
// Return a copy of this reader except using the given capability context.
......
// 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.
#include "membrane.h"
#include <kj/test.h>
#include "test-util.h"
#include <kj/function.h>
#include <kj/async-io.h>
#include "rpc-twoparty.h"
namespace capnp {
namespace _ {
namespace {
using Thing = test::TestMembrane::Thing;
class ThingImpl: public Thing::Server {
public:
ThingImpl(kj::StringPtr text): text(text) {}
protected:
kj::Promise<void> passThrough(PassThroughContext context) override {
context.getResults().setText(text);
return kj::READY_NOW;
}
kj::Promise<void> intercept(InterceptContext context) override {
context.getResults().setText(text);
return kj::READY_NOW;
}
private:
kj::StringPtr text;
};
class TestMembraneImpl: public test::TestMembrane::Server {
protected:
kj::Promise<void> makeThing(MakeThingContext context) override {
context.getResults().setThing(kj::heap<ThingImpl>("inside"));
return kj::READY_NOW;
}
kj::Promise<void> callPassThrough(CallPassThroughContext context) override {
auto params = context.getParams();
auto req = params.getThing().passThroughRequest();
if (params.getTailCall()) {
return context.tailCall(kj::mv(req));
} else {
return req.send().then([context](Response<test::TestMembrane::Result>&& result) mutable {
context.setResults(result);
});
}
}
kj::Promise<void> callIntercept(CallInterceptContext context) override {
auto params = context.getParams();
auto req = params.getThing().interceptRequest();
if (params.getTailCall()) {
return context.tailCall(kj::mv(req));
} else {
return req.send().then([context](Response<test::TestMembrane::Result>&& result) mutable {
context.setResults(result);
});
}
}
kj::Promise<void> loopback(LoopbackContext context) override {
context.getResults().setThing(context.getParams().getThing());
return kj::READY_NOW;
}
};
class MembranePolicyImpl: public MembranePolicy, public kj::Refcounted {
public:
kj::Maybe<Capability::Client> inboundCall(uint64_t interfaceId, uint16_t methodId) override {
if (interfaceId == capnp::typeId<Thing>() && methodId == 1) {
return Capability::Client(kj::heap<ThingImpl>("inbound"));
} else {
return nullptr;
}
}
kj::Maybe<Capability::Client> outboundCall(uint64_t interfaceId, uint16_t methodId) override {
if (interfaceId == capnp::typeId<Thing>() && methodId == 1) {
return Capability::Client(kj::heap<ThingImpl>("outbound"));
} else {
return nullptr;
}
}
kj::Own<MembranePolicy> addRef() override {
return kj::addRef(*this);
}
};
void testThingImpl(kj::WaitScope& waitScope, test::TestMembrane::Client membraned,
kj::Function<Thing::Client()> makeThing,
kj::StringPtr localPassThrough, kj::StringPtr localIntercept,
kj::StringPtr remotePassThrough, kj::StringPtr remoteIntercept) {
KJ_EXPECT(makeThing().passThroughRequest().send().wait(waitScope).getText() == localPassThrough);
KJ_EXPECT(makeThing().interceptRequest().send().wait(waitScope).getText() == localIntercept);
{
auto req = membraned.callPassThroughRequest();
req.setThing(makeThing());
req.setTailCall(false);
KJ_EXPECT(req.send().wait(waitScope).getText() == remotePassThrough);
}
{
auto req = membraned.callInterceptRequest();
req.setThing(makeThing());
req.setTailCall(false);
KJ_EXPECT(req.send().wait(waitScope).getText() == remoteIntercept);
}
{
auto req = membraned.callPassThroughRequest();
req.setThing(makeThing());
req.setTailCall(true);
KJ_EXPECT(req.send().wait(waitScope).getText() == remotePassThrough);
}
{
auto req = membraned.callInterceptRequest();
req.setThing(makeThing());
req.setTailCall(true);
KJ_EXPECT(req.send().wait(waitScope).getText() == remoteIntercept);
}
}
struct TestEnv {
kj::EventLoop loop;
kj::WaitScope waitScope;
test::TestMembrane::Client membraned;
TestEnv()
: waitScope(loop),
membraned(membrane(kj::heap<TestMembraneImpl>(), kj::refcounted<MembranePolicyImpl>())) {}
void testThing(kj::Function<Thing::Client()> makeThing,
kj::StringPtr localPassThrough, kj::StringPtr localIntercept,
kj::StringPtr remotePassThrough, kj::StringPtr remoteIntercept) {
testThingImpl(waitScope, membraned, kj::mv(makeThing),
localPassThrough, localIntercept, remotePassThrough, remoteIntercept);
}
};
KJ_TEST("call local object inside membrane") {
TestEnv env;
env.testThing([&]() {
return env.membraned.makeThingRequest().send().wait(env.waitScope).getThing();
}, "inside", "inbound", "inside", "inside");
}
KJ_TEST("call local promise inside membrane") {
TestEnv env;
env.testThing([&]() {
return env.membraned.makeThingRequest().send().getThing();
}, "inside", "inbound", "inside", "inside");
}
KJ_TEST("call local resolved promise inside membrane") {
TestEnv env;
env.testThing([&]() {
auto thing = env.membraned.makeThingRequest().send().getThing();
thing.whenResolved().wait(env.waitScope);
return thing;
}, "inside", "inbound", "inside", "inside");
}
KJ_TEST("call local object outside membrane") {
TestEnv env;
env.testThing([&]() {
return kj::heap<ThingImpl>("outside");
}, "outside", "outside", "outside", "outbound");
}
KJ_TEST("call local capability that has passed into and back out of membrane") {
TestEnv env;
env.testThing([&]() {
auto req = env.membraned.loopbackRequest();
req.setThing(kj::heap<ThingImpl>("outside"));
return req.send().wait(env.waitScope).getThing();
}, "outside", "outside", "outside", "outbound");
}
KJ_TEST("call local promise pointing into membrane that eventually resolves to outside") {
TestEnv env;
env.testThing([&]() {
auto req = env.membraned.loopbackRequest();
req.setThing(kj::heap<ThingImpl>("outside"));
return req.send().getThing();
}, "outside", "outside", "outside", "outbound");
}
struct TestRpcEnv {
kj::AsyncIoContext io;
kj::TwoWayPipe pipe;
TwoPartyClient client;
TwoPartyClient server;
test::TestMembrane::Client membraned;
TestRpcEnv()
: io(kj::setupAsyncIo()),
pipe(io.provider->newTwoWayPipe()),
client(*pipe.ends[0]),
server(*pipe.ends[1],
membrane(kj::heap<TestMembraneImpl>(), kj::refcounted<MembranePolicyImpl>()),
rpc::twoparty::Side::SERVER),
membraned(client.bootstrap().castAs<test::TestMembrane>()) {}
void testThing(kj::Function<Thing::Client()> makeThing,
kj::StringPtr localPassThrough, kj::StringPtr localIntercept,
kj::StringPtr remotePassThrough, kj::StringPtr remoteIntercept) {
testThingImpl(io.waitScope, membraned, kj::mv(makeThing),
localPassThrough, localIntercept, remotePassThrough, remoteIntercept);
}
};
KJ_TEST("call remote object inside membrane") {
TestRpcEnv env;
env.testThing([&]() {
return env.membraned.makeThingRequest().send().wait(env.io.waitScope).getThing();
}, "inside", "inbound", "inside", "inside");
}
KJ_TEST("call remote promise inside membrane") {
TestRpcEnv env;
env.testThing([&]() {
return env.membraned.makeThingRequest().send().getThing();
}, "inside", "inbound", "inside", "inside");
}
KJ_TEST("call remote resolved promise inside membrane") {
TestEnv env;
env.testThing([&]() {
auto thing = env.membraned.makeThingRequest().send().getThing();
thing.whenResolved().wait(env.waitScope);
return thing;
}, "inside", "inbound", "inside", "inside");
}
KJ_TEST("call remote object outside membrane") {
TestRpcEnv env;
env.testThing([&]() {
return kj::heap<ThingImpl>("outside");
}, "outside", "outside", "outside", "outbound");
}
KJ_TEST("call remote capability that has passed into and back out of membrane") {
TestRpcEnv env;
env.testThing([&]() {
auto req = env.membraned.loopbackRequest();
req.setThing(kj::heap<ThingImpl>("outside"));
return req.send().wait(env.io.waitScope).getThing();
}, "outside", "outside", "outside", "outbound");
}
KJ_TEST("call remote promise pointing into membrane that eventually resolves to outside") {
TestRpcEnv env;
env.testThing([&]() {
auto req = env.membraned.loopbackRequest();
req.setThing(kj::heap<ThingImpl>("outside"));
return req.send().getThing();
}, "outside", "outside", "outside", "outbound");
}
} // namespace
} // namespace _
} // namespace capnp
This diff is collapsed.
// 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.
#ifndef CAPNP_MEMBRANE_H_
#define CAPNP_MEMBRANE_H_
// 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:
virtual kj::Maybe<Capability::Client> inboundCall(uint64_t interfaceId, uint16_t methodId) = 0;
// 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.
virtual kj::Maybe<Capability::Client> outboundCall(uint64_t interfaceId, uint16_t methodId) = 0;
// 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.
};
Capability::Client membrane(Capability::Client inner, kj::Own<MembranePolicy> policy);
// Wrap `inner` in a membrane specified by `filter`. `inner` is considered "inside" the membrane,
// 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.
// =======================================================================================
// 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))
.castAs<typename ServerType::Serves::Client>();
}
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))
.castAs<typename ServerType::Serves::Client>();
}
} // namespace capnp
#endif // CAPNP_MEMBRANE_H_
......@@ -182,14 +182,17 @@ TwoPartyClient::TwoPartyClient(kj::AsyncIoStream& connection)
TwoPartyClient::TwoPartyClient(kj::AsyncIoStream& connection,
Capability::Client bootstrapInterface)
: network(connection, rpc::twoparty::Side::CLIENT),
Capability::Client bootstrapInterface,
rpc::twoparty::Side side)
: network(connection, side),
rpcSystem(network, bootstrapInterface) {}
Capability::Client TwoPartyClient::bootstrap() {
MallocMessageBuilder message(4);
auto vatId = message.getRoot<rpc::twoparty::VatId>();
vatId.setSide(rpc::twoparty::Side::SERVER);
vatId.setSide(network.getSide() == rpc::twoparty::Side::CLIENT
? rpc::twoparty::Side::SERVER
: rpc::twoparty::Side::CLIENT);
return rpcSystem.bootstrap(vatId);
}
......
......@@ -59,6 +59,8 @@ public:
kj::Promise<void> onDisconnect() { return disconnectPromise.addBranch(); }
// Returns a promise that resolves when the peer disconnects.
rpc::twoparty::Side getSide() { return side; }
// implements VatNetwork -----------------------------------------------------
kj::Maybe<kj::Own<TwoPartyVatNetworkBase::Connection>> connect(
......@@ -136,7 +138,8 @@ class TwoPartyClient {
public:
explicit TwoPartyClient(kj::AsyncIoStream& connection);
TwoPartyClient(kj::AsyncIoStream& connection, Capability::Client bootstrapInterface);
TwoPartyClient(kj::AsyncIoStream& connection, Capability::Client bootstrapInterface,
rpc::twoparty::Side side = rpc::twoparty::Side::CLIENT);
Capability::Client bootstrap();
// Get the server's bootstrap interface.
......
......@@ -803,6 +803,22 @@ interface TestMoreStuff extends(TestCallOrder) {
# this can be used to test garbage collection.
}
interface TestMembrane {
makeThing @0 () -> (thing :Thing);
callPassThrough @1 (thing :Thing, tailCall :Bool) -> Result;
callIntercept @2 (thing :Thing, tailCall :Bool) -> Result;
loopback @3 (thing :Thing) -> (thing :Thing);
interface Thing {
passThrough @0 () -> Result;
intercept @1 () -> Result;
}
struct Result {
text @0 :Text;
}
}
struct TestTransferCap {
list @0 :List(Element);
struct Element {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment