From 9eb2ceada5e31f2c4bc9fa372a8622efaad67398 Mon Sep 17 00:00:00 2001 From: Kenneth Skovhede Date: Tue, 1 Jul 2025 11:45:29 +0200 Subject: [PATCH] Update permission check to drop privilege This PR adjusts the check for SeBackupPrivilege and unassigns the privilege after testing if it could be enabled. This fixes #6317 --- Duplicati/Library/Utility/PermissionHelper.cs | 63 ++++++++++++++----- 1 file changed, 49 insertions(+), 14 deletions(-) diff --git a/Duplicati/Library/Utility/PermissionHelper.cs b/Duplicati/Library/Utility/PermissionHelper.cs index e27a6bb4d..0b20a5c8c 100644 --- a/Duplicati/Library/Utility/PermissionHelper.cs +++ b/Duplicati/Library/Utility/PermissionHelper.cs @@ -124,6 +124,10 @@ public static class PermissionHelper /// Constant for privilege enabled attribute. /// const int SE_PRIVILEGE_ENABLED = 0x00000002; + /// + /// The TOKEN_INFORMATION_CLASS.TokenPrivileges value + /// + const int TOKEN_INFORMATION_TOKEN_PRIVILEGES = 3; /// /// Structure that contains a locally unique identifier (LUID). @@ -244,7 +248,7 @@ public static class PermissionHelper ref TOKEN_PRIVILEGES NewState, int BufferLength, IntPtr PreviousState, - IntPtr ReturnLength + out uint ReturnLength ); /// @@ -254,6 +258,15 @@ public static class PermissionHelper [DllImport("kernel32.dll")] static extern uint GetLastError(); + /// + /// Closes an open object handle. + /// + /// The handle to the object to be closed. + /// true if the function succeeds; otherwise, false. + [DllImport("kernel32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool CloseHandle(IntPtr hObject); + /// /// Flag tracking if backup privilege is enabled /// @@ -272,37 +285,59 @@ public static class PermissionHelper if (_isPrivilegeEnabled.HasValue) return _isPrivilegeEnabled.Value; + _isPrivilegeEnabled = false; + + // Open the primary token for this process + if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, out var hTok)) + return false; + try { - _isPrivilegeEnabled = false; - if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, out IntPtr tokenHandle)) - return false; - - if (!LookupPrivilegeValue(null, privilegeName, out LUID luid)) + // Translate the textual privilege name (e.g. "SeBackupPrivilege") to a LUID + if (!LookupPrivilegeValue(null, privilegeName, out var luid)) return false; + // Build TOKEN_PRIVILEGES {1 entry, SE_PRIVILEGE_ENABLED} var tp = new TOKEN_PRIVILEGES { PrivilegeCount = 1, - Privileges = new LUID_AND_ATTRIBUTES() + Privileges = new LUID_AND_ATTRIBUTES { Luid = luid, Attributes = SE_PRIVILEGE_ENABLED } }; - if (!AdjustTokenPrivileges(tokenHandle, false, ref tp, 0, IntPtr.Zero, IntPtr.Zero)) - return false; + // Query the size of the current privileges set + GetTokenInformation(hTok, TOKEN_INFORMATION_TOKEN_PRIVILEGES, IntPtr.Zero, 0, out int requiredLen); + + // Allocate a buffer of the requested size + var prevStatePtr = Marshal.AllocHGlobal(requiredLen); + + try + { + // Enable the privilege, capturing the previous state + if (!AdjustTokenPrivileges(hTok, false, ref tp, requiredLen, prevStatePtr, out var _)) + return false; + + // If GetLastError() == ERROR_SUCCESS (0), the privilege is now enabled + _isPrivilegeEnabled = Marshal.GetLastWin32Error() == 0; + + // Restore the original state + var oldState = Marshal.PtrToStructure(prevStatePtr); + AdjustTokenPrivileges(hTok, false, ref oldState, 0, IntPtr.Zero, out var _); + } + finally + { + Marshal.FreeHGlobal(prevStatePtr); + } - _isPrivilegeEnabled = (GetLastError() == 0); // If not zero, then the privilege wasn't assigned (e.g., ERROR_NOT_ALL_ASSIGNED) return _isPrivilegeEnabled.Value; } - catch (Exception) + finally { - // Ignore this error + CloseHandle(hTok); } - - return _isPrivilegeEnabled ?? false; } ///