Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for logging entitlements in EXEC events #1225

Merged
merged 2 commits into from
Nov 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions Source/common/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,12 @@ objc_library(
],
)

objc_library(
name = "SNTDeepCopy",
srcs = ["SNTDeepCopy.m"],
hdrs = ["SNTDeepCopy.h"],
)

cc_library(
name = "SantaCache",
hdrs = ["SantaCache.h"],
Expand Down
1 change: 1 addition & 0 deletions Source/common/SNTCachedDecision.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
@property NSArray<MOLCertificate *> *certChain;
@property NSString *teamID;
@property NSString *signingID;
@property NSDictionary *entitlements;

@property NSString *quarantineURL;

Expand Down
27 changes: 27 additions & 0 deletions Source/common/SNTDeepCopy.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/// Copyright 2023 Google LLC
///
/// 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
///
/// https://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 <Foundation/Foundation.h>

@interface NSArray (SNTDeepCopy)

- (instancetype)sntDeepCopy;

@end

@interface NSDictionary (SNTDeepCopy)

- (instancetype)sntDeepCopy;

@end
53 changes: 53 additions & 0 deletions Source/common/SNTDeepCopy.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/// Copyright 2023 Google LLC
///
/// 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
///
/// https://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 "Source/common/SNTDeepCopy.h"

@implementation NSArray (SNTDeepCopy)

- (instancetype)sntDeepCopy {
NSMutableArray<__kindof NSObject *> *deepCopy = [NSMutableArray arrayWithCapacity:self.count];
for (id object in self) {
if ([object respondsToSelector:@selector(sntDeepCopy)]) {
[deepCopy addObject:[object sntDeepCopy]];
} else if ([object respondsToSelector:@selector(copyWithZone:)]) {
[deepCopy addObject:[object copy]];
} else {
[deepCopy addObject:object];
}
}
return deepCopy;
}

@end

@implementation NSDictionary (SNTDeepCopy)

- (instancetype)sntDeepCopy {
NSMutableDictionary<__kindof NSObject *, __kindof NSObject *> *deepCopy =
[NSMutableDictionary dictionary];
for (id key in self) {
id value = self[key];
if ([value respondsToSelector:@selector(sntDeepCopy)]) {
deepCopy[key] = [value sntDeepCopy];
} else if ([value respondsToSelector:@selector(copyWithZone:)]) {
deepCopy[key] = [value copy];
} else {
deepCopy[key] = value;
}
}
return deepCopy;
}

@end
6 changes: 5 additions & 1 deletion Source/common/TestUtils.mm
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,11 @@ es_process_t MakeESProcess(es_file_t *file, audit_token_t tok, audit_token_t par
}

uint32_t MaxSupportedESMessageVersionForCurrentOS() {
// Note: ES message v3 was only in betas.
// Notes:
// 1. ES message v3 was only in betas.
// 2. Message version 7 appeared in macOS 13.3, but features from that are
// not currently used. Leaving off support here so as to not require
// adding v7 test JSON files.
if (@available(macOS 13.0, *)) {
return 6;
} else if (@available(macOS 12.3, *)) {
Expand Down
10 changes: 10 additions & 0 deletions Source/common/santa.proto
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,11 @@ message CertificateInfo {
optional string common_name = 2;
}

message Entitlement {
mlw marked this conversation as resolved.
Show resolved Hide resolved
string key = 1;
string value = 2;
}

// Information about a process execution event
message Execution {
// The process that executed the new image (e.g. the process that called
Expand Down Expand Up @@ -286,6 +291,11 @@ message Execution {
// The original path on disk of the target executable
// Applies when executables are translocated
optional string original_path = 15;

// The set of entitlements associated with the target executable
// Only top level keys are represented
// Values (including nested keys) are JSON serialized
repeated Entitlement entitlements = 16;
}

// Information about a fork event
Expand Down
1 change: 1 addition & 0 deletions Source/santad/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,7 @@ objc_library(
"//Source/common:SNTCachedDecision",
"//Source/common:SNTCommonEnums",
"//Source/common:SNTConfigurator",
"//Source/common:SNTDeepCopy",
"//Source/common:SNTFileInfo",
"//Source/common:SNTLogging",
"//Source/common:SNTRule",
Expand Down
114 changes: 114 additions & 0 deletions Source/santad/Logs/EndpointSecurity/Serializers/Protobuf.mm
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,9 @@

namespace santa::santad::logs::endpoint_security::serializers {

static constexpr NSUInteger kMaxEncodeObjectEntries = 64;
static constexpr NSUInteger kMaxEncodeObjectLevels = 5;

std::shared_ptr<Protobuf> Protobuf::Create(std::shared_ptr<EndpointSecurityAPI> esapi,
SNTDecisionCache *decision_cache, bool json) {
return std::make_shared<Protobuf>(esapi, std::move(decision_cache), json);
Expand Down Expand Up @@ -449,6 +452,115 @@ static inline void EncodeCertificateInfo(::pbv1::CertificateInfo *pb_cert_info,
return FinalizeProto(santa_msg);
}

id StandardizedNestedObjects(id obj, int level) {
if (level-- == 0) {
return [obj description];
}

if ([obj isKindOfClass:[NSNumber class]] || [obj isKindOfClass:[NSString class]]) {
return obj;
} else if ([obj isKindOfClass:[NSArray class]]) {
NSMutableArray *arr = [NSMutableArray array];
for (id item in obj) {
[arr addObject:StandardizedNestedObjects(item, level)];
}
return arr;
} else if ([obj isKindOfClass:[NSDictionary class]]) {
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
for (id key in obj) {
[dict setObject:StandardizedNestedObjects(obj[key], level) forKey:key];
}
return dict;
} else if ([obj isKindOfClass:[NSData class]]) {
return [obj base64EncodedStringWithOptions:0];
} else if ([obj isKindOfClass:[NSDate class]]) {
return [NSISO8601DateFormatter stringFromDate:obj
timeZone:[NSTimeZone timeZoneWithAbbreviation:@"UTC"]
formatOptions:NSISO8601DateFormatWithFractionalSeconds | NSISO8601DateFormatWithInternetDateTime];

} else {
NSLog(@"Got unknown... %d", level);
LOGW(@"Unexpected object encountered: %@", obj);
return [obj description];
}
}

void EncodeEntitlements(::pbv1::Execution *pb_exec, NSDictionary *entitlements) {
if (!entitlements) {
return;
}

// Since nested objects with varying types is hard for the API to serialize to
// JSON, first go through and standardize types to ensure better serialization
// as well as a consitent view of data.
entitlements = StandardizedNestedObjects(entitlements, kMaxEncodeObjectLevels);

__block int numObjectsToEncode = (int)std::min(kMaxEncodeObjectEntries, entitlements.count);

pb_exec->mutable_entitlements()->Reserve(numObjectsToEncode);

[entitlements enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
if (numObjectsToEncode-- == 0) {
*stop = YES;
return;
}

if (![key isKindOfClass:[NSString class]]) {
LOGW(@"Skipping entitlement key with unexpected key type: %@", key);
return;
}

NSError *err;
NSData *jsonData;
@try {
jsonData = [NSJSONSerialization dataWithJSONObject:obj
options:NSJSONWritingFragmentsAllowed
error:&err];
} @catch (NSException *e) {
LOGW(@"Encountered entitlement that cannot directly convert to JSON: %@: %@", key, obj);
}

if (!jsonData) {
// If the first attempt to serialize to JSON failed, get a string
// representation of the object via the `description` method and attempt
// to serialize that instead. Serialization can fail for a number of
// reasons, such as arrays including invalid types.
@try {
jsonData = [NSJSONSerialization dataWithJSONObject:[obj description]
options:NSJSONWritingFragmentsAllowed
error:&err];
} @catch (NSException *e) {
LOGW(@"Unable to create fallback string: %@: %@", key, obj);
}

if (!jsonData) {
@try {
// As a final fallback, simply serialize an error message so that the
// entitlement key is still logged.
jsonData = [NSJSONSerialization dataWithJSONObject:@"JSON Serialization Failed"
options:NSJSONWritingFragmentsAllowed
error:&err];
} @catch (NSException *e) {
kallsyms marked this conversation as resolved.
Show resolved Hide resolved
// This shouldn't be able to happen...
LOGW(@"Failed to serialize fallback error message");
}
}
}

// This shouldn't be possible given the fallback code above. But handle it
// just in case to prevent a crash.
if (!jsonData) {
LOGW(@"Failed to create valid JSON for entitlement: %@", key);
return;
}

::pbv1::Entitlement *pb_entitlement = pb_exec->add_entitlements();
pb_entitlement->set_key(NSStringToUTF8StringView(key));
pb_entitlement->set_value(NSStringToUTF8StringView(
[[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]));
}];
}

std::vector<uint8_t> Protobuf::SerializeMessage(const EnrichedExec &msg, SNTCachedDecision *cd) {
Arena arena;
::pbv1::SantaMessage *santa_msg = CreateDefaultProto(&arena, msg);
Expand Down Expand Up @@ -525,6 +637,8 @@ static inline void EncodeCertificateInfo(::pbv1::CertificateInfo *pb_cert_info,
NSString *orig_path = Utilities::OriginalPathForTranslocation(msg.es_msg().event.exec.target);
EncodeString([pb_exec] { return pb_exec->mutable_original_path(); }, orig_path);

EncodeEntitlements(pb_exec, cd.entitlements);

return FinalizeProto(santa_msg);
}

Expand Down
Loading