From d8a35521e8d2c4a33154105deb2c06d405edcd24 Mon Sep 17 00:00:00 2001 From: Matt White <436037+mlw@users.noreply.github.com> Date: Mon, 4 Dec 2023 16:29:51 -0500 Subject: [PATCH 1/3] Add some scoped types to handle automatic releasing --- Source/common/BUILD | 52 +++++++ Source/common/ScopedCFTypeRef.h | 29 ++++ Source/common/ScopedCFTypeRefTest.mm | 141 ++++++++++++++++++ Source/common/ScopedIOObjectRef.h | 30 ++++ Source/common/ScopedIOObjectRefTest.mm | 104 +++++++++++++ Source/common/ScopedTypeRef.h | 80 ++++++++++ Source/santad/BUILD | 4 +- .../EndpointSecurity/Serializers/Utilities.h | 2 + .../EndpointSecurity/Serializers/Utilities.mm | 2 +- Source/santad/SNTCompilerController.mm | 3 +- 10 files changed, 444 insertions(+), 3 deletions(-) create mode 100644 Source/common/ScopedCFTypeRef.h create mode 100644 Source/common/ScopedCFTypeRefTest.mm create mode 100644 Source/common/ScopedIOObjectRef.h create mode 100644 Source/common/ScopedIOObjectRefTest.mm create mode 100644 Source/common/ScopedTypeRef.h diff --git a/Source/common/BUILD b/Source/common/BUILD index d741e502c..1d2dd42d9 100644 --- a/Source/common/BUILD +++ b/Source/common/BUILD @@ -60,6 +60,56 @@ santa_unit_test( ], ) +# This target shouldn't be used directly. +# Use a more specific scoped type instead. +objc_library( + name = "ScopedTypeRef", + hdrs = ["ScopedTypeRef.h"], + visibility = ["//Source/common:__pkg__"], +) + +objc_library( + name = "ScopedCFTypeRef", + hdrs = ["ScopedCFTypeRef.h"], + deps = [ + ":ScopedTypeRef", + ], +) + +santa_unit_test( + name = "ScopedCFTypeRefTest", + srcs = ["ScopedCFTypeRefTest.mm"], + sdk_frameworks = [ + "Security", + ], + deps = [ + ":ScopedCFTypeRef", + ], +) + +objc_library( + name = "ScopedIOObjectRef", + hdrs = ["ScopedIOObjectRef.h"], + sdk_frameworks = [ + "IOKit", + ], + deps = [ + ":ScopedTypeRef", + ], +) + +santa_unit_test( + name = "ScopedIOObjectRefTest", + srcs = ["ScopedIOObjectRefTest.mm"], + sdk_frameworks = [ + "IOKit", + ], + deps = [ + ":ScopedIOObjectRef", + "//Source/santad:EndpointSecuritySerializerUtilities", + ], +) + objc_library( name = "BranchPrediction", hdrs = ["BranchPrediction.h"], @@ -438,6 +488,8 @@ test_suite( ":SNTMetricSetTest", ":SNTRuleTest", ":SantaCacheTest", + ":ScopedCFTypeRefTest", + ":ScopedIOObjectRefTest", ], visibility = ["//:santa_package_group"], ) diff --git a/Source/common/ScopedCFTypeRef.h b/Source/common/ScopedCFTypeRef.h new file mode 100644 index 000000000..86213828d --- /dev/null +++ b/Source/common/ScopedCFTypeRef.h @@ -0,0 +1,29 @@ +/// 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. + +#ifndef SANTA__COMMON__SCOPEDCFTYPEREF_H +#define SANTA__COMMON__SCOPEDCFTYPEREF_H + +#include + +#include "Source/common/ScopedTypeRef.h" + +namespace santa::common { + +template +using ScopedCFTypeRef = ScopedTypeRef; + +} // namespace santa::common + +#endif diff --git a/Source/common/ScopedCFTypeRefTest.mm b/Source/common/ScopedCFTypeRefTest.mm new file mode 100644 index 000000000..08782037d --- /dev/null +++ b/Source/common/ScopedCFTypeRefTest.mm @@ -0,0 +1,141 @@ +/// 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. + +#include +#include +#import +#include "XCTest/XCTest.h" + +#include "Source/common/ScopedCFTypeRef.h" + +using santa::common::ScopedCFTypeRef; + +@interface ScopedCFTypeRefTest : XCTestCase +@end + +@implementation ScopedCFTypeRefTest + +- (void)testDefaultConstruction { + // Default construction creates wraps a NULL object + ScopedCFTypeRef scopedRef; + XCTAssertFalse(scopedRef.Unsafe()); +} + +- (void)testOperatorBool { + // Operator bool is `false` when object is null + { + ScopedCFTypeRef scopedNullRef; + XCTAssertFalse(scopedNullRef.Unsafe()); + XCTAssertFalse(scopedNullRef); + } + + // Operator bool is `true` when object is NOT null + { + int x = 123; + CFNumberRef numRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &x); + + ScopedCFTypeRef scopedNumRef = ScopedCFTypeRef::Assume(numRef); + XCTAssertTrue(scopedNumRef.Unsafe()); + XCTAssertTrue(scopedNumRef); + } +} + +// Note that CFMutableArray is used for testing, even when subtypes aren't +// needed, because it is never optimized into immortal constant values, unlike +// other types. +- (void)testAssume { + int want = 123; + int got = 0; + CFMutableArrayRef array = CFArrayCreateMutable(nullptr, /*capacity=*/0, &kCFTypeArrayCallBacks); + + // Baseline state, initial retain count is 1 after object creation + XCTAssertEqual(1, CFGetRetainCount(array)); + + CFNumberRef numRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &want); + CFArrayAppendValue(array, numRef); + CFRelease(numRef); + + XCTAssertEqual(1, CFArrayGetCount(array)); + + { + ScopedCFTypeRef scopedArray = + ScopedCFTypeRef::Assume(array); + + // Ensure ownership was taken, and retain count remains unchanged + XCTAssertTrue(scopedArray.Unsafe()); + XCTAssertEqual(1, CFGetRetainCount(scopedArray.Unsafe())); + + // Make sure the object contains expected contents + CFMutableArrayRef ref = scopedArray.Unsafe(); + XCTAssertEqual(1, CFArrayGetCount(ref)); + XCTAssertTrue( + CFNumberGetValue((CFNumberRef)CFArrayGetValueAtIndex(ref, 0), kCFNumberIntType, &got)); + XCTAssertEqual(want, got); + } +} + +// Note that CFMutableArray is used for testing, even when subtypes aren't +// needed, because it is never optimized into immortal constant values, unlike +// other types. +- (void)testRetain { + int want = 123; + int got = 0; + CFMutableArrayRef array = CFArrayCreateMutable(nullptr, /*capacity=*/0, &kCFTypeArrayCallBacks); + + // Baseline state, initial retain count is 1 after object creation + XCTAssertEqual(1, CFGetRetainCount(array)); + + CFNumberRef numRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &want); + CFArrayAppendValue(array, numRef); + CFRelease(numRef); + + XCTAssertEqual(1, CFArrayGetCount(array)); + + { + ScopedCFTypeRef scopedArray = + ScopedCFTypeRef::Retain(array); + + // Ensure ownership was taken, and retain count was incremented + XCTAssertTrue(scopedArray.Unsafe()); + XCTAssertEqual(2, CFGetRetainCount(scopedArray.Unsafe())); + + // Make sure the object contains expected contents + CFMutableArrayRef ref = scopedArray.Unsafe(); + XCTAssertEqual(1, CFArrayGetCount(ref)); + XCTAssertTrue( + CFNumberGetValue((CFNumberRef)CFArrayGetValueAtIndex(ref, 0), kCFNumberIntType, &got)); + XCTAssertEqual(want, got); + } + + // The original `array` object should still be invalid due to the extra retain. + // Ensure the retain count has decreased since `scopedArray` went out of scope + XCTAssertEqual(1, CFArrayGetCount(array)); +} + +- (void)testInto { + ScopedCFTypeRef scopedURLRef = + ScopedCFTypeRef::Assume(CFURLCreateWithFileSystemPath( + kCFAllocatorDefault, CFSTR("/usr/bin/true"), kCFURLPOSIXPathStyle, YES)); + + ScopedCFTypeRef scopedCodeRef; + XCTAssertFalse(scopedCodeRef); + + SecStaticCodeCreateWithPath(scopedURLRef.Unsafe(), kSecCSDefaultFlags, + scopedCodeRef.InitializeInto()); + + // Ensure the scoped object was initialized + XCTAssertTrue(scopedCodeRef); +} + +@end diff --git a/Source/common/ScopedIOObjectRef.h b/Source/common/ScopedIOObjectRef.h new file mode 100644 index 000000000..a21e09e14 --- /dev/null +++ b/Source/common/ScopedIOObjectRef.h @@ -0,0 +1,30 @@ +/// 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. + +#ifndef SANTA__COMMON__SCOPEDIOOBJECTREF_H +#define SANTA__COMMON__SCOPEDIOOBJECTREF_H + +#include + +#include "Source/common/ScopedTypeRef.h" + +namespace santa::common { + +template +using ScopedIOObjectRef = + ScopedTypeRef; + +} + +#endif diff --git a/Source/common/ScopedIOObjectRefTest.mm b/Source/common/ScopedIOObjectRefTest.mm new file mode 100644 index 000000000..dd1b1330e --- /dev/null +++ b/Source/common/ScopedIOObjectRefTest.mm @@ -0,0 +1,104 @@ +/// 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. + +#include +#include +#include +#import + +#include "Source/common/ScopedIOObjectRef.h" +#include "Source/santad/Logs/EndpointSecurity/Serializers/Utilities.h" + +using santa::common::ScopedIOObjectRef; +using santa::santad::logs::endpoint_security::serializers::Utilities::GetDefaultIOKitCommsPort; + +@interface ScopedIOObjectRefTest : XCTestCase +@end + +@implementation ScopedIOObjectRefTest + +- (void)testDefaultConstruction { + // Default construction creates wraps a NULL object + ScopedIOObjectRef scopedRef; + XCTAssertFalse(scopedRef.Unsafe()); +} + +- (void)testOperatorBool { + // Operator bool is `false` when object is null + { + ScopedIOObjectRef scopedNullRef; + XCTAssertFalse(scopedNullRef.Unsafe()); + XCTAssertFalse(scopedNullRef); + } + + // Operator bool is `true` when object is NOT null + { + CFMutableDictionaryRef matchingDict = IOServiceMatching(kIOUSBDeviceClassName); + XCTAssertNotEqual((CFMutableDictionaryRef)NULL, matchingDict); + + io_service_t service = IOServiceGetMatchingService(GetDefaultIOKitCommsPort(), matchingDict); + + ScopedIOObjectRef scopedServiceRef = + ScopedIOObjectRef::Assume(service); + + XCTAssertTrue(scopedServiceRef.Unsafe()); + XCTAssertTrue(scopedServiceRef); + } +} + +- (void)testAssume { + CFMutableDictionaryRef matchingDict = IOServiceMatching(kIOUSBDeviceClassName); + XCTAssertNotEqual((CFMutableDictionaryRef)NULL, matchingDict); + + io_service_t service = IOServiceGetMatchingService(GetDefaultIOKitCommsPort(), matchingDict); + + // Baseline state, initial retain count is 1 after object creation + XCTAssertEqual(1, IOObjectGetUserRetainCount(service)); + XCTAssertNotEqual(IO_OBJECT_NULL, service); + + { + ScopedIOObjectRef scopedIORef = ScopedIOObjectRef::Assume(service); + + // Ensure ownership was taken, and retain count remains unchanged + XCTAssertTrue(scopedIORef.Unsafe()); + XCTAssertEqual(1, IOObjectGetUserRetainCount(scopedIORef.Unsafe())); + XCTAssertNotEqual(IO_OBJECT_NULL, scopedIORef.Unsafe()); + } +} + +- (void)testRetain { + CFMutableDictionaryRef matchingDict = IOServiceMatching(kIOUSBDeviceClassName); + XCTAssertNotEqual((CFMutableDictionaryRef)NULL, matchingDict); + + io_service_t service = IOServiceGetMatchingService(GetDefaultIOKitCommsPort(), matchingDict); + + // Baseline state, initial retain count is 1 after object creation + XCTAssertEqual(1, IOObjectGetUserRetainCount(service)); + XCTAssertNotEqual(IO_OBJECT_NULL, service); + + { + ScopedIOObjectRef scopedIORef = ScopedIOObjectRef::Retain(service); + + // Ensure ownership was taken, and retain count was incremented + XCTAssertTrue(scopedIORef.Unsafe()); + XCTAssertEqual(2, IOObjectGetUserRetainCount(scopedIORef.Unsafe())); + XCTAssertNotEqual(IO_OBJECT_NULL, scopedIORef.Unsafe()); + } + + // The original `service` object should still be invalid due to the extra retain. + // Ensure the retain count has decreased since `scopedIORef` went out of scope. + XCTAssertEqual(1, IOObjectGetUserRetainCount(service)); +} + +@end diff --git a/Source/common/ScopedTypeRef.h b/Source/common/ScopedTypeRef.h new file mode 100644 index 000000000..b50262bf7 --- /dev/null +++ b/Source/common/ScopedTypeRef.h @@ -0,0 +1,80 @@ +/// 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. + +#ifndef SANTA__COMMON__SCOPEDTYPEREF_H +#define SANTA__COMMON__SCOPEDTYPEREF_H + +#include +#include + +namespace santa::common { + +template +class ScopedTypeRef { + public: + // Take ownership of a given object + static ScopedTypeRef Assume( + ElementT object) { + return ScopedTypeRef(object); + } + + // Retain and take ownership of a given object + static ScopedTypeRef Retain( + ElementT object) { + if (object) { + RetainFunc(object); + } + return ScopedTypeRef(object); + } + + ~ScopedTypeRef() { + if (object_) { + ReleaseFunc(object_); + object_ = InvalidV; + } + } + + explicit operator bool() { return object_ != InvalidV; } + + ElementT Unsafe() { return object_; } + + // This is to be used only to take ownership of objects that are created by + // pass-by-pointer create functions. The object must not already be valid. + // In non-opt builds, this is enforced by an assert that will terminate the + // process. + ElementT* InitializeInto() { + assert(object_ == InvalidV); + return &object_; + } + + ScopedTypeRef() : object_(InvalidV) {} + + // Can be implemented safely, but not currently needed + ScopedTypeRef(ScopedTypeRef&& other) = delete; + ScopedTypeRef& operator=(ScopedTypeRef&& rhs) = delete; + ScopedTypeRef(const ScopedTypeRef& other) = delete; + ScopedTypeRef& operator=(const ScopedTypeRef& other) = delete; + + private: + // Not API. + // Use Assume or Retain static methods. + ScopedTypeRef(ElementT object) : object_(object) {} + + ElementT object_; +}; + +} // namespace santa::common + +#endif diff --git a/Source/santad/BUILD b/Source/santad/BUILD index 79d64b271..db9402162 100644 --- a/Source/santad/BUILD +++ b/Source/santad/BUILD @@ -21,6 +21,9 @@ objc_library( name = "SNTRuleTable", srcs = ["DataLayer/SNTRuleTable.m"], hdrs = ["DataLayer/SNTRuleTable.h"], + sdk_dylibs = [ + "EndpointSecurity", + ], deps = [ ":SNTDatabaseTable", "//Source/common:Platform", @@ -475,7 +478,6 @@ objc_library( "bsm", ], deps = [ - ":EndpointSecurityEnrichedTypes", ":EndpointSecurityMessage", ":SNTDecisionCache", "//Source/common:SantaCache", diff --git a/Source/santad/Logs/EndpointSecurity/Serializers/Utilities.h b/Source/santad/Logs/EndpointSecurity/Serializers/Utilities.h index ada70eb84..3dab190df 100644 --- a/Source/santad/Logs/EndpointSecurity/Serializers/Utilities.h +++ b/Source/santad/Logs/EndpointSecurity/Serializers/Utilities.h @@ -59,6 +59,8 @@ NSString *MountFromName(NSString *path); es_file_t *GetAllowListTargetFile( const santa::santad::event_providers::endpoint_security::Message &msg); +const mach_port_t GetDefaultIOKitCommsPort(); + } // namespace santa::santad::logs::endpoint_security::serializers::Utilities #endif diff --git a/Source/santad/Logs/EndpointSecurity/Serializers/Utilities.mm b/Source/santad/Logs/EndpointSecurity/Serializers/Utilities.mm index fdd0cd724..b2c65e0a4 100644 --- a/Source/santad/Logs/EndpointSecurity/Serializers/Utilities.mm +++ b/Source/santad/Logs/EndpointSecurity/Serializers/Utilities.mm @@ -80,7 +80,7 @@ static inline void SetThreadIDs(uid_t uid, gid_t gid) { return [origURL path]; } -static inline const mach_port_t GetDefaultIOKitCommsPort() { +const mach_port_t GetDefaultIOKitCommsPort() { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" return kIOMasterPortDefault; diff --git a/Source/santad/SNTCompilerController.mm b/Source/santad/SNTCompilerController.mm index 376629d70..e6ac90c6e 100644 --- a/Source/santad/SNTCompilerController.mm +++ b/Source/santad/SNTCompilerController.mm @@ -132,7 +132,8 @@ - (void)createTransitiveRule:(const Message &)esMsg NSError *error = nil; SNTFileInfo *fi = [[SNTFileInfo alloc] initWithEndpointSecurityFile:targetFile error:&error]; if (error) { - LOGD(@"Unable to create SNTFileInfo while attempting to create transitive rule. Event: %d | Path: %@ | Error: %@", + LOGD(@"Unable to create SNTFileInfo while attempting to create transitive rule. Event: %d | " + @"Path: %@ | Error: %@", (int)esMsg->event_type, @(targetFile->path.data), error); return; } From c14618c93e34be7a623451c03876fd25c2ae8bb7 Mon Sep 17 00:00:00 2001 From: Matt White <436037+mlw@users.noreply.github.com> Date: Mon, 4 Dec 2023 16:41:53 -0500 Subject: [PATCH 2/3] style --- Source/common/ScopedTypeRef.h | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Source/common/ScopedTypeRef.h b/Source/common/ScopedTypeRef.h index b50262bf7..be8e12bae 100644 --- a/Source/common/ScopedTypeRef.h +++ b/Source/common/ScopedTypeRef.h @@ -24,6 +24,14 @@ template class ScopedTypeRef { public: + ScopedTypeRef() : object_(InvalidV) {} + + // Can be implemented safely, but not currently needed + ScopedTypeRef(ScopedTypeRef&& other) = delete; + ScopedTypeRef& operator=(ScopedTypeRef&& rhs) = delete; + ScopedTypeRef(const ScopedTypeRef& other) = delete; + ScopedTypeRef& operator=(const ScopedTypeRef& other) = delete; + // Take ownership of a given object static ScopedTypeRef Assume( ElementT object) { @@ -59,14 +67,6 @@ class ScopedTypeRef { return &object_; } - ScopedTypeRef() : object_(InvalidV) {} - - // Can be implemented safely, but not currently needed - ScopedTypeRef(ScopedTypeRef&& other) = delete; - ScopedTypeRef& operator=(ScopedTypeRef&& rhs) = delete; - ScopedTypeRef(const ScopedTypeRef& other) = delete; - ScopedTypeRef& operator=(const ScopedTypeRef& other) = delete; - private: // Not API. // Use Assume or Retain static methods. From 3529f2c52eb23e861cc338ee7543b83614dd4862 Mon Sep 17 00:00:00 2001 From: Matt White <436037+mlw@users.noreply.github.com> Date: Tue, 5 Dec 2023 09:28:46 -0500 Subject: [PATCH 3/3] comment typo --- Source/common/ScopedCFTypeRefTest.mm | 2 +- Source/common/ScopedIOObjectRefTest.mm | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/common/ScopedCFTypeRefTest.mm b/Source/common/ScopedCFTypeRefTest.mm index 08782037d..f82b3b793 100644 --- a/Source/common/ScopedCFTypeRefTest.mm +++ b/Source/common/ScopedCFTypeRefTest.mm @@ -118,7 +118,7 @@ - (void)testRetain { XCTAssertEqual(want, got); } - // The original `array` object should still be invalid due to the extra retain. + // The original `array` object should still be valid due to the extra retain. // Ensure the retain count has decreased since `scopedArray` went out of scope XCTAssertEqual(1, CFArrayGetCount(array)); } diff --git a/Source/common/ScopedIOObjectRefTest.mm b/Source/common/ScopedIOObjectRefTest.mm index dd1b1330e..52ba946cd 100644 --- a/Source/common/ScopedIOObjectRefTest.mm +++ b/Source/common/ScopedIOObjectRefTest.mm @@ -96,7 +96,7 @@ - (void)testRetain { XCTAssertNotEqual(IO_OBJECT_NULL, scopedIORef.Unsafe()); } - // The original `service` object should still be invalid due to the extra retain. + // The original `service` object should still be valid due to the extra retain. // Ensure the retain count has decreased since `scopedIORef` went out of scope. XCTAssertEqual(1, IOObjectGetUserRetainCount(service)); }