From 2eba48ff5064ec01430e9ad617ad476c5f85133e Mon Sep 17 00:00:00 2001 From: Seyedamin Mousavichashmi <76259887+SA-Mousavichashmi@users.noreply.github.com> Date: Thu, 8 Oct 2026 09:55:43 +0330 Subject: [PATCH] fix: enable SeIncreaseBasePriorityPrivilege for high I/O priority and map privilege errors --- .../IProcessMemoryPriorityNativeApi.cs | 5 ++ .../ProcessMemoryPriorityNativeMethods.cs | 87 +++++++++++++++++++ Services/ProcessMemoryPriorityService.cs | 7 +- .../ProcessMemoryPriorityServiceTests.cs | 44 +++++++++- 4 files changed, 141 insertions(+), 2 deletions(-) diff --git a/Platforms/Windows/IProcessMemoryPriorityNativeApi.cs b/Platforms/Windows/IProcessMemoryPriorityNativeApi.cs index 1511d3c..1fcd615 100644 --- a/Platforms/Windows/IProcessMemoryPriorityNativeApi.cs +++ b/Platforms/Windows/IProcessMemoryPriorityNativeApi.cs @@ -34,6 +34,8 @@ int QueryIoPriority(SafeProcessHandle process, ref int priority, out uint return } int SetIoPriority(SafeProcessHandle process, ref int priority) => unchecked((int)0xC0000002); + + bool TryEnableProcessPrivilege(string privilegeName) => false; } public sealed class ProcessMemoryPriorityNativeApi : IProcessMemoryPriorityNativeApi @@ -96,6 +98,9 @@ public int SetIoPriority(SafeProcessHandle process, ref int priority) => ProcessMemoryPriorityNativeMethods.ProcessIoPriority, ref priority, sizeof(int)); + + public bool TryEnableProcessPrivilege(string privilegeName) => + ProcessMemoryPriorityNativeMethods.TryEnablePrivilege(privilegeName); } public enum ProcessInformationClass diff --git a/Platforms/Windows/ProcessMemoryPriorityNativeMethods.cs b/Platforms/Windows/ProcessMemoryPriorityNativeMethods.cs index d0ba944..68029dd 100644 --- a/Platforms/Windows/ProcessMemoryPriorityNativeMethods.cs +++ b/Platforms/Windows/ProcessMemoryPriorityNativeMethods.cs @@ -3,12 +3,32 @@ */ namespace ThreadPilot.Platforms.Windows { + using System; using System.Runtime.InteropServices; using Microsoft.Win32.SafeHandles; internal static partial class ProcessMemoryPriorityNativeMethods { internal const int ProcessIoPriority = 33; + internal const string SeIncreaseBasePriorityPrivilege = "SeIncreaseBasePriorityPrivilege"; + private const uint TokenAdjustPrivileges = 0x0020; + private const uint TokenQuery = 0x0008; + private const uint SePrivilegeEnabled = 0x00000002; + + [StructLayout(LayoutKind.Sequential)] + internal struct Luid + { + public uint LowPart; + public int HighPart; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct TokenPrivileges + { + public uint PrivilegeCount; + public Luid Luid; + public uint Attributes; + } [LibraryImport("kernel32.dll", SetLastError = true)] public static partial SafeProcessHandle OpenProcess( @@ -46,5 +66,72 @@ public static partial int NtSetInformationProcess( int processInformationClass, ref int processInformation, int processInformationLength); + + [LibraryImport("kernel32.dll")] + public static partial IntPtr GetCurrentProcess(); + + [LibraryImport("advapi32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + public static partial bool OpenProcessToken( + IntPtr processHandle, + uint desiredAccess, + out SafeAccessTokenHandle tokenHandle); + + [LibraryImport("advapi32.dll", EntryPoint = "LookupPrivilegeValueW", SetLastError = true, StringMarshalling = StringMarshalling.Utf16)] + [return: MarshalAs(UnmanagedType.Bool)] + public static partial bool LookupPrivilegeValue( + string? lpSystemName, + string lpName, + out Luid lpLuid); + + [LibraryImport("advapi32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + public static partial bool AdjustTokenPrivileges( + SafeAccessTokenHandle tokenHandle, + [MarshalAs(UnmanagedType.Bool)] bool disableAllPrivileges, + ref TokenPrivileges newState, + uint bufferLength, + IntPtr previousState, + IntPtr returnLength); + + internal static bool TryEnablePrivilege(string privilegeName) + { + if (string.IsNullOrWhiteSpace(privilegeName)) + { + return false; + } + + if (!OpenProcessToken(GetCurrentProcess(), TokenAdjustPrivileges | TokenQuery, out var tokenHandle)) + { + return false; + } + + using (tokenHandle) + { + if (tokenHandle.IsInvalid) + { + return false; + } + + if (!LookupPrivilegeValue(null, privilegeName, out var luid)) + { + return false; + } + + var tp = new TokenPrivileges + { + PrivilegeCount = 1, + Luid = luid, + Attributes = SePrivilegeEnabled, + }; + + if (!AdjustTokenPrivileges(tokenHandle, false, ref tp, 0, IntPtr.Zero, IntPtr.Zero)) + { + return false; + } + + return Marshal.GetLastWin32Error() == 0; + } + } } } diff --git a/Services/ProcessMemoryPriorityService.cs b/Services/ProcessMemoryPriorityService.cs index 592d9bd..8536d8f 100644 --- a/Services/ProcessMemoryPriorityService.cs +++ b/Services/ProcessMemoryPriorityService.cs @@ -234,6 +234,11 @@ public Task SetIoPriorityAsync(ProcessModel process, Pro try { + if (priority == ProcessIoPriority.High) + { + this.nativeApi.TryEnableProcessPrivilege(ProcessMemoryPriorityNativeMethods.SeIncreaseBasePriorityPrivilege); + } + using var handle = this.nativeApi.OpenProcess( ProcessAccessFlags.PROCESS_SET_INFORMATION, inheritHandle: false, @@ -254,7 +259,7 @@ public Task SetIoPriorityAsync(ProcessModel process, Pro return Task.FromResult(status switch { - unchecked((int)0xC0000022) => ProcessOperationResult.Failed( + unchecked((int)0xC0000022) or unchecked((int)0xC0000061) => ProcessOperationResult.Failed( AffinityApplyErrorCodes.AccessDenied, ProcessOperationUserMessages.AccessDenied, $"NtSetInformationProcess failed with NTSTATUS 0x{status:X8}.", diff --git a/Tests/ThreadPilot.Core.Tests/ProcessMemoryPriorityServiceTests.cs b/Tests/ThreadPilot.Core.Tests/ProcessMemoryPriorityServiceTests.cs index 13414cb..2600bf1 100644 --- a/Tests/ThreadPilot.Core.Tests/ProcessMemoryPriorityServiceTests.cs +++ b/Tests/ThreadPilot.Core.Tests/ProcessMemoryPriorityServiceTests.cs @@ -60,6 +60,38 @@ public async Task IoPriority_WithValidProcess_RoundTripsThroughNativeApi() Assert.Equal(ProcessIoPriority.High, nativeApi.LastSetIoPriority); } + [Fact] + public async Task SetIoPriorityAsync_WhenStatusPrivilegeNotHeld_ReturnsSafeAccessDeniedFailure() + { + var nativeApi = new FakeProcessMemoryPriorityNativeApi + { + SetIoPriorityStatus = unchecked((int)0xC0000061), + }; + var service = CreateService(nativeApi); + var process = CreateProcess(); + + var result = await service.SetIoPriorityAsync(process, ProcessIoPriority.High); + + Assert.False(result.Success); + Assert.True(result.IsAccessDenied); + Assert.Equal(AffinityApplyErrorCodes.AccessDenied, result.ErrorCode); + Assert.Equal(ProcessOperationUserMessages.AccessDenied, result.UserMessage); + Assert.Contains("0xC0000061", result.TechnicalMessage); + } + + [Fact] + public async Task SetIoPriorityAsync_WhenHighPriority_AttemptsToEnableSeIncreaseBasePriorityPrivilege() + { + var nativeApi = new FakeProcessMemoryPriorityNativeApi(); + var service = CreateService(nativeApi); + var process = CreateProcess(); + + var result = await service.SetIoPriorityAsync(process, ProcessIoPriority.High); + + Assert.True(result.Success); + Assert.Contains(ProcessMemoryPriorityNativeMethods.SeIncreaseBasePriorityPrivilege, nativeApi.RequestedPrivileges); + } + [Theory] [InlineData(1, ProcessMemoryPriority.VeryLow)] [InlineData(2, ProcessMemoryPriority.Low)] @@ -228,6 +260,10 @@ private sealed class FakeProcessMemoryPriorityNativeApi : IProcessMemoryPriority public int LastError { get; init; } + public int SetIoPriorityStatus { get; init; } + + public List RequestedPrivileges { get; } = new(); + public SafeProcessHandle OpenProcess(ProcessAccessFlags access, bool inheritHandle, uint processId) { this.LastOpenAccess = access; @@ -276,7 +312,13 @@ public int QueryIoPriority(SafeProcessHandle process, ref int priority, out uint public int SetIoPriority(SafeProcessHandle process, ref int priority) { this.LastSetIoPriority = (ProcessIoPriority)priority; - return 0; + return this.SetIoPriorityStatus; + } + + public bool TryEnableProcessPrivilege(string privilegeName) + { + this.RequestedPrivileges.Add(privilegeName); + return true; } } }