Skip to content

Commit

Permalink
Ensure hardened runtime for cdhash eval. Update docs.
Browse files Browse the repository at this point in the history
  • Loading branch information
mlw committed Mar 5, 2024
1 parent 61dcc48 commit 106c788
Show file tree
Hide file tree
Showing 8 changed files with 62 additions and 29 deletions.
Expand Up @@ -436,7 +436,7 @@ - (void)testGetReasonString {
{SNTEventStateBlockScope, "SCOPE"},
{SNTEventStateBlockTeamID, "TEAMID"},
{SNTEventStateBlockSigningID, "SIGNINGID"},
{SNTEventStateBlockCDHash, "CDHash"},
{SNTEventStateBlockCDHash, "CDHASH"},
{SNTEventStateBlockLongPath, "LONG_PATH"},
{SNTEventStateAllowUnknown, "UNKNOWN"},
{SNTEventStateAllowBinary, "BINARY"},
Expand All @@ -447,7 +447,7 @@ - (void)testGetReasonString {
{SNTEventStateAllowPendingTransitive, "PENDING_TRANSITIVE"},
{SNTEventStateAllowTeamID, "TEAMID"},
{SNTEventStateAllowSigningID, "SIGNINGID"},
{SNTEventStateAllowCDHash, "CDHash"},
{SNTEventStateAllowCDHash, "CDHASH"},
};

for (const auto &kv : stateToReason) {
Expand Down
31 changes: 21 additions & 10 deletions Source/santad/SNTExecutionControllerTest.mm
Expand Up @@ -226,16 +226,6 @@ - (void)validateExecEvent:(SNTAction)wantAction {
}

- (void)stubRule:(SNTRule *)rule forIdentifiers:(struct RuleIdentifiers)wantIdentifiers {
SNTRule *myRule = [[SNTRule alloc] init];
myRule.state = rule.state;
myRule.type = rule.type;

// Most tests do not set a specific cdhash, however an es_process_t created
// from MakeESProcess will have an all zeroes cdhash, not nil. To alleviate
// the need for all tests to specifiy the default cdhash, fix it up here.
static NSString *const zeroCDHash = RepeatedString(@"0", CS_CDHASH_LEN * 2);
wantIdentifiers.cdhash = wantIdentifiers.cdhash ?: zeroCDHash;

OCMStub([self.mockRuleDatabase ruleForIdentifiers:wantIdentifiers])
.ignoringNonObjectArgs()
.andDo(^(NSInvocation *inv) {
Expand Down Expand Up @@ -289,6 +279,24 @@ - (void)testCDHashAllowRule {
[self validateExecEvent:SNTActionRespondAllow
messageSetup:^(es_message_t *msg) {
msg->event.exec.target->cdhash[0] = 0xaa;
msg->event.exec.target->codesigning_flags = CS_SIGNED | CS_VALID | CS_KILL | CS_HARD;
}];
[self checkMetricCounters:kAllowCDHash expected:@1];
}

- (void)testCDHashNoHardenedRuntimeRule {
SNTRule *rule = [[SNTRule alloc] init];
rule.state = SNTRuleStateAllow;
rule.type = SNTRuleTypeCDHash;

// No CDHash should be set when hardened runtime CS flags are not set
[self stubRule:rule forIdentifiers:{.cdhash = nil}];

[self validateExecEvent:SNTActionRespondAllow
messageSetup:^(es_message_t *msg) {
msg->event.exec.target->cdhash[0] = 0xaa;
// Ensure CS_HARD and CS_KILL are not set
msg->event.exec.target->codesigning_flags = CS_SIGNED | CS_VALID;
}];
[self checkMetricCounters:kAllowCDHash expected:@1];
}
Expand All @@ -303,6 +311,7 @@ - (void)testCDHashBlockRule {
[self validateExecEvent:SNTActionRespondDeny
messageSetup:^(es_message_t *msg) {
msg->event.exec.target->cdhash[0] = 0xaa;
msg->event.exec.target->codesigning_flags = CS_SIGNED | CS_VALID | CS_KILL | CS_HARD;
}];
[self checkMetricCounters:kBlockCDHash expected:@1];
}
Expand All @@ -319,6 +328,7 @@ - (void)testCDHashAllowCompilerRule {
[self validateExecEvent:SNTActionRespondAllowCompiler
messageSetup:^(es_message_t *msg) {
msg->event.exec.target->cdhash[0] = 0xaa;
msg->event.exec.target->codesigning_flags = CS_SIGNED | CS_VALID | CS_KILL | CS_HARD;
}];

[self checkMetricCounters:kAllowCompiler expected:@1];
Expand All @@ -336,6 +346,7 @@ - (void)testCDHashAllowCompilerRuleTransitiveRuleDisabled {
[self validateExecEvent:SNTActionRespondAllow
messageSetup:^(es_message_t *msg) {
msg->event.exec.target->cdhash[0] = 0xaa;
msg->event.exec.target->codesigning_flags = CS_SIGNED | CS_VALID | CS_KILL | CS_HARD;
}];

[self checkMetricCounters:kAllowCDHash expected:@1];
Expand Down
21 changes: 13 additions & 8 deletions Source/santad/SNTPolicyProcessor.m
Expand Up @@ -307,14 +307,19 @@ - (nonnull SNTCachedDecision *)decisionForFileInfo:(nonnull SNTFileInfo *)fileIn
}
}

static NSString *const kCDHashFormatString = @"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x"
"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x";

const uint8_t *buf = targetProc->cdhash;
cdhash = [[NSString alloc] initWithFormat:kCDHashFormatString, buf[0], buf[1], buf[2], buf[3],
buf[4], buf[5], buf[6], buf[7], buf[8], buf[9],
buf[10], buf[11], buf[12], buf[13], buf[14], buf[15],
buf[16], buf[17], buf[18], buf[19]];
// Only consider the CDHash for processes that have CS_KILL or CS_HARD set.
// This ensures that the OS will kill the process if the CDHash was tampered
// with and code was loaded that didn't match a page hash.
if (targetProc->codesigning_flags & CS_KILL || targetProc->codesigning_flags & CS_HARD) {
static NSString *const kCDHashFormatString = @"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x"
"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x";

const uint8_t *buf = targetProc->cdhash;
cdhash = [[NSString alloc] initWithFormat:kCDHashFormatString, buf[0], buf[1], buf[2], buf[3],
buf[4], buf[5], buf[6], buf[7], buf[8], buf[9],
buf[10], buf[11], buf[12], buf[13], buf[14],
buf[15], buf[16], buf[17], buf[18], buf[19]];
}
}

return [self decisionForFileInfo:fileInfo
Expand Down
2 changes: 1 addition & 1 deletion Source/santad/SantadTest.mm
Expand Up @@ -157,7 +157,7 @@ - (BOOL)checkBinaryExecution:(NSString *)binaryName
es_file_t file = MakeESFile([binaryPath UTF8String], fileStat);
es_process_t proc = MakeESProcess(&file);
proc.is_platform_binary = false;
proc.codesigning_flags = CS_SIGNED | CS_VALID;
proc.codesigning_flags = CS_SIGNED | CS_VALID | CS_HARD | CS_KILL;

// Set a 6.5 second deadline for the message and clamp deadline headroom to 5
// seconds. This means there is a 1.5 second leeway given for the processing block
Expand Down
1 change: 1 addition & 0 deletions docs/concepts/events.md
Expand Up @@ -73,6 +73,7 @@ JSON blob. Here is an example of Firefox being blocked and sent for upload:
],
"team_id": "43AQ936H96",
"signing_id": "org.mozilla.firefox",
"cdhash": "ac14c49901a9cd05ff7bceea122f534d3c6c6ab7",
"file_bundle_name": "Firefox",
"executing_user": "bur",
"ppid": 1,
Expand Down
16 changes: 13 additions & 3 deletions docs/concepts/rules.md
@@ -1,3 +1,4 @@

---
parent: Concepts
---
Expand All @@ -9,11 +10,20 @@ parent: Concepts
Rules provide the primary evaluation mechanism for allowing and blocking
binaries with Santa on macOS.

### CDHash Rules

CDHash rules use the a binary's code directory hash as an identifier. This is
the most specific rule in Santa. The code directory hash identifies a specific
version of a program, similar to a file hash. Note that the operating system
evaluates the cdhash lazily, only verifying pages of code when they're mapped
in. This means that it is possible for a file hash to change, but a binary could
still execute as long as modified pages are not mapped in. Santa only considers
CDHash rules for processes that have `CS_KILL` or `CS_HARD` codesigning flags
set to ensure that a process will be killed if the CDHash was tampered with.

### Binary Rules

Binary rules use the SHA-256 hash of the entire binary as an identifier. This is
the most specific rule in Santa. Even a small change in the binary will alter
the SHA-256 hash, invalidating the rule.
Binary rules use the SHA-256 hash of the entire binary file as an identifier.

### Signing ID Rules

Expand Down
14 changes: 10 additions & 4 deletions docs/development/sync-protocol.md
Expand Up @@ -97,6 +97,8 @@ The request consists of the following JSON keys:
| compiler_rule_count | NO | int | Number of compiler rules the client has time of sync |
| transitive_rule_count | NO | int | Number of transitive rules the client has at the time of sync |
| teamid_rule_count | NO | int | Number of TeamID rules the client has at the time of sync | 24 |
| signingid_rule_count | NO | int | Number of SigningID rules the client has at the time of sync | 11 |
| cdhash_rule_count | NO | int | Number of CDHash rules the client has at the time of sync | 22 |
| client_mode | YES | string | The mode the client is operating in, either "LOCKDOWN" or "MONITOR" | LOCKDOWN |
| request_clean_sync | NO | bool | The client has requested a clean sync of its rules from the server | true |

Expand All @@ -114,6 +116,8 @@ The request consists of the following JSON keys:
"primary_user" : "markowsky",
"certificate_rule_count" : 2364,
"teamid_rule_count" : 0,
"signingid_rule_count" : 12,
"cdhash_rule_count" : 34,
"os_build" : "21F5048e",
"transitive_rule_count" : 0,
"os_version" : "12.4",
Expand Down Expand Up @@ -208,7 +212,7 @@ sequenceDiagram
| execution_time | NO | float64 | Unix timestamp of when the execution occurred | 23344234232 |
| loggedin_users | NO | list of strings | List of usernames logged in according to utmp | ["markowsky"] |
| current_sessions | NO | list of strings | List of user sessions | ["markowsky@console", "markowsky@ttys000"] |
| decision | YES | string | The decision Santa made for this binary, BUNDLE_BINARY is used to preemptively report binaries in a bundle. **Must be one of the examples** | "ALLOW_BINARY", "ALLOW_CERTIFICATE", "ALLOW_SCOPE", "ALLOW_TEAMID", "ALLOW_UNKNOWN", "BLOCK_BINARY", "BLOCK_CERTIFICATE", "BLOCK_SCOPE", "BLOCK_TEAMID", "BLOCK_UNKNOWN", "BUNDLE_BINARY" |
| decision | YES | string | The decision Santa made for this binary, BUNDLE_BINARY is used to preemptively report binaries in a bundle. **Must be one of the examples** | "ALLOW_BINARY", "ALLOW_CERTIFICATE", "ALLOW_SCOPE", "ALLOW_TEAMID", "ALLOW_SIGNINGID", "ALLOW_CDHASH" "ALLOW_UNKNOWN", "BLOCK_BINARY", "BLOCK_CERTIFICATE", "BLOCK_SCOPE", "BLOCK_TEAMID", "BLOCK_SIGNINGID", "BLOCK_CDHASH", "BLOCK_UNKNOWN", "BUNDLE_BINARY" |
| file_bundle_id | NO | string | The executable's containing bundle's identifier as specified in the Info.plist | "com.apple.safari" |
| file_bundle_path | NO | string | The path that the bundle resids in | /Applications/Santa.app |
| file_bundle_executable_rel_path | NO | string | The relative path of the binary within the Bundle | "Contents/MacOS/AppName" |
Expand All @@ -228,6 +232,7 @@ sequenceDiagram
| signing_chain | NO | list of signing chain objects | Certs used to code sign the executable | See next section |
| signing_id | NO | string | Signing ID of the binary that was executed | "EQHXZ8M8AV:com.google.Chrome" |
| team_id | NO | string | Team ID of the binary that was executed | "EQHXZ8M8AV" |
| cdhash | NO | string | CDHash of the binary that was executed | "dbe8c39801f93e05fc7bc53a02af5b4d3cfc670a" |

#### Signing Chain Objects

Expand Down Expand Up @@ -296,7 +301,8 @@ sequenceDiagram
"markowsky@ttys003"
],
"team_id": "EQHXZ8M8AV",
"signing_id": "EQHXZ8M8AV:com.google.santa"
"signing_id": "EQHXZ8M8AV:com.google.santa",
"cdhash": "dbe8c39801f93e05fc7bc53a02af5b4d3cfc670a"
}]
}
```
Expand Down Expand Up @@ -380,9 +386,9 @@ downloading if the rules need to be downloaded in multiple batches.

| Key | Required | Type | Meaning | Example Value |
|---|---|---|---|---|
| identifier | YES | string | The attribute of the binary the rule should match on e.g. the team ID of a binary or sha256 hash value | "ff2a7daa4c25cbd5b057e4471c6a22aba7d154dadfb5cce139c37cf795f41c9c" |
| identifier | YES | string | The attribute of the binary the rule should match on e.g. the signing ID, team ID, or CDHash of a binary or sha256 hash value | "ff2a7daa4c25cbd5b057e4471c6a22aba7d154dadfb5cce139c37cf795f41c9c" |
| policy | YES | string | Identifies the action to perform in response to the rule matching (must be one of the examples) | "ALLOWLIST","ALLOWLIST_COMPILER", "BLOCKLIST", "REMOVE", "SILENT_BLOCKLIST" |
| rule\_type | YES | string | Identifies the type of rule (must be one of the examples) | "BINARY", "CERTIFICATE", "SIGNINGID", "TEAMID" |
| rule\_type | YES | string | Identifies the type of rule (must be one of the examples) | "BINARY", "CERTIFICATE", "SIGNINGID", "TEAMID", "CDHASH" |
| custom\_msg | NO | string | A custom message to display when the rule matches | "Hello" |
| custom\_url | NO | string | A custom URL to use for the open button when the rule matches | http://lmgtfy.app/?q=dont+download+malware |
| creation\_time | NO | float64 | Time the rule was created | 1573543803.349378 |
Expand Down
2 changes: 1 addition & 1 deletion docs/index.md
Expand Up @@ -15,7 +15,7 @@ The project and the latest release is available on [**GitHub**](https://github.c

* [**Multiple modes:**](concepts/mode.md) In the default `MONITOR` mode, all binaries except those marked as blocked will be allowed to run, whilst being logged and recorded in the events database. In `LOCKDOWN` mode, only listed binaries are allowed to run.
* [**Event logging:**](concepts/events.md) All binary launches are logged. When in either mode, all unknown or denied binaries are stored in the database to enable later aggregation.
* [**Several supported rule types:**](concepts/rules.md) Executions can be allowed or denied by specifying rules based on several attributes. The supported rule types, in order of highest to lowest precedence are: binary hash, Signing ID, certificate hash, or Team ID. Since multiple rules can apply to a given binary, Santa will apply the rule with the highest precedence (i.e. you could use a Team ID rule to allow all binaries from some organization, but also add a Signing ID rule to deny a specific binary). Rules based on code signature properties (Signing ID, certificate hash, and Team ID) only apply if a bianry's signature validates correctly.
* [**Several supported rule types:**](concepts/rules.md) Executions can be allowed or denied by specifying rules based on several attributes. The supported rule types, in order of highest to lowest precedence are: CDHash, binary hash, Signing ID, certificate hash, or Team ID. Since multiple rules can apply to a given binary, Santa will apply the rule with the highest precedence (i.e. you could use a Team ID rule to allow all binaries from some organization, but also add a Signing ID rule to deny a specific binary). Rules based on code signature properties (Signing ID, certificate hash, and Team ID) only apply if a bianry's signature validates correctly.
* **Path-based rules (via NSRegularExpression/ICU):** Binaries can be allowed/blocked based on the path they are launched from by matching against a configurable regex.
* [**Failsafe cert rules:**](concepts/rules.md#built-in-rules) You cannot put in a deny rule that would block the certificate used to sign launchd, a.k.a. pid 1, and therefore all components used in macOS. The binaries in every OS update (and in some cases entire new versions) are therefore automatically allowed. This does not affect binaries from Apple's App Store, which use various certs that change regularly for common apps. Likewise, you cannot block Santa itself.
* [**Components validate each other:**](binaries/index.md) Each of the components (the daemons, the GUI agent, and the command-line utility) communicate with each other using XPC and check that their signing certificates are identical before any communication is accepted.
Expand Down

0 comments on commit 106c788

Please sign in to comment.