Merge remote-tracking branch 'nearby/main'

# Conflicts:
#	connections/implementation/bwu_manager_test.cc
#	sharing/BUILD
#	sharing/certificates/fake_nearby_share_certificate_manager.cc
#	sharing/certificates/fake_nearby_share_certificate_manager.h
#	sharing/internal/base/utf_string_conversions.h
This commit is contained in:
Lasan Mahaliyana
2026-07-26 22:46:37 +05:30
155 changed files with 5257 additions and 2018 deletions
@@ -386,6 +386,10 @@ static const int kMaxAdvertisementLengthOnIOS = 23;
[_peripheralManager respondToRequest:request withResult:CBATTErrorAttributeNotFound];
return;
}
if (request.offset > value.length) {
[_peripheralManager respondToRequest:request withResult:CBATTErrorInvalidOffset];
return;
}
request.value =
[value subdataWithRange:NSMakeRange(request.offset, value.length - request.offset)];
[_peripheralManager respondToRequest:request withResult:CBATTErrorSuccess];
@@ -24,6 +24,7 @@
#import "internal/platform/implementation/apple/Mediums/BLE/GNCMConnection.h"
enum { kL2CAPPacketLength = 4 };
static const NSUInteger kGNCBLEL2CAPMaxFrameLength = 5 * 1024 * 1024; // 5 MB
static const CGFloat kRequestDataConnectionDelayInSeconds = 0.0;
static const UInt8 kRequestDataConnectionTimeoutInSeconds = 5;
@@ -266,6 +267,20 @@ static NSData *PrefixLengthData(NSData *data) {
}
_expectedDataLength = CFSwapInt32BigToHost(
*(int *)([[data subdataWithRange:NSMakeRange(0, kL2CAPPacketLength)] bytes]));
if (_expectedDataLength == 0 || _expectedDataLength > kGNCBLEL2CAPMaxFrameLength) {
GNCLoggerError(@"[NEARBY] Rejecting L2CAP frame: declared length %lu out of range "
@"(max %lu); closing.",
(unsigned long)_expectedDataLength,
(unsigned long)kGNCBLEL2CAPMaxFrameLength);
_expectedDataLength = 0;
[_stream close];
if (_connectionHandlers.disconnectedHandler) {
dispatch_async(_callbackQueue, ^{
_connectionHandlers.disconnectedHandler();
});
}
return nil;
}
}
NSUInteger realDataLength = data.length - kL2CAPPacketLength;
if (realDataLength < _expectedDataLength) {
@@ -74,6 +74,9 @@ enum { READ_BUFFER_SIZE = 409600 };
/// Whether the stream is closed.
BOOL _closed;
/// Buffer for reading data from the input stream.
uint8_t _readBuffer[READ_BUFFER_SIZE];
}
#pragma mark Public
@@ -319,12 +322,11 @@ enum { READ_BUFFER_SIZE = 409600 };
/// Receives data from device and invokes |_receivedDataBlock|.
- (void)receiveStreamData {
dispatch_assert_queue_debug(_streamQueue);
uint8_t readBuffer[READ_BUFFER_SIZE];
NSInteger bytesRead = [self.inputStream read:readBuffer maxLength:READ_BUFFER_SIZE];
NSInteger bytesRead = [self.inputStream read:_readBuffer maxLength:READ_BUFFER_SIZE];
if (bytesRead > 0) {
NSMutableData *data = [NSMutableData data];
[data appendBytes:readBuffer length:(NSUInteger)bytesRead];
[data appendBytes:_readBuffer length:(NSUInteger)bytesRead];
if (_verboseLoggingEnabled) {
GNCLoggerDebug(@"[NEARBY] Stream data from device of length %@", @(data.length));
@@ -140,7 +140,11 @@ NS_ASSUME_NONNULL_BEGIN
return;
}
if (![[data subdataWithRange:NSMakeRange(0, prefixLength)] isEqual:_serviceIDHash]) {
// IntroductionFrame.service_id_hash. We MUST bounds-check before
// -subdataWithRange:, otherwise a short follow-up packet throws
// NSRangeException on CoreBluetooth's dispatch queue -> objc_terminate.
if (data.length < prefixLength ||
![[data subdataWithRange:NSMakeRange(0, prefixLength)] isEqual:_serviceIDHash]) {
GNCLoggerInfo(@"[NEARBY] Input stream: Received wrong data packet and discarded");
return;
}
@@ -78,6 +78,11 @@ NSData *_Nullable GNCMParseBLEFramesIntroductionPacket(NSData *_Nullable data) {
::location::nearby::mediums::SocketVersion::V2 &&
socket_control_frame.introduction().has_service_id_hash()) {
std::string service_id_hash = socket_control_frame.introduction().service_id_hash();
// service_id_hash is attacker-supplied; clamp to the protocol-defined
// 3-byte length so it cannot be used to inflate prefixLength downstream.
if (service_id_hash.size() != GNCMBleAdvertisementLengthServiceIDHash) {
return nil;
}
return [NSData dataWithBytes:service_id_hash.data() length:service_id_hash.length()];
}
}
@@ -111,15 +111,17 @@ static NSTimeInterval gKBTCrashLoopMaxTimeBetweenResetting = 15.f;
- (void)addPeripheralServiceManager:(GNSPeripheralServiceManager *)peripheralServiceManager
bleServiceAddedCompletion:(GNSErrorHandler)completion {
[_peripheralServiceManagers setObject:peripheralServiceManager
forKey:peripheralServiceManager.serviceUUID];
[peripheralServiceManager addedToPeripheralManager:self bleServiceAddedCompletion:completion];
if (_started) {
[self addBleServiceForServiceManager:peripheralServiceManager];
}
// Update all advertised services to make sure that the right services are advertised in case
// all BLE services were already added.
[self updateAdvertisedServices];
dispatch_async(_queue, ^{
[self->_peripheralServiceManagers setObject:peripheralServiceManager
forKey:peripheralServiceManager.serviceUUID];
[peripheralServiceManager addedToPeripheralManager:self bleServiceAddedCompletion:completion];
if (self->_started) {
[self addBleServiceForServiceManager:peripheralServiceManager];
}
// Update all advertised services to make sure that the right services are advertised in case
// all BLE services were already added.
[self updateAdvertisedServices];
});
}
- (void)start {
@@ -211,18 +213,24 @@ static NSTimeInterval gKBTCrashLoopMaxTimeBetweenResetting = 15.f;
- (void)removePeripheralServiceManagerForServiceUUID:(CBUUID *)serviceUUID
bleServiceRemovedCompletion:(GNSErrorHandler)completion {
GNSPeripheralServiceManager *peripheralServiceManager =
[_peripheralServiceManagers objectForKey:serviceUUID];
if (peripheralServiceManager == nil) {
completion(nil);
return;
}
[_cbPeripheralManager removeService:peripheralServiceManager.cbService];
[_peripheralServiceManagers removeObjectForKey:serviceUUID];
[peripheralServiceManager didRemoveCBService];
dispatch_async(_queue, ^{
GNSPeripheralServiceManager *peripheralServiceManager =
[self->_peripheralServiceManagers objectForKey:serviceUUID];
if (peripheralServiceManager == nil) {
if (completion) {
completion(nil);
}
return;
}
[self->_cbPeripheralManager removeService:peripheralServiceManager.cbService];
[self->_peripheralServiceManagers removeObjectForKey:serviceUUID];
[peripheralServiceManager didRemoveCBService];
[self updateAdvertisedServices];
completion(nil);
[self updateAdvertisedServices];
if (completion) {
completion(nil);
}
});
}
- (void)removeAllBleServices {
File diff suppressed because it is too large Load Diff
@@ -140,6 +140,32 @@ static const NSTimeInterval kTestTimeout = 1.0;
XCTAssertNil(realData);
}
- (void)testExtractRealDataFromData_oversizedData {
_connection = [self createConnectionWithIncoming:YES];
XCTestExpectation *disconnectionExpectation =
[self expectationWithDescription:@"disconnection handler"];
_connection.connectionHandlers = [GNCMConnectionHandlers
payloadHandler:^(NSData *data) {
XCTFail(@"Unexpected payload");
}
disconnectedHandler:^{
[disconnectionExpectation fulfill];
}];
// 6 MB frame length
uint32_t oversizedLength = 6 * 1024 * 1024;
uint32_t lengthBigEndian = CFSwapInt32HostToBig(oversizedLength);
NSMutableData *prefixData = [NSMutableData dataWithCapacity:sizeof(uint32_t)];
[prefixData appendBytes:&lengthBigEndian length:sizeof(uint32_t)];
NSData *realData = [_connection extractRealDataFromData:prefixData];
XCTAssertNil(realData);
XCTAssertEqual(_connection.expectedDataLength, 0);
[self waitForExpectationsWithTimeout:kTestTimeout handler:nil];
}
- (void)testExtractRealDataFromData_moreThanExpectedData {
_connection = [self createConnectionWithIncoming:YES];
NSData *testData = [self createDataWithLength:10];
@@ -47,6 +47,9 @@ NS_ASSUME_NONNULL_BEGIN
/** Expectation fulfilled when peripheral responds to a request with an error. */
@property(nonatomic, readonly) XCTestExpectation *respondToRequestErrorExpectation;
/** The last response result. */
@property(nonatomic, assign) CBATTError lastResponseResult;
/** Expectation fulfilled when peripheral unpublishes an L2CAP channel. */
@property(nonatomic, readonly) XCTestExpectation *unpublishExpectation;
@@ -117,6 +120,20 @@ NS_ASSUME_NONNULL_BEGIN
- (void)simulatePeripheralManagerDidReceiveReadRequestForService:(CBUUID *)service
characteristic:(CBUUID *)characteristic;
/**
* Simulates a read request event with an offset.
*
* Creates a fake read request with the given offset for the given service and characteristic UUIDs
* and calls the @c gnc_peripheralManager:didReceiveReadRequest: delegate method.
*
* @param service The service UUID of the characteristic to read from.
* @param characteristic The characteristic UUID to read from.
* @param offset The offset to read from.
*/
- (void)simulatePeripheralManagerDidReceiveReadRequestForService:(CBUUID *)service
characteristic:(CBUUID *)characteristic
offset:(NSUInteger)offset;
@end
NS_ASSUME_NONNULL_END
@@ -37,6 +37,9 @@
// Keep a strong reference to the service.
@property(readwrite, nonatomic) CBService *service;
// Change property to readwrite for tests.
@property(readwrite, nonatomic) NSUInteger offset;
- (instancetype)initWithService:(CBUUID *)service characteristic:(CBUUID *)characteristic;
@end
@@ -115,6 +118,7 @@ static const uint16_t kPSM = 192;
}
- (void)respondToRequest:(CBATTRequest *)request withResult:(CBATTError)result {
self.lastResponseResult = result;
if (result == CBATTErrorSuccess) {
[_respondToRequestSuccessExpectation fulfill];
return;
@@ -176,6 +180,15 @@ static const uint16_t kPSM = 192;
[_peripheralDelegate gnc_peripheralManager:self didReceiveReadRequest:request];
}
- (void)simulatePeripheralManagerDidReceiveReadRequestForService:(CBUUID *)service
characteristic:(CBUUID *)characteristic
offset:(NSUInteger)offset {
CBATTRequest *request = [[CBATTRequest alloc] initWithService:service
characteristic:characteristic];
request.offset = offset;
[_peripheralDelegate gnc_peripheralManager:self didReceiveReadRequest:request];
}
- (void)setDelegate:(id<CBPeripheralManagerDelegate>)delegate {
self.peripheralDelegate = (id<GNCPeripheralManagerDelegate>)delegate;
}
@@ -241,4 +241,33 @@ static const NSTimeInterval kTimeout = 1.0;
[self waitForExpectationsWithTimeout:kTimeout handler:nil];
}
- (void)testReceiveShortDataPacketAfterIntro {
_connection = [GNCMBleConnection connectionWithSocket:(GNSSocket *)_fakeSocket
serviceID:nil
expectedIntroPacket:YES
callbackQueue:_callbackQueue];
NSData *introPacket = GNCMGenerateBLEFramesIntroductionPacket(GNCMServiceIDHash(kServiceID));
// Receive the intro packet first to set `_serviceIDHash`.
[_fakeSocket simulateSocketDidReceiveData:introPacket];
// Receive a data packet that is shorter than the service ID hash length.
// This should not crash; it should just be discarded.
NSData *shortPacket = [@"ab" dataUsingEncoding:NSUTF8StringEncoding];
XCTestExpectation *expectation = [self expectationWithDescription:@"Payload handler not called"];
expectation.inverted = YES;
GNCMConnectionHandlers *handlers = [[GNCMConnectionHandlers alloc] init];
handlers.payloadHandler = ^(NSData *data) {
[expectation fulfill];
};
_connection.connectionHandlers = handlers;
[_fakeSocket simulateSocketDidReceiveData:shortPacket];
[self waitForExpectationsWithTimeout:kTimeout handler:nil];
}
@end
@@ -45,6 +45,18 @@ static const NSTimeInterval kWaitForConnectionTimeout = 6.0; // Allow for the 5
XCTAssertEqualObjects(parsedHash, serviceIDHash);
}
- (void)testParseBLEFramesIntroductionPacketFailure_InvalidHashLength {
// Too long hash (4 bytes, protocol expects 3 bytes)
NSData *longHash = [@"1234" dataUsingEncoding:NSUTF8StringEncoding];
NSData *longPacket = GNCMGenerateBLEFramesIntroductionPacket(longHash);
XCTAssertNil(GNCMParseBLEFramesIntroductionPacket(longPacket));
// Too short hash (2 bytes, protocol expects 3 bytes)
NSData *shortHash = [@"12" dataUsingEncoding:NSUTF8StringEncoding];
NSData *shortPacket = GNCMGenerateBLEFramesIntroductionPacket(shortHash);
XCTAssertNil(GNCMParseBLEFramesIntroductionPacket(shortPacket));
}
- (void)testParseBLEFramesIntroductionPacketFailure_NilData {
NSData *parsedHash = GNCMParseBLEFramesIntroductionPacket(nil);
XCTAssertNil(parsedHash);