Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions Platforms/Windows/IProcessMemoryPriorityNativeApi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
87 changes: 87 additions & 0 deletions Platforms/Windows/ProcessMemoryPriorityNativeMethods.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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;
}
}
}
}
7 changes: 6 additions & 1 deletion Services/ProcessMemoryPriorityService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,11 @@ public Task<ProcessOperationResult> 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,
Expand All @@ -254,7 +259,7 @@ public Task<ProcessOperationResult> 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}.",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down Expand Up @@ -228,6 +260,10 @@ private sealed class FakeProcessMemoryPriorityNativeApi : IProcessMemoryPriority

public int LastError { get; init; }

public int SetIoPriorityStatus { get; init; }

public List<string> RequestedPrivileges { get; } = new();

public SafeProcessHandle OpenProcess(ProcessAccessFlags access, bool inheritHandle, uint processId)
{
this.LastOpenAccess = access;
Expand Down Expand Up @@ -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;
}
}
}
Expand Down
Loading