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

Commit

Permalink
Initial support for some scoped types (#1250)
Browse files Browse the repository at this point in the history
* Add some scoped types to handle automatic releasing

* style

* comment typo
  • Loading branch information
mlw authored Dec 5, 2023
1 parent 2cbf155 commit e2e83a0
Show file tree
Hide file tree
Showing 10 changed files with 444 additions and 3 deletions.
52 changes: 52 additions & 0 deletions Source/common/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -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"],
Expand Down Expand Up @@ -438,6 +488,8 @@ test_suite(
":SNTMetricSetTest",
":SNTRuleTest",
":SantaCacheTest",
":ScopedCFTypeRefTest",
":ScopedIOObjectRefTest",
],
visibility = ["//:santa_package_group"],
)
Expand Down
29 changes: 29 additions & 0 deletions Source/common/ScopedCFTypeRef.h
Original file line number Diff line number Diff line change
@@ -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 <CoreFoundation/CoreFoundation.h>

#include "Source/common/ScopedTypeRef.h"

namespace santa::common {

template <typename CFT>
using ScopedCFTypeRef = ScopedTypeRef<CFT, (CFT)NULL, CFRetain, CFRelease>;

} // namespace santa::common

#endif
141 changes: 141 additions & 0 deletions Source/common/ScopedCFTypeRefTest.mm
Original file line number Diff line number Diff line change
@@ -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 <CoreFoundation/CoreFoundation.h>
#include <Security/Security.h>
#import <XCTest/XCTest.h>
#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<CFNumberRef> scopedRef;
XCTAssertFalse(scopedRef.Unsafe());
}

- (void)testOperatorBool {
// Operator bool is `false` when object is null
{
ScopedCFTypeRef<CFNumberRef> 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<CFNumberRef> scopedNumRef = ScopedCFTypeRef<CFNumberRef>::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<CFMutableArrayRef> scopedArray =
ScopedCFTypeRef<CFMutableArrayRef>::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<CFMutableArrayRef> scopedArray =
ScopedCFTypeRef<CFMutableArrayRef>::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 valid due to the extra retain.
// Ensure the retain count has decreased since `scopedArray` went out of scope
XCTAssertEqual(1, CFArrayGetCount(array));
}

- (void)testInto {
ScopedCFTypeRef<CFURLRef> scopedURLRef =
ScopedCFTypeRef<CFURLRef>::Assume(CFURLCreateWithFileSystemPath(
kCFAllocatorDefault, CFSTR("/usr/bin/true"), kCFURLPOSIXPathStyle, YES));

ScopedCFTypeRef<SecStaticCodeRef> scopedCodeRef;
XCTAssertFalse(scopedCodeRef);

SecStaticCodeCreateWithPath(scopedURLRef.Unsafe(), kSecCSDefaultFlags,
scopedCodeRef.InitializeInto());

// Ensure the scoped object was initialized
XCTAssertTrue(scopedCodeRef);
}

@end
30 changes: 30 additions & 0 deletions Source/common/ScopedIOObjectRef.h
Original file line number Diff line number Diff line change
@@ -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 <IOKit/IOKitLib.h>

#include "Source/common/ScopedTypeRef.h"

namespace santa::common {

template <typename IOT>
using ScopedIOObjectRef =
ScopedTypeRef<IOT, (IOT)IO_OBJECT_NULL, IOObjectRetain, IOObjectRelease>;

}

#endif
104 changes: 104 additions & 0 deletions Source/common/ScopedIOObjectRefTest.mm
Original file line number Diff line number Diff line change
@@ -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 <CoreFoundation/CoreFoundation.h>
#include <IOKit/IOKitLib.h>
#include <IOKit/usb/IOUSBLib.h>
#import <XCTest/XCTest.h>

#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<io_object_t> scopedRef;
XCTAssertFalse(scopedRef.Unsafe());
}

- (void)testOperatorBool {
// Operator bool is `false` when object is null
{
ScopedIOObjectRef<io_object_t> 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<io_service_t> scopedServiceRef =
ScopedIOObjectRef<io_service_t>::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<io_service_t> scopedIORef = ScopedIOObjectRef<io_service_t>::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<io_service_t> scopedIORef = ScopedIOObjectRef<io_service_t>::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 valid due to the extra retain.
// Ensure the retain count has decreased since `scopedIORef` went out of scope.
XCTAssertEqual(1, IOObjectGetUserRetainCount(service));
}

@end
Loading

0 comments on commit e2e83a0

Please sign in to comment.