Skip to content

Commit

Permalink
santa-driver: add an acknowledge feature to allow timeouts (google#220)
Browse files Browse the repository at this point in the history
* santa-driver: Add an acknowledge feature to allow timeouts for lost requests

* project: cocoapods 1.3.1 update

* review updates
  • Loading branch information
tburgin authored and dskfh committed Jul 17, 2020
1 parent 93afa3e commit aaca348
Show file tree
Hide file tree
Showing 10 changed files with 113 additions and 11 deletions.
2 changes: 1 addition & 1 deletion Podfile.lock
Expand Up @@ -29,4 +29,4 @@ SPEC CHECKSUMS:

PODFILE CHECKSUM: acd378b3727c923d912e09812da344f7375c14fe

COCOAPODS: 1.2.1
COCOAPODS: 1.3.1
25 changes: 20 additions & 5 deletions Santa.xcodeproj/project.pbxproj
Expand Up @@ -1221,13 +1221,16 @@
files = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-santad-santabs-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n";
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
2146C1274A8C11B3755A8A67 /* [CP] Check Pods Manifest.lock */ = {
Expand All @@ -1236,13 +1239,16 @@
files = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-santad-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n";
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
23869BA352E2C86DEFE62819 /* [CP] Copy Pods Resources */ = {
Expand Down Expand Up @@ -1296,13 +1302,16 @@
files = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-LogicTests-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n";
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
B193461D3612BCB47C16E407 /* [CP] Check Pods Manifest.lock */ = {
Expand All @@ -1311,13 +1320,16 @@
files = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-Santa-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n";
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
BA20035148DDEF5808B2C7EF /* [CP] Embed Pods Frameworks */ = {
Expand Down Expand Up @@ -1358,13 +1370,16 @@
files = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-santactl-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n";
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
D49A3AB950AFD99741E9AF89 /* [CP] Embed Pods Frameworks */ = {
Expand Down
4 changes: 3 additions & 1 deletion Source/common/SNTKernelCommon.h
Expand Up @@ -37,6 +37,7 @@ enum SantaDriverMethods {
kSantaUserClientOpen,
kSantaUserClientAllowBinary,
kSantaUserClientDenyBinary,
kSantaUserClientAcknowledgeBinary,
kSantaUserClientClearCache,
kSantaUserClientCacheCount,
kSantaUserClientCheckCache,
Expand Down Expand Up @@ -64,7 +65,8 @@ typedef enum {
ACTION_RESPOND_ALLOW = 20,
ACTION_RESPOND_DENY = 21,
ACTION_RESPOND_TOOLONG = 22,

ACTION_RESPOND_ACK = 23,

// NOTIFY
ACTION_NOTIFY_EXEC = 30,
ACTION_NOTIFY_WRITE = 31,
Expand Down
19 changes: 15 additions & 4 deletions Source/santa-driver/SantaDecisionManager.cc
Expand Up @@ -233,10 +233,15 @@ void SantaDecisionManager::AddToCache(
case ACTION_REQUEST_BINARY:
decision_cache->set(identifier, val, 0);
break;
case ACTION_RESPOND_ACK:
decision_cache->set(identifier, val, ((uint64_t)ACTION_REQUEST_BINARY << 56));
break;
case ACTION_RESPOND_ALLOW:
case ACTION_RESPOND_DENY:
decision_cache->set(
identifier, val, ((uint64_t)ACTION_REQUEST_BINARY << 56));
// TODO(bur): Avoid calling set() twice, finding and locking buckets is fast, but not free.
if (decision_cache->set(identifier, val, ((uint64_t)ACTION_REQUEST_BINARY << 56))) {
decision_cache->set(identifier, val, ((uint64_t)ACTION_RESPOND_ACK << 56));
}
break;
default:
break;
Expand Down Expand Up @@ -316,10 +321,16 @@ santa_action_t SantaDecisionManager::GetFromDaemon(
return ACTION_ERROR;
}

// Check the cache every kRequestLoopSleepMilliseconds. Break this loop and send the request
// again if kRequestCacheChecks is reached. Don't break the loop if the daemon is working on the
// request, indicated with ACTION_RESPOND_ACK.
auto cache_check_count = 0;
do {
msleep((void *)message->vnode_id, NULL, 0, "", &ts_);
return_action = GetFromCache(identifier);
} while (return_action == ACTION_REQUEST_BINARY && ClientConnected());
} while (ClientConnected() &&
((return_action == ACTION_REQUEST_BINARY && ++cache_check_count < kRequestCacheChecks)
|| (return_action == ACTION_RESPOND_ACK)));
} while (!RESPONSE_VALID(return_action) && ClientConnected());

// If response is still not valid, the daemon exited
Expand Down Expand Up @@ -354,7 +365,7 @@ santa_action_t SantaDecisionManager::FetchDecision(
// If item is not in cache, break out of loop to send request to daemon.
if (RESPONSE_VALID(return_action)) {
return return_action;
} else if (return_action == ACTION_REQUEST_BINARY) {
} else if (return_action == ACTION_REQUEST_BINARY || return_action == ACTION_RESPOND_ACK) {
// This thread will now sleep for kRequestLoopSleepMilliseconds (1s) or
// until AddToCache is called, indicating a response has arrived.
msleep((void *)vnode_id, NULL, 0, "", &ts_);
Expand Down
6 changes: 6 additions & 0 deletions Source/santa-driver/SantaDecisionManager.h
Expand Up @@ -144,6 +144,12 @@ class SantaDecisionManager : public OSObject {
*/
static const uint32_t kRequestLoopSleepMilliseconds = 1000;

/**
While waiting for a response from the daemon, this is the maximum number cache checks before
re-sending the request.
*/
static const uint32_t kRequestCacheChecks = 5;

/**
The maximum number of milliseconds a cached deny message should be
considered valid.
Expand Down
13 changes: 13 additions & 0 deletions Source/santa-driver/SantaDriverClient.cc
Expand Up @@ -152,6 +152,18 @@ IOReturn SantaDriverClient::deny_binary(
return kIOReturnSuccess;
}

IOReturn SantaDriverClient::acknowledge_binary(
OSObject *target, void *reference, IOExternalMethodArguments *arguments) {
SantaDriverClient *me = OSDynamicCast(SantaDriverClient, target);
if (!me) return kIOReturnBadArgument;

const uint64_t vnode_id = static_cast<const uint64_t>(arguments->scalarInput[0]);
if (!vnode_id) return kIOReturnInvalid;
me->decisionManager->AddToCache(vnode_id, ACTION_RESPOND_ACK, 0);

return kIOReturnSuccess;
}

IOReturn SantaDriverClient::clear_cache(
OSObject *target, void *reference, IOExternalMethodArguments *arguments) {
SantaDriverClient *me = OSDynamicCast(SantaDriverClient, target);
Expand Down Expand Up @@ -199,6 +211,7 @@ IOReturn SantaDriverClient::externalMethod(
{ &SantaDriverClient::open, 0, 0, 0, 0 },
{ &SantaDriverClient::allow_binary, 1, 0, 0, 0 },
{ &SantaDriverClient::deny_binary, 1, 0, 0, 0 },
{ &SantaDriverClient::acknowledge_binary, 1, 0, 0, 0 },
{ &SantaDriverClient::clear_cache, 1, 0, 0, 0 },
{ &SantaDriverClient::cache_count, 0, 0, 2, 0 },
{ &SantaDriverClient::check_cache, 1, 0, 1, 0 }
Expand Down
5 changes: 5 additions & 0 deletions Source/santa-driver/SantaDriverClient.h
Expand Up @@ -87,6 +87,11 @@ class com_google_SantaDriverClient : public IOUserClient {
static IOReturn deny_binary(
OSObject *target, void *reference, IOExternalMethodArguments *arguments);

/// The daemon calls this to acknowledge a binary request. This is used for large binaries that
/// may take a while to reach a decision.
static IOReturn acknowledge_binary(
OSObject *target, void *reference, IOExternalMethodArguments *arguments);

/// The daemon calls this to empty the cache.
static IOReturn clear_cache(
OSObject *target, void *reference, IOExternalMethodArguments *arguments);
Expand Down
7 changes: 7 additions & 0 deletions Source/santad/SNTDriverManager.m
Expand Up @@ -162,6 +162,13 @@ - (kern_return_t)postToKernelAction:(santa_action_t)action forVnodeID:(uint64_t)
1,
0,
0);
case ACTION_RESPOND_ACK:
return IOConnectCallScalarMethod(_connection,
kSantaUserClientAcknowledgeBinary,
&vnodeId,
1,
0,
0);
default:
return KERN_INVALID_ARGUMENT;
}
Expand Down
13 changes: 13 additions & 0 deletions Source/santad/SNTExecutionController.m
Expand Up @@ -38,6 +38,12 @@
#import "SNTStoredEvent.h"
#import "SNTSyncdQueue.h"

// A binary is considered large at ~30MB. Large binaries take longer to hash and consequently
// longer to post a decision back to santa-driver. When a binary is considered large santad will
// let santa-driver know it has received its request and is working on a decision. This allows
// santa-driver to relax; it does not have to worry about resending the request due to a timeout.
static size_t kLargeBinarySize = 30 * 1024 * 1024;

@interface SNTExecutionController ()
@property SNTDriverManager *driverManager;
@property SNTEventLog *eventLog;
Expand Down Expand Up @@ -102,6 +108,13 @@ - (void)validateBinaryWithMessage:(santa_message_t)message {
return;
}

// If the binary is large let santa-driver know we received the request and we are working on it.
if (binInfo.fileSize > kLargeBinarySize) {
LOGD(@"%@ is larger than %zu. Letting santa-driver know we are working on it.",
binInfo.path, kLargeBinarySize);
[_driverManager postToKernelAction:ACTION_RESPOND_ACK forVnodeID:message.vnode_id];
}

// Get codesigning info about the file.
NSError *csError;
MOLCodesignChecker *csInfo = [[MOLCodesignChecker alloc] initWithBinaryPath:binInfo.path
Expand Down
30 changes: 30 additions & 0 deletions Tests/KernelTests/main.mm
Expand Up @@ -99,6 +99,9 @@ - (void)postToKernelAction:(santa_action_t)action forVnodeID:(uint64_t)vnodeid {
IOConnectCallScalarMethod(self.connection, kSantaUserClientAllowBinary, &vnodeid, 1, 0, 0);
} else if (action == ACTION_RESPOND_DENY) {
IOConnectCallScalarMethod(self.connection, kSantaUserClientDenyBinary, &vnodeid, 1, 0, 0);
} else if (action == ACTION_RESPOND_ACK) {
IOConnectCallScalarMethod(self.connection, kSantaUserClientAcknowledgeBinary,
&vnodeid, 1, 0, 0);
}
}

Expand Down Expand Up @@ -239,6 +242,18 @@ - (void)beginListening {
} else if (strncmp("/bin/cat", vdata.path, strlen("/bin/cat")) == 0) {
[self postToKernelAction:ACTION_RESPOND_ALLOW forVnodeID:vdata.vnode_id];
self.timesSeenCat++;
} else if (strncmp("/usr/bin/cal", vdata.path, strlen("/usr/bin/cal")) == 0) {
static int count = 0;
if (count++) TFAILINFO("Large binary should not re-request");
[self postToKernelAction:ACTION_RESPOND_ACK forVnodeID:vdata.vnode_id];
for (int i = 0; i < 15; ++i) {
printf("\033[s"); // save cursor position
printf("%i/15", i);
sleep(1);
printf("\033[u"); // restore cursor position
}
printf("\033[K\033[u"); // clear line, restore cursor position
[self postToKernelAction:ACTION_RESPOND_ALLOW forVnodeID:vdata.vnode_id];
} else if (strncmp("/bin/ln", vdata.path, strlen("/bin/ln")) == 0) {
[self postToKernelAction:ACTION_RESPOND_ALLOW forVnodeID:vdata.vnode_id];

Expand Down Expand Up @@ -554,6 +569,20 @@ - (void)testCachePerformance {
}
}

- (void)testLargeBinary {
TSTART("Handles large binary");

@try {
NSTask *testexec = [self taskWithPath:@"/usr/bin/cal"];
[testexec launch];
[testexec waitUntilExit];
} @catch (NSException *e) {
TFAILINFO("Failed to launch");
}

TPASS();
}

#pragma mark - Main

- (void)runTests {
Expand All @@ -580,6 +609,7 @@ - (void)runTests {

printf("\n-> Performance tests:\033[m\n");
[self testCachePerformance];
[self testLargeBinary];
[self handlesLotsOfBinaries];

printf("\nAll tests passed.\n\n");
Expand Down

0 comments on commit aaca348

Please sign in to comment.