mirror of
https://github.com/kidfromjupiter/nearby.git
synced 2026-09-14 14:46:12 -04:00
Fix DoS in Quick Share via malformed mDNS on Apple platforms
PiperOrigin-RevId: 949295891
This commit is contained in:
committed by
Copybara-Service
parent
a6f799af7f
commit
953d9ea26c
@@ -57,7 +57,10 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
- (nullable NSString *)getBonjourServiceNameFromEndpoint:(nw_endpoint_t)endpoint {
|
||||
const char *name = nw_endpoint_get_bonjour_service_name(endpoint);
|
||||
return name ? @(name) : nil;
|
||||
if (name == NULL) return nil;
|
||||
// @() returns nil on non-UTF-8 input; the wire format does not guarantee UTF-8.
|
||||
// Round-trip through Latin-1 so callers always get a non-nil NSString.
|
||||
return @(name) ?: [NSString stringWithCString:name encoding:NSISOLatin1StringEncoding];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -63,11 +63,14 @@ NSDictionary<NSString *, NSString *> *GNCTXTRecordForBrowseResult(nw_browse_resu
|
||||
block:^bool(const char *key, const nw_txt_record_find_key_t found,
|
||||
const uint8_t *value, const size_t value_len) {
|
||||
if (found == nw_txt_record_find_key_non_empty_value) {
|
||||
NSString *keyString = @(key);
|
||||
NSString *valueString =
|
||||
[[NSString alloc] initWithBytes:value
|
||||
length:value_len
|
||||
encoding:NSUTF8StringEncoding];
|
||||
[txtRecords setValue:valueString forKey:@(key)];
|
||||
if (keyString != nil && valueString != nil) {
|
||||
[txtRecords setObject:valueString forKey:keyString];
|
||||
}
|
||||
}
|
||||
return YES;
|
||||
}];
|
||||
@@ -231,6 +234,11 @@ NSDictionary<NSString *, NSString *> *GNCTXTRecordForBrowseResult(nw_browse_resu
|
||||
[browseResultWrapper copyEndpointFromResult:new_result];
|
||||
NSString *name = [browseResultWrapper
|
||||
getBonjourServiceNameFromEndpoint:endpoint];
|
||||
if (name == nil) {
|
||||
GNCLoggerInfo(
|
||||
@"Dropping mDNS result with unrepresentable name.");
|
||||
break;
|
||||
}
|
||||
NSDictionary<NSString *, NSString *> *txtRecords =
|
||||
GNCTXTRecordForBrowseResult(new_result);
|
||||
serviceFoundHandler(name, txtRecords);
|
||||
@@ -250,6 +258,11 @@ NSDictionary<NSString *, NSString *> *GNCTXTRecordForBrowseResult(nw_browse_resu
|
||||
[browseResultWrapper copyEndpointFromResult:old_result];
|
||||
NSString *oldName = [browseResultWrapper
|
||||
getBonjourServiceNameFromEndpoint:old_endpoint];
|
||||
if (oldName == nil) {
|
||||
GNCLoggerInfo(
|
||||
@"Dropping mDNS result with unrepresentable old name.");
|
||||
break;
|
||||
}
|
||||
NSDictionary<NSString *, NSString *> *oldTXTRecords =
|
||||
GNCTXTRecordForBrowseResult(old_result);
|
||||
serviceLostHandler(oldName, oldTXTRecords);
|
||||
@@ -258,6 +271,11 @@ NSDictionary<NSString *, NSString *> *GNCTXTRecordForBrowseResult(nw_browse_resu
|
||||
[browseResultWrapper copyEndpointFromResult:new_result];
|
||||
NSString *newName = [browseResultWrapper
|
||||
getBonjourServiceNameFromEndpoint:new_endpoint];
|
||||
if (newName == nil) {
|
||||
GNCLoggerInfo(
|
||||
@"Dropping mDNS result with unrepresentable new name.");
|
||||
break;
|
||||
}
|
||||
NSDictionary<NSString *, NSString *> *newTXTRecords =
|
||||
GNCTXTRecordForBrowseResult(new_result);
|
||||
serviceFoundHandler(newName, newTXTRecords);
|
||||
@@ -276,6 +294,11 @@ NSDictionary<NSString *, NSString *> *GNCTXTRecordForBrowseResult(nw_browse_resu
|
||||
[browseResultWrapper copyEndpointFromResult:old_result];
|
||||
NSString *name = [browseResultWrapper
|
||||
getBonjourServiceNameFromEndpoint:endpoint];
|
||||
if (name == nil) {
|
||||
GNCLoggerInfo(
|
||||
@"Dropping mDNS result with unrepresentable name.");
|
||||
break;
|
||||
}
|
||||
NSDictionary<NSString *, NSString *> *txtRecords =
|
||||
GNCTXTRecordForBrowseResult(old_result);
|
||||
serviceLostHandler(name, txtRecords);
|
||||
|
||||
+1
@@ -31,6 +31,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
@property(nonatomic) nw_browse_result_change_t getChangesFromResult;
|
||||
@property(nonatomic, nullable) nw_endpoint_t endpointFromResultResult;
|
||||
@property(nonatomic, nullable) NSString *getBonjourServiceNameFromEndpointResult;
|
||||
@property(nonatomic) BOOL returnNilServiceName;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
+3
@@ -67,6 +67,9 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
}
|
||||
|
||||
- (nullable NSString *)getBonjourServiceNameFromEndpoint:(nw_endpoint_t)endpoint {
|
||||
if (self.returnNilServiceName) {
|
||||
return nil;
|
||||
}
|
||||
return self.getBonjourServiceNameFromEndpointResult ?: @"FakeService";
|
||||
}
|
||||
|
||||
|
||||
+4
-4
@@ -53,16 +53,16 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
* @param serviceName The name of the service found.
|
||||
* @param txtRecords The TXT records of the service found.
|
||||
*/
|
||||
- (void)triggerServiceFound:(NSString*)serviceName
|
||||
txtRecords:(NSDictionary<NSString*, NSString*>*)txtRecords;
|
||||
- (void)triggerServiceFound:(nullable NSString*)serviceName
|
||||
txtRecords:(nullable NSDictionary<NSString*, NSString*>*)txtRecords;
|
||||
/**
|
||||
* Triggers the service lost handler with the given service info.
|
||||
*
|
||||
* @param serviceName The name of the service lost.
|
||||
* @param txtRecords The TXT records of the service lost.
|
||||
*/
|
||||
- (void)triggerServiceLost:(NSString*)serviceName
|
||||
txtRecords:(NSDictionary<NSString*, NSString*>*)txtRecords;
|
||||
- (void)triggerServiceLost:(nullable NSString*)serviceName
|
||||
txtRecords:(nullable NSDictionary<NSString*, NSString*>*)txtRecords;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
+4
-4
@@ -122,15 +122,15 @@
|
||||
return serverSocket;
|
||||
}
|
||||
|
||||
- (void)triggerServiceFound:(NSString *)serviceName
|
||||
txtRecords:(NSDictionary<NSString *, NSString *> *)txtRecords {
|
||||
- (void)triggerServiceFound:(nullable NSString *)serviceName
|
||||
txtRecords:(nullable NSDictionary<NSString *, NSString *> *)txtRecords {
|
||||
if (self.serviceFoundHandler) {
|
||||
self.serviceFoundHandler(serviceName, txtRecords);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)triggerServiceLost:(NSString *)serviceName
|
||||
txtRecords:(NSDictionary<NSString *, NSString *> *)txtRecords {
|
||||
- (void)triggerServiceLost:(nullable NSString *)serviceName
|
||||
txtRecords:(nullable NSDictionary<NSString *, NSString *> *)txtRecords {
|
||||
if (self.serviceLostHandler) {
|
||||
self.serviceLostHandler(serviceName, txtRecords);
|
||||
}
|
||||
|
||||
+13
@@ -67,4 +67,17 @@
|
||||
XCTAssertEqualObjects(results[@"key2"], @"value2");
|
||||
}
|
||||
|
||||
- (void)testGetBonjourServiceName_InvalidUTF8 {
|
||||
GNCNWBrowseResultImpl *browseResult = [[GNCNWBrowseResultImpl alloc] init];
|
||||
const char *raw_invalid = "\xc3\x28"
|
||||
"abc";
|
||||
nw_endpoint_t endpoint =
|
||||
nw_endpoint_create_bonjour_service(raw_invalid, "_servicetype._tcp", "local.");
|
||||
XCTAssertNotNil(endpoint);
|
||||
|
||||
NSString *serviceName = [browseResult getBonjourServiceNameFromEndpoint:endpoint];
|
||||
XCTAssertNotNil(serviceName);
|
||||
XCTAssertEqualObjects(serviceName, @"�(abc");
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
+55
-25
@@ -61,7 +61,6 @@ static NSString *const kHostAddress = @"127.0.0.1";
|
||||
_mockConnectionImpl = OCMClassMock([GNCNWConnectionImpl class]);
|
||||
}
|
||||
|
||||
|
||||
- (void)testGNCNWFrameworkCanBeInstantiated {
|
||||
GNCNWFramework *framework = [[GNCNWFramework alloc] init];
|
||||
XCTAssertNotNil(framework);
|
||||
@@ -272,6 +271,49 @@ static NSString *const kHostAddress = @"127.0.0.1";
|
||||
XCTAssertEqualObjects(foundTXTRecords, @{@"key" : @"value"});
|
||||
}
|
||||
|
||||
- (void)testStartDiscoveryForServiceTypeNilName API_AVAILABLE(ios(13.0)) {
|
||||
GNCNWFramework *framework = [[GNCNWFramework alloc] init];
|
||||
GNCFakeNWBrowser *fakeBrowser = [[GNCFakeNWBrowser alloc] init];
|
||||
fakeBrowser.createWithDescriptorResult = (nw_browser_t)fakeBrowser;
|
||||
OCMStub([_mockBrowserImpl alloc]).andReturn(fakeBrowser);
|
||||
|
||||
XCTestExpectation *serviceFoundExpectation = [self expectationWithDescription:@"Service found"];
|
||||
serviceFoundExpectation.inverted = YES;
|
||||
|
||||
NSError *error = nil;
|
||||
BOOL result = [framework startDiscoveryForServiceType:kServiceType
|
||||
serviceFoundHandler:^(NSString *serviceName,
|
||||
NSDictionary<NSString *, NSString *> *txtRecords) {
|
||||
[serviceFoundExpectation fulfill];
|
||||
}
|
||||
serviceLostHandler:^(NSString *serviceName,
|
||||
NSDictionary<NSString *, NSString *> *txtRecords) {
|
||||
}
|
||||
includePeerToPeer:NO
|
||||
error:&error];
|
||||
|
||||
XCTAssertTrue(result);
|
||||
XCTAssertNil(error);
|
||||
|
||||
// Simulate a service being found with nil name.
|
||||
GNCFakeNWBrowseResult *fakeBrowseResult = [[GNCFakeNWBrowseResult alloc] init];
|
||||
fakeBrowseResult.txtRecord = @{@"key" : @"value"};
|
||||
fakeBrowseResult.getChangesFromResult = nw_browse_result_change_result_added;
|
||||
nw_endpoint_t fakeEndpoint =
|
||||
nw_endpoint_create_host("localhost", [[NSString stringWithFormat:@"%ld", kPort] UTF8String]);
|
||||
fakeBrowseResult.endpointFromResultResult = fakeEndpoint;
|
||||
fakeBrowseResult.returnNilServiceName = YES;
|
||||
OCMStub([_mockBrowseResultImpl sharedInstance]).andReturn(fakeBrowseResult);
|
||||
|
||||
if (fakeBrowser.browseResultsChangedHandler) {
|
||||
GNCFakeNWBrowseResult *oldFakeBrowseResult = [[GNCFakeNWBrowseResult alloc] init];
|
||||
fakeBrowser.browseResultsChangedHandler((nw_browse_result_t)oldFakeBrowseResult,
|
||||
(nw_browse_result_t)fakeBrowseResult, true);
|
||||
}
|
||||
|
||||
[self waitForExpectations:@[ serviceFoundExpectation ] timeout:0.1];
|
||||
}
|
||||
|
||||
- (void)testStartDiscoveryForServiceTypeDuplicate API_AVAILABLE(ios(13.0)) {
|
||||
GNCNWFramework *framework = [[GNCNWFramework alloc] init];
|
||||
GNCFakeNWBrowser *fakeBrowser = [[GNCFakeNWBrowser alloc] init];
|
||||
@@ -449,12 +491,13 @@ static NSString *const kHostAddress = @"127.0.0.1";
|
||||
// TODO: b/377543997 - Migrate to dependency injection and remove mocks.
|
||||
OCMStub([_mockBrowserImpl alloc]).andReturn(fakeBrowser);
|
||||
|
||||
__block BOOL serviceFound = NO;
|
||||
XCTestExpectation *serviceFoundExpectation = [self expectationWithDescription:@"Service found"];
|
||||
serviceFoundExpectation.inverted = YES;
|
||||
NSError *error = nil;
|
||||
[framework startDiscoveryForServiceType:kServiceType
|
||||
serviceFoundHandler:^(NSString *serviceName,
|
||||
NSDictionary<NSString *, NSString *> *txtRecords) {
|
||||
serviceFound = YES;
|
||||
[serviceFoundExpectation fulfill];
|
||||
}
|
||||
serviceLostHandler:^(NSString *serviceName,
|
||||
NSDictionary<NSString *, NSString *> *txtRecords) {
|
||||
@@ -478,14 +521,7 @@ static NSString *const kHostAddress = @"127.0.0.1";
|
||||
(nw_browse_result_t)fakeBrowseResult, true);
|
||||
}
|
||||
|
||||
// Allow async blocks to run.
|
||||
XCTestExpectation *delay = [[XCTestExpectation alloc] initWithDescription:@"delay"];
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.1 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
|
||||
[delay fulfill];
|
||||
});
|
||||
[self waitForExpectations:@[ delay ] timeout:0.5];
|
||||
|
||||
XCTAssertFalse(serviceFound);
|
||||
[self waitForExpectations:@[ serviceFoundExpectation ] timeout:0.1];
|
||||
}
|
||||
|
||||
- (void)testStartDiscoveryIgnoresLoopbackRemove API_AVAILABLE(ios(13.0)) {
|
||||
@@ -495,7 +531,8 @@ static NSString *const kHostAddress = @"127.0.0.1";
|
||||
// TODO: b/377543997 - Migrate to dependency injection and remove mocks.
|
||||
OCMStub([_mockBrowserImpl alloc]).andReturn(fakeBrowser);
|
||||
|
||||
__block BOOL serviceLost = NO;
|
||||
XCTestExpectation *serviceLostExpectation = [self expectationWithDescription:@"Service lost"];
|
||||
serviceLostExpectation.inverted = YES;
|
||||
NSError *error = nil;
|
||||
[framework startDiscoveryForServiceType:kServiceType
|
||||
serviceFoundHandler:^(NSString *serviceName,
|
||||
@@ -503,7 +540,7 @@ static NSString *const kHostAddress = @"127.0.0.1";
|
||||
}
|
||||
serviceLostHandler:^(NSString *serviceName,
|
||||
NSDictionary<NSString *, NSString *> *txtRecords) {
|
||||
serviceLost = YES;
|
||||
[serviceLostExpectation fulfill];
|
||||
}
|
||||
includePeerToPeer:NO
|
||||
error:&error];
|
||||
@@ -524,14 +561,7 @@ static NSString *const kHostAddress = @"127.0.0.1";
|
||||
(nw_browse_result_t)newFakeBrowseResult, true);
|
||||
}
|
||||
|
||||
// Allow async blocks to run.
|
||||
XCTestExpectation *delay = [[XCTestExpectation alloc] initWithDescription:@"delay"];
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.1 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
|
||||
[delay fulfill];
|
||||
});
|
||||
[self waitForExpectations:@[ delay ] timeout:0.5];
|
||||
|
||||
XCTAssertFalse(serviceLost);
|
||||
[self waitForExpectations:@[ serviceLostExpectation ] timeout:0.1];
|
||||
}
|
||||
|
||||
- (void)testStopDiscoveryForServiceType API_AVAILABLE(ios(13.0)) {
|
||||
@@ -722,8 +752,8 @@ static NSString *const kHostAddress = @"127.0.0.1";
|
||||
GNCNWFrameworkSocket *socket = [framework connectToHost:address
|
||||
port:kPort
|
||||
includePeerToPeer:NO
|
||||
cancelSource:nil
|
||||
queue:nil
|
||||
cancelSource:nil
|
||||
queue:nil
|
||||
error:&error];
|
||||
|
||||
XCTAssertNotNil(socket);
|
||||
@@ -742,8 +772,8 @@ static NSString *const kHostAddress = @"127.0.0.1";
|
||||
GNCNWFrameworkSocket *socket = [framework connectToHost:address
|
||||
port:kPort
|
||||
includePeerToPeer:NO
|
||||
cancelSource:nil
|
||||
queue:nil
|
||||
cancelSource:nil
|
||||
queue:nil
|
||||
error:&error];
|
||||
|
||||
XCTAssertNil(socket);
|
||||
|
||||
@@ -157,4 +157,30 @@ static NSString *const kServiceType = @"_test._tcp";
|
||||
[self waitForExpectationsWithTimeout:1.0 handler:nil];
|
||||
}
|
||||
|
||||
- (void)testStartDiscoveryCallbacksWithNilName {
|
||||
XCTestExpectation *foundExpectation = [self expectationWithDescription:@"Service found callback"];
|
||||
XCTestExpectation *lostExpectation = [self expectationWithDescription:@"Service lost callback"];
|
||||
|
||||
nearby::apple::network_utils::NetworkDiscoveredServiceCallback callback;
|
||||
callback.network_service_discovered_cb = [&](const nearby::NsdServiceInfo &service_info) {
|
||||
XCTAssertEqual(service_info.GetServiceName(), std::string(""));
|
||||
XCTAssertEqual(service_info.GetServiceType(), kServiceType.UTF8String);
|
||||
[foundExpectation fulfill];
|
||||
};
|
||||
callback.network_service_lost_cb = [&](const nearby::NsdServiceInfo &service_info) {
|
||||
XCTAssertEqual(service_info.GetServiceName(), std::string(""));
|
||||
XCTAssertEqual(service_info.GetServiceType(), kServiceType.UTF8String);
|
||||
[lostExpectation fulfill];
|
||||
};
|
||||
|
||||
BOOL result = nearby::apple::network_utils::StartDiscovery(
|
||||
_fakeNWFramework, kServiceType.UTF8String, std::move(callback), YES);
|
||||
XCTAssertTrue(result);
|
||||
|
||||
[_fakeNWFramework triggerServiceFound:nil txtRecords:@{}];
|
||||
[_fakeNWFramework triggerServiceLost:nil txtRecords:@{}];
|
||||
|
||||
[self waitForExpectationsWithTimeout:1.0 handler:nil];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -63,7 +63,7 @@ bool StartDiscovery(GNCNWFramework* medium, const std::string& service_type,
|
||||
serviceFoundHandler:^(NSString* name, NSDictionary<NSString*, NSString*>* txtRecords) {
|
||||
NsdServiceInfo nsd_service_info;
|
||||
nsd_service_info.SetServiceType([serviceType UTF8String]);
|
||||
nsd_service_info.SetServiceName([name UTF8String]);
|
||||
nsd_service_info.SetServiceName(name ? [name UTF8String] : "");
|
||||
[txtRecords
|
||||
enumerateKeysAndObjectsUsingBlock:[nsd_service_info = &nsd_service_info](
|
||||
NSString* key, NSString* val, BOOL* stop) {
|
||||
@@ -74,7 +74,7 @@ bool StartDiscovery(GNCNWFramework* medium, const std::string& service_type,
|
||||
serviceLostHandler:^(NSString* name, NSDictionary<NSString*, NSString*>* txtRecords) {
|
||||
NsdServiceInfo nsd_service_info;
|
||||
nsd_service_info.SetServiceType([serviceType UTF8String]);
|
||||
nsd_service_info.SetServiceName([name UTF8String]);
|
||||
nsd_service_info.SetServiceName(name ? [name UTF8String] : "");
|
||||
[txtRecords
|
||||
enumerateKeysAndObjectsUsingBlock:[nsd_service_info = &nsd_service_info](
|
||||
NSString* key, NSString* val, BOOL* stop) {
|
||||
|
||||
Reference in New Issue
Block a user