Skip to content
This repository has been archived by the owner on Feb 5, 2025. It is now read-only.

Added handling for Remount events to USB mass storage blocking #818

Merged
Prev Previous commit
Next Next commit
Applied review feedback.
  • Loading branch information
Pete Markowsky committed May 27, 2022
commit 62238394b6bebd7f815b7aec49acb1fdcd66f3c2
1 change: 1 addition & 0 deletions Source/santad/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -453,6 +453,7 @@ santa_unit_test(
"@MOLXPCConnection",
"@OCMock",
],
tags = ["exclusive"],
)

santa_unit_test(
Expand Down
53 changes: 23 additions & 30 deletions Source/santad/EventProviders/SNTDeviceManager.mm
Original file line number Diff line number Diff line change
Expand Up @@ -204,50 +204,46 @@ - (void)handleAuthMount:(const es_message_t *)m
BOOL isRemount = NO;

switch (m->event_type) {
case ES_EVENT_TYPE_AUTH_MOUNT:
eventStatFS = m->event.mount.statfs;
break;
case ES_EVENT_TYPE_AUTH_MOUNT: eventStatFS = m->event.mount.statfs; break;
case ES_EVENT_TYPE_AUTH_REMOUNT:
eventStatFS = m->event.remount.statfs;
isRemount = YES;
break;
default:
LOGE(@"Unexpected Event Type passed to DeviceManager handleAuthMount: %d", m->event_type);
assert("Wrong event type");
// Fail closed.
es_respond_auth_result(self.client, m, ES_AUTH_RESULT_DENY, false);
return;
break;
return;
}

long mountMode = eventStatFS->f_flags;
pid_t pid = audit_token_to_pid(m->process->audit_token);
LOGI(@"SNTDeviceManager: mount syscall arriving from path: %s, pid: %d, fflags: %lu",
LOGD(@"SNTDeviceManager: mount syscall arriving from path: %s, pid: %d, fflags: %lu",
m->process->executable->path.data, pid, mountMode);

DADiskRef disk =
DADiskCreateFromBSDName(NULL, self.diskArbSession, eventStatFS->f_mntfromname);
DADiskRef disk = DADiskCreateFromBSDName(NULL, self.diskArbSession, eventStatFS->f_mntfromname);
CFAutorelease(disk);

// TODO(tnek): Log all of the other attributes available in diskInfo into a structured log format.
NSDictionary *diskInfo = CFBridgingRelease(DADiskCopyDescription(disk));
BOOL isInternal = [diskInfo[(__bridge NSString *)kDADiskDescriptionDeviceInternalKey] boolValue];
BOOL isInternal = [diskInfo[(__bridge NSString *)kDADiskDescriptionDeviceInternalKey] boolValue];
BOOL isRemovable = [diskInfo[(__bridge NSString *)kDADiskDescriptionMediaRemovableKey] boolValue];
BOOL isEjectable = [diskInfo[(__bridge NSString *)kDADiskDescriptionMediaEjectableKey] boolValue];
NSString* protocol = diskInfo[(__bridge NSString *)kDADiskDescriptionDeviceProtocolKey];
NSString *protocol = diskInfo[(__bridge NSString *)kDADiskDescriptionDeviceProtocolKey];
BOOL isUSB = [protocol isEqualToString:@"USB"];
BOOL isDMG = [protocol isEqualToString:@"Virtual Interface"];
BOOL isVirtual = [protocol isEqualToString: @"Virtual Interface"];

NSString* kind = diskInfo[(__bridge NSString *)kDADiskDescriptionMediaKindKey];
NSString *kind = diskInfo[(__bridge NSString *)kDADiskDescriptionMediaKindKey];

//TODO: check kind and protocol for banned things (e.g. MTP).
LOGD(@"SNTDeviceManager: DiskInfo Protocol: %@", protocol);
LOGD(@"SNTDeviceManager: DiskInfo Kind: %@", kind);
LOGD(@"SNTDeviceManager: DiskInfo isInternal: %d", isInternal);
LOGD(@"SNTDeviceManager: DiskInfo isRemovable: %d", isRemovable);
LOGD(@"SNTDeviceManager: DiskInfo isEjectable: %d", isEjectable);
// TODO: check kind and protocol for banned things (e.g. MTP).
LOGD(@"SNTDeviceManager: DiskInfo Protocol: %@ Kind: %@ isInternal: %d isRemovable: %d "
@"isEjectable: %d",
protocol, kind, isInternal, isRemovable, isEjectable);

// If the device isn't a DMG or removable we're ok with the operation.
if (isInternal || isDMG || (!isRemovable && !isEjectable && !isUSB)) {
// If the device isn't a Virtual device (DMG/ramdisk etc.) or removable we're
// ok with the operation.
if (isInternal || isVirtual || (!isRemovable && !isEjectable && !isUSB)) {
es_respond_auth_result(self.client, m, ES_AUTH_RESULT_ALLOW, false);
return;
}
Expand All @@ -256,7 +252,7 @@ - (void)handleAuthMount:(const es_message_t *)m
initWithOnName:[NSString stringWithUTF8String:eventStatFS->f_mntonname]
fromName:[NSString stringWithUTF8String:eventStatFS->f_mntfromname]];

BOOL shouldRemount = self.remountArgs != nil && [self.remountArgs count] > 0;
BOOL shouldRemount = self.remountArgs != nil && [self.remountArgs count] > 0;

if (shouldRemount) {
event.remountArgs = self.remountArgs;
Expand All @@ -273,8 +269,7 @@ - (void)handleAuthMount:(const es_message_t *)m

long newMode = mountMode | remountOpts;
LOGI(@"SNTDeviceManager: remounting device '%s'->'%s', flags (%lu) -> (%lu)",
eventStatFS->f_mntfromname, eventStatFS->f_mntonname, mountMode,
newMode);
eventStatFS->f_mntfromname, eventStatFS->f_mntonname, mountMode, newMode);
[self remount:disk mountMode:newMode];
}

Expand Down Expand Up @@ -336,18 +331,16 @@ - (void)handleESMessage:(const es_message_t *)m
withClient:(es_client_t *)c API_AVAILABLE(macos(10.15)) {
switch (m->event_type) {
case ES_EVENT_TYPE_AUTH_REMOUNT: {
[[fallthrough]];
[[fallthrough]];
}
case ES_EVENT_TYPE_AUTH_MOUNT: {
[self handleAuthMount:m withClient:c];
// Intentional fallthrough
[[fallthrough]];
}
// TODO(tnek): log any extra data here about mounts.
case ES_EVENT_TYPE_NOTIFY_MOUNT: {
break;
}
default: LOGE(@"SNTDeviceManager: unexpected event type: %d", m->event_type);

default:
LOGE(@"SNTDeviceManager: unexpected event type: %d", m->event_type);
break;
}
}

Expand Down
63 changes: 48 additions & 15 deletions Source/santad/EventProviders/SNTDeviceManagerTest.mm
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,10 @@ - (void)setUp {
}

- (ESResponse *)triggerTestMountEvent:(SNTDeviceManager *)deviceManager
mockES:(MockEndpointSecurity *)mockES
mockDA:(MockDiskArbitration *)mockDA
eventType:(es_event_type_t)eventType
diskInfoProtocol:(NSString *)protocol {

mockES:(MockEndpointSecurity *)mockES
mockDA:(MockDiskArbitration *)mockDA
eventType:(es_event_type_t)eventType
diskInfoOverrides:(NSDictionary *)diskInfo {
if (!deviceManager.subscribed) {
// [deviceManager listen] is synchronous, but we want to asynchronously dispatch it
// with an enforced timeout to ensure that we never run into issues where the client
Expand Down Expand Up @@ -76,7 +75,7 @@ - (ESResponse *)triggerTestMountEvent:(SNTDeviceManager *)deviceManager

MockDADisk *disk = [[MockDADisk alloc] init];
disk.diskDescription = @{
(__bridge NSString *)kDADiskDescriptionDeviceProtocolKey : protocol,
(__bridge NSString *)kDADiskDescriptionDeviceProtocolKey : @"USB",
(__bridge NSString *)kDADiskDescriptionMediaRemovableKey : @YES,
@"DAVolumeMountable" : @YES,
@"DAVolumePath" : test_mntonname,
Expand All @@ -87,6 +86,14 @@ - (ESResponse *)triggerTestMountEvent:(SNTDeviceManager *)deviceManager
@"DAMediaBSDName" : test_mntfromname,
};

if (diskInfo != nil) {
NSMutableDictionary *mergedDiskDescription = [disk.diskDescription mutableCopy];
for (NSString *key in diskInfo) {
mergedDiskDescription[key] = diskInfo[key];
}
disk.diskDescription = (NSDictionary *)mergedDiskDescription;
}

[mockDA insert:disk bsdName:test_mntfromname];

ESMessage *m = [[ESMessage alloc] initWithBlock:^(ESMessage *m) {
Expand All @@ -100,7 +107,8 @@ - (ESResponse *)triggerTestMountEvent:(SNTDeviceManager *)deviceManager
}
}];

XCTestExpectation *mountExpectation = [self expectationWithDescription:@"Wait for response from ES"];
XCTestExpectation *mountExpectation =
[self expectationWithDescription:@"Wait for response from ES"];
__block ESResponse *got;
[mockES registerResponseCallback:eventType
withCallback:^(ESResponse *r) {
Expand All @@ -125,8 +133,12 @@ - (void)testUSBBlockDisabled {

SNTDeviceManager *deviceManager = [[SNTDeviceManager alloc] init];
deviceManager.blockUSBMount = NO;
ESResponse *got = [self triggerTestMountEvent:deviceManager mockES:mockES
mockDA:mockDA eventType:ES_EVENT_TYPE_AUTH_MOUNT diskInfoProtocol:@"USB"];
ESResponse *got = [self triggerTestMountEvent:deviceManager
mockES:mockES
mockDA:mockDA
eventType:ES_EVENT_TYPE_AUTH_MOUNT
diskInfoOverrides:nil];

XCTAssertEqual(got.result, ES_AUTH_RESULT_ALLOW);
}

Expand All @@ -153,7 +165,11 @@ - (void)testRemount {
[expectation fulfill];
};

ESResponse *got = [self triggerTestMountEvent:deviceManager mockES:mockES mockDA:mockDA eventType:ES_EVENT_TYPE_AUTH_MOUNT diskInfoProtocol:@"USB"];
ESResponse *got = [self triggerTestMountEvent:deviceManager
mockES:mockES
mockDA:mockDA
eventType:ES_EVENT_TYPE_AUTH_MOUNT
diskInfoOverrides:nil];

XCTAssertEqual(got.result, ES_AUTH_RESULT_DENY);
XCTAssertEqual(mockDA.wasRemounted, YES);
Expand Down Expand Up @@ -187,7 +203,11 @@ - (void)testBlockNoRemount {
[expectation fulfill];
};

ESResponse *got = [self triggerTestMountEvent:deviceManager mockES:mockES mockDA:mockDA eventType:ES_EVENT_TYPE_AUTH_MOUNT diskInfoProtocol:@"USB"];
ESResponse *got = [self triggerTestMountEvent:deviceManager
mockES:mockES
mockDA:mockDA
eventType:ES_EVENT_TYPE_AUTH_MOUNT
diskInfoOverrides:nil];

XCTAssertEqual(got.result, ES_AUTH_RESULT_DENY);

Expand Down Expand Up @@ -221,7 +241,11 @@ - (void)testEnsureRemountsCannotChangePerms {
[expectation fulfill];
};

ESResponse *got = [self triggerTestMountEvent: deviceManager mockES: mockES mockDA: mockDA eventType:ES_EVENT_TYPE_AUTH_REMOUNT diskInfoProtocol:@"USB"];
ESResponse *got = [self triggerTestMountEvent:deviceManager
mockES:mockES
mockDA:mockDA
eventType:ES_EVENT_TYPE_AUTH_REMOUNT
diskInfoOverrides:nil];

XCTAssertEqual(got.result, ES_AUTH_RESULT_DENY);
XCTAssertEqual(mockDA.wasRemounted, YES);
Expand All @@ -234,8 +258,7 @@ - (void)testEnsureRemountsCannotChangePerms {
}

- (void)testEnsureDMGsDoNotPrompt {

MockEndpointSecurity *mockES = [MockEndpointSecurity mockEndpointSecurity];
MockEndpointSecurity *mockES = [MockEndpointSecurity mockEndpointSecurity];
[mockES reset];

MockDiskArbitration *mockDA = [MockDiskArbitration mockDiskArbitration];
Expand All @@ -249,8 +272,18 @@ - (void)testEnsureDMGsDoNotPrompt {
XCTFail(@"Should not be called");
};

NSDictionary *diskInfo = @{
(__bridge NSString *)kDADiskDescriptionDeviceProtocolKey: @"Virtual Interface",
(__bridge NSString *)kDADiskDescriptionDeviceModelKey: @"Disk Image",
(__bridge NSString *)kDADiskDescriptionMediaNameKey: @"disk image",
};


ESResponse *got = [self triggerTestMountEvent:deviceManager mockES:mockES mockDA: mockDA eventType:ES_EVENT_TYPE_AUTH_MOUNT diskInfoProtocol:@"Virtual Interface"];
ESResponse *got = [self triggerTestMountEvent:deviceManager
mockES:mockES
mockDA:mockDA
eventType:ES_EVENT_TYPE_AUTH_MOUNT
diskInfoOverrides:diskInfo];

XCTAssertEqual(got.result, ES_AUTH_RESULT_ALLOW);
XCTAssertEqual(mockDA.wasRemounted, NO);
Expand Down