diff --git a/Santa.xcodeproj/project.pbxproj b/Santa.xcodeproj/project.pbxproj index 0150abf40..87d245a7c 100644 --- a/Santa.xcodeproj/project.pbxproj +++ b/Santa.xcodeproj/project.pbxproj @@ -166,6 +166,7 @@ C7479F051E53704E0054C1CF /* SNTXPCBundleServiceInterface.m in Sources */ = {isa = PBXBuildFile; fileRef = C7C721B01E23FF300051FAA6 /* SNTXPCBundleServiceInterface.m */; }; C7479F071E5374BF0054C1CF /* SNTXPCControlInterface.m in Sources */ = {isa = PBXBuildFile; fileRef = 0DCD605419115D17006B445C /* SNTXPCControlInterface.m */; }; C7479F091E5374E50054C1CF /* SNTRule.m in Sources */ = {isa = PBXBuildFile; fileRef = 0DE50F671912716A007B2B0C /* SNTRule.m */; }; + C74D6CC61EEB3B9B00BB5A33 /* BundleExample.app in Resources */ = {isa = PBXBuildFile; fileRef = C74D6CC51EEB3B9B00BB5A33 /* BundleExample.app */; }; C76614EC1D142D3C00D150C1 /* SNTCommandCheckCache.m in Sources */ = {isa = PBXBuildFile; fileRef = C76614EB1D142D3C00D150C1 /* SNTCommandCheckCache.m */; }; C776A1071DEE160500A56616 /* SNTCommandSyncManager.m in Sources */ = {isa = PBXBuildFile; fileRef = C776A1061DEE160500A56616 /* SNTCommandSyncManager.m */; }; C78227631E1C3C7D006EB2D6 /* santabs.xpc in CopyFiles */ = {isa = PBXBuildFile; fileRef = C78227541E1C3C58006EB2D6 /* santabs.xpc */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; @@ -431,6 +432,7 @@ A6A91785C40257CC156B4F05 /* Pods-Santa.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Santa.release.xcconfig"; path = "Pods/Target Support Files/Pods-Santa/Pods-Santa.release.xcconfig"; sourceTree = ""; }; C11A10A5D6E112788769CF70 /* libPods-santad.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-santad.a"; sourceTree = BUILT_PRODUCTS_DIR; }; C72E8D931D7F399900C86DD3 /* SNTCommandFileInfoTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SNTCommandFileInfoTest.m; sourceTree = ""; }; + C74D6CC51EEB3B9B00BB5A33 /* BundleExample.app */ = {isa = PBXFileReference; lastKnownFileType = wrapper.application; path = BundleExample.app; sourceTree = ""; }; C76614EB1D142D3C00D150C1 /* SNTCommandCheckCache.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SNTCommandCheckCache.m; sourceTree = ""; }; C776A1051DEE160500A56616 /* SNTCommandSyncManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SNTCommandSyncManager.h; sourceTree = ""; }; C776A1061DEE160500A56616 /* SNTCommandSyncManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SNTCommandSyncManager.m; sourceTree = ""; }; @@ -543,6 +545,7 @@ 0D260DB018B68E12002A0B55 /* Resources */ = { isa = PBXGroup; children = ( + C74D6CC51EEB3B9B00BB5A33 /* BundleExample.app */, 0D536ED51B8E7A2E0039A26D /* bad_pagezero */, 0D2CD4601A81C7B100C9C910 /* dn.plist */, 0D536ED61B8E7A2E0039A26D /* missing_pagezero */, @@ -1082,6 +1085,7 @@ files = ( 0DEA5F761CF6482E00704398 /* sync_eventupload_input_basic.plist in Resources */, 0D536ED71B8E7A2E0039A26D /* bad_pagezero in Resources */, + C74D6CC61EEB3B9B00BB5A33 /* BundleExample.app in Resources */, 0DEA5F7A1CF64C9200704398 /* sync_ruledownload_batch1.json in Resources */, 0DEA5F741CF63B9600704398 /* sync_eventupload_input_quarantine.plist in Resources */, 0D2CD4611A81C7B100C9C910 /* dn.plist in Resources */, diff --git a/Source/common/SNTFileInfo.h b/Source/common/SNTFileInfo.h index 974f26fab..099895d68 100644 --- a/Source/common/SNTFileInfo.h +++ b/Source/common/SNTFileInfo.h @@ -116,6 +116,21 @@ /// - (BOOL)isMissingPageZero; +/// +/// If set to YES, the bundle* and infoPlist methods will search for and use the highest NSBundle +/// found in the tree. Defaults to NO, which uses the first found bundle, if any. +/// +/// @example: +/// An SNTFileInfo object that represents +/// /Applications/Photos.app/Contents/XPCServices/com.apple.Photos.librarychooserservice.xpc +/// useAncestorBundle is set to YES +/// /Applications/Photos.app will be used to get data backing all the bundle methods +/// +/// @note: The NSBundle object backing the bundle* and infoPlist methods is cached once found. +/// Setting the useAncestorBundle propery will clear this cache and force a re-search. +/// +@property(nonatomic) BOOL useAncestorBundle; + /// /// @return An NSBundle if this file is part of a bundle. /// diff --git a/Source/common/SNTFileInfo.m b/Source/common/SNTFileInfo.m index 56cc63123..2161f6c9c 100644 --- a/Source/common/SNTFileInfo.m +++ b/Source/common/SNTFileInfo.m @@ -285,23 +285,29 @@ - (BOOL)isMissingPageZero { /// /// This method walks up the path until a bundle is found, if any. /// +/// @param ancestor YES this will return the highest NSBundle found in the tree. No will return the +/// the lowest. +/// +-(NSBundle *)findBundleWithAncestor:(BOOL)ancestor { + NSBundle *bundle; + NSMutableArray *pathComponents = [[self.path pathComponents] mutableCopy]; + + // Ignore the root path "/", for some reason this is considered a bundle. + while (pathComponents.count > 1) { + NSBundle *bndl = [NSBundle bundleWithPath:[NSString pathWithComponents:pathComponents]]; + if (bndl && [bndl objectForInfoDictionaryKey:@"CFBundleIdentifier"]) { + bundle = bndl; + if (!ancestor) break; + } + [pathComponents removeLastObject]; + } + return bundle; +} + - (NSBundle *)bundle { if (!self.bundleRef) { - self.bundleRef = (NSBundle *)[NSNull null]; - - NSMutableArray *pathComponents = [[self.path pathComponents] mutableCopy]; - - // Ignore the checking the root path "/". For some reason this produces a valid bundle. - // Also only walk back a max 10 dirs. - for (short deep = 0; - pathComponents.count > 1 && deep < 10; - ++deep, [pathComponents removeLastObject]) { - NSBundle *bndl = [NSBundle bundleWithPath:[NSString pathWithComponents:pathComponents]]; - if (bndl && [bndl objectForInfoDictionaryKey:@"CFBundleIdentifier"]) { - self.bundleRef = bndl; - break; - } - } + self.bundleRef = + [self findBundleWithAncestor:self.useAncestorBundle] ?: (NSBundle *)[NSNull null]; } return self.bundleRef == (NSBundle *)[NSNull null] ? nil : self.bundleRef; } @@ -310,6 +316,14 @@ - (NSString *)bundlePath { return [self.bundle bundlePath]; } +- (void)setUseAncestorBundle:(BOOL)useAncestorBundle { + if (self.useAncestorBundle != useAncestorBundle) { + self.bundleRef = nil; + self.infoDict = nil; + } + _useAncestorBundle = useAncestorBundle; +} + - (NSDictionary *)infoPlist { if (!self.infoDict) { NSDictionary *d = [self embeddedPlist]; diff --git a/Source/santabs/SNTBundleService.m b/Source/santabs/SNTBundleService.m index 0445c7680..c04cab3ed 100644 --- a/Source/santabs/SNTBundleService.m +++ b/Source/santabs/SNTBundleService.m @@ -91,6 +91,16 @@ - (void)hashBundleBinariesForEvent:(SNTStoredEvent *)event dispatch_semaphore_t sema = dispatch_semaphore_create(0); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{ + // Use the highest bundle we can find. Save and reuse the bundle infomation when creating + // the related binary events. + SNTFileInfo *b = [[SNTFileInfo alloc] initWithPath:event.fileBundlePath]; + b.useAncestorBundle = YES; + event.fileBundlePath = b.bundlePath; + event.fileBundleID = b.bundleIdentifier; + event.fileBundleName = b.bundleName; + event.fileBundleVersion = b.bundleVersion; + event.fileBundleVersionString = b.bundleShortVersionString; + NSArray *relatedBinaries = [self findRelatedBinaries:event progress:progress]; NSString *bundleHash = [self calculateBundleHashFromEvents:relatedBinaries]; NSNumber *ms = [NSNumber numberWithDouble:[startTime timeIntervalSinceNow] * -1000.0]; @@ -236,11 +246,12 @@ - (NSArray *)findRelatedBinaries:(SNTStoredEvent *)event progress:(NSProgress *) se.fileSHA256 = fi.SHA256; se.occurrenceDate = [NSDate distantFuture]; se.decision = SNTEventStateBundleBinary; - se.fileBundleID = fi.bundleIdentifier; - se.fileBundleName = fi.bundleName; - se.fileBundlePath = fi.bundlePath; - se.fileBundleVersion = fi.bundleVersion; - se.fileBundleVersionString = fi.bundleShortVersionString; + + se.fileBundlePath = event.fileBundlePath; + se.fileBundleID = event.fileBundleID; + se.fileBundleName = event.fileBundleName; + se.fileBundleVersion = event.fileBundleVersion; + se.fileBundleVersionString = event.fileBundleVersionString; MOLCodesignChecker *cs = [[MOLCodesignChecker alloc] initWithBinaryPath:se.filePath]; se.signingChain = cs.certificates; diff --git a/Tests/LogicTests/Resources/BundleExample.app/Contents/Info.plist b/Tests/LogicTests/Resources/BundleExample.app/Contents/Info.plist new file mode 100644 index 000000000..d9c402f17 --- /dev/null +++ b/Tests/LogicTests/Resources/BundleExample.app/Contents/Info.plist @@ -0,0 +1,52 @@ + + + + + BuildMachineOSBuild + 16F73 + CFBundleDevelopmentRegion + en + CFBundleExecutable + BundleExample + CFBundleIdentifier + com.google.santa.BundleExample + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + BundleExample + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleSupportedPlatforms + + MacOSX + + CFBundleVersion + 1 + DTCompiler + com.apple.compilers.llvm.clang.1_0 + DTPlatformBuild + 7D1014 + DTPlatformVersion + GM + DTSDKBuild + 15E60 + DTSDKName + macosx10.11 + DTXcode + 0731 + DTXcodeBuild + 7D1014 + LSMinimumSystemVersion + 10.12 + NSHumanReadableCopyright + Copyright © 2017 Google. All rights reserved. + NSMainNibFile + MainMenu + NSPrincipalClass + NSApplication + + diff --git a/Tests/LogicTests/Resources/BundleExample.app/Contents/MacOS/BundleExample b/Tests/LogicTests/Resources/BundleExample.app/Contents/MacOS/BundleExample new file mode 100755 index 000000000..f4202ea1e Binary files /dev/null and b/Tests/LogicTests/Resources/BundleExample.app/Contents/MacOS/BundleExample differ diff --git a/Tests/LogicTests/SNTFileInfoTest.m b/Tests/LogicTests/SNTFileInfoTest.m index 342f2b882..a285a5b95 100644 --- a/Tests/LogicTests/SNTFileInfoTest.m +++ b/Tests/LogicTests/SNTFileInfoTest.m @@ -120,16 +120,88 @@ - (void)testScript { } - (void)testBundle { - SNTFileInfo *sut = - [[SNTFileInfo alloc] initWithPath:@"/Applications/Safari.app/Contents/MacOS/Safari"]; + NSString *path = [[NSBundle bundleForClass:[self class]] pathForResource:@"BundleExample" + ofType:@"app"]; + SNTFileInfo *sut = [[SNTFileInfo alloc] initWithPath:path]; + + XCTAssertNotNil([sut bundle]); + + XCTAssertEqualObjects([sut bundleIdentifier], @"com.google.santa.BundleExample"); + XCTAssertEqualObjects([sut bundleName], @"BundleExample"); + XCTAssertEqualObjects([sut bundleVersion], @"1"); + XCTAssertEqualObjects([sut bundleShortVersionString], @"1.0"); + XCTAssertEqualObjects([sut bundlePath], path); +} + +- (void)testAncestorBundle { + NSString *path = [[NSBundle bundleForClass:[self class]] pathForResource:@"BundleExample" + ofType:@"app"]; + SNTFileInfo *sut = [[SNTFileInfo alloc] initWithPath:path]; + sut.useAncestorBundle = YES; + + XCTAssertNotNil([sut bundle]); + + XCTAssertEqualObjects([sut bundleIdentifier], @"com.google.LogicTests"); + XCTAssertNotNil([sut bundleVersion]); + XCTAssertNotNil([sut bundleShortVersionString]); + + NSString *ancestorBundlePath = path; + for (int i = 0; i < 3; i++) { + ancestorBundlePath = [ancestorBundlePath stringByDeletingLastPathComponent]; + } + XCTAssertEqualObjects([sut bundlePath], ancestorBundlePath); +} + +- (void)testBundleIsAncestor { + NSString *path = [NSBundle bundleForClass:[self class]].bundlePath; + SNTFileInfo *sut = [[SNTFileInfo alloc] initWithPath:path]; + sut.useAncestorBundle = YES; + + XCTAssertNotNil([sut bundle]); + + XCTAssertEqualObjects([sut bundleIdentifier], @"com.google.LogicTests"); + XCTAssertNotNil([sut bundleVersion]); + XCTAssertNotNil([sut bundleShortVersionString]); + XCTAssertEqualObjects([sut bundlePath], path); +} + +- (void)testBundleCacheReset { + NSString *path = [[NSBundle bundleForClass:[self class]] pathForResource:@"BundleExample" + ofType:@"app"]; + SNTFileInfo *sut = [[SNTFileInfo alloc] initWithPath:path]; + + XCTAssertNotNil([sut bundle]); + + XCTAssertEqualObjects([sut bundleIdentifier], @"com.google.santa.BundleExample"); + XCTAssertEqualObjects([sut bundleName], @"BundleExample"); + XCTAssertEqualObjects([sut bundleVersion], @"1"); + XCTAssertEqualObjects([sut bundleShortVersionString], @"1.0"); + XCTAssertEqualObjects([sut bundlePath], path); + + sut.useAncestorBundle = YES; XCTAssertNotNil([sut bundle]); - XCTAssertEqualObjects([sut bundleIdentifier], @"com.apple.Safari"); - XCTAssertEqualObjects([sut bundleName], @"Safari"); + XCTAssertEqualObjects([sut bundleIdentifier], @"com.google.LogicTests"); XCTAssertNotNil([sut bundleVersion]); XCTAssertNotNil([sut bundleShortVersionString]); - XCTAssertEqualObjects([sut bundlePath], @"/Applications/Safari.app"); + + NSString *ancestorBundlePath = path; + for (int i = 0; i < 3; i++) { + ancestorBundlePath = [ancestorBundlePath stringByDeletingLastPathComponent]; + } + XCTAssertEqualObjects([sut bundlePath], ancestorBundlePath); +} + +- (void)testNonBundle { + SNTFileInfo *sut = + [[SNTFileInfo alloc] initWithPath:@"/usr/bin/yes"]; + + XCTAssertNil([sut bundle]); + + sut.useAncestorBundle = YES; + + XCTAssertNil([sut bundle]); } - (void)testEmbeddedInfoPlist {