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

Commit

Permalink
USB mass storage blocking and remounting (#685)
Browse files Browse the repository at this point in the history
* USB mass storage blocking.

* Add the sync service and config key for enabling mass USB storage blocking
* Update docs with the sync service key
* Add ability to forcibly remount USBs with different flags
* update EndpointSecurityTestUtil and tests that use it to properly handle multiple ES clients
  • Loading branch information
tnek authored Dec 16, 2021
1 parent 91f3168 commit 197109a
Show file tree
Hide file tree
Showing 23 changed files with 722 additions and 107 deletions.
1 change: 1 addition & 0 deletions BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,7 @@ test_suite(
"//Source/santactl:unit_tests",
"//Source/santad:SNTApplicationCoreMetricsTest",
"//Source/santad:SNTApplicationTest",
"//Source/santad:SNTDeviceManagerTest",
"//Source/santad:SNTEndpointSecurityManagerTest",
"//Source/santad:SNTEventTableTest",
"//Source/santad:SNTExecutionControllerTest",
Expand Down
17 changes: 17 additions & 0 deletions Source/common/SNTConfigurator.h
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,23 @@
///
@property(nonatomic) BOOL syncCleanRequired;

///
/// USB Mount Blocking. Defaults to false.
///
@property(nonatomic) BOOL blockUSBMount;

///
/// Comma-seperated `$ mount -o` arguments used for forced remounting of USB devices. Default
/// to fully allow/deny without remounting if unset.
///
@property(nonatomic) NSArray<NSString *> *remountUSBMode;

///
/// When `blockUSBMount` is set, this is the message shown to the user when a device is blocked
/// If this message is not configured, a reasonable default is provided.
///
@property(readonly, nonatomic) NSString *usbBlockMessage;

///
/// If set, this over-rides the default machine ID used for syncing.
///
Expand Down
29 changes: 29 additions & 0 deletions Source/common/SNTConfigurator.m
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,8 @@ @implementation SNTConfigurator

// The keys managed by a sync server or mobileconfig.
static NSString *const kClientModeKey = @"ClientMode";
static NSString *const kBlockUSBMountKey = @"BlockUSBMount";
static NSString *const kRemountUSBModeKey = @"RemountUSBMode";
static NSString *const kEnableTransitiveRulesKey = @"EnableTransitiveRules";
static NSString *const kEnableTransitiveRulesKeyDeprecated = @"EnableTransitiveWhitelisting";
static NSString *const kAllowedPathRegexKey = @"AllowedPathRegex";
Expand Down Expand Up @@ -131,6 +133,8 @@ - (instancetype)init {
kAllowedPathRegexKeyDeprecated : re,
kBlockedPathRegexKey : re,
kBlockedPathRegexKeyDeprecated : re,
kBlockUSBMountKey : number,
kRemountUSBModeKey : array,
kFullSyncLastSuccess : date,
kRuleSyncLastSuccess : date,
kSyncCleanRequired : number
Expand All @@ -145,6 +149,8 @@ - (instancetype)init {
kAllowedPathRegexKeyDeprecated : re,
kBlockedPathRegexKey : re,
kBlockedPathRegexKeyDeprecated : re,
kBlockUSBMountKey : number,
kRemountUSBModeKey : array,
kEnablePageZeroProtectionKey : number,
kEnableBadSignatureProtectionKey : number,
kAboutText : string,
Expand Down Expand Up @@ -486,6 +492,20 @@ - (NSArray *)fileChangesPrefixFilters {
return filters;
}

- (void)setRemountUSBMode:(NSArray<NSString *> *)args {
[self updateSyncStateForKey:kRemountUSBModeKey value:args];
}

- (NSArray<NSString *> *)remountUSBMode {
NSArray<NSString *> *args = self.configState[kRemountUSBModeKey];
for (id arg in args) {
if (![arg isKindOfClass:[NSString class]]) {
return nil;
}
}
return args;
}

- (NSURL *)syncBaseURL {
NSString *urlString = self.configState[kSyncBaseURLKey];
if (![urlString hasSuffix:@"/"]) urlString = [urlString stringByAppendingString:@"/"];
Expand Down Expand Up @@ -679,6 +699,15 @@ - (BOOL)fcmEnabled {
return (self.fcmProject.length && self.fcmEntity.length && self.fcmAPIKey.length);
}

- (void)setBlockUSBMount:(BOOL)enabled {
[self updateSyncStateForKey:kBlockUSBMountKey value:@(enabled)];
}

- (BOOL)blockUSBMount {
NSNumber *number = self.configState[kBlockUSBMountKey];
return number ? [number boolValue] : NO;
}

///
/// Returns YES if all of the necessary options are set to export metrics, NO
/// otherwise.
Expand Down
2 changes: 2 additions & 0 deletions Source/common/SNTXPCControlInterface.h
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@
- (void)setSyncCleanRequired:(BOOL)cleanReqd reply:(void (^)(void))reply;
- (void)setAllowedPathRegex:(NSString *)pattern reply:(void (^)(void))reply;
- (void)setBlockedPathRegex:(NSString *)pattern reply:(void (^)(void))reply;
- (void)setBlockUSBMount:(BOOL)enabled reply:(void (^)(void))reply;
- (void)setRemountUSBMode:(NSArray *)remountUSBMode reply:(void (^)(void))reply;
- (void)setEnableBundles:(BOOL)bundlesEnabled reply:(void (^)(void))reply;
- (void)setEnableTransitiveRules:(BOOL)enabled reply:(void (^)(void))reply;

Expand Down
27 changes: 27 additions & 0 deletions Source/santad/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ objc_library(
"DataLayer/SNTRuleTable.m",
"EventProviders/SNTCachingEndpointSecurityManager.h",
"EventProviders/SNTCachingEndpointSecurityManager.mm",
"EventProviders/SNTDeviceManager.h",
"EventProviders/SNTDeviceManager.mm",
"EventProviders/SNTDriverManager.h",
"EventProviders/SNTDriverManager.m",
"EventProviders/SNTEndpointSecurityManager.h",
Expand Down Expand Up @@ -99,6 +101,10 @@ objc_library(
"EndpointSecurity",
"bsm",
],
sdk_frameworks = [
"DiskArbitration",
"IOKit",
],
)

macos_bundle(
Expand Down Expand Up @@ -200,6 +206,7 @@ santa_unit_test(
"EventProviders/SNTEndpointSecurityManagerTest.mm",
"EventProviders/SNTEventProvider.h",
],
minimum_os_version = "10.15",
sdk_dylibs = [
"EndpointSecurity",
"bsm",
Expand All @@ -212,6 +219,26 @@ santa_unit_test(
],
)

santa_unit_test(
name = "SNTDeviceManagerTest",
srcs = [
"EventProviders/SNTDeviceManagerTest.mm",
],
minimum_os_version = "10.15",
sdk_dylibs = [
"EndpointSecurity",
"bsm",
],
deps = [
":EndpointSecurityTestLib",
":santad_lib",
"//Source/common:SNTKernelCommon",
"//Source/common:SNTPrefixTree",
"//Source/common:SantaCache",
"@OCMock",
],
)

santa_unit_test(
name = "SNTApplicationTest",
srcs = [
Expand Down
2 changes: 1 addition & 1 deletion Source/santad/EventProviders/EndpointSecurityTestUtil.h
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ typedef void (^ESCallback)(ESResponse *_Nonnull);
@interface MockEndpointSecurity : NSObject
@property NSMutableArray *_Nonnull subscriptions;
- (void)reset;
- (void)registerResponseCallback:(ESCallback _Nonnull)callback;
- (void)registerResponseCallback:(es_event_type_t)t withCallback:(ESCallback _Nonnull)callback;
- (void)triggerHandler:(es_message_t *_Nonnull)msg;

/// Retrieve an initialized singleton MockEndpointSecurity object
Expand Down
110 changes: 87 additions & 23 deletions Source/santad/EventProviders/EndpointSecurityTestUtil.mm
Original file line number Diff line number Diff line change
Expand Up @@ -89,19 +89,22 @@ - (void)dealloc {
@implementation ESResponse
@end

@interface MockEndpointSecurity ()
@property NSMutableArray<ESCallback> *responseCallbacks;
@property NSObject *client;
@interface MockESClient : NSObject
@property NSMutableArray *_Nonnull subscriptions;
@property es_handler_block_t handler;
@end

@implementation MockEndpointSecurity
@implementation MockESClient

- (instancetype)init {
self = [super init];
if (self) {
_responseCallbacks = [NSMutableArray array];
_subscriptions = [NSMutableArray arrayWithCapacity:ES_EVENT_TYPE_LAST];
[self resetSubscriptions];
@synchronized(self) {
_subscriptions = [NSMutableArray arrayWithCapacity:ES_EVENT_TYPE_LAST];
for (size_t i = 0; i < ES_EVENT_TYPE_LAST; i++) {
[self.subscriptions addObject:@NO];
}
}
}
return self;
};
Expand All @@ -112,31 +115,78 @@ - (void)resetSubscriptions {
}
}

- (void)triggerHandler:(es_message_t *_Nonnull)msg {
self.handler((__bridge es_client_t *_Nullable)self, msg);
}

- (void)dealloc {
@synchronized(self) {
[self.subscriptions removeAllObjects];
}
}

@end

@interface MockEndpointSecurity ()
@property NSMutableArray<MockESClient *> *clients;

// Array of collections of ESCallback blocks
// This should be of size ES_EVENT_TYPE_LAST, allowing for indexing by ES_EVENT_TYPE_xxx members.
@property NSMutableArray<NSMutableArray<ESCallback> *> *responseCallbacks;
@end

@implementation MockEndpointSecurity
- (instancetype)init {
self = [super init];
if (self) {
@synchronized(self) {
_clients = [NSMutableArray array];
_responseCallbacks = [NSMutableArray arrayWithCapacity:ES_EVENT_TYPE_LAST];
for (size_t i = 0; i < ES_EVENT_TYPE_LAST; i++) {
[self.responseCallbacks addObject:[NSMutableArray array]];
}
[self reset];
}
}
return self;
};

- (void)resetResponseCallbacks {
for (NSMutableArray *callback in self.responseCallbacks) {
if (callback != nil) {
[callback removeAllObjects];
}
}
}

- (void)reset {
@synchronized(self) {
[self.responseCallbacks removeAllObjects];
self.handler = nil;
self.client = nil;
[self.clients removeAllObjects];
[self resetResponseCallbacks];
}
};

- (void)newClient:(es_client_t *_Nullable *_Nonnull)client
handler:(es_handler_block_t __strong)handler {
// es_client_t is generally used as a pointer to an opaque struct (secretly a mach port).
// We just want to set it to something nonnull for passing initialization checks. It shouldn't
// ever be directly dereferenced.
self.client = [[NSObject alloc] init];
*client = (__bridge es_client_t *)self.client;
self.handler = handler;
// There is also a few nonnull initialization checks on it.
MockESClient *mockClient = [[MockESClient alloc] init];
*client = (__bridge es_client_t *)mockClient;
mockClient.handler = handler;
[self.clients addObject:mockClient];
}

- (void)triggerHandler:(es_message_t *_Nonnull)msg {
self.handler((__bridge es_client_t *_Nullable)self.client, msg);
for (MockESClient *client in self.clients) {
if (client.subscriptions[msg->event_type]) {
[client triggerHandler:msg];
}
}
}

- (void)registerResponseCallback:(ESCallback _Nonnull)callback {
- (void)registerResponseCallback:(es_event_type_t)t withCallback:(ESCallback _Nonnull)callback {
@synchronized(self) {
[self.responseCallbacks addObject:callback];
[self.responseCallbacks[t] addObject:callback];
}
}

Expand All @@ -147,7 +197,7 @@ - (es_respond_result_t)respond_auth_result:(const es_message_t *_Nonnull)msg
ESResponse *response = [[ESResponse alloc] init];
response.result = result;
response.shouldCache = cache;
for (void (^callback)(ESResponse *) in self.responseCallbacks) {
for (void (^callback)(ESResponse *) in self.responseCallbacks[msg->event_type]) {
callback(response);
}
}
Expand All @@ -156,10 +206,22 @@ - (es_respond_result_t)respond_auth_result:(const es_message_t *_Nonnull)msg

- (void)setSubscriptions:(const es_event_type_t *_Nonnull)events
event_count:(uint32_t)event_count
value:(NSNumber *)value {
value:(NSNumber *)value
client:(es_client_t *)client {
@synchronized(self) {
MockESClient *toUpdate = nil;
for (MockESClient *c in self.clients) {
if (client == (__bridge es_client_t *)c) {
toUpdate = c;
}
}
if (toUpdate == nil) {
NSLog(@"setting subscription for unknown client");
return;
}

for (size_t i = 0; i < event_count; i++) {
self.subscriptions[events[i]] = value;
toUpdate.subscriptions[events[i]] = value;
}
}
}
Expand Down Expand Up @@ -212,7 +274,8 @@ es_return_t es_subscribe(es_client_t *_Nonnull client, const es_event_type_t *_N
uint32_t event_count) {
[[MockEndpointSecurity mockEndpointSecurity] setSubscriptions:events
event_count:event_count
value:@YES];
value:@YES
client:client];
return ES_RETURN_SUCCESS;
}
API_AVAILABLE(macos(10.15))
Expand All @@ -221,7 +284,8 @@ es_return_t es_unsubscribe(es_client_t *_Nonnull client, const es_event_type_t *
uint32_t event_count) {
[[MockEndpointSecurity mockEndpointSecurity] setSubscriptions:events
event_count:event_count
value:@NO];
value:@NO
client:client];

return ES_RETURN_SUCCESS;
};
33 changes: 33 additions & 0 deletions Source/santad/EventProviders/SNTDeviceManager.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/// Copyright 2021 Google Inc. All rights reserved.
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
#import <DiskArbitration/DiskArbitration.h>
#import <Foundation/Foundation.h>

#include <EndpointSecurity/EndpointSecurity.h>

/*
* Manages DiskArbitration and EndpointSecurity to monitor/block/remount USB
* storage devices.
*/
@interface SNTDeviceManager : NSObject

@property(nonatomic, readwrite) BOOL subscribed;
@property(nonatomic, readwrite) BOOL blockUSBMount;
@property(nonatomic, readwrite) NSArray<NSString *> *remountArgs;

- (instancetype)init;
- (void)listen;
- (BOOL)subscribed;

@end
Loading

0 comments on commit 197109a

Please sign in to comment.