Skip to content
This repository has been archived by the owner on Jul 26, 2023. It is now read-only.

Kernel32 Threading API #463

Merged
merged 12 commits into from
Jul 3, 2020
Merged
178 changes: 178 additions & 0 deletions src/Kernel32.Tests/storebanned/Kernel32Facts.cs
Original file line number Diff line number Diff line change
Expand Up @@ -944,6 +944,184 @@ public void SetHandleInformation_DoesNotThrow()
HandleFlags.HANDLE_FLAG_NONE));
}

/// <summary>
/// Basic validation for <see cref="Kernel32.CreateThread(SECURITY_ATTRIBUTES*, SIZE_T, THREAD_START_ROUTINE, IntPtr, CreateProcessFlags, int*)"/>
///
/// Creates a thread by supplying <see cref="CreateThread_Test_ThreadMain(IntPtr)"/> as its ThreadProc/<see cref="Kernel32.THREAD_START_ROUTINE"/>.
/// The ThreadProc updates a bool value (supplied by the thread that created it) from false -> true. This change
/// is observed by the calling thread as proof of successful thread-creation.
///
/// Also validates that the (native) Thread-ID for the newly created Thread is different than the (native) Thread-ID
/// of that of the calling thread.
/// </summary>
[Fact]
public unsafe void CreateThread_Test()
{
var secAttrs = new Kernel32.SECURITY_ATTRIBUTES
{
bInheritHandle = 1,
lpSecurityDescriptor = IntPtr.Zero,
nLength = Marshal.SizeOf<Kernel32.SECURITY_ATTRIBUTES>()
vatsan-madhavan marked this conversation as resolved.
Show resolved Hide resolved
};

var dwThreadId = Kernel32.GetCurrentThreadId();

var result = false;
var gcHandle = GCHandle.Alloc(result);
try
{
var dwNewThreadId = 0;
using var hThread =
Kernel32.CreateThread(
&secAttrs,
SIZE_T.Zero,
new Kernel32.THREAD_START_ROUTINE(CreateThread_Test_ThreadMain),
GCHandle.ToIntPtr(gcHandle),
Kernel32.CreateProcessFlags.None,
&dwNewThreadId);
Kernel32.WaitForSingleObject(hThread, -1);

result = (bool)gcHandle.Target;
Assert.True(result);
Assert.NotEqual(dwThreadId, dwNewThreadId);
}
finally
{
gcHandle.Free();
}
}

/// <summary>
/// Basic validation for <see cref="CreateRemoteThread(IntPtr, SECURITY_ATTRIBUTES*, SIZE_T, THREAD_START_ROUTINE, IntPtr, CreateProcessFlags, int*)"/>
/// Note that this test DOES NOT create a true REMOTE thread in a foreign process; it just leverages this function to create a thread in the current (i.e, the test) procrss.
/// Nevertheless, this approach provides modest confidence that the P/Invoke definition is well-formed.
///
/// Creates a thread by supplying <see cref="CreateThread_Test_ThreadMain(IntPtr)"/> as its ThreadProc/<see cref="Kernel32.THREAD_START_ROUTINE"/>.
/// The ThreadProc updates a bool value (supplied by the thread that created it) from false -> true. This change
/// is observed by the calling thread as proof of successful thread-creation.
///
/// Also validates that the (native) Thread-ID for the newly created Thread is different than the (native) Thread-ID
/// of that of the calling thread.
/// </summary>
[Fact]
public unsafe void CreateRemoteThread_PseudoTest()
{
var secAttrs = new Kernel32.SECURITY_ATTRIBUTES
{
bInheritHandle = 1,
lpSecurityDescriptor = IntPtr.Zero,
nLength = Marshal.SizeOf<Kernel32.SECURITY_ATTRIBUTES>()
};

var dwThreadId = Kernel32.GetCurrentThreadId();
using var hProcess = Kernel32.GetCurrentProcess();

var result = false;
var gcHandle = GCHandle.Alloc(result);
try
{
var dwNewThreadId = 0;
using var hThread =
Kernel32.CreateRemoteThread(
hProcess.DangerousGetHandle(),
&secAttrs,
SIZE_T.Zero,
new Kernel32.THREAD_START_ROUTINE(CreateThread_Test_ThreadMain),
GCHandle.ToIntPtr(gcHandle),
Kernel32.CreateProcessFlags.None,
&dwNewThreadId);
Kernel32.WaitForSingleObject(hThread, -1);

result = (bool)gcHandle.Target;
Assert.True(result);
Assert.NotEqual(dwThreadId, dwNewThreadId);
}
finally
{
gcHandle.Free();
}
}

/// <summary>
/// Basic validation for <see cref="CreateRemoteThreadEx(IntPtr, SECURITY_ATTRIBUTES*, SIZE_T, THREAD_START_ROUTINE, IntPtr, CreateProcessFlags, PROC_THREAD_ATTRIBUTE_LIST*, int*)"/>
/// Note that this test DOES NOT create a true REMOTE thread in a foreign process; it just leverages this function to create a thread in the current (i.e, the test) procrss.
/// Nevertheless, this approach provides modest confidence that the P/Invoke definition is well-formed.
///
/// Creates a thread by supplying <see cref="CreateThread_Test_ThreadMain(IntPtr)"/> as its ThreadProc/<see cref="Kernel32.THREAD_START_ROUTINE"/>.
/// The ThreadProc updates a bool value (supplied by the thread that created it) from false -> true. This change
/// is observed by the calling thread as proof of successful thread-creation.
///
/// Also validates that the (native) Thread-ID for the newly created Thread is different than the (native) Thread-ID
/// of that of the calling thread.
/// </summary>
[Fact]
public unsafe void CreateRemoteThreadEx_PseudoTest()
{
var secAttrs = new Kernel32.SECURITY_ATTRIBUTES
{
bInheritHandle = 1,
lpSecurityDescriptor = IntPtr.Zero,
nLength = Marshal.SizeOf<Kernel32.SECURITY_ATTRIBUTES>()
};

var dwThreadId = Kernel32.GetCurrentThreadId();
using var hProcess = Kernel32.GetCurrentProcess();

var result = false;
var gcHandle = GCHandle.Alloc(result);
vatsan-madhavan marked this conversation as resolved.
Show resolved Hide resolved
try
{
var dwNewThreadId = 0;
using var hThread =
Kernel32.CreateRemoteThreadEx(
hProcess.DangerousGetHandle(),
&secAttrs,
SIZE_T.Zero,
new Kernel32.THREAD_START_ROUTINE(CreateThread_Test_ThreadMain),
vatsan-madhavan marked this conversation as resolved.
Show resolved Hide resolved
GCHandle.ToIntPtr(gcHandle),
Kernel32.CreateProcessFlags.None,
null,
&dwNewThreadId);
Kernel32.WaitForSingleObject(hThread, -1);

result = (bool)gcHandle.Target;
Assert.True(result);
Assert.NotEqual(dwThreadId, dwNewThreadId);
}
finally
{
gcHandle.Free();
}
}

/// <summary>
/// Helper for <see cref="CreateThread_Test"/>, <see cref="CreateRemoteThread_PseudoTest"/> and
/// <see cref="CreateRemoteThreadEx_PseudoTest"/> tests.
/// </summary>
/// <param name="data">
/// Data passed by the test. This is a pinned <see cref="GCHandle"/> to a <see cref="bool"/>
/// which will be updated to <code>true</code> in this method.
/// </param>
/// <returns>
/// Returns 1 on successfully updating the <see cref="GCHandle"/> associated with
/// <paramref name="data"/>, otherwise returns 0
/// </returns>
/// <remarks>See <see cref=" Kernel32.THREAD_START_ROUTINE"/> for general documentation</remarks>
private static int CreateThread_Test_ThreadMain(IntPtr data)
{
var gcHandle = GCHandle.FromIntPtr(data);
try
{
gcHandle.Target = true;
}
catch (Exception e) when (e is InvalidCastException || e is InvalidOperationException)
{
return 0;
}

return 1;
}

private ArraySegment<byte> GetRandomSegment(int size)
{
var result = new ArraySegment<byte>(new byte[size]);
Expand Down
16 changes: 16 additions & 0 deletions src/Kernel32/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
PInvoke.Kernel32.CreateProcessFlags.STACK_SIZE_PARAM_IS_A_RESERVATION = 65536 -> PInvoke.Kernel32.CreateProcessFlags
PInvoke.Kernel32.ErrorModes
PInvoke.Kernel32.ErrorModes.SEM_DEFAULT = 0 -> PInvoke.Kernel32.ErrorModes
PInvoke.Kernel32.ErrorModes.SEM_FAILCRITICALERRORS = 1 -> PInvoke.Kernel32.ErrorModes
Expand Down Expand Up @@ -54,6 +55,9 @@ PInvoke.Kernel32.STARTUPINFO.lpReserved_IntPtr.set -> void
PInvoke.Kernel32.STARTUPINFO.lpTitle -> char*
PInvoke.Kernel32.STARTUPINFO.lpTitle_IntPtr.get -> System.IntPtr
PInvoke.Kernel32.STARTUPINFO.lpTitle_IntPtr.set -> void
PInvoke.Kernel32.SafeThreadHandle
PInvoke.Kernel32.SafeThreadHandle.SafeThreadHandle() -> void
PInvoke.Kernel32.THREAD_START_ROUTINE
PInvoke.Kernel32.VER_CONDITION
PInvoke.Kernel32.VER_CONDITION.VER_AND = 6 -> PInvoke.Kernel32.VER_CONDITION
PInvoke.Kernel32.VER_CONDITION.VER_EQUAL = 1 -> PInvoke.Kernel32.VER_CONDITION
Expand All @@ -71,8 +75,17 @@ PInvoke.Kernel32.VER_MASK.VER_PRODUCT_TYPE = 128 -> PInvoke.Kernel32.VER_MASK
PInvoke.Kernel32.VER_MASK.VER_SERVICEPACKMAJOR = 32 -> PInvoke.Kernel32.VER_MASK
PInvoke.Kernel32.VER_MASK.VER_SERVICEPACKMINOR = 16 -> PInvoke.Kernel32.VER_MASK
PInvoke.Kernel32.VER_MASK.VER_SUITENAME = 64 -> PInvoke.Kernel32.VER_MASK
override PInvoke.Kernel32.SafeThreadHandle.ReleaseHandle() -> bool
static PInvoke.Kernel32.CompareFileTime(PInvoke.Kernel32.FILETIME lpFileTime1, PInvoke.Kernel32.FILETIME lpFileTime2) -> int
static PInvoke.Kernel32.CompareFileTime(System.IntPtr lpFileTime1, System.IntPtr lpFileTime2) -> int
static PInvoke.Kernel32.CreateRemoteThread(System.IntPtr hProcess, PInvoke.Kernel32.SECURITY_ATTRIBUTES lpThreadAttributes, PInvoke.SIZE_T dwStackSize, PInvoke.Kernel32.THREAD_START_ROUTINE lpStartAddress, System.IntPtr lpParameter, PInvoke.Kernel32.CreateProcessFlags dwCreationFlags, System.IntPtr lpThreadId) -> PInvoke.Kernel32.SafeThreadHandle
static PInvoke.Kernel32.CreateRemoteThread(System.IntPtr hProcess, PInvoke.Kernel32.SECURITY_ATTRIBUTES lpThreadAttributes, PInvoke.SIZE_T dwStackSize, PInvoke.Kernel32.THREAD_START_ROUTINE lpStartAddress, System.IntPtr lpParameter, PInvoke.Kernel32.CreateProcessFlags dwCreationFlags, int* lpThreadId) -> PInvoke.Kernel32.SafeThreadHandle
static PInvoke.Kernel32.CreateRemoteThread(System.IntPtr hProcess, System.IntPtr lpThreadAttributes, PInvoke.SIZE_T dwStackSize, PInvoke.Kernel32.THREAD_START_ROUTINE lpStartAddress, System.IntPtr lpParameter, PInvoke.Kernel32.CreateProcessFlags dwCreationFlags, System.IntPtr lpThreadId) -> PInvoke.Kernel32.SafeThreadHandle
static PInvoke.Kernel32.CreateRemoteThreadEx(System.IntPtr hProcess, PInvoke.Kernel32.SECURITY_ATTRIBUTES lpThreadAttributes, PInvoke.SIZE_T dwStackSize, PInvoke.Kernel32.THREAD_START_ROUTINE lpStartAddress, System.IntPtr lpParameter, PInvoke.Kernel32.CreateProcessFlags dwCreationFlags, PInvoke.Kernel32.PROC_THREAD_ATTRIBUTE_LIST* lpAttributeList, int* lpThreadId) -> PInvoke.Kernel32.SafeThreadHandle
static PInvoke.Kernel32.CreateRemoteThreadEx(System.IntPtr hProcess, PInvoke.Kernel32.SECURITY_ATTRIBUTES lpThreadAttributes, PInvoke.SIZE_T dwStackSize, PInvoke.Kernel32.THREAD_START_ROUTINE lpStartAddress, System.IntPtr lpParameter, PInvoke.Kernel32.CreateProcessFlags dwCreationFlags, System.IntPtr lpAttributeList, System.IntPtr lpThreadId) -> PInvoke.Kernel32.SafeThreadHandle
static PInvoke.Kernel32.CreateRemoteThreadEx(System.IntPtr hProcess, System.IntPtr lpThreadAttributes, PInvoke.SIZE_T dwStackSize, PInvoke.Kernel32.THREAD_START_ROUTINE lpStartAddress, System.IntPtr lpParameter, PInvoke.Kernel32.CreateProcessFlags dwCreationFlags, System.IntPtr lpAttributeList, System.IntPtr lpThreadId) -> PInvoke.Kernel32.SafeThreadHandle
static PInvoke.Kernel32.CreateThread(PInvoke.Kernel32.SECURITY_ATTRIBUTES lpThreadAttributes, PInvoke.SIZE_T dwStackSize, PInvoke.Kernel32.THREAD_START_ROUTINE lpStartAddress, System.IntPtr lpParameter, PInvoke.Kernel32.CreateProcessFlags dwCreationFlags, out int lpThreadId) -> PInvoke.Kernel32.SafeThreadHandle
static PInvoke.Kernel32.CreateThread(System.IntPtr lpThreadAttributes, PInvoke.SIZE_T dwStackSize, PInvoke.Kernel32.THREAD_START_ROUTINE lpStartAddress, System.IntPtr lpParameter, PInvoke.Kernel32.CreateProcessFlags dwCreationFlags, System.IntPtr lpThreadId) -> PInvoke.Kernel32.SafeThreadHandle
static PInvoke.Kernel32.FILETIME.explicit operator System.DateTime(PInvoke.Kernel32.FILETIME fileTime) -> System.DateTime
static PInvoke.Kernel32.FileTimeToSystemTime(PInvoke.Kernel32.FILETIME lpFileTime, out PInvoke.Kernel32.SYSTEMTIME lpSystemTime) -> bool
static PInvoke.Kernel32.FileTimeToSystemTime(System.IntPtr lpFileTime, System.IntPtr lpSystemTime) -> bool
Expand All @@ -90,6 +103,9 @@ static PInvoke.Kernel32.VerifyVersionInfo(System.IntPtr lpVersionInformation, PI
static PInvoke.Kernel32.VerifyVersionInfo(ref PInvoke.Kernel32.OSVERSIONINFOEX lpVersionInformation, PInvoke.Kernel32.VER_MASK dwTypeMask, long dwlConditionMask) -> PInvoke.NTSTATUS
static PInvoke.Kernel32.WriteProcessMemory(System.IntPtr hProcess, System.IntPtr lpBaseAddress, System.IntPtr lpBuffer, System.UIntPtr nSize, out System.UIntPtr lpNumberOfBytesWritten) -> bool
static extern PInvoke.Kernel32.CompareFileTime(PInvoke.Kernel32.FILETIME* lpFileTime1, PInvoke.Kernel32.FILETIME* lpFileTime2) -> int
static extern PInvoke.Kernel32.CreateRemoteThread(System.IntPtr hProcess, PInvoke.Kernel32.SECURITY_ATTRIBUTES* lpThreadAttributes, PInvoke.SIZE_T dwStackSize, PInvoke.Kernel32.THREAD_START_ROUTINE lpStartAddress, System.IntPtr lpParameter, PInvoke.Kernel32.CreateProcessFlags dwCreationFlags, int* lpThreadId) -> PInvoke.Kernel32.SafeThreadHandle
static extern PInvoke.Kernel32.CreateRemoteThreadEx(System.IntPtr hProcess, PInvoke.Kernel32.SECURITY_ATTRIBUTES* lpThreadAttributes, PInvoke.SIZE_T dwStackSize, PInvoke.Kernel32.THREAD_START_ROUTINE lpStartAddress, System.IntPtr lpParameter, PInvoke.Kernel32.CreateProcessFlags dwCreationFlags, PInvoke.Kernel32.PROC_THREAD_ATTRIBUTE_LIST* lpAttributeList, int* lpThreadId) -> PInvoke.Kernel32.SafeThreadHandle
static extern PInvoke.Kernel32.CreateThread(PInvoke.Kernel32.SECURITY_ATTRIBUTES* lpThreadAttributes, PInvoke.SIZE_T dwStackSize, PInvoke.Kernel32.THREAD_START_ROUTINE lpStartAddress, System.IntPtr lpParameter, PInvoke.Kernel32.CreateProcessFlags dwCreationFlags, int* lpThreadId) -> PInvoke.Kernel32.SafeThreadHandle
static extern PInvoke.Kernel32.FileTimeToSystemTime(PInvoke.Kernel32.FILETIME* lpFileTime, PInvoke.Kernel32.SYSTEMTIME* lpSystemTime) -> bool
static extern PInvoke.Kernel32.GetHandleInformation(System.Runtime.InteropServices.SafeHandle hObject, PInvoke.Kernel32.HandleFlags* lpdwFlags) -> bool
static extern PInvoke.Kernel32.GetProcessId(System.IntPtr Process) -> int
Expand Down
9 changes: 8 additions & 1 deletion src/Kernel32/storebanned/Kernel32+CreateProcessFlags.cs
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,14 @@ public enum CreateProcessFlags
PROCESS_MODE_BACKGROUND_END = 0x00200000,
PROFILE_USER = 0x10000000,
PROFILE_KERNEL = 0x20000000,
PROFILE_SERVER = 0x40000000
PROFILE_SERVER = 0x40000000,

/// <summary>
/// The dwStackSize parameter in <see cref="CreateThread(SECURITY_ATTRIBUTES*, SIZE_T, THREAD_START_ROUTINE, IntPtr, CreateProcessFlags, int*)"/>
/// specifies the initial reserve size of the stack. If this flag is not specified,
/// dwStackSize specifies the commit size.
/// </summary>
STACK_SIZE_PARAM_IS_A_RESERVATION = 0x00010000,
}
}
}
32 changes: 32 additions & 0 deletions src/Kernel32/storebanned/Kernel32+SafeThreadHandle.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// Copyright © .NET Foundation and Contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

#pragma warning disable SA1625 // Element documentation must not be copied and pasted

namespace PInvoke
{
using System.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles;

/// <summary>
/// Contains definition for <see cref="SafeThreadHandle"/>
/// </summary>
public partial class Kernel32
{
/// <summary>
/// A <see cref="SafeHandle"/> for thread handles.
/// </summary>
public class SafeThreadHandle : SafeHandleZeroOrMinusOneIsInvalid
vatsan-madhavan marked this conversation as resolved.
Show resolved Hide resolved
{
public SafeThreadHandle()
: base(ownsHandle: true)
{
}

protected override bool ReleaseHandle()
{
return Kernel32.CloseHandle(this.handle);
}
}
}
}
Loading