diff --git a/ProjFS.Mac/PrjFSKext/KauthHandler.cpp b/ProjFS.Mac/PrjFSKext/KauthHandler.cpp index ba5a0780b4..8ce4358351 100644 --- a/ProjFS.Mac/PrjFSKext/KauthHandler.cpp +++ b/ProjFS.Mac/PrjFSKext/KauthHandler.cpp @@ -69,7 +69,7 @@ static bool TryReadVNodeFileFlags(vnode_t vn, vfs_context_t _Nonnull context, ui KEXT_STATIC_INLINE bool FileFlagsBitIsSet(uint32_t fileFlags, uint32_t bit); KEXT_STATIC_INLINE bool TryGetFileIsFlaggedAsInRoot(vnode_t vnode, vfs_context_t _Nonnull context, bool* flaggedInRoot); KEXT_STATIC_INLINE bool ActionBitIsSet(kauth_action_t action, kauth_action_t mask); -KEXT_STATIC bool CurrentProcessWasSpawnedByRegularUser(); +KEXT_STATIC bool CurrentProcessIsAllowedToHydrate(); KEXT_STATIC bool IsFileSystemCrawler(const char* procname); static void WaitForListenerCompletion(); @@ -1144,10 +1144,10 @@ static bool TryGetVirtualizationRoot( return false; } - if (callbackPolicy == CallbackPolicy_UserInitiatedOnly && !CurrentProcessWasSpawnedByRegularUser()) + if (callbackPolicy == CallbackPolicy_UserInitiatedOnly && !CurrentProcessIsAllowedToHydrate()) { // Prevent hydration etc. by system services - KextLog_Info("TryGetVirtualizationRoot: process %d restricted due to owner UID.", pidMakingRequest); + KextLog_Info("TryGetVirtualizationRoot: process %d is not allowed to hydrate.", pidMakingRequest); perfTracer->IncrementCount(PrjFSPerfCounter_VnodeOp_GetVirtualizationRoot_UserRestriction); *kauthResult = KAUTH_RESULT_DENY; @@ -1159,7 +1159,7 @@ static bool TryGetVirtualizationRoot( return true; } -KEXT_STATIC bool CurrentProcessWasSpawnedByRegularUser() +KEXT_STATIC bool CurrentProcessIsAllowedToHydrate() { bool nonServiceUser = false; @@ -1188,7 +1188,7 @@ KEXT_STATIC bool CurrentProcessWasSpawnedByRegularUser() process = parentProcess; if (parentProcess == nullptr) { - KextLog_Error("CurrentProcessIsOwnedOrWasSpawnedByRegularUser: Failed to locate ancestor process %d for current process %d\n", parentPID, proc_selfpid()); + KextLog_Error("CurrentProcessIsAllowedToHydrate: Failed to locate ancestor process %d for current process %d\n", parentPID, proc_selfpid()); break; } } @@ -1199,6 +1199,19 @@ KEXT_STATIC bool CurrentProcessWasSpawnedByRegularUser() proc_rele(process); } + if (!nonServiceUser) + { + // When amfid is invoked to check the code signature of a library which has not been hydrated, + // blocking hydration would cause the launch of an application which depends on the library to fail, + // so amfid should always be allowed to hydrate. + char buffer[MAXCOMLEN + 1] = ""; + proc_selfname(buffer, sizeof(buffer)); + if (0 == strcmp(buffer, "amfid")) + { + nonServiceUser = true; + } + } + return nonServiceUser; } diff --git a/ProjFS.Mac/PrjFSKext/KauthHandlerTestable.hpp b/ProjFS.Mac/PrjFSKext/KauthHandlerTestable.hpp index 1cae4b6d94..a609a54b65 100644 --- a/ProjFS.Mac/PrjFSKext/KauthHandlerTestable.hpp +++ b/ProjFS.Mac/PrjFSKext/KauthHandlerTestable.hpp @@ -67,7 +67,7 @@ KEXT_STATIC bool ShouldHandleFileOpEvent( VirtualizationRootHandle* root, int* pid); KEXT_STATIC void UseMainForkIfNamedStream(vnode_t& vnode, bool& putVnodeWhenDone); -KEXT_STATIC bool CurrentProcessWasSpawnedByRegularUser(); +KEXT_STATIC bool CurrentProcessIsAllowedToHydrate(); KEXT_STATIC bool InitPendingRenames(); KEXT_STATIC void CleanupPendingRenames(); KEXT_STATIC void ResizePendingRenames(uint32_t newMaxPendingRenames); diff --git a/ProjFS.Mac/PrjFSKextTests/HandleFileOpOperationTests.mm b/ProjFS.Mac/PrjFSKextTests/HandleFileOpOperationTests.mm index a4bd0105cd..3b5400e5aa 100644 --- a/ProjFS.Mac/PrjFSKextTests/HandleFileOpOperationTests.mm +++ b/ProjFS.Mac/PrjFSKextTests/HandleFileOpOperationTests.mm @@ -95,7 +95,7 @@ - (void) setUp self->otherRepoHandle = result.root; MockProcess_AddContext(context, 501 /*pid*/); - MockProcess_SetSelfPid(501); + MockProcess_SetSelfInfo(501, "Test"); MockProcess_AddProcess(501 /*pid*/, 1 /*credentialId*/, 1 /*ppid*/, "test" /*name*/); ProvidermessageMock_ResetResultCount(); @@ -402,7 +402,7 @@ - (void)testFileopHardlinkOtherRepoProviderPID { MockProcess_Reset(); MockProcess_AddContext(context, self->dummyClientPid /*pid*/); - MockProcess_SetSelfPid(self->dummyClientPid); + MockProcess_SetSelfInfo(self->dummyClientPid, "Test"); MockProcess_AddProcess(self->dummyClientPid /*pid*/, 1 /*credentialId*/, 1 /*ppid*/, "GVFS.Mount" /*name*/); testFileVnode->attrValues.va_flags = FileFlags_IsInVirtualizationRoot; @@ -432,7 +432,7 @@ - (void)testFileopHardlinkOtherRepoOtherProviderPID { MockProcess_Reset(); MockProcess_AddContext(context, self->otherDummyClientPid /*pid*/); - MockProcess_SetSelfPid(self->otherDummyClientPid); + MockProcess_SetSelfInfo(self->otherDummyClientPid, "Test"); MockProcess_AddProcess(self->otherDummyClientPid /*pid*/, 1 /*credentialId*/, 1 /*ppid*/, "GVFS.Mount" /*name*/); testFileVnode->attrValues.va_flags = FileFlags_IsInVirtualizationRoot; diff --git a/ProjFS.Mac/PrjFSKextTests/HandleOperationTests.mm b/ProjFS.Mac/PrjFSKextTests/HandleOperationTests.mm index d94128af9d..06c8beadfd 100644 --- a/ProjFS.Mac/PrjFSKextTests/HandleOperationTests.mm +++ b/ProjFS.Mac/PrjFSKextTests/HandleOperationTests.mm @@ -100,7 +100,7 @@ - (void) setUp self->dummyRepoHandle = result.root; MockProcess_AddContext(context, 501 /*pid*/); - MockProcess_SetSelfPid(501); + MockProcess_SetSelfInfo(501, "Test"); MockProcess_AddProcess(501 /*pid*/, 1 /*credentialId*/, 1 /*ppid*/, "test" /*name*/); ProvidermessageMock_ResetResultCount(); diff --git a/ProjFS.Mac/PrjFSKextTests/KauthHandlerTests.mm b/ProjFS.Mac/PrjFSKextTests/KauthHandlerTests.mm index edcd996dc5..f265127574 100644 --- a/ProjFS.Mac/PrjFSKextTests/KauthHandlerTests.mm +++ b/ProjFS.Mac/PrjFSKextTests/KauthHandlerTests.mm @@ -29,7 +29,7 @@ - (void) setUp { self->cacheWrapper.AllocateCache(); context = vfs_context_create(nullptr); MockProcess_AddContext(context, 501 /*pid*/); - MockProcess_SetSelfPid(501); + MockProcess_SetSelfInfo(501, "Test"); MockProcess_AddProcess(501 /*pid*/, 1 /*credentialId*/, 1 /*ppid*/, "test" /*name*/); } @@ -247,7 +247,7 @@ - (void)testShouldHandleVnodeOpEvent { // Test with file crawler trying to populate an empty file testVnode->attrValues.va_flags = FileFlags_IsEmpty | FileFlags_IsInVirtualizationRoot; MockProcess_Reset(); - MockProcess_SetSelfPid(501); + MockProcess_SetSelfInfo(501, "Test"); MockProcess_AddContext(context, 501 /*pid*/); MockProcess_AddProcess(501 /*pid*/, 1 /*credentialId*/, 1 /*ppid*/, "mds" /*name*/); XCTAssertFalse( @@ -265,7 +265,7 @@ - (void)testShouldHandleVnodeOpEvent { // Test with finder trying to populate an empty file MockProcess_Reset(); - MockProcess_SetSelfPid(501); + MockProcess_SetSelfInfo(501, "Test"); MockProcess_AddContext(context, 501 /*pid*/); MockProcess_AddProcess(501 /*pid*/, 1 /*credentialId*/, 1 /*ppid*/, "Finder" /*name*/); XCTAssertTrue( @@ -282,47 +282,55 @@ - (void)testShouldHandleVnodeOpEvent { XCTAssertEqual(kauthResult, KAUTH_RESULT_DEFER); } -- (void)testCurrentProcessWasSpawnedByRegularUser { +- (void)testCurrentProcessIsAllowedToHydrate { // Defaults should pass for all tests - XCTAssertTrue(CurrentProcessWasSpawnedByRegularUser()); + XCTAssertTrue(CurrentProcessIsAllowedToHydrate()); MockProcess_Reset(); - // Process is a service user and does not have a parent + // Process and its parents are owned by a system user, and the executable is not a whitelisted service. MockProcess_AddContext(context, 500 /*pid*/); - MockProcess_SetSelfPid(500); + MockProcess_SetSelfInfo(500, "Test"); MockProcess_AddCredential(1 /*credentialId*/, 1 /*UID*/); MockProcess_AddProcess(500 /*pid*/, 1 /*credentialId*/, 501 /*ppid*/, "test" /*name*/); - XCTAssertFalse(CurrentProcessWasSpawnedByRegularUser()); + XCTAssertFalse(CurrentProcessIsAllowedToHydrate()); + MockProcess_Reset(); + + // The service "amfid" and its parents are owned by system users, but the process name is whitelisted for hydration. + MockProcess_AddContext(context, 500 /*pid*/); + MockProcess_SetSelfInfo(500, "amfid"); + MockProcess_AddCredential(1 /*credentialId*/, 1 /*UID*/); + MockProcess_AddProcess(500 /*pid*/, 1 /*credentialId*/, 501 /*ppid*/, "test" /*name*/); + XCTAssertTrue(CurrentProcessIsAllowedToHydrate()); MockProcess_Reset(); // Test a process with a service UID, valid parent pid, but proc_find fails to find parent pid MockCalls::Clear(); MockProcess_AddContext(context, 500 /*pid*/); - MockProcess_SetSelfPid(500); + MockProcess_SetSelfInfo(500, "Test"); MockProcess_AddCredential(1 /*credentialId*/, 1 /*UID*/); MockProcess_AddProcess(500 /*pid*/, 1 /*credentialId*/, 501 /*ppid*/, "test" /*name*/); - XCTAssertFalse(CurrentProcessWasSpawnedByRegularUser()); + XCTAssertFalse(CurrentProcessIsAllowedToHydrate()); XCTAssertTrue(MockCalls::DidCallFunction(KextMessageLogged, KEXTLOG_ERROR)); MockProcess_Reset(); // 'sudo' scenario: Root process with non-root parent MockProcess_AddContext(context, 502 /*pid*/); - MockProcess_SetSelfPid(502); + MockProcess_SetSelfInfo(502, "Test"); MockProcess_AddCredential(1 /*credentialId*/, 1 /*UID*/); MockProcess_AddCredential(2 /*credentialId*/, 501 /*UID*/); MockProcess_AddProcess(502 /*pid*/, 1 /*credentialId*/, 501 /*ppid*/, "test" /*name*/); MockProcess_AddProcess(501 /*pid*/, 2 /*credentialId*/, 1 /*ppid*/, "test" /*name*/); - XCTAssertTrue(CurrentProcessWasSpawnedByRegularUser()); + XCTAssertTrue(CurrentProcessIsAllowedToHydrate()); MockProcess_Reset(); // Process and it's parent are service users MockProcess_AddContext(context, 502 /*pid*/); - MockProcess_SetSelfPid(502); + MockProcess_SetSelfInfo(502, "Test"); MockProcess_AddCredential(1 /*credentialId*/, 1 /*UID*/); MockProcess_AddCredential(2 /*credentialId*/, 2 /*UID*/); MockProcess_AddProcess(502 /*pid*/, 1 /*credentialId*/, 501 /*ppid*/, "test" /*name*/); MockProcess_AddProcess(501 /*pid*/, 2 /*credentialId*/, 1 /*ppid*/, "test" /*name*/); - XCTAssertFalse(CurrentProcessWasSpawnedByRegularUser()); + XCTAssertFalse(CurrentProcessIsAllowedToHydrate()); } - (void)testUseMainForkIfNamedStream { diff --git a/ProjFS.Mac/PrjFSKextTests/MockProc.cpp b/ProjFS.Mac/PrjFSKextTests/MockProc.cpp index 7d96fbea0c..3f7fd938c7 100644 --- a/ProjFS.Mac/PrjFSKextTests/MockProc.cpp +++ b/ProjFS.Mac/PrjFSKextTests/MockProc.cpp @@ -14,6 +14,7 @@ static map s_credentialMap; static map s_contextMap; static map s_processMap; static int s_selfPid; +static string s_selfName; static uint16_t s_currentThreadIndex = 0; static thread s_threadPool[MockProcess_ThreadPoolSize] = {}; @@ -25,9 +26,10 @@ void MockProcess_Reset() MockProcess_SetCurrentThreadIndex(0); } -void MockProcess_SetSelfPid(int selfPid) +void MockProcess_SetSelfInfo(int selfPid, const string& selfName) { s_selfPid = selfPid; + s_selfName = selfName; } int proc_pid(proc_t proc) @@ -149,6 +151,11 @@ int proc_selfpid(void) return s_selfPid; } +void proc_selfname(char* buf, int size) +{ + strlcpy(buf, s_selfName.c_str(), size); +} + void MockProcess_AddCredential(uintptr_t credentialId, uid_t UID) { s_credentialMap.insert(make_pair(credentialId, UID)); diff --git a/ProjFS.Mac/PrjFSKextTests/MockProc.hpp b/ProjFS.Mac/PrjFSKextTests/MockProc.hpp index a015e116bb..e81ad20e27 100644 --- a/ProjFS.Mac/PrjFSKextTests/MockProc.hpp +++ b/ProjFS.Mac/PrjFSKextTests/MockProc.hpp @@ -21,6 +21,7 @@ extern "C" int proc_rele(proc_t p); int proc_selfpid(void); kernel_thread_t current_thread(void); + void proc_selfname(char* buf, int size); } struct proc { @@ -30,7 +31,7 @@ struct proc { std::string name; }; -void MockProcess_SetSelfPid(int selfPid); +void MockProcess_SetSelfInfo(int selfPid, const std::string& selfName); void MockProcess_AddCredential(uintptr_t credentialId, uid_t UID); void MockProcess_AddContext(vfs_context_t context, int pid); void MockProcess_AddProcess(int pid, uintptr_t credentialId, int ppid, std::string procName); diff --git a/ProjFS.Mac/PrjFSKextTests/ShouldHandleFileOpTests.mm b/ProjFS.Mac/PrjFSKextTests/ShouldHandleFileOpTests.mm index 0d7c7f5595..7c1bfbe6c4 100644 --- a/ProjFS.Mac/PrjFSKextTests/ShouldHandleFileOpTests.mm +++ b/ProjFS.Mac/PrjFSKextTests/ShouldHandleFileOpTests.mm @@ -36,7 +36,7 @@ - (void) setUp { self->cacheWrapper.AllocateCache(); self->context = vfs_context_create(nullptr); MockProcess_AddContext(self->context, 501 /*pid*/); - MockProcess_SetSelfPid(501); + MockProcess_SetSelfInfo(501, "Test"); MockProcess_AddProcess(501 /*pid*/, 1 /*credentialId*/, 1 /*ppid*/, "test" /*name*/); self->testMount = mount::Create(); @@ -191,7 +191,7 @@ - (void)testProviderInitiatedIO { // Fail when pid matches provider pid MockProcess_Reset(); MockProcess_AddContext(self->context, 0 /*pid*/); - MockProcess_SetSelfPid(0); + MockProcess_SetSelfInfo(0, "Test"); MockProcess_AddProcess(0 /*pid*/, 1 /*credentialId*/, 1 /*ppid*/, "test" /*name*/); int pid; XCTAssertFalse(