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(