From 18fc1a1bd264cd331cba20e07be14b4887dfac57 Mon Sep 17 00:00:00 2001 From: eDO Team Date: Mon, 9 Nov 2026 14:24:51 -0800 Subject: [PATCH] Validate wire-supplied object addresses against EDOHostService. Resolve target and method-signature addresses through EDOHostService's tracked object table instead of bridge-casting raw wire integers to Objective-C object pointers. PiperOrigin-RevId: 1017638324 --- Service/Sources/EDOClientService.m | 5 +- Service/Sources/EDOHostService+Private.h | 11 +++ Service/Sources/EDOHostService.m | 22 +++--- Service/Sources/EDOInvocationMessage.m | 16 ++++- Service/Sources/EDOMethodSignatureMessage.m | 6 +- Service/Tests/UnitTests/EDOMessageTest.m | 75 +++++++++++++++++++++ 6 files changed, 124 insertions(+), 11 deletions(-) diff --git a/Service/Sources/EDOClientService.m b/Service/Sources/EDOClientService.m index e00f80c..0672d34 100644 --- a/Service/Sources/EDOClientService.m +++ b/Service/Sources/EDOClientService.m @@ -112,7 +112,10 @@ + (id)unwrappedObjectFromObject:(id)object { // Otherwise, we send EDOObjectAlive message to another service running in the same process. if ([service isObjectAliveWithPort:edoObject.servicePort remoteAddress:edoObject.remoteAddress]) { - return (__bridge id)(void *)edoObject.remoteAddress; + id resolvedObject = [service localObjectForAddress:edoObject.remoteAddress]; + if (resolvedObject) { + return resolvedObject; + } } else if (edoObject.isLocalEdo) { // If the underlying object is not a local object (but in the same process) then this could // return nil. For example, the service becomes invalide, or the remote object is already diff --git a/Service/Sources/EDOHostService+Private.h b/Service/Sources/EDOHostService+Private.h index 08d98eb..8bd1d49 100644 --- a/Service/Sources/EDOHostService+Private.h +++ b/Service/Sources/EDOHostService+Private.h @@ -64,6 +64,17 @@ 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. + * + * @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..519a7d9 100644 --- a/Service/Sources/EDOHostService.m +++ b/Service/Sources/EDOHostService.m @@ -390,18 +390,24 @@ - (EDOObject *)distantObjectForLocalObject:(id)object hostPort:(EDOHostPort *)ho } } -- (BOOL)isObjectAliveWithPort:(EDOServicePort *)port remoteAddress:(EDOPointerType)remoteAddress { - if (![_port match:port]) { - return NO; +- (id)localObjectForAddress:(EDOPointerType)remoteAddress { + if (_rootLocalObject && (EDOPointerType)_rootLocalObject == remoteAddress) { + return _rootLocalObject; } - __block BOOL isAlive; + NSNumber *edoKey = [NSNumber numberWithLongLong:remoteAddress]; + __block id object; dispatch_sync(_localObjectsSyncQueue, ^{ - NSNumber *EDOKey = [NSNumber numberWithLongLong:remoteAddress]; - isAlive = - self.localObjects[EDOKey] != nil || ((EDOPointerType)self.rootLocalObject) == remoteAddress; + object = self.localObjects[edoKey]; }); + return object; +} + +- (BOOL)isObjectAliveWithPort:(EDOServicePort *)port remoteAddress:(EDOPointerType)remoteAddress { // ivar is used directly here to avoid the service lazily creating listen port. - return isAlive; + if (![_port match:port]) { + return NO; + } + return [self localObjectForAddress:remoteAddress] != nil; } - (BOOL)removeObjectWithAddress:(EDOPointerType)remoteAddress { diff --git a/Service/Sources/EDOInvocationMessage.m b/Service/Sources/EDOInvocationMessage.m index d273f0e..83aa681 100644 --- a/Service/Sources/EDOInvocationMessage.m +++ b/Service/Sources/EDOInvocationMessage.m @@ -341,7 +341,21 @@ + (EDORequestHandler)requestHandler { NSAssert([request isKindOfClass:[EDOInvocationRequest class]], @"EDOInvocationRequest is expected."); EDOHostPort *hostPort = request.hostPort; - id target = (__bridge id)(void *)request.target; + 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..bb9ad9b 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,10 @@ + (EDORequestHandler)requestHandler { } EDOMethodSignatureRequest *methodRequest = (EDOMethodSignatureRequest *)request; - id object = (__bridge Class)(void *)methodRequest.object; + id object = [service localObjectForAddress:methodRequest.object]; + if (!object) { + 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..4789448 100644 --- a/Service/Tests/UnitTests/EDOMessageTest.m +++ b/Service/Tests/UnitTests/EDOMessageTest.m @@ -30,6 +30,7 @@ #import "Service/Sources/EDOObjectMessage.h" #import "Service/Sources/EDOParameter.h" // IWYU pragma: keep #import "Service/Sources/EDORemoteException.h" +#import "Service/Sources/EDOServiceException.h" #import "Service/Sources/EDOServicePort.h" #import "Service/Tests/TestsBundle/EDOTestDummy.h" @@ -435,6 +436,9 @@ - (void)testClassMethodInvocationHandler { EDOTestDummy *dummyLocal = [[EDOTestDummy alloc] init]; [self edo_createQueueAndServiceWithRootObject:dummyLocal block:^(EDOHostService *service) { + [service + distantObjectForLocalObject:[dummyLocal class] + hostPort:service.port.hostPort]; EDOInvocationResponse *response = [self edo_runInvocationWithService:service target:[dummyLocal class] @@ -509,6 +513,7 @@ - (void)testMethodSignatureRequestHandler { EDOHostService *service = [EDOHostService serviceWithPort:0 rootObject:self queue:dispatch_get_main_queue()]; + [service distantObjectForLocalObject:dummyLocal hostPort:service.port.hostPort]; [EDOTestDummy enumerateSelector:^(SEL selector) { EDOMethodSignatureRequest *request = @@ -531,6 +536,76 @@ - (void)testMethodSignatureRequestHandler { [service invalidate]; } +- (void)testUntrackedTargetInvocationHandlerReturnsException { + XCTestExpectation *blockExecuted = [self expectationWithDescription:@"Executed the test block."]; + EDOTestDummy *dummyLocal = [[EDOTestDummy alloc] init]; + [self edo_createQueueAndServiceWithRootObject:dummyLocal + block:^(EDOHostService *service) { + EDOPointerType bogusAddress = + (EDOPointerType)0x4141414141414140LL; + EDOInvocationRequest *request = [EDOInvocationRequest + requestWithTarget:bogusAddress + selector:@selector(returnInt) + arguments:@[] + hostPort:service.port.hostPort + returnByValue:NO]; + EDOInvocationResponse *response = + (EDOInvocationResponse *)EDOInvocationRequest + .requestHandler(request, service); + XCTAssertNil(response.returnValue); + XCTAssertEqualObjects(response.exception.name, + EDOServiceGenericException); + [blockExecuted fulfill]; + }]; + [self waitForExpectationsWithTimeout:1 handler:nil]; +} + +- (void)testReleasedTargetInvocationHandlerReturnsException { + XCTestExpectation *blockExecuted = [self expectationWithDescription:@"Executed the test block."]; + EDOTestDummy *rootLocal = [[EDOTestDummy alloc] init]; + [self edo_createQueueAndServiceWithRootObject:rootLocal + block:^(EDOHostService *service) { + EDOPointerType releasedAddress = 0; + @autoreleasepool { + EDOTestDummy *transient = [[EDOTestDummy alloc] init]; + EDOObject *proxy = + [service distantObjectForLocalObject:transient + hostPort:nil]; + releasedAddress = proxy.remoteAddress; + [service removeObjectWithAddress:releasedAddress]; + } + EDOInvocationRequest *request = [EDOInvocationRequest + requestWithTarget:releasedAddress + selector:@selector(returnInt) + arguments:@[] + hostPort:service.port.hostPort + returnByValue:NO]; + EDOInvocationResponse *response = + (EDOInvocationResponse *)EDOInvocationRequest + .requestHandler(request, service); + XCTAssertNil(response.returnValue); + XCTAssertEqualObjects(response.exception.name, + EDOServiceGenericException); + [blockExecuted fulfill]; + }]; + [self waitForExpectationsWithTimeout:1 handler:nil]; +} + +- (void)testUntrackedObjectMethodSignatureRequestHandlerReturnsNil { + EDOHostService *service = [EDOHostService serviceWithPort:0 + rootObject:self + queue:dispatch_get_main_queue()]; + EDOPointerType bogusAddress = (EDOPointerType)0x4141414141414140LL; + EDOMethodSignatureRequest *request = + [EDOMethodSignatureRequest requestWithObject:bogusAddress + port:service.port + selector:@selector(description)]; + EDOMethodSignatureResponse *response = + (EDOMethodSignatureResponse *)EDOMethodSignatureRequest.requestHandler(request, service); + XCTAssertNil(response.signature); + [service invalidate]; +} + - (void)testMethodSignatureForward { id dummyLocal = [[EDOTestDummy alloc] init]; NS_VALID_UNTIL_END_OF_SCOPE dispatch_queue_t queue =