mirror of
https://github.com/google/santa.git
synced 2026-01-15 01:08:12 -05:00
Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a67801d5ed | ||
|
|
3d37a3a5ae | ||
|
|
bfae5dc828 | ||
|
|
fde5f52a11 | ||
|
|
01bd1bfdca | ||
|
|
ae13900676 | ||
|
|
a65c91874b | ||
|
|
6a3fda069c | ||
|
|
4d34099142 |
@@ -28,12 +28,11 @@
|
||||
@protocol SNTUnprivilegedDaemonControlXPC
|
||||
|
||||
///
|
||||
/// Kernel ops
|
||||
/// Cache Ops
|
||||
///
|
||||
- (void)cacheCounts:(void (^)(uint64_t rootCache, uint64_t nonRootCache))reply;
|
||||
- (void)cacheBucketCount:(void (^)(NSArray *))reply;
|
||||
- (void)checkCacheForVnodeID:(santa_vnode_id_t)vnodeID withReply:(void (^)(santa_action_t))reply;
|
||||
- (void)driverConnectionEstablished:(void (^)(BOOL))reply;
|
||||
|
||||
///
|
||||
/// Database ops
|
||||
|
||||
@@ -48,16 +48,10 @@ REGISTER_COMMAND_NAME(@"status")
|
||||
dispatch_group_t group = dispatch_group_create();
|
||||
|
||||
// Daemon status
|
||||
__block BOOL driverConnected;
|
||||
__block NSString *clientMode;
|
||||
__block uint64_t cpuEvents, ramEvents;
|
||||
__block double cpuPeak, ramPeak;
|
||||
dispatch_group_enter(group);
|
||||
[[self.daemonConn remoteObjectProxy] driverConnectionEstablished:^(BOOL connected) {
|
||||
driverConnected = connected;
|
||||
dispatch_group_leave(group);
|
||||
}];
|
||||
dispatch_group_enter(group);
|
||||
[[self.daemonConn remoteObjectProxy] clientMode:^(SNTClientMode cm) {
|
||||
switch (cm) {
|
||||
case SNTClientModeMonitor: clientMode = @"Monitor"; break;
|
||||
@@ -181,7 +175,7 @@ REGISTER_COMMAND_NAME(@"status")
|
||||
if ([arguments containsObject:@"--json"]) {
|
||||
NSMutableDictionary *stats = [@{
|
||||
@"daemon" : @{
|
||||
@"driver_connected" : @(driverConnected),
|
||||
@"driver_connected" : @(YES),
|
||||
@"mode" : clientMode ?: @"null",
|
||||
@"file_logging" : @(fileLogging),
|
||||
@"watchdog_cpu_events" : @(cpuEvents),
|
||||
@@ -223,16 +217,15 @@ REGISTER_COMMAND_NAME(@"status")
|
||||
printf("%s\n", [statsStr UTF8String]);
|
||||
} else {
|
||||
printf(">>> Daemon Info\n");
|
||||
printf(" %-25s | %s\n", "Driver Connected", driverConnected ? "Yes" : "No");
|
||||
printf(" %-25s | %s\n", "Mode", [clientMode UTF8String]);
|
||||
printf(" %-25s | %s\n", "File Logging", (fileLogging ? "Yes" : "No"));
|
||||
printf(" %-25s | %lld (Peak: %.2f%%)\n", "Watchdog CPU Events", cpuEvents, cpuPeak);
|
||||
printf(" %-25s | %lld (Peak: %.2fMB)\n", "Watchdog RAM Events", ramEvents, ramPeak);
|
||||
printf(" %-25s | %s\n", "USB Blocking", (configurator.blockUSBMount ? "Yes" : "No"));
|
||||
if (configurator.blockUSBMount && configurator.remountUSBMode.count > 0) {
|
||||
printf(" %-25s | %s\n", "USB Remounting Mode:",
|
||||
[[configurator.remountUSBMode componentsJoinedByString:@", "] UTF8String]);
|
||||
}
|
||||
printf(" %-25s | %lld (Peak: %.2f%%)\n", "Watchdog CPU Events", cpuEvents, cpuPeak);
|
||||
printf(" %-25s | %lld (Peak: %.2fMB)\n", "Watchdog RAM Events", ramEvents, ramPeak);
|
||||
|
||||
if (cachingEnabled) {
|
||||
printf(">>> Cache Info\n");
|
||||
|
||||
@@ -453,6 +453,7 @@ santa_unit_test(
|
||||
"@MOLXPCConnection",
|
||||
"@OCMock",
|
||||
],
|
||||
tags = ["exclusive"],
|
||||
)
|
||||
|
||||
santa_unit_test(
|
||||
|
||||
@@ -69,6 +69,10 @@ API_UNAVAILABLE(ios, tvos, watchos)
|
||||
es_new_client_result_t es_new_client(es_client_t *_Nullable *_Nonnull client,
|
||||
es_handler_block_t _Nonnull handler);
|
||||
|
||||
API_AVAILABLE(macos(10.15)) API_UNAVAILABLE(ios, tvos, watchos)
|
||||
es_return_t es_mute_process(es_client_t * _Nonnull client,
|
||||
const audit_token_t * _Nonnull audit_token);
|
||||
|
||||
#if defined(MAC_OS_VERSION_12_0) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_VERSION_12_0
|
||||
API_AVAILABLE(macos(12.0))
|
||||
API_UNAVAILABLE(ios, tvos, watchos)
|
||||
|
||||
@@ -301,6 +301,11 @@ es_new_client_result_t es_new_client(es_client_t *_Nullable *_Nonnull client,
|
||||
return ES_NEW_CLIENT_RESULT_SUCCESS;
|
||||
};
|
||||
|
||||
es_return_t es_mute_process(es_client_t * _Nonnull client,
|
||||
const audit_token_t * _Nonnull audit_token) {
|
||||
return ES_RETURN_SUCCESS;
|
||||
}
|
||||
|
||||
#if defined(MAC_OS_VERSION_12_0) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_VERSION_12_0
|
||||
API_AVAILABLE(macos(12.0))
|
||||
API_UNAVAILABLE(ios, tvos, watchos)
|
||||
|
||||
@@ -170,6 +170,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
es_event_type_t events[] = {
|
||||
ES_EVENT_TYPE_AUTH_MOUNT,
|
||||
ES_EVENT_TYPE_AUTH_REMOUNT,
|
||||
};
|
||||
|
||||
es_return_t sret = es_subscribe(self.client, events, sizeof(events) / sizeof(es_event_type_t));
|
||||
@@ -199,44 +200,77 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
return;
|
||||
}
|
||||
|
||||
long mountMode = m->event.mount.statfs->f_flags;
|
||||
struct statfs *eventStatFS;
|
||||
BOOL isRemount = NO;
|
||||
|
||||
switch (m->event_type) {
|
||||
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);
|
||||
// Fail closed.
|
||||
es_respond_auth_result(self.client, m, ES_AUTH_RESULT_DENY, false);
|
||||
assert(0 && "SNTDeviceManager: unexpected event type");
|
||||
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, m->event.mount.statfs->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 isRemovable = [diskInfo[(__bridge NSString *)kDADiskDescriptionMediaRemovableKey] boolValue];
|
||||
BOOL isUSB =
|
||||
[diskInfo[(__bridge NSString *)kDADiskDescriptionDeviceProtocolKey] isEqualTo:@"USB"];
|
||||
BOOL isEjectable = [diskInfo[(__bridge NSString *)kDADiskDescriptionMediaEjectableKey] boolValue];
|
||||
NSString *protocol = diskInfo[(__bridge NSString *)kDADiskDescriptionDeviceProtocolKey];
|
||||
BOOL isUSB = [protocol isEqualToString:@"USB"];
|
||||
BOOL isVirtual = [protocol isEqualToString: @"Virtual Interface"];
|
||||
|
||||
if (!isRemovable || !isUSB) {
|
||||
NSString *kind = diskInfo[(__bridge NSString *)kDADiskDescriptionMediaKindKey];
|
||||
|
||||
// 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 is internal or virtual we are okay with the operation. We
|
||||
// also are okay with operations for devices that are non-removal as long as
|
||||
// they are NOT a USB device.
|
||||
if (isInternal || isVirtual || (!isRemovable && !isEjectable && !isUSB)) {
|
||||
es_respond_auth_result(self.client, m, ES_AUTH_RESULT_ALLOW, false);
|
||||
return;
|
||||
}
|
||||
|
||||
SNTDeviceEvent *event = [[SNTDeviceEvent alloc]
|
||||
initWithOnName:[NSString stringWithUTF8String:m->event.mount.statfs->f_mntonname]
|
||||
fromName:[NSString stringWithUTF8String:m->event.mount.statfs->f_mntfromname]];
|
||||
initWithOnName:[NSString stringWithUTF8String:eventStatFS->f_mntonname]
|
||||
fromName:[NSString stringWithUTF8String:eventStatFS->f_mntfromname]];
|
||||
|
||||
BOOL shouldRemount = self.remountArgs != nil && [self.remountArgs count] > 0;
|
||||
|
||||
if (shouldRemount) {
|
||||
event.remountArgs = self.remountArgs;
|
||||
long remountOpts = mountArgsToMask(self.remountArgs);
|
||||
if (mountMode & remountOpts) {
|
||||
|
||||
LOGD(@"SNTDeviceManager: mountMode: %@", maskToMountArgs(mountMode));
|
||||
LOGD(@"SNTDeviceManager: remountOpts: %@", maskToMountArgs(remountOpts));
|
||||
|
||||
if ((mountMode & remountOpts) == remountOpts && !isRemount) {
|
||||
LOGD(@"SNTDeviceManager: Allowing as mount as flags match remountOpts");
|
||||
es_respond_auth_result(self.client, m, ES_AUTH_RESULT_ALLOW, false);
|
||||
return;
|
||||
}
|
||||
|
||||
long newMode = mountMode | remountOpts;
|
||||
LOGI(@"SNTDeviceManager: remounting device '%s'->'%s', flags (%lu) -> (%lu)",
|
||||
m->event.mount.statfs->f_mntfromname, m->event.mount.statfs->f_mntonname, mountMode,
|
||||
newMode);
|
||||
eventStatFS->f_mntfromname, eventStatFS->f_mntonname, mountMode, newMode);
|
||||
[self remount:disk mountMode:newMode];
|
||||
}
|
||||
|
||||
@@ -270,26 +304,34 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
// ES will kill our whole client if we don't meet the es_message auth deadline, so we try to
|
||||
// gracefully handle it with a deny-by-default in the worst-case before it can do that.
|
||||
// This isn't an issue for notify events, so we're in no rush for those.
|
||||
std::shared_ptr<std::atomic<bool>> responded;
|
||||
es_message_t *mc = es_copy_message(m);
|
||||
|
||||
dispatch_semaphore_t processingSema = dispatch_semaphore_create(0);
|
||||
// Add 1 to the processing semaphore. We're not creating it with a starting
|
||||
// value of 1 because that requires that the semaphore is not deallocated
|
||||
// until its value matches the starting value, which we don't need.
|
||||
dispatch_semaphore_signal(processingSema);
|
||||
dispatch_semaphore_t deadlineExpiredSema = dispatch_semaphore_create(0);
|
||||
|
||||
if (m->action_type == ES_ACTION_TYPE_AUTH) {
|
||||
responded = std::make_shared<std::atomic<bool>>(false);
|
||||
dispatch_after(timeout, self.esAuthQueue, ^(void) {
|
||||
if (responded->load()) return;
|
||||
if (dispatch_semaphore_wait(processingSema, DISPATCH_TIME_NOW) != 0) {
|
||||
// Handler already responded, nothing to do.
|
||||
return;
|
||||
}
|
||||
LOGE(@"SNTDeviceManager: deadline reached: deny pid=%d ret=%d",
|
||||
audit_token_to_pid(m->process->audit_token),
|
||||
es_respond_auth_result(c, m, ES_AUTH_RESULT_DENY, false));
|
||||
dispatch_semaphore_signal(deadlineExpiredSema);
|
||||
});
|
||||
}
|
||||
|
||||
// TODO(tnek): migrate to es_retain_message.
|
||||
es_message_t *mc = es_copy_message(m);
|
||||
dispatch_async(self.esAuthQueue, ^{
|
||||
[self handleESMessage:m withClient:c];
|
||||
|
||||
if (m->action_type == ES_ACTION_TYPE_AUTH) {
|
||||
responded->store(true);
|
||||
if (dispatch_semaphore_wait(processingSema, DISPATCH_TIME_NOW) != 0) {
|
||||
// Deadline expired, wait for deadline block to finish.
|
||||
dispatch_semaphore_wait(deadlineExpiredSema, DISPATCH_TIME_FOREVER);
|
||||
}
|
||||
|
||||
es_free_message(mc);
|
||||
});
|
||||
}
|
||||
@@ -297,16 +339,17 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
- (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_MOUNT: {
|
||||
[self handleAuthMount:m withClient:c];
|
||||
// Intentional fallthrough
|
||||
case ES_EVENT_TYPE_AUTH_REMOUNT: {
|
||||
[[fallthrough]];
|
||||
}
|
||||
// TODO(tnek): log any extra data here about mounts.
|
||||
case ES_EVENT_TYPE_NOTIFY_MOUNT: {
|
||||
case ES_EVENT_TYPE_AUTH_MOUNT: {
|
||||
[self handleAuthMount:m withClient:c];
|
||||
break;
|
||||
}
|
||||
default: LOGE(@"SNTDeviceManager: unexpected event type: %d", m->event_type);
|
||||
|
||||
default:
|
||||
LOGE(@"SNTDeviceManager: unexpected event type: %d", m->event_type);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -40,9 +40,11 @@
|
||||
fclose(stdout);
|
||||
}
|
||||
|
||||
- (ESResponse *)triggerTestMount:(SNTDeviceManager *)deviceManager
|
||||
mockES:(MockEndpointSecurity *)mockES
|
||||
mockDA:(MockDiskArbitration *)mockDA {
|
||||
- (ESResponse *)triggerTestMountEvent:(SNTDeviceManager *)deviceManager
|
||||
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
|
||||
@@ -84,26 +86,39 @@
|
||||
@"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) {
|
||||
m.binaryPath = @"/System/Library/Filesystems/msdos.fs/Contents/Resources/mount_msdos";
|
||||
m.message->action_type = ES_ACTION_TYPE_AUTH;
|
||||
m.message->event_type = ES_EVENT_TYPE_AUTH_MOUNT;
|
||||
m.message->event = (es_events_t){.mount = {.statfs = fs}};
|
||||
m.message->event_type = eventType;
|
||||
if (eventType == ES_EVENT_TYPE_AUTH_MOUNT) {
|
||||
m.message->event = (es_events_t){.mount = {.statfs = fs}};
|
||||
} else {
|
||||
m.message->event = (es_events_t){.remount = {.statfs = fs}};
|
||||
}
|
||||
}];
|
||||
|
||||
XCTestExpectation *expectation = [self expectationWithDescription:@"Wait for response from ES"];
|
||||
XCTestExpectation *mountExpectation =
|
||||
[self expectationWithDescription:@"Wait for response from ES"];
|
||||
__block ESResponse *got;
|
||||
[mockES registerResponseCallback:ES_EVENT_TYPE_AUTH_MOUNT
|
||||
[mockES registerResponseCallback:eventType
|
||||
withCallback:^(ESResponse *r) {
|
||||
got = r;
|
||||
[expectation fulfill];
|
||||
[mountExpectation fulfill];
|
||||
}];
|
||||
|
||||
[mockES triggerHandler:m.message];
|
||||
|
||||
[self waitForExpectations:@[ expectation ] timeout:60.0];
|
||||
[self waitForExpectations:@[ mountExpectation ] timeout:60.0];
|
||||
free(fs);
|
||||
|
||||
return got;
|
||||
@@ -118,7 +133,12 @@
|
||||
|
||||
SNTDeviceManager *deviceManager = [[SNTDeviceManager alloc] init];
|
||||
deviceManager.blockUSBMount = NO;
|
||||
ESResponse *got = [self triggerTestMount:deviceManager mockES:mockES mockDA:mockDA];
|
||||
ESResponse *got = [self triggerTestMountEvent:deviceManager
|
||||
mockES:mockES
|
||||
mockDA:mockDA
|
||||
eventType:ES_EVENT_TYPE_AUTH_MOUNT
|
||||
diskInfoOverrides:nil];
|
||||
|
||||
XCTAssertEqual(got.result, ES_AUTH_RESULT_ALLOW);
|
||||
}
|
||||
|
||||
@@ -145,7 +165,11 @@
|
||||
[expectation fulfill];
|
||||
};
|
||||
|
||||
ESResponse *got = [self triggerTestMount:deviceManager mockES:mockES mockDA:mockDA];
|
||||
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);
|
||||
@@ -179,7 +203,11 @@
|
||||
[expectation fulfill];
|
||||
};
|
||||
|
||||
ESResponse *got = [self triggerTestMount:deviceManager mockES:mockES mockDA:mockDA];
|
||||
ESResponse *got = [self triggerTestMountEvent:deviceManager
|
||||
mockES:mockES
|
||||
mockDA:mockDA
|
||||
eventType:ES_EVENT_TYPE_AUTH_MOUNT
|
||||
diskInfoOverrides:nil];
|
||||
|
||||
XCTAssertEqual(got.result, ES_AUTH_RESULT_DENY);
|
||||
|
||||
@@ -190,4 +218,74 @@
|
||||
XCTAssertEqualObjects(gotmntfromname, @"/dev/disk2s1");
|
||||
}
|
||||
|
||||
- (void)testEnsureRemountsCannotChangePerms {
|
||||
MockEndpointSecurity *mockES = [MockEndpointSecurity mockEndpointSecurity];
|
||||
[mockES reset];
|
||||
|
||||
MockDiskArbitration *mockDA = [MockDiskArbitration mockDiskArbitration];
|
||||
[mockDA reset];
|
||||
|
||||
SNTDeviceManager *deviceManager = [[SNTDeviceManager alloc] init];
|
||||
deviceManager.blockUSBMount = YES;
|
||||
deviceManager.remountArgs = @[ @"noexec", @"rdonly" ];
|
||||
|
||||
XCTestExpectation *expectation =
|
||||
[self expectationWithDescription:@"Wait for SNTDeviceManager's blockCallback to trigger"];
|
||||
|
||||
__block NSString *gotmntonname, *gotmntfromname;
|
||||
__block NSArray<NSString *> *gotRemountedArgs;
|
||||
deviceManager.deviceBlockCallback = ^(SNTDeviceEvent *event) {
|
||||
gotRemountedArgs = event.remountArgs;
|
||||
gotmntonname = event.mntonname;
|
||||
gotmntfromname = event.mntfromname;
|
||||
[expectation fulfill];
|
||||
};
|
||||
|
||||
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);
|
||||
|
||||
[self waitForExpectations:@[ expectation ] timeout:10.0];
|
||||
|
||||
XCTAssertEqualObjects(gotRemountedArgs, deviceManager.remountArgs);
|
||||
XCTAssertEqualObjects(gotmntonname, @"/Volumes/KATE'S 4G");
|
||||
XCTAssertEqualObjects(gotmntfromname, @"/dev/disk2s1");
|
||||
}
|
||||
|
||||
- (void)testEnsureDMGsDoNotPrompt {
|
||||
MockEndpointSecurity *mockES = [MockEndpointSecurity mockEndpointSecurity];
|
||||
[mockES reset];
|
||||
|
||||
MockDiskArbitration *mockDA = [MockDiskArbitration mockDiskArbitration];
|
||||
[mockDA reset];
|
||||
|
||||
SNTDeviceManager *deviceManager = [[SNTDeviceManager alloc] init];
|
||||
deviceManager.blockUSBMount = YES;
|
||||
deviceManager.remountArgs = @[ @"noexec", @"rdonly" ];
|
||||
|
||||
deviceManager.deviceBlockCallback = ^(SNTDeviceEvent *event) {
|
||||
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
|
||||
diskInfoOverrides:diskInfo];
|
||||
|
||||
XCTAssertEqual(got.result, ES_AUTH_RESULT_ALLOW);
|
||||
XCTAssertEqual(mockDA.wasRemounted, NO);
|
||||
}
|
||||
@end
|
||||
|
||||
@@ -34,7 +34,6 @@ static const pid_t PID_MAX = 99999;
|
||||
@property(nonatomic) SNTPrefixTree *prefixTree;
|
||||
@property(nonatomic, readonly) dispatch_queue_t esAuthQueue;
|
||||
@property(nonatomic, readonly) dispatch_queue_t esNotifyQueue;
|
||||
@property(nonatomic, readonly) pid_t selfPID;
|
||||
|
||||
@end
|
||||
|
||||
@@ -48,6 +47,7 @@ static const pid_t PID_MAX = 99999;
|
||||
_decisionCallback = ^(santa_message_t) {};
|
||||
_logCallback = ^(santa_message_t) {};
|
||||
[self establishClient];
|
||||
[self muteSelf];
|
||||
_prefixTree = new SNTPrefixTree();
|
||||
_esAuthQueue =
|
||||
dispatch_queue_create("com.google.santa.daemon.es_auth", DISPATCH_QUEUE_CONCURRENT);
|
||||
@@ -56,7 +56,6 @@ static const pid_t PID_MAX = 99999;
|
||||
_esNotifyQueue =
|
||||
dispatch_queue_create("com.google.santa.daemon.es_notify", DISPATCH_QUEUE_CONCURRENT);
|
||||
dispatch_set_target_queue(_esNotifyQueue, dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0));
|
||||
_selfPID = getpid();
|
||||
}
|
||||
|
||||
return self;
|
||||
@@ -70,6 +69,24 @@ static const pid_t PID_MAX = 99999;
|
||||
if (_prefixTree) delete _prefixTree;
|
||||
}
|
||||
|
||||
- (void)muteSelf {
|
||||
audit_token_t myAuditToken;
|
||||
mach_msg_type_number_t count = TASK_AUDIT_TOKEN_COUNT;
|
||||
if (task_info(mach_task_self(), TASK_AUDIT_TOKEN, (task_info_t)&myAuditToken, &count) ==
|
||||
KERN_SUCCESS) {
|
||||
if (es_mute_process(self.client, &myAuditToken) == ES_RETURN_SUCCESS) {
|
||||
return;
|
||||
} else {
|
||||
LOGE(@"Failed to mute this client's process, its events will not be muted.");
|
||||
}
|
||||
} else {
|
||||
LOGE(@"Failed to fetch this client's audit token. Its events will not be muted.");
|
||||
}
|
||||
|
||||
// If we get here, Santa was unable to mute itself. Assume transitory and bail.
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
- (void)establishClient API_AVAILABLE(macos(10.15)) {
|
||||
while (!self.client) {
|
||||
SNTConfigurator *config = [SNTConfigurator configurator];
|
||||
@@ -84,9 +101,7 @@ static const pid_t PID_MAX = 99999;
|
||||
if (m->action_type == ES_ACTION_TYPE_AUTH) {
|
||||
es_respond_auth_result(self.client, m, ES_AUTH_RESULT_ALLOW, false);
|
||||
}
|
||||
if (self.selfPID != pid) {
|
||||
LOGD(@"Skipping event type: 0x%x from es_client pid: %d", m->event_type, pid);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -214,30 +229,42 @@ static const pid_t PID_MAX = 99999;
|
||||
|
||||
switch (m->action_type) {
|
||||
case ES_ACTION_TYPE_AUTH: {
|
||||
// Copy the message
|
||||
es_message_t *mc = es_copy_message(m);
|
||||
|
||||
dispatch_semaphore_t processingSema = dispatch_semaphore_create(0);
|
||||
// Add 1 to the processing semaphore. We're not creating it with a starting
|
||||
// value of 1 because that requires that the semaphore is not deallocated
|
||||
// until its value matches the starting value, which we don't need.
|
||||
dispatch_semaphore_signal(processingSema);
|
||||
dispatch_semaphore_t deadlineExpiredSema = dispatch_semaphore_create(0);
|
||||
|
||||
// Create a timer to deny the execution 5 seconds before the deadline,
|
||||
// if a response hasn't already been sent. This block will still be enqueued if
|
||||
// the the deadline - 5 secs is < DISPATCH_TIME_NOW.
|
||||
// As of 10.15.5, a typical deadline is 60 seconds.
|
||||
auto responded = std::make_shared<std::atomic<bool>>(false);
|
||||
dispatch_after(dispatch_time(m->deadline, NSEC_PER_SEC * -5), self.esAuthQueue, ^(void) {
|
||||
if (responded->load()) return;
|
||||
LOGE(@"Deadline reached: deny pid=%d ret=%d", pid,
|
||||
es_respond_auth_result(self.client, m, ES_AUTH_RESULT_DENY, false));
|
||||
if (dispatch_semaphore_wait(processingSema, DISPATCH_TIME_NOW) != 0) {
|
||||
// Handler has already responded, nothing to do.
|
||||
return;
|
||||
}
|
||||
LOGE(@"SNTEndpointSecurityManager: deadline reached: deny pid=%d ret=%d", pid,
|
||||
es_respond_auth_result(self.client, mc, ES_AUTH_RESULT_DENY, false));
|
||||
dispatch_semaphore_signal(deadlineExpiredSema);
|
||||
});
|
||||
|
||||
// Copy the message and return control back to ES
|
||||
es_message_t *mc = es_copy_message(m);
|
||||
// Dispatch off to the handler and return control to ES.
|
||||
dispatch_async(self.esAuthQueue, ^{
|
||||
[self messageHandler:mc];
|
||||
responded->store(true);
|
||||
if (dispatch_semaphore_wait(processingSema, DISPATCH_TIME_NOW) != 0) {
|
||||
// Deadline expired, wait for deadline block to finish.
|
||||
dispatch_semaphore_wait(deadlineExpiredSema, DISPATCH_TIME_FOREVER);
|
||||
}
|
||||
es_free_message(mc);
|
||||
});
|
||||
break;
|
||||
}
|
||||
case ES_ACTION_TYPE_NOTIFY: {
|
||||
// Don't log fileop events from com.google.santa.daemon
|
||||
if (self.selfPID == pid && m->event_type != ES_EVENT_TYPE_NOTIFY_EXEC) return;
|
||||
|
||||
// Copy the message and return control back to ES
|
||||
es_message_t *mc = es_copy_message(m);
|
||||
dispatch_async(self.esNotifyQueue, ^{
|
||||
@@ -322,8 +349,7 @@ static const pid_t PID_MAX = 99999;
|
||||
NSString *path = [[NSString alloc] initWithBytes:pathToken.data
|
||||
length:pathToken.length
|
||||
encoding:NSUTF8StringEncoding];
|
||||
if ([self isDatabasePath:path] &&
|
||||
audit_token_to_pid(m->process->audit_token) != self.selfPID) {
|
||||
if ([self isDatabasePath:path]) {
|
||||
LOGW(@"Preventing attempt to delete Santa databases!");
|
||||
es_respond_auth_result(self.client, m, ES_AUTH_RESULT_DENY, true);
|
||||
return;
|
||||
@@ -337,8 +363,7 @@ static const pid_t PID_MAX = 99999;
|
||||
length:pathToken.length
|
||||
encoding:NSUTF8StringEncoding];
|
||||
|
||||
if ([self isDatabasePath:path] &&
|
||||
audit_token_to_pid(m->process->audit_token) != self.selfPID) {
|
||||
if ([self isDatabasePath:path]) {
|
||||
LOGW(@"Preventing attempt to rename Santa databases!");
|
||||
es_respond_auth_result(self.client, m, ES_AUTH_RESULT_DENY, true);
|
||||
return;
|
||||
@@ -348,8 +373,7 @@ static const pid_t PID_MAX = 99999;
|
||||
NSString *destPath = [[NSString alloc] initWithBytes:destToken.data
|
||||
length:destToken.length
|
||||
encoding:NSUTF8StringEncoding];
|
||||
if ([self isDatabasePath:destPath] &&
|
||||
audit_token_to_pid(m->process->audit_token) != self.selfPID) {
|
||||
if ([self isDatabasePath:destPath]) {
|
||||
LOGW(@"Preventing attempt to overwrite Santa databases!");
|
||||
es_respond_auth_result(self.client, m, ES_AUTH_RESULT_DENY, true);
|
||||
return;
|
||||
|
||||
@@ -85,10 +85,6 @@ double watchdogRAMPeak = 0;
|
||||
reply([self.eventProvider checkCache:vnodeID]);
|
||||
}
|
||||
|
||||
- (void)driverConnectionEstablished:(void (^)(BOOL))reply {
|
||||
reply(self.eventProvider.connectionEstablished);
|
||||
}
|
||||
|
||||
#pragma mark Database ops
|
||||
|
||||
- (void)databaseRuleCounts:(void (^)(int64_t binary, int64_t certificate, int64_t compiler,
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
|
||||
#import "Source/santad/SNTExecutionController.h"
|
||||
|
||||
#include <copyfile.h>
|
||||
#include <libproc.h>
|
||||
#include <pwd.h>
|
||||
#include <utmpx.h>
|
||||
@@ -135,11 +136,14 @@ static NSString *const kPrinterProxyPostMonterey =
|
||||
NSError *fileInfoError;
|
||||
SNTFileInfo *binInfo = [[SNTFileInfo alloc] initWithPath:@(message.path) error:&fileInfoError];
|
||||
if (unlikely(!binInfo)) {
|
||||
LOGE(@"Failed to read file %@: %@", @(message.path), fileInfoError.localizedDescription);
|
||||
if (config.failClosed && config.clientMode == SNTClientModeLockdown) {
|
||||
LOGE(@"Failed to read file %@: %@ and denying action", @(message.path),
|
||||
fileInfoError.localizedDescription);
|
||||
[self.eventProvider postAction:ACTION_RESPOND_DENY forMessage:message];
|
||||
[self.events incrementForFieldValues:@[ (NSString *)kDenyNoFileInfo ]];
|
||||
} else {
|
||||
LOGE(@"Failed to read file %@: %@ but allowing action", @(message.path),
|
||||
fileInfoError.localizedDescription);
|
||||
[self.eventProvider postAction:ACTION_RESPOND_ALLOW forMessage:message];
|
||||
[self.events incrementForFieldValues:@[ (NSString *)kAllowNoFileInfo ]];
|
||||
}
|
||||
@@ -302,15 +306,12 @@ static NSString *const kPrinterProxyPostMonterey =
|
||||
SNTFileInfo *proxyFi = [self printerProxyFileInfo];
|
||||
if ([proxyFi.SHA256 isEqual:fi.SHA256]) return NO;
|
||||
|
||||
NSFileHandle *inFh = [NSFileHandle fileHandleForReadingAtPath:proxyFi.path];
|
||||
NSFileHandle *outFh = [NSFileHandle fileHandleForWritingAtPath:fi.path];
|
||||
[outFh writeData:[inFh readDataToEndOfFile]];
|
||||
[inFh closeFile];
|
||||
[outFh truncateFileAtOffset:[outFh offsetInFile]];
|
||||
[outFh synchronizeFile];
|
||||
[outFh closeFile];
|
||||
|
||||
LOGW(@"PrinterProxy workaround applied to %@", fi.path);
|
||||
copyfile_flags_t copyflags = COPYFILE_ALL | COPYFILE_UNLINK;
|
||||
if (copyfile(proxyFi.path.UTF8String, fi.path.UTF8String, NULL, copyflags) != 0) {
|
||||
LOGE(@"Failed to apply PrinterProxy workaround for %@", fi.path);
|
||||
} else {
|
||||
LOGI(@"PrinterProxy workaround applied to: %@", fi.path);
|
||||
}
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
@@ -1,76 +0,0 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
GIT_ROOT=$(git rev-parse --show-toplevel)
|
||||
TMP_DIR=$(mktemp -d)
|
||||
|
||||
function cleanup() {
|
||||
# Reset randomize_version if we used it
|
||||
if [ -f "$TMP_DIR/version.bzl" ]; then
|
||||
mv "$TMP_DIR/version.bzl" $VERSION_FILE
|
||||
fi
|
||||
rm -rf $TMP_DIR
|
||||
rm -f $GIT_ROOT/bazel-bin/santa-*.tar.gz
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
function build_custom_signed() {
|
||||
SANTAD_PATH=Santa.app/Contents/Library/SystemExtensions/com.google.santa.daemon.systemextension/Contents/MacOS/com.google.santa.daemon
|
||||
SANTA_BIN_PATH=Santa.app/Contents/MacOS
|
||||
KEYCHAIN="santa-dev-test.keychain"
|
||||
SANTAD_ENTITLEMENTS="$GIT_ROOT/Source/santad/com.google.santa.daemon.systemextension.entitlements"
|
||||
SIGNING_IDENTITY="localhost"
|
||||
|
||||
bazel build \
|
||||
--apple_generate_dsym \
|
||||
-c opt \
|
||||
--define=SANTA_BUILD_TYPE=ci \
|
||||
--define=apple.propagate_embedded_extra_outputs=yes \
|
||||
--macos_cpus=x86_64,arm64 \
|
||||
--embed_label="santa_${RANDOM}.${RANDOM}.${RANDOM}"
|
||||
//:release
|
||||
|
||||
echo "> Build complete, installing santa"
|
||||
tar xvf $GIT_ROOT/bazel-bin/santa-*.tar.gz -C $TMP_DIR
|
||||
CS_ARGS="--prefix=EQHXZ8M8AV -fs $SIGNING_IDENTITY --timestamp --options library,kill,runtime"
|
||||
|
||||
for bin in $TMP_DIR/binaries/$SANTA_BIN_PATH/*; do
|
||||
codesign --keychain $KEYCHAIN --preserve-metadata=entitlements ${CS_ARGS} $bin
|
||||
done
|
||||
|
||||
codesign ${CS_ARGS} --keychain $KEYCHAIN --entitlements $SANTAD_ENTITLEMENTS $TMP_DIR/binaries/$SANTAD_PATH
|
||||
}
|
||||
|
||||
function build_provisionprofile_signed() {
|
||||
bazel build --apple_generate_dsym -c opt --define=SANTA_BUILD_TYPE=release --define=apple.propagate_embedded_extra_outputs=yes --macos_cpus=x86_64,arm64 //:release
|
||||
tar xvf $GIT_ROOT/bazel-bin/santa-*.tar.gz -C $TMP_DIR
|
||||
}
|
||||
|
||||
function build() {
|
||||
SANTA_DAEMON_PROVPROFILE=$GIT_ROOT/Source/santad/Santa_Daemon_Dev.provisionprofile
|
||||
SANTA_PROVPROFILE=$GIT_ROOT/Source/santa/Santa_Dev.provisionprofile
|
||||
|
||||
if [[ -f $SANTA_DAEMON_PROVPROFILE && -f $SANTA_PROVPROFILE ]]; then
|
||||
echo "Using provisionprofiles in $SANTA_DAEMON_PROVPROFILE and $SANTA_PROVPROFILE"
|
||||
build_provisionprofile_signed
|
||||
else
|
||||
echo "No provisionprofiles detected, creating self-signed certs"
|
||||
build_custom_signed
|
||||
fi
|
||||
}
|
||||
|
||||
function install() {
|
||||
echo "> Running install.sh"
|
||||
(
|
||||
cd $TMP_DIR
|
||||
sudo ./conf/install.sh
|
||||
)
|
||||
}
|
||||
|
||||
function main() {
|
||||
build
|
||||
install
|
||||
}
|
||||
|
||||
main $@
|
||||
exit $?
|
||||
@@ -1,41 +0,0 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
GIT_ROOT=$(git rev-parse --show-toplevel)
|
||||
CNF_PATH=$GIT_ROOT/Testing/openssl.cnf
|
||||
KEYCHAIN="santa-dev-test.keychain"
|
||||
|
||||
function init() {
|
||||
openssl genrsa -out ./santa.key 2048
|
||||
openssl rsa -in ./santa.key -out ./santa.key
|
||||
openssl req -new -key ./santa.key -out ./santa.csr -config $CNF_PATH
|
||||
openssl x509 -req -days 10 -in ./santa.csr -signkey ./santa.key -out ./santa.crt -extfile $CNF_PATH -extensions codesign
|
||||
openssl pkcs12 -export -out santa.p12 -inkey santa.key -in santa.crt -password pass:santa
|
||||
|
||||
security create-keychain -p santa $KEYCHAIN
|
||||
security import ./santa.p12 -k $KEYCHAIN -A -P santa
|
||||
security add-trusted-cert -d -r trustRoot -k $KEYCHAIN santa.crt
|
||||
}
|
||||
|
||||
function cleanup() {
|
||||
security delete-keychain $KEYCHAIN
|
||||
rm santa.key
|
||||
rm santa.csr
|
||||
rm santa.p12
|
||||
}
|
||||
|
||||
function main() {
|
||||
case $1 in
|
||||
init)
|
||||
init
|
||||
;;
|
||||
cleanup)
|
||||
cleanup
|
||||
;;
|
||||
*)
|
||||
echo "$0 [init|cleanup]"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
main $@
|
||||
exit $?
|
||||
@@ -1,25 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
set -x
|
||||
|
||||
GIT_ROOT=$(git rev-parse --show-toplevel)
|
||||
|
||||
run_tests() {
|
||||
(
|
||||
local -a TEST_FLAGS=( --strategy=TestRunner=standalone --test_output=all )
|
||||
cd $GIT_ROOT/Testing/integration
|
||||
time bazel test "${TEST_FLAGS[@]}" -- ...
|
||||
)
|
||||
}
|
||||
|
||||
setup() {
|
||||
$GIT_ROOT/Testing/start_env.sh
|
||||
sudo santactl sync --debug
|
||||
}
|
||||
|
||||
main() {
|
||||
setup
|
||||
run_tests
|
||||
}
|
||||
|
||||
main "$@"
|
||||
@@ -1,22 +0,0 @@
|
||||
[ ca ]
|
||||
default_ca = CA_default
|
||||
|
||||
[ req ]
|
||||
prompt = no
|
||||
distinguished_name = req_distinguished_name
|
||||
|
||||
[ req_distinguished_name ]
|
||||
commonName = localhost
|
||||
countryName = US
|
||||
organizationName = Google LLC
|
||||
OU=EQHXZ8M8AV
|
||||
name = santa
|
||||
|
||||
[ codesign ]
|
||||
keyUsage = digitalSignature
|
||||
extendedKeyUsage = codeSigning
|
||||
|
||||
[ v3_ca ]
|
||||
basicConstraints = critical,CA:TRUE
|
||||
subjectKeyIdentifier = hash
|
||||
authorityKeyIdentifier = keyid:always,issuer:always
|
||||
@@ -1,6 +0,0 @@
|
||||
#!/bin/sh
|
||||
killall moroz
|
||||
security delete-identity -c "localhost"
|
||||
rm -rf /Applications/Santa.app
|
||||
systemextensionsctl reset
|
||||
security delete-keychain santa-dev-test.keychain
|
||||
@@ -1,47 +0,0 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
GIT_ROOT=$(git rev-parse --show-toplevel)
|
||||
|
||||
SANTA_BIN_PATH=Santa.app/Contents/MacOS
|
||||
SIGNING_IDENTITY="localhost"
|
||||
|
||||
function setup_certs() {
|
||||
echo "> Creating codesigning certs and keys"
|
||||
$GIT_ROOT/Testing/init_dev_certs.sh init
|
||||
}
|
||||
|
||||
function run_moroz() {
|
||||
echo "> Running moroz in the background"
|
||||
go get github.com/groob/moroz/cmd/moroz
|
||||
~/go/bin/moroz -configs="$GIT_ROOT/Testing/global.toml" -tls-key santa.key -tls-cert santa.crt &
|
||||
}
|
||||
|
||||
function install_profile() {
|
||||
echo "> Installing mobileconfig"
|
||||
# The `profiles` tool has been deprecated as of Big Sur. Ugly workaround instead:
|
||||
sudo open /System/Library/PreferencePanes/Profiles.prefPane "$GIT_ROOT/Testing/com.google.santa.mobileconfig"
|
||||
}
|
||||
|
||||
function build_install_santa() {
|
||||
echo "> Building and signing Santa"
|
||||
$GIT_ROOT/Testing/build_and_sign.sh
|
||||
systemextensionsctl list
|
||||
|
||||
# install.sh _should_ already start the system extension, but we want to
|
||||
# explicitly call `--load-system-extension` again to actually log loading
|
||||
# failures.
|
||||
echo "> Install complete, attempting to explicitly start the santa systemextension"
|
||||
/Applications/$SANTA_BIN_PATH/Santa --load-system-extension
|
||||
systemextensionsctl list
|
||||
}
|
||||
|
||||
function main() {
|
||||
install_profile
|
||||
setup_certs
|
||||
run_moroz
|
||||
build_install_santa
|
||||
}
|
||||
|
||||
main $@
|
||||
exit $?
|
||||
Reference in New Issue
Block a user