Reputation: 3586
For debugging purposes, I am iterating the threads of my own application, and trying to report the thread times (looking for a rogue thread). When I iterate the threads, I get access denied if the threadId = GetCurrentThreadId
.
Here's an example of code to demonstrate the problem (delphi):
program Project9;
{$APPTYPE CONSOLE}
{$R *.res}
uses
Windows, System.SysUtils, TlHelp32;
type
TOpenThreadFunc = function(DesiredAccess: DWORD; InheritHandle: BOOL; ThreadID: DWORD): THandle; stdcall;
var
OpenThreadFunc: TOpenThreadFunc;
function OpenThread(id : DWORD) : THandle;
const
THREAD_GET_CONTEXT = $0008;
THREAD_QUERY_INFORMATION = $0040;
var
Kernel32Lib, ThreadHandle: THandle;
begin
Result := 0;
if @OpenThreadFunc = nil then
begin
Kernel32Lib := GetModuleHandle(kernel32);
OpenThreadFunc := GetProcAddress(Kernel32Lib, 'OpenThread');
end;
result := OpenThreadFunc(THREAD_QUERY_INFORMATION, False, id);
end;
procedure dumpThreads;
var
SnapProcHandle: THandle;
NextProc : Boolean;
TThreadEntry : TThreadEntry32;
Proceed : Boolean;
pid, tid : Cardinal;
h : THandle;
begin
pid := GetCurrentProcessId;
tid := GetCurrentThreadId;
SnapProcHandle := CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0); //Takes a snapshot of the all threads
Proceed := (SnapProcHandle <> INVALID_HANDLE_VALUE);
if Proceed then
try
TThreadEntry.dwSize := SizeOf(TThreadEntry);
NextProc := Thread32First(SnapProcHandle, TThreadEntry);//get the first Thread
while NextProc do
begin
if TThreadEntry.th32OwnerProcessID = PID then //Check the owner Pid against the PID requested
begin
write('Thread '+inttostr(TThreadEntry.th32ThreadID));
if (tid = TThreadEntry.th32ThreadID) then
write(' (this thread)');
h := OpenThread(TThreadEntry.th32ThreadID);
if h <> 0 then
try
writeln(': open ok');
finally
CloseHandle(h);
end
else
writeln(': '+SysErrorMessage(GetLastError));
end;
NextProc := Thread32Next(SnapProcHandle, TThreadEntry);//get the Next Thread
end;
finally
CloseHandle(SnapProcHandle);//Close the Handle
end;
end;
function DebugCtrlC(dwCtrlType : DWORD) :BOOL;
begin
writeln('ctrl-c');
dumpThreads;
end;
var
s : String;
begin
SetConsoleCtrlHandler(@DebugCtrlC, true);
try
writeln('enter anything to see threads, ''x'' to exit. or press ctrl-c to see threads');
repeat
readln(s);
if s <> '' then
dumpThreads;
until s = 'x';
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
end.
I get access denied for this thread when pressing ctrl-c - why can't the thread get a handle to itself, but it can for all the other threads in the process?
Upvotes: 1
Views: 2374
Reputation: 3586
So actually it turns out that there's a very specific set of conditions that means that a console can't get a handle to the thread in the thread itself - and that's when the thread is created in the console host to pass a CTRL+C notification to the console (the test conditions I was testing under)
Upvotes: 0
Reputation: 33804
are some kernel object can be opened, based on 2 things:
usually thread can open self handle, but can be exceptions, one is - thread created by system, for handle console control signals.
minimum code for reproduce (c++):
HANDLE g_hEvent;
BOOL WINAPI HandlerRoutine(DWORD dwCtrlType)
{
if (CTRL_C_EVENT == dwCtrlType)
{
if (HANDLE hThread = OpenThread(THREAD_QUERY_INFORMATION,
FALSE, GetCurrentThreadId()))
{
CloseHandle(hThread);
}
else GetLastError();
SetEvent(g_hEvent);
}
return TRUE;
}
and from console application call
if (g_hEvent = CreateEvent(0, TRUE, FALSE, 0))
{
if (SetConsoleCtrlHandler(HandlerRoutine, TRUE))
{
// send ctrl+c, for not manually do this
if (GenerateConsoleCtrlEvent (CTRL_C_EVENT, 0))
{
WaitForSingleObject(g_hEvent, INFINITE);
}
SetConsoleCtrlHandler(HandlerRoutine, FALSE);
}
CloseHandle(g_hEvent);
}
can in test view that
OpenThread(THREAD_QUERY_INFORMATION, FALSE, GetCurrentThreadId())
failed with error - ERROR_ACCESS_DENIED
why this happen ? need look for thread security descriptor. the simply code for this can look like:
void DumpObjectSD(HANDLE hObject = GetCurrentThread())
{
ULONG cb = 0, rcb = 0x40;
static volatile UCHAR guz;
PVOID stack = alloca(guz);
PSECURITY_DESCRIPTOR psd = 0;
do
{
if (cb < rcb)
{
cb = RtlPointerToOffset(psd = alloca(rcb - cb), stack);
}
if (GetKernelObjectSecurity(hObject,
OWNER_SECURITY_INFORMATION|DACL_SECURITY_INFORMATION|LABEL_SECURITY_INFORMATION,
psd, cb, &rcb))
{
PWSTR sz;
if (ConvertSecurityDescriptorToStringSecurityDescriptor(psd, SDDL_REVISION_1,
OWNER_SECURITY_INFORMATION|DACL_SECURITY_INFORMATION|LABEL_SECURITY_INFORMATION, &sz, &rcb))
{
DbgPrint("%S\n", sz);
LocalFree(sz);
}
break;
}
} while (GetLastError() == ERROR_INSUFFICIENT_BUFFER);
}
and call this from console handler thread and from usual (first thread) for compare.
the SD of usual process thread can look like:
for not elevated process:
O:S-1-5-21-*
D:(A;;0x1fffff;;;S-1-5-21-*)(A;;0x1fffff;;;SY)(A;;0x121848;;;S-1-5-5-0-LogonSessionId)
S:AI(ML;;NWNR;;;ME)
or for elevated ( running as admin)
O:BA
D:(A;;0x1fffff;;;BA)(A;;0x1fffff;;;SY)(A;;0x121848;;;S-1-5-5-0-LogonSessionId)
S:AI(ML;;NWNR;;;HI)
but when this called from handler thread (auto created by system) - we got another dacl:
for not elevated:
O:BA
D:(A;;0x1fffff;;;S-1-5-21-*)(A;;0x1fffff;;;SY)(A;;0x121848;;;S-1-5-5-0-LogonSessionId)
S:AI(ML;;NWNR;;;SI)
for elevated:
O:BA
D:(A;;0x1fffff;;;BA)(A;;0x1fffff;;;SY)(A;;0x121848;;;S-1-5-5-0-LogonSessionId)
S:AI(ML;;NWNR;;;SI)
different here in SYSTEM_MANDATORY_LABEL
S:AI(ML;;NWNR;;;SI)
the "ML"
here is SDDL_MANDATORY_LABEL
(SYSTEM_MANDATORY_LABEL_ACE_TYPE
)
Mandatory label rights:
"NW"
- SDDL_NO_WRITE_UP
(SYSTEM_MANDATORY_LABEL_NO_WRITE_UP
)
"NR"
- SDDL_NO_READ_UP
( SYSTEM_MANDATORY_LABEL_NO_READ_UP
)
and point main - label value (sid):
handler thread always have "SI"
- SDDL_ML_SYSTEM
- System integrity level.
while usual threads have "ME"
- SDDL_MLMEDIUM
- Medium integrity level or
"HI"
- SDDL_ML_HIGH
- High integrity level, when run as admin
so - because this thread have higher integrity level ( System ) than usual process integrity level in token ( High integrity level or bellow, if not system process) and with no read and write up rights - we can not open this thread with read or write access, only with execute access.
we can do next test in HandlerRoutine
- try open thread with MAXIMUM_ALLOWED
and look for granted access with NtQueryObject
( use ObjectBasicInformation
)
if (HANDLE hThread = OpenThread(MAXIMUM_ALLOWED, FALSE, GetCurrentThreadId()))
{
OBJECT_BASIC_INFORMATION obi;
if (0 <= ZwQueryObject(hThread, ObjectBasicInformation, &obi, sizeof(obi), 0))
{
DbgPrint("[%08x]\n", obi.GrantedAccess);
}
CloseHandle(hThread);
}
we got here: [00101800]
which mean:
SYNCHRONIZE | THREAD_RESUME | THREAD_QUERY_LIMITED_INFORMATION
also we can query ObjectTypeInformation
and get GENERIC_MAPPING
for thread object.
OBJECT_BASIC_INFORMATION obi;
if (0 <= ZwQueryObject(hThread, ObjectBasicInformation, &obi, sizeof(obi), 0))
{
ULONG rcb, cb = (obi.TypeInfoSize + __alignof(OBJECT_TYPE_INFORMATION) - 1) & ~(__alignof(OBJECT_TYPE_INFORMATION) - 1);
POBJECT_TYPE_INFORMATION poti = (POBJECT_TYPE_INFORMATION)alloca(cb);
if (0 <= ZwQueryObject(hThread, ObjectTypeInformation, poti, cb, &rcb))
{
DbgPrint("a=%08x\nr=%08x\nw=%08x\ne=%08x\n",
poti->GenericMapping.GenericAll,
poti->GenericMapping.GenericRead,
poti->GenericMapping.GenericWrite,
poti->GenericMapping.GenericExecute);
}
}
and got
a=001fffff
r=00020048
w=00020437
e=00121800
so we in general can open this thread with GenericExecute
access, except 00020000
(READ_CONTROL
) because this access in GenericRead and GenericWrite and policy - no read/write up.
however for almost all api, where handle (thread or generic) required, we can use GetCurrentThread()
- pseudo handle for the calling thread. of course this can use only for current thread. so we can call for example
FILETIME CreationTime, ExitTime, KernelTime, UserTime;
GetThreadTimes(GetCurrentThread(), &CreationTime, &ExitTime, &KernelTime, &UserTime);
the CloseHandle(GetCurrentThread());
also valid call - Calling the CloseHandle function with this handle has no effect. (simply nothing will be). and this pseudo handle have GENERIC_ALL
granted access.
so your OpenThread
routine can check for thread id - if it is equal to GetCurrentThreadId()
- simply return GetCurrentThread()
.
also we can call
DuplicateHandle(GetCurrentProcess(), GetCurrentThread(), GetCurrentProcess(), &hThread, 0, 0, DUPLICATE_SAME_ACCESS);
this also will be work well for this thread. however usual use GetCurrentThread()
is enough
Upvotes: 4