diff --git a/Source/common/SNTCommonEnums.h b/Source/common/SNTCommonEnums.h index f571ed880..08da253ad 100644 --- a/Source/common/SNTCommonEnums.h +++ b/Source/common/SNTCommonEnums.h @@ -158,6 +158,12 @@ typedef NS_ENUM(NSInteger, SNTOverrideFileAccessAction) { SNTOverrideFileAccessActionDiable, }; +typedef NS_ENUM(NSInteger, SNTDeviceManagerStartupPreferences) { + SNTDeviceManagerStartupPreferencesNone, + SNTDeviceManagerStartupPreferencesUnmount, + SNTDeviceManagerStartupPreferencesForceUnmount, +}; + #ifdef __cplusplus enum class FileAccessPolicyDecision { kNoPolicy, diff --git a/Source/common/SNTConfigurator.h b/Source/common/SNTConfigurator.h index 2b838860f..30a5c43b7 100644 --- a/Source/common/SNTConfigurator.h +++ b/Source/common/SNTConfigurator.h @@ -454,6 +454,20 @@ /// @property(nonatomic) NSArray *remountUSBMode; +/// +/// If set, defines the action that should be taken on existing USB mounts when +/// Santa starts up. +/// +/// Supported values are: +/// * "Unmount": Unmount mass storage devices +/// * "ForceUnmount": Force unmount mass storage devices +/// +/// +/// Note: Existing mounts with mount flags that are a superset of RemountUSBMode +/// are unaffected and left mounted. +/// +@property(readonly, nonatomic) SNTDeviceManagerStartupPreferences onStartUSBOptions; + /// /// If set, will override the action taken when a file access rule violation /// occurs. This setting will apply across all rules in the file access policy. diff --git a/Source/common/SNTConfigurator.m b/Source/common/SNTConfigurator.m index 2a3e0753f..d7226ee30 100644 --- a/Source/common/SNTConfigurator.m +++ b/Source/common/SNTConfigurator.m @@ -121,6 +121,7 @@ @implementation SNTConfigurator static NSString *const kFailClosedKey = @"FailClosed"; static NSString *const kBlockUSBMountKey = @"BlockUSBMount"; static NSString *const kRemountUSBModeKey = @"RemountUSBMode"; +static NSString *const kOnStartUSBOptions = @"OnStartUSBOptions"; static NSString *const kEnableTransitiveRulesKey = @"EnableTransitiveRules"; static NSString *const kEnableTransitiveRulesKeyDeprecated = @"EnableTransitiveWhitelisting"; static NSString *const kAllowedPathRegexKey = @"AllowedPathRegex"; @@ -181,6 +182,7 @@ - (instancetype)init { kBlockedPathRegexKeyDeprecated : re, kBlockUSBMountKey : number, kRemountUSBModeKey : array, + kOnStartUSBOptions : string, kEnablePageZeroProtectionKey : number, kEnableBadSignatureProtectionKey : number, kEnableSilentModeKey : number, @@ -635,6 +637,18 @@ - (void)setRemountUSBMode:(NSArray *)args { return args; } +- (SNTDeviceManagerStartupPreferences)onStartUSBOptions { + NSString *action = [self.configState[kOnStartUSBOptions] lowercaseString]; + + if ([action isEqualToString:@"unmount"]) { + return SNTDeviceManagerStartupPreferencesUnmount; + } else if ([action isEqualToString:@"forceunmount"]) { + return SNTDeviceManagerStartupPreferencesForceUnmount; + } else { + return SNTDeviceManagerStartupPreferencesNone; + } +} + - (NSDictionary *)staticRules { return self.cachedStaticRules; } diff --git a/Source/santad/BUILD b/Source/santad/BUILD index 2ac33d62b..6ed04fcfd 100644 --- a/Source/santad/BUILD +++ b/Source/santad/BUILD @@ -395,6 +395,7 @@ objc_library( ":Metrics", ":SNTEndpointSecurityClient", ":SNTEndpointSecurityEventHandler", + "//Source/common:SNTCommonEnums", "//Source/common:SNTDeviceEvent", "//Source/common:SNTLogging", ], @@ -1317,6 +1318,7 @@ santa_unit_test( ":Metrics", ":MockEndpointSecurityAPI", ":SNTEndpointSecurityDeviceManager", + "//Source/common:SNTCommonEnums", "//Source/common:SNTConfigurator", "//Source/common:SNTDeviceEvent", "//Source/common:TestUtils", diff --git a/Source/santad/EventProviders/DiskArbitrationTestUtil.h b/Source/santad/EventProviders/DiskArbitrationTestUtil.h index a01430258..f7f960960 100644 --- a/Source/santad/EventProviders/DiskArbitrationTestUtil.h +++ b/Source/santad/EventProviders/DiskArbitrationTestUtil.h @@ -16,6 +16,9 @@ #include #include #include +#include +#include +#include NS_ASSUME_NONNULL_BEGIN @@ -27,6 +30,7 @@ NS_ASSUME_NONNULL_BEGIN @interface MockDADisk : NSObject @property(nonatomic) NSDictionary *diskDescription; @property(nonatomic, readwrite) NSString *name; +@property(nonatomic) BOOL wasUnmounted; @end typedef void (^MockDADiskAppearedCallback)(DADiskRef ref); @@ -49,6 +53,23 @@ typedef void (^MockDADiskAppearedCallback)(DADiskRef ref); + (instancetype _Nonnull)mockDiskArbitration; @end +@interface MockStatfs : NSObject +@property NSString *fromName; +@property NSString *onName; +@property NSNumber *flags; + +- (instancetype _Nonnull)initFrom:(NSString *)from on:(NSString *)on flags:(NSNumber *)flags; +@end + +@interface MockMounts : NSObject +@property(nonatomic) NSMutableDictionary *mounts; + +- (instancetype _Nonnull)init; +- (void)reset; +- (void)insert:(MockStatfs *)sfs; ++ (instancetype _Nonnull)mockMounts; +@end + // // All DiskArbitration functions used in SNTEndpointSecurityDeviceManager // and shimmed out accordingly. @@ -81,5 +102,9 @@ void DARegisterDiskDescriptionChangedCallback(DASessionRef session, void DASessionSetDispatchQueue(DASessionRef session, dispatch_queue_t __nullable queue); DASessionRef __nullable DASessionCreate(CFAllocatorRef __nullable allocator); +void DADiskUnmount(DADiskRef disk, DADiskUnmountOptions options, + DADiskUnmountCallback __nullable callback, void *__nullable context); +int getmntinfo_r_np(struct statfs *__nullable *__nullable mntbufp, int flags); + CF_EXTERN_C_END NS_ASSUME_NONNULL_END diff --git a/Source/santad/EventProviders/DiskArbitrationTestUtil.mm b/Source/santad/EventProviders/DiskArbitrationTestUtil.mm index 69af68f70..9387b047a 100644 --- a/Source/santad/EventProviders/DiskArbitrationTestUtil.mm +++ b/Source/santad/EventProviders/DiskArbitrationTestUtil.mm @@ -14,6 +14,9 @@ #import #include +#include +#include +#include #import "Source/santad/EventProviders/DiskArbitrationTestUtil.h" @@ -62,6 +65,47 @@ + (instancetype _Nonnull)mockDiskArbitration { @end +@implementation MockStatfs +- (instancetype _Nonnull)initFrom:(NSString *)from on:(NSString *)on flags:(NSNumber *)flags { + self = [super init]; + if (self) { + _fromName = from; + _onName = on; + _flags = flags; + } + return self; +} +@end + +@implementation MockMounts + +- (instancetype _Nonnull)init { + self = [super init]; + if (self) { + _mounts = [NSMutableDictionary dictionary]; + } + return self; +} + +- (void)reset { + [self.mounts removeAllObjects]; +} + +- (void)insert:(MockStatfs *)sfs { + self.mounts[sfs.fromName] = sfs; +} + ++ (instancetype _Nonnull)mockMounts { + static MockMounts *sharedMounts; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + sharedMounts = [[MockMounts alloc] init]; + }); + return sharedMounts; +} + +@end + void DADiskMountWithArguments(DADiskRef _Nonnull disk, CFURLRef __nullable path, DADiskMountOptions options, DADiskMountCallback __nullable callback, void *__nullable context, @@ -117,4 +161,32 @@ DASessionRef __nullable DASessionCreate(CFAllocatorRef __nullable allocator) { return (__bridge DASessionRef)[MockDiskArbitration mockDiskArbitration]; }; +void DADiskUnmount(DADiskRef disk, DADiskUnmountOptions options, + DADiskUnmountCallback __nullable callback, void *__nullable context) { + MockDADisk *mockDisk = (__bridge MockDADisk *)disk; + mockDisk.wasUnmounted = YES; + + dispatch_semaphore_t sema = (__bridge dispatch_semaphore_t)context; + dispatch_semaphore_signal(sema); +} + +int getmntinfo_r_np(struct statfs *__nullable *__nullable mntbufp, int flags) { + MockMounts *mockMounts = [MockMounts mockMounts]; + + struct statfs *sfs = (struct statfs *)calloc(mockMounts.mounts.count, sizeof(struct statfs)); + + __block NSUInteger i = 0; + [mockMounts.mounts + enumerateKeysAndObjectsUsingBlock:^(NSString *key, MockStatfs *mockSfs, BOOL *stop) { + strlcpy(sfs[i].f_mntfromname, mockSfs.fromName.UTF8String, sizeof(sfs[i].f_mntfromname)); + strlcpy(sfs[i].f_mntonname, mockSfs.onName.UTF8String, sizeof(sfs[i].f_mntonname)); + sfs[i].f_flags = [mockSfs.flags unsignedIntValue]; + i++; + }]; + + *mntbufp = sfs; + + return (int)mockMounts.mounts.count; +} + NS_ASSUME_NONNULL_END diff --git a/Source/santad/EventProviders/SNTEndpointSecurityDeviceManager.h b/Source/santad/EventProviders/SNTEndpointSecurityDeviceManager.h index dee7d0ec4..41eab8196 100644 --- a/Source/santad/EventProviders/SNTEndpointSecurityDeviceManager.h +++ b/Source/santad/EventProviders/SNTEndpointSecurityDeviceManager.h @@ -15,6 +15,7 @@ #include #import +#import "Source/common/SNTCommonEnums.h" #import "Source/common/SNTDeviceEvent.h" #import "Source/santad/EventProviders/AuthResultCache.h" #include "Source/santad/EventProviders/EndpointSecurity/EndpointSecurityAPI.h" @@ -39,11 +40,16 @@ typedef void (^SNTDeviceBlockCallback)(SNTDeviceEvent *event); @property(nonatomic, nullable) SNTDeviceBlockCallback deviceBlockCallback; - (instancetype) - initWithESAPI: - (std::shared_ptr)esApi - metrics:(std::shared_ptr)metrics - logger:(std::shared_ptr)logger - authResultCache:(std::shared_ptr)authResultCache; + initWithESAPI: + (std::shared_ptr) + esApi + metrics:(std::shared_ptr)metrics + logger:(std::shared_ptr)logger + authResultCache: + (std::shared_ptr)authResultCache + blockUSBMount:(BOOL)blockUSBMount + remountUSBMode:(nullable NSArray *)remountUSBMode + startupPreferences:(SNTDeviceManagerStartupPreferences)startupPrefs; @end diff --git a/Source/santad/EventProviders/SNTEndpointSecurityDeviceManager.mm b/Source/santad/EventProviders/SNTEndpointSecurityDeviceManager.mm index 5588b0177..1015ceb4c 100644 --- a/Source/santad/EventProviders/SNTEndpointSecurityDeviceManager.mm +++ b/Source/santad/EventProviders/SNTEndpointSecurityDeviceManager.mm @@ -24,6 +24,8 @@ #include #include #include +#include +#include #import "Source/common/SNTDeviceEvent.h" #import "Source/common/SNTLogging.h" @@ -45,6 +47,7 @@ - (void)logDiskDisappeared:(NSDictionary *)props; @property DASessionRef diskArbSession; @property(nonatomic, readonly) dispatch_queue_t diskQueue; +@property dispatch_semaphore_t startupUnmountSema; @end @@ -91,7 +94,7 @@ void diskDisappearedCallback(DADiskRef disk, void *context) { [dm logDiskDisappeared:props]; } -NSArray *maskToMountArgs(long remountOpts) { +NSArray *maskToMountArgs(uint32_t remountOpts) { NSMutableArray *args = [NSMutableArray array]; if (remountOpts & MNT_RDONLY) [args addObject:@"rdonly"]; if (remountOpts & MNT_NOEXEC) [args addObject:@"noexec"]; @@ -104,8 +107,8 @@ void diskDisappearedCallback(DADiskRef disk, void *context) { return args; } -long mountArgsToMask(NSArray *args) { - long flags = 0; +uint32_t mountArgsToMask(NSArray *args) { + uint32_t flags = 0; for (NSString *i in args) { NSString *arg = [i lowercaseString]; if ([arg isEqualToString:@"rdonly"]) @@ -130,6 +133,21 @@ long mountArgsToMask(NSArray *args) { return flags; } +void UnmountCallback(DADiskRef disk, DADissenterRef dissenter, void *context) { + if (dissenter) { + LOGW(@"Unable to unmount device: %@", CFBridgingRelease(DADissenterGetStatusString(dissenter))); + } else if (disk) { + NSDictionary *diskInfo = CFBridgingRelease(DADiskCopyDescription(disk)); + LOGI(@"Unmounted device: Model: %@, Vendor: %@, Path: %@", + diskInfo[(__bridge NSString *)kDADiskDescriptionDeviceModelKey], + diskInfo[(__bridge NSString *)kDADiskDescriptionDeviceVendorKey], + diskInfo[(__bridge NSString *)kDADiskDescriptionVolumePathKey]); + } + + dispatch_semaphore_t sema = (__bridge dispatch_semaphore_t)context; + dispatch_semaphore_signal(sema); +} + NS_ASSUME_NONNULL_BEGIN @implementation SNTEndpointSecurityDeviceManager { @@ -140,25 +158,134 @@ @implementation SNTEndpointSecurityDeviceManager { - (instancetype)initWithESAPI:(std::shared_ptr)esApi metrics:(std::shared_ptr)metrics logger:(std::shared_ptr)logger - authResultCache:(std::shared_ptr)authResultCache { + authResultCache:(std::shared_ptr)authResultCache + blockUSBMount:(BOOL)blockUSBMount + remountUSBMode:(nullable NSArray *)remountUSBMode + startupPreferences:(SNTDeviceManagerStartupPreferences)startupPrefs { self = [super initWithESAPI:std::move(esApi) metrics:std::move(metrics) processor:santa::santad::Processor::kDeviceManager]; if (self) { _logger = logger; _authResultCache = authResultCache; - _blockUSBMount = false; + _blockUSBMount = blockUSBMount; + _remountArgs = remountUSBMode; _diskQueue = dispatch_queue_create("com.google.santa.daemon.disk_queue", DISPATCH_QUEUE_SERIAL); _diskArbSession = DASessionCreate(NULL); DASessionSetDispatchQueue(_diskArbSession, _diskQueue); + [self performStartupTasks:startupPrefs]; + [self establishClientOrDie]; } return self; } +- (BOOL)shouldOperateOnDisk:(DADiskRef)disk { + NSDictionary *diskInfo = CFBridgingRelease(DADiskCopyDescription(disk)); + + 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]; + BOOL isUSB = [protocol isEqualToString:@"USB"]; + BOOL isSecureDigital = [protocol isEqualToString:@"Secure Digital"]; + BOOL isVirtual = [protocol isEqualToString:@"Virtual Interface"]; + + NSString *kind = diskInfo[(__bridge NSString *)kDADiskDescriptionMediaKindKey]; + + // TODO: check kind and protocol for banned things (e.g. MTP). + LOGD(@"SNTEndpointSecurityDeviceManager: DiskInfo Protocol: %@ Kind: %@ isInternal: %d " + @"isRemovable: %d isEjectable: %d", + protocol, kind, isInternal, isRemovable, isEjectable); + + // if the device is internal, or virtual *AND* is not an SD Card, + // then allow the mount. This is to ensure we block SD cards inserted into + // the internal reader of some Macs, whilst also ensuring we don't block + // the internal storage device. + if ((isInternal || isVirtual) && !isSecureDigital) { + return false; + } + + // We are okay with operations for devices that are non-removable as long as + // they are NOT a USB device, or an SD Card. + if (!isRemovable && !isEjectable && !isUSB && !isSecureDigital) { + return false; + } + + return true; +} + +- (BOOL)remountUSBModeContainsFlags:(uint32_t)flags { + uint32_t requiredFlags = mountArgsToMask(self.remountArgs); + + LOGD(@" Got mount flags: 0x%08x | %@", flags, maskToMountArgs(flags)); + LOGD(@"Want mount flags: 0x%08x | %@", mountArgsToMask(self.remountArgs), self.remountArgs); + + return (flags & requiredFlags) == requiredFlags; +} + +- (void)performStartupTasks:(SNTDeviceManagerStartupPreferences)startupPrefs { + if (!self.blockUSBMount || (startupPrefs != SNTDeviceManagerStartupPreferencesUnmount && + startupPrefs != SNTDeviceManagerStartupPreferencesForceUnmount)) { + return; + } + + struct statfs *mnts; + int numMounts = getmntinfo_r_np(&mnts, MNT_WAIT); + + if (numMounts == 0) { + LOGE(@"Failed to get mount info: %d: %s", errno, strerror(errno)); + return; + } + + self.startupUnmountSema = dispatch_semaphore_create(0); + int numUnmountAttempts = 0; + + for (int i = 0; i < numMounts; i++) { + struct statfs *sfs = &mnts[i]; + + DADiskRef disk = DADiskCreateFromBSDName(NULL, self.diskArbSession, sfs->f_mntfromname); + if (!disk) { + LOGW(@"Unable to create disk reference for device: '%s' -> '%s'", sfs->f_mntfromname, + sfs->f_mntonname); + continue; + } + + CFAutorelease(disk); + + if (![self shouldOperateOnDisk:disk]) { + continue; + } + + if (self.remountArgs != nil && [self remountUSBModeContainsFlags:sfs->f_flags]) { + LOGI(@"Allowing existing mount as flags contain RemountUSBMode. '%s' -> '%s'", + sfs->f_mntfromname, sfs->f_mntonname); + continue; + } + + DADiskUnmountOptions unmountOptions = kDADiskUnmountOptionDefault; + if (startupPrefs == SNTDeviceManagerStartupPreferencesForceUnmount) { + unmountOptions = kDADiskUnmountOptionForce; + } + + LOGI(@"Attempting to unmount device: '%s' mounted on '%s'", sfs->f_mntfromname, + sfs->f_mntonname); + + DADiskUnmount(disk, unmountOptions, UnmountCallback, (__bridge void *)self.startupUnmountSema); + numUnmountAttempts++; + } + + while (numUnmountAttempts-- > 0) { + if (dispatch_semaphore_wait(self.startupUnmountSema, + dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC))) { + LOGW(@"An unmount attempt took longer than expected. Device may still be mounted."); + } + } +} + - (void)logDiskAppeared:(NSDictionary *)props { self->_logger->LogDiskAppeared(props); } @@ -225,44 +352,16 @@ - (es_auth_result_t)handleAuthMount:(const Message &)m { exit(EXIT_FAILURE); } - long mountMode = eventStatFS->f_flags; + uint32_t mountMode = eventStatFS->f_flags; pid_t pid = audit_token_to_pid(m->process->audit_token); LOGD( - @"SNTEndpointSecurityDeviceManager: mount syscall arriving from path: %s, pid: %d, fflags: %lu", + @"SNTEndpointSecurityDeviceManager: mount syscall arriving from path: %s, pid: %d, fflags: %u", m->process->executable->path.data, pid, mountMode); 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 isEjectable = [diskInfo[(__bridge NSString *)kDADiskDescriptionMediaEjectableKey] boolValue]; - NSString *protocol = diskInfo[(__bridge NSString *)kDADiskDescriptionDeviceProtocolKey]; - BOOL isUSB = [protocol isEqualToString:@"USB"]; - BOOL isSecureDigital = [protocol isEqualToString:@"Secure Digital"]; - BOOL isVirtual = [protocol isEqualToString:@"Virtual Interface"]; - - NSString *kind = diskInfo[(__bridge NSString *)kDADiskDescriptionMediaKindKey]; - - // TODO: check kind and protocol for banned things (e.g. MTP). - LOGD(@"SNTEndpointSecurityDeviceManager: DiskInfo Protocol: %@ Kind: %@ isInternal: %d " - @"isRemovable: %d " - @"isEjectable: %d", - protocol, kind, isInternal, isRemovable, isEjectable); - - // if the device is internal, or virtual *AND* is not an SD Card, - // then allow the mount. This is to ensure we block SD cards inserted into - // the internal reader of some Macs, whilst also ensuring we don't block - // the internal storage device. - if ((isInternal || isVirtual) && !isSecureDigital) { - return ES_AUTH_RESULT_ALLOW; - } - - // We are okay with operations for devices that are non-removable as long as - // they are NOT a USB device, or an SD Card. - if (!isRemovable && !isEjectable && !isUSB && !isSecureDigital) { + if (![self shouldOperateOnDisk:disk]) { return ES_AUTH_RESULT_ALLOW; } @@ -274,18 +373,17 @@ - (es_auth_result_t)handleAuthMount:(const Message &)m { if (shouldRemount) { event.remountArgs = self.remountArgs; - long remountOpts = mountArgsToMask(self.remountArgs); - - LOGD(@"SNTEndpointSecurityDeviceManager: mountMode: %@", maskToMountArgs(mountMode)); - LOGD(@"SNTEndpointSecurityDeviceManager: remountOpts: %@", maskToMountArgs(remountOpts)); + uint32_t remountOpts = mountArgsToMask(self.remountArgs); - if ((mountMode & remountOpts) == remountOpts && m->event_type != ES_EVENT_TYPE_AUTH_REMOUNT) { - LOGD(@"SNTEndpointSecurityDeviceManager: Allowing as mount as flags match remountOpts"); + if ([self remountUSBModeContainsFlags:mountMode] && + m->event_type != ES_EVENT_TYPE_AUTH_REMOUNT) { + LOGD(@"Allowing mount as flags contain RemountUSBMode. '%s' -> '%s'", + eventStatFS->f_mntfromname, eventStatFS->f_mntonname); return ES_AUTH_RESULT_ALLOW; } - long newMode = mountMode | remountOpts; - LOGI(@"SNTEndpointSecurityDeviceManager: remounting device '%s'->'%s', flags (%lu) -> (%lu)", + uint32_t newMode = mountMode | remountOpts; + LOGI(@"SNTEndpointSecurityDeviceManager: remounting device '%s'->'%s', flags (%u) -> (%u)", eventStatFS->f_mntfromname, eventStatFS->f_mntonname, mountMode, newMode); [self remount:disk mountMode:newMode]; } @@ -297,7 +395,7 @@ - (es_auth_result_t)handleAuthMount:(const Message &)m { return ES_AUTH_RESULT_DENY; } -- (void)remount:(DADiskRef)disk mountMode:(long)remountMask { +- (void)remount:(DADiskRef)disk mountMode:(uint32_t)remountMask { NSArray *args = maskToMountArgs(remountMask); CFStringRef *argv = (CFStringRef *)calloc(args.count + 1, sizeof(CFStringRef)); CFArrayGetValues((__bridge CFArrayRef)args, CFRangeMake(0, (CFIndex)args.count), diff --git a/Source/santad/EventProviders/SNTEndpointSecurityDeviceManagerTest.mm b/Source/santad/EventProviders/SNTEndpointSecurityDeviceManagerTest.mm index 2c980e593..ea5e1400a 100644 --- a/Source/santad/EventProviders/SNTEndpointSecurityDeviceManagerTest.mm +++ b/Source/santad/EventProviders/SNTEndpointSecurityDeviceManagerTest.mm @@ -26,6 +26,7 @@ #include #include +#import "Source/common/SNTCommonEnums.h" #import "Source/common/SNTConfigurator.h" #import "Source/common/SNTDeviceEvent.h" #include "Source/common/TestUtils.h" @@ -50,12 +51,16 @@ }; @interface SNTEndpointSecurityDeviceManager (Testing) +- (instancetype)init; - (void)logDiskAppeared:(NSDictionary *)props; +- (BOOL)shouldOperateOnDisk:(DADiskRef)disk; +- (void)performStartupTasks:(SNTDeviceManagerStartupPreferences)startupPrefs; @end @interface SNTEndpointSecurityDeviceManagerTest : XCTestCase @property id mockConfigurator; @property MockDiskArbitration *mockDA; +@property MockMounts *mockMounts; @end @implementation SNTEndpointSecurityDeviceManagerTest @@ -70,6 +75,9 @@ - (void)setUp { self.mockDA = [MockDiskArbitration mockDiskArbitration]; [self.mockDA reset]; + self.mockMounts = [MockMounts mockMounts]; + [self.mockMounts reset]; + fclose(stdout); } @@ -112,7 +120,10 @@ - (void)triggerTestMountEvent:(es_event_type_t)eventType [[SNTEndpointSecurityDeviceManager alloc] initWithESAPI:mockESApi metrics:nullptr logger:nullptr - authResultCache:nullptr]; + authResultCache:nullptr + blockUSBMount:false + remountUSBMode:nil + startupPreferences:SNTDeviceManagerStartupPreferencesNone]; setupDMCallback(deviceManager); @@ -324,7 +335,10 @@ - (void)testNotifyUnmountFlushesCache { [[SNTEndpointSecurityDeviceManager alloc] initWithESAPI:mockESApi metrics:nullptr logger:nullptr - authResultCache:mockAuthCache]; + authResultCache:mockAuthCache + blockUSBMount:YES + remountUSBMode:nil + startupPreferences:SNTDeviceManagerStartupPreferencesNone]; deviceManager.blockUSBMount = YES; @@ -340,6 +354,54 @@ - (void)testNotifyUnmountFlushesCache { XCTBubbleMockVerifyAndClearExpectations(mockAuthCache.get()); } +- (void)testPerformStartupTasks { + SNTEndpointSecurityDeviceManager *deviceManager = [[SNTEndpointSecurityDeviceManager alloc] init]; + + id partialDeviceManager = OCMPartialMock(deviceManager); + OCMStub([partialDeviceManager shouldOperateOnDisk:nil]).ignoringNonObjectArgs().andReturn(YES); + + deviceManager.blockUSBMount = YES; + deviceManager.remountArgs = @[ @"noexec", @"rdonly" ]; + + [self.mockMounts insert:[[MockStatfs alloc] initFrom:@"d1" on:@"v1" flags:@(0x0)]]; + [self.mockMounts insert:[[MockStatfs alloc] initFrom:@"d2" + on:@"v2" + flags:@(MNT_RDONLY | MNT_NOEXEC | MNT_JOURNALED)]]; + + MockDADisk *disk1 = [[MockDADisk alloc] init]; + MockDADisk *disk2 = [[MockDADisk alloc] init]; + + disk1.diskDescription = @{ + @"DAVolumePath" : @"v1", // f_mntonname, + @"DADevicePath" : @"v1", // f_mntonname, + @"DAMediaBSDName" : @"d1", // f_mntfromname, + }; + + disk2.diskDescription = @{ + @"DAVolumePath" : @"v2", // f_mntonname, + @"DADevicePath" : @"v2", // f_mntonname, + @"DAMediaBSDName" : @"d2", // f_mntfromname, + }; + + [self.mockDA insert:disk1 bsdName:disk1.diskDescription[@"DAMediaBSDName"]]; + [self.mockDA insert:disk2 bsdName:disk2.diskDescription[@"DAMediaBSDName"]]; + + [deviceManager performStartupTasks:SNTDeviceManagerStartupPreferencesUnmount]; + + XCTAssertTrue(disk1.wasUnmounted); + XCTAssertFalse(disk2.wasUnmounted); + + // Re-run tests with no remount args set, everything should be unmounted + disk1.wasUnmounted = NO; + disk2.wasUnmounted = NO; + deviceManager.remountArgs = nil; + + [deviceManager performStartupTasks:SNTDeviceManagerStartupPreferencesUnmount]; + + XCTAssertTrue(disk1.wasUnmounted); + XCTAssertTrue(disk2.wasUnmounted); +} + - (void)testEnable { // Ensure the client subscribes to expected event types std::set expectedEventSubs{ diff --git a/Source/santad/Santad.mm b/Source/santad/Santad.mm index 54508fd2d..fa24170db 100644 --- a/Source/santad/Santad.mm +++ b/Source/santad/Santad.mm @@ -103,10 +103,11 @@ void SantadMain(std::shared_ptr esapi, std::shared_ptr