diff --git a/Service/Sources/EDOHostService+Private.h b/Service/Sources/EDOHostService+Private.h index 08d98eb..33d3e23 100644 --- a/Service/Sources/EDOHostService+Private.h +++ b/Service/Sources/EDOHostService+Private.h @@ -64,6 +64,18 @@ NS_ASSUME_NONNULL_BEGIN */ - (BOOL)isObjectAliveWithPort:(EDOServicePort *)port remoteAddress:(EDOPointerType)remoteAddress; +/** + * Resolves the local object that this service has previously vended for the given address. + * + * The address is treated purely as an opaque lookup key into the service's tracked-object table; it + * is never dereferenced. This is the safe replacement for casting a wire-supplied @c EDOPointerType + * back to @c id. + * + * @param remoteAddress The address that was previously returned to the client in an @c EDOObject. + * @return The tracked local object, or @c nil if @c remoteAddress is not known to this service. + */ +- (nullable id)localObjectForAddress:(EDOPointerType)remoteAddress; + /** * Removes an EDOObject with the specified address in the host cache. * diff --git a/Service/Sources/EDOHostService.m b/Service/Sources/EDOHostService.m index 0a8c33a..3ffe774 100644 --- a/Service/Sources/EDOHostService.m +++ b/Service/Sources/EDOHostService.m @@ -389,6 +389,18 @@ - (EDOObject *)distantObjectForLocalObject:(id)object hostPort:(EDOHostPort *)ho return [EDOObject edo_remoteProxyFromUnderlyingObject:object withPort:port]; } } +- (id)localObjectForAddress:(EDOPointerType)remoteAddress { + // ivar is used directly here to avoid the service lazily creating the listen port. + if (_rootLocalObject && (EDOPointerType)_rootLocalObject == remoteAddress) { + return _rootLocalObject; + } + NSNumber *edoKey = [NSNumber numberWithLongLong:remoteAddress]; + __block id object; + dispatch_sync(_localObjectsSyncQueue, ^{ + object = self.localObjects[edoKey]; + }); + return object; +} - (BOOL)isObjectAliveWithPort:(EDOServicePort *)port remoteAddress:(EDOPointerType)remoteAddress { if (![_port match:port]) { diff --git a/Service/Sources/EDOInvocationMessage.m b/Service/Sources/EDOInvocationMessage.m index d273f0e..483abe4 100644 --- a/Service/Sources/EDOInvocationMessage.m +++ b/Service/Sources/EDOInvocationMessage.m @@ -341,7 +341,24 @@ + (EDORequestHandler)requestHandler { NSAssert([request isKindOfClass:[EDOInvocationRequest class]], @"EDOInvocationRequest is expected."); EDOHostPort *hostPort = request.hostPort; - id target = (__bridge id)(void *)request.target; + // The target address arrives off the wire as a raw 64-bit integer. It must NOT be cast to id + // until the service has confirmed it is an object it previously vended; otherwise an attacker + // can supply an arbitrary pointer and obtain a wild dereference / objc_msgSend on a fake isa. + id target = [service localObjectForAddress:request.target]; + if (!target) { + NSString *reason = + [NSString stringWithFormat:@"The target address (%llx) is not tracked by this service.", + request.target]; + EDORemoteException *remoteException = + [[EDORemoteException alloc] initWithName:EDOServiceGenericException + reason:reason + callStackSymbols:@[]]; + return [EDOInvocationResponse responseWithReturnValue:nil + exception:remoteException + outValues:nil + forRequest:request + targetClass:Nil]; + } SEL sel = NSSelectorFromString(request.selectorName); EDOBoxedValueType *returnValue; diff --git a/Service/Sources/EDOMethodSignatureMessage.m b/Service/Sources/EDOMethodSignatureMessage.m index 2e39c6d..a0f16a4 100644 --- a/Service/Sources/EDOMethodSignatureMessage.m +++ b/Service/Sources/EDOMethodSignatureMessage.m @@ -16,6 +16,7 @@ #import "Service/Sources/EDOMethodSignatureMessage.h" +#import "Service/Sources/EDOHostService+Private.h" #import "Service/Sources/EDOHostService.h" #import "Service/Sources/EDOMessage.h" #import "Service/Sources/EDOObject+Private.h" @@ -97,7 +98,12 @@ + (EDORequestHandler)requestHandler { } EDOMethodSignatureRequest *methodRequest = (EDOMethodSignatureRequest *)request; - id object = (__bridge Class)(void *)methodRequest.object; + id object = [service localObjectForAddress:methodRequest.object]; + if (!object) { + // If the object is not found, we can't get method signature. + // Returning nil signature will eventually result in an exception at the client. + return [[EDOMethodSignatureResponse alloc] initWithSignature:nil forRequest:request]; + } SEL sel = NSSelectorFromString(methodRequest.selectorName); NSMethodSignature *signature = EDOGetMethodSignature(object, sel); diff --git a/Service/Tests/UnitTests/EDOMessageTest.m b/Service/Tests/UnitTests/EDOMessageTest.m index a454d33..a0e3cef 100644 --- a/Service/Tests/UnitTests/EDOMessageTest.m +++ b/Service/Tests/UnitTests/EDOMessageTest.m @@ -435,6 +435,13 @@ - (void)testClassMethodInvocationHandler { EDOTestDummy *dummyLocal = [[EDOTestDummy alloc] init]; [self edo_createQueueAndServiceWithRootObject:dummyLocal block:^(EDOHostService *service) { + // Simulate the client asking for the class first to + // register it securely. + EDOServiceRequest *classRequest = [EDOClassRequest + requestWithClassName:@"EDOTestDummy" + hostPort:service.port.hostPort]; + EDOClassRequest.requestHandler(classRequest, service); + EDOInvocationResponse *response = [self edo_runInvocationWithService:service target:[dummyLocal class] @@ -507,7 +514,7 @@ - (void)testMethodSignatureRequestHandler { void *remoteAddress = (__bridge void *)dummyLocal; EDOHostService *service = [EDOHostService serviceWithPort:0 - rootObject:self + rootObject:dummyLocal queue:dispatch_get_main_queue()]; [EDOTestDummy enumerateSelector:^(SEL selector) {