From 755dda6ff536e3a3471f0e8d1781edf40a40df80 Mon Sep 17 00:00:00 2001 From: Phil Dennis-Jordan Date: Mon, 1 Oct 2018 16:51:55 +0200 Subject: [PATCH 1/6] Mac kext: Handle named streams MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Occasionally, the vnode auth handler is called on a vnode representing a named stream/named fork. (Typically, the resource fork.) Forks don’t have their own xattrs, etc. so some of the logic we apply fails. We don’t care specifically about named forks, so treat any access to them as an access to the main file instead. Note: we need to balance the vnode_getparent() call with vnode_put() --- ProjFS.Mac/PrjFSKext/PrjFSKext/KauthHandler.cpp | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/ProjFS.Mac/PrjFSKext/PrjFSKext/KauthHandler.cpp b/ProjFS.Mac/PrjFSKext/PrjFSKext/KauthHandler.cpp index a0ed82e25c..b0729babd9 100644 --- a/ProjFS.Mac/PrjFSKext/PrjFSKext/KauthHandler.cpp +++ b/ProjFS.Mac/PrjFSKext/PrjFSKext/KauthHandler.cpp @@ -260,6 +260,18 @@ static int HandleVnodeOperation( // arg2 is the (vnode_t) parent vnode int* kauthError = reinterpret_cast(arg3); int kauthResult = KAUTH_RESULT_DEFER; + bool putVnodeWhenDone = false; + + // A lot of our file checks such as attribute tests behave oddly if the vnode + // refers to a named fork/stream; apply the logic as if the vnode operation was + // occurring on the file itself. (/path/to/file/..namedfork/rsrc) + if (vnode_isnamedstream(currentVnode)) + { + vnode_t mainFileFork = vnode_getparent(currentVnode); + assert(NULLVP != mainFileFork); + currentVnode = mainFileFork; + putVnodeWhenDone = true; + } const char* vnodePath = nullptr; char vnodePathBuffer[PrjFSMaxPath]; @@ -384,6 +396,11 @@ static int HandleVnodeOperation( } CleanupAndReturn: + if (putVnodeWhenDone) + { + vnode_put(currentVnode); + } + atomic_fetch_sub(&s_numActiveKauthEvents, 1); return kauthResult; } From f147f8c02634c9d73299c4cac461783dfae1745a Mon Sep 17 00:00:00 2001 From: Phil Dennis-Jordan Date: Fri, 28 Sep 2018 13:04:06 +0200 Subject: [PATCH 2/6] Mac kext: Replaces some C-style casts with C++ style. --- ProjFS.Mac/PrjFSKext/PrjFSKext/KauthHandler.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ProjFS.Mac/PrjFSKext/PrjFSKext/KauthHandler.cpp b/ProjFS.Mac/PrjFSKext/PrjFSKext/KauthHandler.cpp index b0729babd9..c782672781 100644 --- a/ProjFS.Mac/PrjFSKext/PrjFSKext/KauthHandler.cpp +++ b/ProjFS.Mac/PrjFSKext/PrjFSKext/KauthHandler.cpp @@ -425,7 +425,7 @@ static int HandleFileOpOperation( KAUTH_FILEOP_LINK == action) { // arg0 is the (const char *) fromPath (or the file being linked to) - const char* newPath = (const char*)arg1; + const char* newPath = reinterpret_cast(arg1); // TODO(Mac): We need to handle failures to lookup the vnode. If we fail to lookup the vnode // it's possible that we'll miss notifications @@ -481,7 +481,7 @@ static int HandleFileOpOperation( else if (KAUTH_FILEOP_CLOSE == action) { vnode_t currentVnode = reinterpret_cast(arg0); - const char* path = (const char*)arg1; + const char* path = reinterpret_cast(arg1); int closeFlags = static_cast(arg2); if (vnode_isdir(currentVnode)) From d21d165d15db312ee4b83a2fb3c8cf4d3f6707e4 Mon Sep 17 00:00:00 2001 From: Phil Dennis-Jordan Date: Mon, 1 Oct 2018 16:42:08 +0200 Subject: [PATCH 3/6] Mac kext: Enables warning for shadowed variables. Shadowing variables frequently is a source of bugs, so this enables the corresponding compiler warning. --- ProjFS.Mac/PrjFSKext/PrjFSKext.xcodeproj/project.pbxproj | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/ProjFS.Mac/PrjFSKext/PrjFSKext.xcodeproj/project.pbxproj b/ProjFS.Mac/PrjFSKext/PrjFSKext.xcodeproj/project.pbxproj index 74700506e7..dbd7e2453f 100644 --- a/ProjFS.Mac/PrjFSKext/PrjFSKext.xcodeproj/project.pbxproj +++ b/ProjFS.Mac/PrjFSKext/PrjFSKext.xcodeproj/project.pbxproj @@ -408,6 +408,7 @@ "$(inherited)", "MACH_ASSERT=1", ); + GCC_WARN_SHADOW = YES; INFOPLIST_FILE = PrjFSKext/Info.plist; MODULE_NAME = io.gvfs.PrjFSKext; MODULE_START = PrjFSKext_Start; @@ -430,7 +431,11 @@ buildSettings = { CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = UBF8T346G9; - GCC_PREPROCESSOR_DEFINITIONS = "MACH_ASSERT=1"; + GCC_PREPROCESSOR_DEFINITIONS = ( + "MACH_ASSERT=1", + "$(inherited)", + ); + GCC_WARN_SHADOW = YES; INFOPLIST_FILE = PrjFSKext/Info.plist; MODULE_NAME = io.gvfs.PrjFSKext; MODULE_START = PrjFSKext_Start; From e50e488ede2723162593b9a316a83915d29b1dae Mon Sep 17 00:00:00 2001 From: Phil Dennis-Jordan Date: Mon, 1 Oct 2018 13:52:41 +0200 Subject: [PATCH 4/6] Mac kext: Adds typed array memory allocation function. --- ProjFS.Mac/PrjFSKext/PrjFSKext/Memory.hpp | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/ProjFS.Mac/PrjFSKext/PrjFSKext/Memory.hpp b/ProjFS.Mac/PrjFSKext/PrjFSKext/Memory.hpp index eddfdf0fc5..0749a65533 100644 --- a/ProjFS.Mac/PrjFSKext/PrjFSKext/Memory.hpp +++ b/ProjFS.Mac/PrjFSKext/PrjFSKext/Memory.hpp @@ -7,4 +7,17 @@ kern_return_t Memory_Cleanup(); void* Memory_Alloc(uint32_t size); void Memory_Free(void* buffer, uint32_t size); +template +T* Memory_AllocArray(uint32_t arrayLength) +{ + size_t allocBytes = arrayLength * sizeof(T); + if (allocBytes > UINT32_MAX) + { + return nullptr; + } + + return static_cast(Memory_Alloc(static_cast(allocBytes))); +} + + #endif /* Memory_h */ From 3211ed1a87322fdf3173fc7a2c02527853f42674 Mon Sep 17 00:00:00 2001 From: Phil Dennis-Jordan Date: Fri, 28 Sep 2018 13:03:57 +0200 Subject: [PATCH 5/6] Mac kext: Use only VirtualizationRoot handles, not direct pointers This change moves the VirtualizationRoot structure definition out of the header file, and unifies the API to only use integer handles, which internally are array indices for non-negative values, and negative values from an enum of special cases. Where indices were previously used outside the VirtualizationRoot implementation functions, the terminology has been changed to "handles." An immediate advantage of the move away from pointers is an improvement in the ability to control thread safety of the root structures; additionally, unlike pointers, handles remain valid outside of held locks, so in a follow-on change, we can reallocate the array for resizing. --- .../PrjFSKext/PrjFSKext/KauthHandler.cpp | 70 ++++---- .../PrjFSKext/PrjFSProviderUserClient.cpp | 20 ++- .../PrjFSKext/PrjFSProviderUserClient.hpp | 5 +- .../PrjFSKext/VirtualizationRoots.cpp | 155 ++++++++++++++---- .../PrjFSKext/VirtualizationRoots.hpp | 43 +++-- 5 files changed, 186 insertions(+), 107 deletions(-) diff --git a/ProjFS.Mac/PrjFSKext/PrjFSKext/KauthHandler.cpp b/ProjFS.Mac/PrjFSKext/PrjFSKext/KauthHandler.cpp index c782672781..111c9355d8 100644 --- a/ProjFS.Mac/PrjFSKext/PrjFSKext/KauthHandler.cpp +++ b/ProjFS.Mac/PrjFSKext/PrjFSKext/KauthHandler.cpp @@ -42,11 +42,9 @@ static inline bool ActionBitIsSet(kauth_action_t action, kauth_action_t mask); static bool IsFileSystemCrawler(char* procname); -static const char* GetRelativePath(const char* path, const char* root); - static void Sleep(int seconds, void* channel); static bool TrySendRequestAndWaitForResponse( - const VirtualizationRoot* root, + VirtualizationRootHandle root, MessageType messageType, const vnode_t vnode, const FsidInode& vnodeFsidInode, @@ -65,7 +63,7 @@ static bool ShouldHandleVnodeOpEvent( kauth_action_t action, // Out params: - VirtualizationRoot** root, + VirtualizationRootHandle* root, vtype* vnodeType, uint32_t* vnodeFileFlags, FsidInode* vnodeFsidInode, @@ -80,7 +78,7 @@ static bool ShouldHandleFileOpEvent( kauth_action_t action, // Out params: - VirtualizationRoot** root, + VirtualizationRootHandle* root, FsidInode* vnodeFsidInode, int* pid); @@ -277,7 +275,7 @@ static int HandleVnodeOperation( char vnodePathBuffer[PrjFSMaxPath]; int vnodePathLength = PrjFSMaxPath; - VirtualizationRoot* root = nullptr; + VirtualizationRootHandle root = RootHandle_None; vtype vnodeType; uint32_t currentVnodeFileFlags; FsidInode vnodeFsidInode; @@ -435,7 +433,7 @@ static int HandleFileOpOperation( goto CleanupAndReturn; } - VirtualizationRoot* root = nullptr; + VirtualizationRootHandle root = RootHandle_None; FsidInode vnodeFsidInode; int pid; if (!ShouldHandleFileOpEvent( @@ -495,7 +493,7 @@ static int HandleFileOpOperation( goto CleanupAndReturn; } - VirtualizationRoot* root = nullptr; + VirtualizationRootHandle root = RootHandle_None; FsidInode vnodeFsidInode; int pid; if (!ShouldHandleFileOpEvent( @@ -571,7 +569,7 @@ static bool ShouldHandleVnodeOpEvent( kauth_action_t action, // Out params: - VirtualizationRoot** root, + VirtualizationRootHandle* root, vtype* vnodeType, uint32_t* vnodeFileFlags, FsidInode* vnodeFsidInode, @@ -579,8 +577,8 @@ static bool ShouldHandleVnodeOpEvent( char procname[MAXCOMLEN + 1], int* kauthResult) { - *root = nullptr; *kauthResult = KAUTH_RESULT_DEFER; + *root = RootHandle_None; if (!VirtualizationRoot_VnodeIsOnAllowedFilesystem(vnode)) { @@ -626,19 +624,22 @@ static bool ShouldHandleVnodeOpEvent( } *vnodeFsidInode = Vnode_GetFsidAndInode(vnode, context); - *root = VirtualizationRoots_FindForVnode(vnode, *vnodeFsidInode); + *root = VirtualizationRoot_FindForVnode(vnode, *vnodeFsidInode); - if (nullptr == *root) + if (RootHandle_ProviderTemporaryDirectory == *root) + { + *kauthResult = KAUTH_RESULT_DEFER; + return false; + } + else if (RootHandle_None == *root) { KextLog_FileNote(vnode, "No virtualization root found for file with set flag."); *kauthResult = KAUTH_RESULT_DEFER; return false; } - else if (nullptr == (*root)->providerUserClient) + else if (!VirtualizationRoot_IsOnline(*root)) { - // There is no registered provider for this root - // TODO(Mac): Protect files in the worktree from modification (and prevent // the creation of new files) when the provider is offline @@ -647,7 +648,7 @@ static bool ShouldHandleVnodeOpEvent( } // If the calling process is the provider, we must exit right away to avoid deadlocks - if (*pid == (*root)->providerPid) + if (VirtualizationRoot_PIDMatchesProvider(*root, *pid)) { *kauthResult = KAUTH_RESULT_DEFER; return false; @@ -663,10 +664,12 @@ static bool ShouldHandleFileOpEvent( kauth_action_t action, // Out params: - VirtualizationRoot** root, + VirtualizationRootHandle* root, FsidInode* vnodeFsidInode, int* pid) { + *root = RootHandle_None; + vtype vnodeType = vnode_vtype(vnode); if (ShouldIgnoreVnodeType(vnodeType, vnode)) { @@ -674,21 +677,17 @@ static bool ShouldHandleFileOpEvent( } *vnodeFsidInode = Vnode_GetFsidAndInode(vnode, context); - *root = VirtualizationRoots_FindForVnode(vnode, *vnodeFsidInode); - if (nullptr == *root) - { - return false; - } - else if (nullptr == (*root)->providerUserClient) + *root = VirtualizationRoot_FindForVnode(vnode, *vnodeFsidInode); + if (!VirtualizationRoot_IsValidRootHandle(*root)) { - // There is no registered provider for this root + // This VNode is not part of a root return false; } - // If the calling process is the provider, we must exit right away to avoid deadlocks *pid = GetPid(context); - if (*pid == (*root)->providerPid) + if (VirtualizationRoot_PIDMatchesProvider(*root, *pid)) { + // If the calling process is the provider, we must exit right away to avoid deadlocks return false; } @@ -696,7 +695,7 @@ static bool ShouldHandleFileOpEvent( } static bool TrySendRequestAndWaitForResponse( - const VirtualizationRoot* root, + VirtualizationRootHandle root, MessageType messageType, const vnode_t vnode, const FsidInode& vnodeFsidInode, @@ -719,7 +718,7 @@ static bool TrySendRequestAndWaitForResponse( return false; } - const char* relativePath = GetRelativePath(vnodePath, root->path); + const char* relativePath = VirtualizationRoot_GetRootRelativePath(root, vnodePath); int nextMessageId = OSIncrementAtomic(&s_nextMessageId); @@ -748,7 +747,7 @@ static bool TrySendRequestAndWaitForResponse( // TODO(Mac): Should we pass in the root directly, rather than root->index? // The index seems more like a private implementation detail. - if (!isShuttingDown && 0 != ActiveProvider_SendMessage(root->index, messageSpec)) + if (!isShuttingDown && 0 != ActiveProvider_SendMessage(root, messageSpec)) { // TODO: appropriately handle unresponsive providers @@ -885,19 +884,6 @@ static bool IsFileSystemCrawler(char* procname) return false; } -static const char* GetRelativePath(const char* path, const char* root) -{ - assert(strlen(path) >= strlen(root)); - - const char* relativePath = path + strlen(root); - if (relativePath[0] == '/') - { - relativePath++; - } - - return relativePath; -} - static bool ShouldIgnoreVnodeType(vtype vnodeType, vnode_t vnode) { switch (vnodeType) diff --git a/ProjFS.Mac/PrjFSKext/PrjFSKext/PrjFSProviderUserClient.cpp b/ProjFS.Mac/PrjFSKext/PrjFSKext/PrjFSProviderUserClient.cpp index 6e9b75427c..83b2fcb19b 100644 --- a/ProjFS.Mac/PrjFSKext/PrjFSKext/PrjFSProviderUserClient.cpp +++ b/ProjFS.Mac/PrjFSKext/PrjFSKext/PrjFSProviderUserClient.cpp @@ -19,7 +19,11 @@ static const IOExternalMethodDispatch ProviderUserClientDispatch[] = { [ProviderSelector_RegisterVirtualizationRootPath] = { - &PrjFSProviderUserClient::registerVirtualizationRoot, 0, kIOUCVariableStructureSize, 1, 0 + .function = &PrjFSProviderUserClient::registerVirtualizationRoot, + .checkScalarInputCount = 0, + .checkStructureInputSize = kIOUCVariableStructureSize, // null-terminated string: virtualisation root path + .checkScalarOutputCount = 1, // returned errno + .checkStructureOutputSize = 0 }, [ProviderSelector_KernelMessageResponse] = { @@ -37,7 +41,7 @@ bool PrjFSProviderUserClient::initWithTask( UInt32 type, OSDictionary* properties) { - this->virtualizationRootIndex = -1; + this->virtualizationRootHandle = RootHandle_None; this->pid = proc_selfpid(); if (!this->super::initWithTask(owningTask, securityToken, type, properties)) @@ -92,9 +96,9 @@ void PrjFSProviderUserClient::free() // the connection. IOReturn PrjFSProviderUserClient::clientClose() { - int32_t root = this->virtualizationRootIndex; - this->virtualizationRootIndex = -1; - if (-1 != root) + VirtualizationRootHandle root = this->virtualizationRootHandle; + this->virtualizationRootHandle = RootHandle_None; + if (RootHandle_None != root) { ActiveProvider_Disconnect(root); } @@ -210,7 +214,7 @@ IOReturn PrjFSProviderUserClient::registerVirtualizationRoot(const char* rootPat *outError = EINVAL; return kIOReturnSuccess; } - else if (this->virtualizationRootIndex != -1) + else if (this->virtualizationRootHandle != RootHandle_None) { // Already set *outError = EBUSY; @@ -220,11 +224,11 @@ IOReturn PrjFSProviderUserClient::registerVirtualizationRoot(const char* rootPat VirtualizationRootResult result = VirtualizationRoot_RegisterProviderForPath(this, this->pid, rootPath); if (0 == result.error) { - this->virtualizationRootIndex = result.rootIndex; + this->virtualizationRootHandle = result.root; // Sets the root index in the IORegistry for diagnostic purposes char location[5] = ""; - snprintf(location, sizeof(location), "%d", result.rootIndex); + snprintf(location, sizeof(location), "%d", result.root); this->setLocation(location); } diff --git a/ProjFS.Mac/PrjFSKext/PrjFSKext/PrjFSProviderUserClient.hpp b/ProjFS.Mac/PrjFSKext/PrjFSKext/PrjFSProviderUserClient.hpp index 12621ede4d..de487108c1 100644 --- a/ProjFS.Mac/PrjFSKext/PrjFSKext/PrjFSProviderUserClient.hpp +++ b/ProjFS.Mac/PrjFSKext/PrjFSKext/PrjFSProviderUserClient.hpp @@ -3,6 +3,7 @@ #include "PrjFSClasses.hpp" #include "Locks.hpp" #include "Message.h" +#include "VirtualizationRoots.hpp" #include struct MessageHeader; @@ -18,8 +19,8 @@ class PrjFSProviderUserClient : public IOUserClient Mutex dataQueueWriterMutex; public: pid_t pid; - // The root for which this is the provider; -1 prior to registration - int32_t virtualizationRootIndex; + // The root for which this is the provider; RootHandle_None prior to registration + VirtualizationRootHandle virtualizationRootHandle; // IOUserClient methods: virtual bool initWithTask( diff --git a/ProjFS.Mac/PrjFSKext/PrjFSKext/VirtualizationRoots.cpp b/ProjFS.Mac/PrjFSKext/PrjFSKext/VirtualizationRoots.cpp index 46a5704d9f..ecb7924481 100644 --- a/ProjFS.Mac/PrjFSKext/PrjFSKext/VirtualizationRoots.cpp +++ b/ProjFS.Mac/PrjFSKext/PrjFSKext/VirtualizationRoots.cpp @@ -12,6 +12,27 @@ #include "VnodeUtilities.hpp" +struct VirtualizationRoot +{ + bool inUse; + // If this is a nullptr, there is no active provider for this virtualization root (offline root) + PrjFSProviderUserClient* providerUserClient; + int providerPid; + // For an active root, this is retained (vnode_get), for an offline one, it is not, so it may be stale (check the vid) + vnode_t rootVNode; + uint32_t rootVNodeVid; + + // Mount point ID + persistent, on-disk ID for the root directory, so we can + // identify it if the vnode of an offline root gets recycled. + fsid_t rootFsid; + uint64_t rootInode; + + // TODO(Mac): this should eventually be entirely diagnostic and not used for decisions + char path[PrjFSMaxPath]; + + int32_t index; +}; + static RWLock s_rwLock = {}; // Arbitrary choice, but prevents user space attacker from causing @@ -20,9 +41,52 @@ static const size_t MaxVirtualizationRoots = 128; static VirtualizationRoot s_virtualizationRoots[MaxVirtualizationRoots] = {}; -static int16_t FindRootForVnode_Locked(vnode_t vnode, uint32_t vid, FsidInode fileId); -static int16_t FindUnusedIndex_Locked(); -static int16_t InsertVirtualizationRoot_Locked(PrjFSProviderUserClient* userClient, pid_t clientPID, vnode_t vnode, uint32_t vid, FsidInode persistentIds, const char* path); +// Looks up the vnode/vid and fsid/inode pairs among the known roots +static VirtualizationRootHandle FindRootAtVnode_Locked(vnode_t vnode, uint32_t vid, FsidInode fileId); + +// Looks up the vnode and fsid/inode pair among the known roots, and if not found, +// detects if there is a hitherto-unknown root at vnode by checking attributes. +static VirtualizationRootHandle FindOrDetectRootAtVnode(vnode_t vnode, vfs_context_t context, const FsidInode& vnodeFsidInode); + +static VirtualizationRootHandle FindUnusedIndex_Locked(); +static VirtualizationRootHandle InsertVirtualizationRoot_Locked(PrjFSProviderUserClient* userClient, pid_t clientPID, vnode_t vnode, uint32_t vid, FsidInode persistentIds, const char* path); + +bool VirtualizationRoot_IsOnline(VirtualizationRootHandle rootIndex) +{ + if (rootIndex < 0 || rootIndex >= MaxVirtualizationRoots) + { + return false; + } + + bool result; + RWLock_AcquireShared(s_rwLock); + { + result = (nullptr != s_virtualizationRoots[rootIndex].providerUserClient); + } + RWLock_ReleaseShared(s_rwLock); + + return result; +} + +bool VirtualizationRoot_PIDMatchesProvider(VirtualizationRootHandle rootIndex, pid_t pid) +{ + bool result; + RWLock_AcquireShared(s_rwLock); + { + result = + (rootIndex >= 0 && rootIndex < MaxVirtualizationRoots) + && (nullptr != s_virtualizationRoots[rootIndex].providerUserClient) + && pid == s_virtualizationRoots[rootIndex].providerPid; + } + RWLock_ReleaseShared(s_rwLock); + + return result; +} + +bool VirtualizationRoot_IsValidRootHandle(VirtualizationRootHandle rootIndex) +{ + return (rootIndex > RootHandle_None); +} kern_return_t VirtualizationRoots_Init() { @@ -37,7 +101,7 @@ kern_return_t VirtualizationRoots_Init() return KERN_FAILURE; } - for (uint32_t i = 0; i < MaxVirtualizationRoots; ++i) + for (VirtualizationRootHandle i = 0; i < MaxVirtualizationRoots; ++i) { s_virtualizationRoots[i].index = i; } @@ -56,18 +120,19 @@ kern_return_t VirtualizationRoots_Cleanup() return KERN_FAILURE; } -VirtualizationRoot* VirtualizationRoots_FindForVnode(vnode_t vnode, const FsidInode& vnodeFsidInode) +VirtualizationRootHandle VirtualizationRoot_FindForVnode(vnode_t vnode, const FsidInode& vnodeFsidInode) { - VirtualizationRoot* root = nullptr; + VirtualizationRootHandle rootHandle = RootHandle_None; vnode_get(vnode); // Search up the tree until we hit a known virtualization root or THE root of the file system - while (nullptr == root && NULLVP != vnode && !vnode_isvroot(vnode)) + while (RootHandle_None == rootHandle && NULLVP != vnode && !vnode_isvroot(vnode)) { - int16_t rootIndex = VirtualizationRoots_LookupVnode(vnode, /*context*/ nullptr, vnodeFsidInode); - if (rootIndex >= 0) + rootHandle = FindOrDetectRootAtVnode(vnode, nullptr /* vfs context */, vnodeFsidInode); + // Note: if FindOrDetectRootAtVnode returns a "special" handle other + // than RootHandle_None, we want to stop the search and return that. + if (rootHandle != RootHandle_None) { - root = &s_virtualizationRoots[rootIndex]; break; } @@ -81,22 +146,22 @@ VirtualizationRoot* VirtualizationRoots_FindForVnode(vnode_t vnode, const FsidIn vnode_put(vnode); } - return root; + return rootHandle; } -int16_t VirtualizationRoots_LookupVnode(vnode_t vnode, vfs_context_t context, const FsidInode& vnodeFsidInode) +static VirtualizationRootHandle FindOrDetectRootAtVnode(vnode_t vnode, vfs_context_t context, const FsidInode& vnodeFsidInode) { uint32_t vid = vnode_vid(vnode); - int16_t rootIndex; + VirtualizationRootHandle rootIndex; RWLock_AcquireShared(s_rwLock); { - rootIndex = FindRootForVnode_Locked(vnode, vid, vnodeFsidInode); + rootIndex = FindRootAtVnode_Locked(vnode, vid, vnodeFsidInode); } RWLock_ReleaseShared(s_rwLock); - if (rootIndex < 0) + if (rootIndex == RootHandle_None) { PrjFSVirtualizationRootXAttrData rootXattr = {}; SizeOrError xattrResult = Vnode_ReadXattr(vnode, PrjFSVirtualizationRootXAttrName, &rootXattr, sizeof(rootXattr), context); @@ -111,9 +176,9 @@ int16_t VirtualizationRoots_LookupVnode(vnode_t vnode, vfs_context_t context, co RWLock_AcquireExclusive(s_rwLock); { // Vnode may already have been inserted as a root in the interim - rootIndex = FindRootForVnode_Locked(vnode, vid, vnodeFsidInode); + rootIndex = FindRootAtVnode_Locked(vnode, vid, vnodeFsidInode); - if (rootIndex < 0) + if (RootHandle_None == rootIndex) { // Insert new offline root rootIndex = InsertVirtualizationRoot_Locked(nullptr, 0, vnode, vid, vnodeFsidInode, path); @@ -130,9 +195,9 @@ int16_t VirtualizationRoots_LookupVnode(vnode_t vnode, vfs_context_t context, co return rootIndex; } -static int16_t FindUnusedIndex_Locked() +static VirtualizationRootHandle FindUnusedIndex_Locked() { - for (int16_t i = 0; i < MaxVirtualizationRoots; ++i) + for (VirtualizationRootHandle i = 0; i < MaxVirtualizationRoots; ++i) { if (!s_virtualizationRoots[i].inUse) { @@ -140,7 +205,7 @@ static int16_t FindUnusedIndex_Locked() } } - return -1; + return RootHandle_None; } static bool FsidsAreEqual(fsid_t a, fsid_t b) @@ -148,9 +213,9 @@ static bool FsidsAreEqual(fsid_t a, fsid_t b) return a.val[0] == b.val[0] && a.val[1] == b.val[1]; } -static int16_t FindRootForVnode_Locked(vnode_t vnode, uint32_t vid, FsidInode fileId) +static VirtualizationRootHandle FindRootAtVnode_Locked(vnode_t vnode, uint32_t vid, FsidInode fileId) { - for (int16_t i = 0; i < MaxVirtualizationRoots; ++i) + for (VirtualizationRootHandle i = 0; i < MaxVirtualizationRoots; ++i) { VirtualizationRoot& rootEntry = s_virtualizationRoots[i]; if (!rootEntry.inUse) @@ -172,14 +237,13 @@ static int16_t FindRootForVnode_Locked(vnode_t vnode, uint32_t vid, FsidInode fi return i; } } - return -1; + return RootHandle_None; } // Returns negative value if it failed, or inserted index on success -static int16_t InsertVirtualizationRoot_Locked(PrjFSProviderUserClient* userClient, pid_t clientPID, vnode_t vnode, uint32_t vid, FsidInode persistentIds, const char* path) +static VirtualizationRootHandle InsertVirtualizationRoot_Locked(PrjFSProviderUserClient* userClient, pid_t clientPID, vnode_t vnode, uint32_t vid, FsidInode persistentIds, const char* path) { - // New root - int16_t rootIndex = FindUnusedIndex_Locked(); + VirtualizationRootHandle rootIndex = FindUnusedIndex_Locked(); if (rootIndex >= 0) { @@ -215,7 +279,7 @@ VirtualizationRootResult VirtualizationRoot_RegisterProviderForPath(PrjFSProvide vnode_t virtualizationRootVNode = NULLVP; vfs_context_t vfsContext = vfs_context_create(nullptr); - int32_t rootIndex = -1; + VirtualizationRootHandle rootIndex = RootHandle_None; errno_t err = vnode_lookup(virtualizationRootPath, 0 /* flags */, &virtualizationRootVNode, vfsContext); if (0 == err) { @@ -234,7 +298,7 @@ VirtualizationRootResult VirtualizationRoot_RegisterProviderForPath(PrjFSProvide RWLock_AcquireExclusive(s_rwLock); { - rootIndex = FindRootForVnode_Locked(virtualizationRootVNode, rootVid, vnodeIds); + rootIndex = FindRootAtVnode_Locked(virtualizationRootVNode, rootVid, vnodeIds); if (rootIndex >= 0) { // Reattaching to existing root @@ -242,7 +306,7 @@ VirtualizationRootResult VirtualizationRoot_RegisterProviderForPath(PrjFSProvide { // Only one provider per root err = EBUSY; - rootIndex = -1; + rootIndex = RootHandle_None; } else { @@ -292,7 +356,7 @@ VirtualizationRootResult VirtualizationRoot_RegisterProviderForPath(PrjFSProvide return VirtualizationRootResult { err, rootIndex }; } -void ActiveProvider_Disconnect(int32_t rootIndex) +void ActiveProvider_Disconnect(VirtualizationRootHandle rootIndex) { assert(rootIndex >= 0); assert(rootIndex <= MaxVirtualizationRoots); @@ -311,7 +375,7 @@ void ActiveProvider_Disconnect(int32_t rootIndex) RWLock_ReleaseExclusive(s_rwLock); } -errno_t ActiveProvider_SendMessage(int32_t rootIndex, const Message message) +errno_t ActiveProvider_SendMessage(VirtualizationRootHandle rootIndex, const Message message) { assert(rootIndex >= 0); assert(rootIndex < MaxVirtualizationRoots); @@ -356,3 +420,32 @@ bool VirtualizationRoot_VnodeIsOnAllowedFilesystem(vnode_t vnode) || 0 == strncmp("apfs", vfsStat->f_fstypename, sizeof(vfsStat->f_fstypename)); } +static const char* GetRelativePath(const char* path, const char* root) +{ + assert(strlen(path) >= strlen(root)); + + const char* relativePath = path + strlen(root); + if (relativePath[0] == '/') + { + relativePath++; + } + + return relativePath; +} + +const char* VirtualizationRoot_GetRootRelativePath(VirtualizationRootHandle rootIndex, const char* path) +{ + assert(rootIndex >= 0); + assert(rootIndex <= MaxVirtualizationRoots); + + const char* relativePath; + + RWLock_AcquireShared(s_rwLock); + { + assert(s_virtualizationRoots[rootIndex].inUse); + relativePath = GetRelativePath(path, s_virtualizationRoots[rootIndex].path); + } + RWLock_ReleaseShared(s_rwLock); + + return relativePath; +} diff --git a/ProjFS.Mac/PrjFSKext/PrjFSKext/VirtualizationRoots.hpp b/ProjFS.Mac/PrjFSKext/PrjFSKext/VirtualizationRoots.hpp index 3d1c86aefa..2b19b0681d 100644 --- a/ProjFS.Mac/PrjFSKext/PrjFSKext/VirtualizationRoots.hpp +++ b/ProjFS.Mac/PrjFSKext/PrjFSKext/VirtualizationRoots.hpp @@ -4,42 +4,37 @@ #include "PrjFSClasses.hpp" #include "kernel-header-wrappers/vnode.h" -struct VirtualizationRoot -{ - bool inUse; - // If this is a nullptr, there is no active provider for this virtualization root (offline root) - PrjFSProviderUserClient* providerUserClient; - int providerPid; - // For an active root, this is retained (vnode_get), for an offline one, it is not, so it may be stale (check the vid) - vnode_t rootVNode; - uint32_t rootVNodeVid; - - // Mount point ID + persistent, on-disk ID for the root directory, so we can - // identify it if the vnode of an offline root gets recycled. - fsid_t rootFsid; - uint64_t rootInode; - - // TODO(Mac): this should eventually be entirely diagnostic and not used for decisions - char path[PrjFSMaxPath]; +typedef int16_t VirtualizationRootHandle; - int32_t index; +// Zero and positive values indicate a handle for a valid virtualization +// root. Other values have special meanings: +enum VirtualizationRootSpecialHandle : VirtualizationRootHandle +{ + // Not in a virtualization root. + RootHandle_None = -1, + // Root/non-root state not known. Useful reset value for invalidating cached state. + RootHandle_Indeterminate = -2, + // Vnode is not in a virtualization root, but below a provider's registered temp directory + RootHandle_ProviderTemporaryDirectory = -3, }; kern_return_t VirtualizationRoots_Init(void); kern_return_t VirtualizationRoots_Cleanup(void); -VirtualizationRoot* VirtualizationRoots_FindForVnode(vnode_t vnode, const FsidInode& vnodeFsidInode); +VirtualizationRootHandle VirtualizationRoot_FindForVnode(vnode_t vnode, const FsidInode& vnodeFsidInode); struct VirtualizationRootResult { errno_t error; - int32_t rootIndex; + VirtualizationRootHandle root; }; VirtualizationRootResult VirtualizationRoot_RegisterProviderForPath(PrjFSProviderUserClient* userClient, pid_t clientPID, const char* virtualizationRootPath); -void ActiveProvider_Disconnect(int32_t rootIndex); +void ActiveProvider_Disconnect(VirtualizationRootHandle rootHandle); struct Message; -errno_t ActiveProvider_SendMessage(int32_t rootIndex, const Message message); +errno_t ActiveProvider_SendMessage(VirtualizationRootHandle rootHandle, const Message message); bool VirtualizationRoot_VnodeIsOnAllowedFilesystem(vnode_t vnode); - -int16_t VirtualizationRoots_LookupVnode(vnode_t vnode, vfs_context_t context, const FsidInode& vnodeFsidInode); +bool VirtualizationRoot_IsOnline(VirtualizationRootHandle rootHandle); +bool VirtualizationRoot_PIDMatchesProvider(VirtualizationRootHandle rootHandle, pid_t pid); +bool VirtualizationRoot_IsValidRootHandle(VirtualizationRootHandle rootHandle); +const char* VirtualizationRoot_GetRootRelativePath(VirtualizationRootHandle rootHandle, const char* path); From 0a0ac8838f2a2a9daddaed88682e411e421bd9fc Mon Sep 17 00:00:00 2001 From: Phil Dennis-Jordan Date: Mon, 1 Oct 2018 21:02:03 +0200 Subject: [PATCH 6/6] Mac kext: Dynamically alloc virtualisation root array & resize when full. --- .../PrjFSKext/VirtualizationRoots.cpp | 105 +++++++++++++----- 1 file changed, 76 insertions(+), 29 deletions(-) diff --git a/ProjFS.Mac/PrjFSKext/PrjFSKext/VirtualizationRoots.cpp b/ProjFS.Mac/PrjFSKext/PrjFSKext/VirtualizationRoots.cpp index ecb7924481..88b0a97dba 100644 --- a/ProjFS.Mac/PrjFSKext/PrjFSKext/VirtualizationRoots.cpp +++ b/ProjFS.Mac/PrjFSKext/PrjFSKext/VirtualizationRoots.cpp @@ -1,5 +1,6 @@ #include #include +#include #include "PrjFSCommon.h" #include "PrjFSXattrs.h" @@ -29,17 +30,13 @@ struct VirtualizationRoot // TODO(Mac): this should eventually be entirely diagnostic and not used for decisions char path[PrjFSMaxPath]; - - int32_t index; }; static RWLock s_rwLock = {}; -// Arbitrary choice, but prevents user space attacker from causing -// allocation of too much wired kernel memory. -static const size_t MaxVirtualizationRoots = 128; - -static VirtualizationRoot s_virtualizationRoots[MaxVirtualizationRoots] = {}; +// Current length of the s_virtualizationRoots array +static uint16_t s_maxVirtualizationRoots = 0; +static VirtualizationRoot* s_virtualizationRoots = nullptr; // Looks up the vnode/vid and fsid/inode pairs among the known roots static VirtualizationRootHandle FindRootAtVnode_Locked(vnode_t vnode, uint32_t vid, FsidInode fileId); @@ -51,9 +48,9 @@ static VirtualizationRootHandle FindOrDetectRootAtVnode(vnode_t vnode, vfs_conte static VirtualizationRootHandle FindUnusedIndex_Locked(); static VirtualizationRootHandle InsertVirtualizationRoot_Locked(PrjFSProviderUserClient* userClient, pid_t clientPID, vnode_t vnode, uint32_t vid, FsidInode persistentIds, const char* path); -bool VirtualizationRoot_IsOnline(VirtualizationRootHandle rootIndex) +bool VirtualizationRoot_IsOnline(VirtualizationRootHandle rootHandle) { - if (rootIndex < 0 || rootIndex >= MaxVirtualizationRoots) + if (rootHandle < 0) { return false; } @@ -61,22 +58,27 @@ bool VirtualizationRoot_IsOnline(VirtualizationRootHandle rootIndex) bool result; RWLock_AcquireShared(s_rwLock); { - result = (nullptr != s_virtualizationRoots[rootIndex].providerUserClient); + result = + rootHandle < s_maxVirtualizationRoots + && s_virtualizationRoots[rootHandle].inUse + && nullptr != s_virtualizationRoots[rootHandle].providerUserClient; } RWLock_ReleaseShared(s_rwLock); return result; } -bool VirtualizationRoot_PIDMatchesProvider(VirtualizationRootHandle rootIndex, pid_t pid) +bool VirtualizationRoot_PIDMatchesProvider(VirtualizationRootHandle rootHandle, pid_t pid) { bool result; RWLock_AcquireShared(s_rwLock); { result = - (rootIndex >= 0 && rootIndex < MaxVirtualizationRoots) - && (nullptr != s_virtualizationRoots[rootIndex].providerUserClient) - && pid == s_virtualizationRoots[rootIndex].providerPid; + rootHandle >= 0 + && rootHandle < s_maxVirtualizationRoots + && s_virtualizationRoots[rootHandle].inUse + && nullptr != s_virtualizationRoots[rootHandle].providerUserClient + && pid == s_virtualizationRoots[rootHandle].providerPid; } RWLock_ReleaseShared(s_rwLock); @@ -101,11 +103,20 @@ kern_return_t VirtualizationRoots_Init() return KERN_FAILURE; } - for (VirtualizationRootHandle i = 0; i < MaxVirtualizationRoots; ++i) + s_maxVirtualizationRoots = 128; + s_virtualizationRoots = Memory_AllocArray(s_maxVirtualizationRoots); + if (nullptr == s_virtualizationRoots) { - s_virtualizationRoots[i].index = i; + return KERN_RESOURCE_SHORTAGE; } + for (VirtualizationRootHandle i = 0; i < s_maxVirtualizationRoots; ++i) + { + s_virtualizationRoots[i] = VirtualizationRoot{ }; + } + + atomic_thread_fence(memory_order_seq_cst); + return KERN_SUCCESS; } @@ -197,7 +208,7 @@ static VirtualizationRootHandle FindOrDetectRootAtVnode(vnode_t vnode, vfs_conte static VirtualizationRootHandle FindUnusedIndex_Locked() { - for (VirtualizationRootHandle i = 0; i < MaxVirtualizationRoots; ++i) + for (VirtualizationRootHandle i = 0; i < s_maxVirtualizationRoots; ++i) { if (!s_virtualizationRoots[i].inUse) { @@ -208,6 +219,42 @@ static VirtualizationRootHandle FindUnusedIndex_Locked() return RootHandle_None; } +static VirtualizationRootHandle FindUnusedIndexOrGrow_Locked() +{ + VirtualizationRootHandle rootIndex = FindUnusedIndex_Locked(); + + if (RootHandle_None == rootIndex) + { + // No space, resize array + uint16_t newLength = MIN(s_maxVirtualizationRoots * 2u, INT16_MAX + 1u); + if (newLength <= s_maxVirtualizationRoots) + { + return RootHandle_None; + } + + VirtualizationRoot* grownArray = Memory_AllocArray(newLength); + if (nullptr == grownArray) + { + return RootHandle_None; + } + + uint32_t oldSizeBytes = sizeof(s_virtualizationRoots[0]) * s_maxVirtualizationRoots; + memcpy(grownArray, s_virtualizationRoots, oldSizeBytes); + Memory_Free(s_virtualizationRoots, oldSizeBytes); + s_virtualizationRoots = grownArray; + + for (uint16_t i = s_maxVirtualizationRoots; i < newLength; ++i) + { + s_virtualizationRoots[i] = VirtualizationRoot{ }; + } + + rootIndex = s_maxVirtualizationRoots; + s_maxVirtualizationRoots = newLength; + } + + return rootIndex; +} + static bool FsidsAreEqual(fsid_t a, fsid_t b) { return a.val[0] == b.val[0] && a.val[1] == b.val[1]; @@ -215,7 +262,7 @@ static bool FsidsAreEqual(fsid_t a, fsid_t b) static VirtualizationRootHandle FindRootAtVnode_Locked(vnode_t vnode, uint32_t vid, FsidInode fileId) { - for (VirtualizationRootHandle i = 0; i < MaxVirtualizationRoots; ++i) + for (VirtualizationRootHandle i = 0; i < s_maxVirtualizationRoots; ++i) { VirtualizationRoot& rootEntry = s_virtualizationRoots[i]; if (!rootEntry.inUse) @@ -243,17 +290,16 @@ static VirtualizationRootHandle FindRootAtVnode_Locked(vnode_t vnode, uint32_t v // Returns negative value if it failed, or inserted index on success static VirtualizationRootHandle InsertVirtualizationRoot_Locked(PrjFSProviderUserClient* userClient, pid_t clientPID, vnode_t vnode, uint32_t vid, FsidInode persistentIds, const char* path) { - VirtualizationRootHandle rootIndex = FindUnusedIndex_Locked(); + VirtualizationRootHandle rootIndex = FindUnusedIndexOrGrow_Locked(); - if (rootIndex >= 0) + if (RootHandle_None != rootIndex) { - assert(rootIndex < MaxVirtualizationRoots); + assert(rootIndex < s_maxVirtualizationRoots); VirtualizationRoot* root = &s_virtualizationRoots[rootIndex]; root->providerUserClient = userClient; root->providerPid = clientPID; root->inUse = true; - root->index = rootIndex; root->rootVNode = vnode; root->rootVNodeVid = vid; @@ -321,7 +367,7 @@ VirtualizationRootResult VirtualizationRoot_RegisterProviderForPath(PrjFSProvide rootIndex = InsertVirtualizationRoot_Locked(userClient, clientPID, virtualizationRootVNode, rootVid, vnodeIds, virtualizationRootPath); if (rootIndex >= 0) { - assert(rootIndex < MaxVirtualizationRoots); + assert(rootIndex < s_maxVirtualizationRoots); VirtualizationRoot* root = &s_virtualizationRoots[rootIndex]; strlcpy(root->path, virtualizationRootPath, sizeof(root->path)); @@ -359,10 +405,10 @@ VirtualizationRootResult VirtualizationRoot_RegisterProviderForPath(PrjFSProvide void ActiveProvider_Disconnect(VirtualizationRootHandle rootIndex) { assert(rootIndex >= 0); - assert(rootIndex <= MaxVirtualizationRoots); - RWLock_AcquireExclusive(s_rwLock); { + assert(rootIndex <= s_maxVirtualizationRoots); + VirtualizationRoot* root = &s_virtualizationRoots[rootIndex]; assert(nullptr != root->providerUserClient); @@ -378,19 +424,20 @@ void ActiveProvider_Disconnect(VirtualizationRootHandle rootIndex) errno_t ActiveProvider_SendMessage(VirtualizationRootHandle rootIndex, const Message message) { assert(rootIndex >= 0); - assert(rootIndex < MaxVirtualizationRoots); PrjFSProviderUserClient* userClient = nullptr; - RWLock_AcquireExclusive(s_rwLock); + RWLock_AcquireShared(s_rwLock); { + assert(rootIndex < s_maxVirtualizationRoots); + userClient = s_virtualizationRoots[rootIndex].providerUserClient; if (nullptr != userClient) { userClient->retain(); } } - RWLock_ReleaseExclusive(s_rwLock); + RWLock_ReleaseShared(s_rwLock); if (nullptr != userClient) { @@ -436,12 +483,12 @@ static const char* GetRelativePath(const char* path, const char* root) const char* VirtualizationRoot_GetRootRelativePath(VirtualizationRootHandle rootIndex, const char* path) { assert(rootIndex >= 0); - assert(rootIndex <= MaxVirtualizationRoots); const char* relativePath; RWLock_AcquireShared(s_rwLock); { + assert(rootIndex < s_maxVirtualizationRoots); assert(s_virtualizationRoots[rootIndex].inUse); relativePath = GetRelativePath(path, s_virtualizationRoots[rootIndex].path); }