Does WriteFile signal the event if it completes synchronously - c

Does the WriteFile function signal the event passed in via the lpOverlapped parameter if it completes synchronously and succeeds? Does it signal the event if it fails synchronously? I have opened the handle to a file with the FILE_FLAG_OVERLAPPED flag. I wasn't to able to figure this out from the documentation and couldn't repro this case easily in code.

first of all this question related not only to WriteFile but to any asynchronous I/O function - almost all functions which get pointer to an OVERLAPPED structure. because for all this functions IRP (I/O request packet) (look it definition in wdm.h) is allocated. hEvent handle from OVERLAPPED converted to object pointer and stored in PKEVENT UserEvent; member of IRP. the event is set (or not set) exactly when IRP is completed in IopCompleteRequest routine. the IRP completion function is common for all I/O api, so and rules (when completion fire) is related to all. unfortunately this is very bad documented. the win32 layer (compare NT layer) added additional ,very thin, issues here.
based on wrk src code, we can see that I/O Manager fire completion (was 3 types - event, apc and iocp (mutually exclusive)) for asynchronous io when
!NT_ERROR( irp->IoStatus.Status ) or irp->PendingReturned.
if we use native api, which direct return NTSTATUS - when (ULONG)status < 0xc0000000. but here was very problematic range 0x80000000 <= status < 0xc0000000 or NT_WARNING(status) when unclear - are completion (even set, apc or packet to iocp queue) will be set. this is because before allocate IRP I/O Manager do some basic checks and can return error from here. usually I/O Manager return errors from NT_ERROR(status) , which correct mean that will be no completion (event will be not set)), but exist and rarely exceptions. for example for ReadDirectoryChangesW (or ZwNotifyChangeDirectoryFile) the lpBuffer pointer must be DWORD-aligned (aligned exactly as FILE_NOTIFY_INFORMATION) otherwise I/O Manager return STATUS_DATATYPE_MISALIGNMENT (0x80000002) from NT_WARNING range. but will be no completion (event set) in this case, because function fail before allocate IRP. from another case, if we call FSCTL_FILESYSTEM_GET_STATISTICS with not large enough buffer - file system driver (not I/O Manager ) return STATUS_BUFFER_OVERFLOW (0x80000005). but because at this point IRP already allocated and code not from NT_ERROR range - will be event set.
so if error from I/O Manager (before IRP allocated) - will be no completion. otherwise if error from driver (to which passed IRP) completion will be if function return !NT_ERROR(status). as result if we get:
NT_SUCCESS(status) (the STATUS_PENDING (0x103) is part of this) - will
be
completion
NT_ERROR(status) will be no completion
NT_WARNING(status) - unclear, depend this error from I/O Manager
(no) or driver(yes)
but with win32 layer make situation more worse. because unclear how it interpret NT_WARNING(status) - most win32 api interpret this as error - return false and set last error (converted from status). but some api - for example ReadDirectoryChangesW interpret this as success code - return true and not set last error. as result if we call ReadDirectoryChangesW with bad aligned buffer (but valid other parameters) - it return.. true and not set any error. but api call is really fail. the ZwNotifyChangeDirectoryFile internal return STATUS_DATATYPE_MISALIGNMENT here.
from another side, if DeviceIoControl for FSCTL_FILESYSTEM_GET_STATISTICS fail (return false) with code ERROR_MORE_DATA (converted from STATUS_BUFFER_OVERFLOW) event(completion) will be set in this case.
also by win32 error code we can not understand - are initial status was NT_ERROR or NT_WARNING code - conversion (RtlNtStatusToDosError) status to win32 error lost this info
problem with NT_WARNING(status) range, begin from vista, can be resolved if we use IOCP completion (instead event) and set FILE_SKIP_COMPLETION_PORT_ON_SUCCESS on file - in this case I/O manager queue a completion entry to the port, when and only when STATUS_PENDING will be returned by native api call. for win32 layer this usually mean that api return false and last error is ERROR_IO_PENDING. exceptions - WriteFileEx, ReadFileEx which return true here. however this not help in case ReadDirectoryChangesW anyway (I assume that this is windows bug)
also read FILE_SKIP_SET_EVENT_ON_HANDLE section - this implicitly say when explicit event (from overlapped) set in case asynchronous function - when request returns with a success code, or the error returned is ERROR_IO_PENDING. but here question - what is success code ? true returned by win32 api ? not always, as visible from FSCTL_FILESYSTEM_GET_STATISTICS - the ERROR_MORE_DATA (STATUS_BUFFER_OVERFLOW) also success code. or STATUS_NO_MORE_FILES returned by NtQueryDirectoryFile also success code - event (apc or iocp completion) will be set. but same NtQueryDirectoryFile can return STATUS_DATATYPE_MISALIGNMENT, when FileInformation bad aligned - this is fail code, because returned from I/O Manager before allocate IRP
the NT_WARNING status in most case is success code (will be completion), but win32 layer in most case interpret it as fail code (return false).
code example for tests:
ULONG BOOL_TO_ERROR(BOOL fOk)
{
return fOk ? NOERROR : GetLastError();
}
void CheckEventState(HANDLE hEvent, ULONG err, NTSTATUS status = RtlGetLastNtStatus())
{
DbgPrint("error = %u(%x)", err, err ? status : STATUS_SUCCESS);
switch (WaitForSingleObject(hEvent, 0))
{
case WAIT_OBJECT_0:
DbgPrint("Signaled\n");
break;
case WAIT_TIMEOUT:
DbgPrint("NON signaled\n");
break;
default:
DbgPrint("error=%u\n", GetLastError());
}
#if 1
EVENT_BASIC_INFORMATION ebi;
if (0 <= ZwQueryEvent(hEvent, EventBasicInformation, &ebi, sizeof(ebi), 0))
{
DbgPrint("EventState = %x\n", ebi.EventState);
}
#endif
}
void demoIoEvent()
{
WCHAR sz[MAX_PATH];
GetSystemDirectoryW(sz, RTL_NUMBER_OF(sz));
HANDLE hFile = CreateFileW(sz, 0, FILE_SHARE_VALID_FLAGS, 0,
OPEN_EXISTING, FILE_FLAG_OVERLAPPED|FILE_FLAG_BACKUP_SEMANTICS, 0);
if (hFile != INVALID_HANDLE_VALUE)
{
FILESYSTEM_STATISTICS fs;
OVERLAPPED ov = {};
if (ov.hEvent = CreateEvent(0, TRUE, FALSE, 0))
{
FILE_NOTIFY_INFORMATION fni;
IO_STATUS_BLOCK iosb;
// STATUS_DATATYPE_MISALIGNMENT from I/O manager
// event will be not set
NTSTATUS status = ZwNotifyChangeDirectoryFile(hFile, ov.hEvent, 0, 0, &iosb,
(FILE_NOTIFY_INFORMATION*)(1 + (PBYTE)&fni), 1, FILE_NOTIFY_VALID_MASK, FALSE);
CheckEventState(ov.hEvent, ERROR_NOACCESS, status);
// windows bug ! ReadDirectoryChangesW return .. true and no last error
// but really api fail. event will be not set and no notifications
ULONG err = BOOL_TO_ERROR(ReadDirectoryChangesW(hFile,
(FILE_NOTIFY_INFORMATION*)(1 + (PBYTE)&fni), 1, 0, FILE_NOTIFY_VALID_MASK, 0, &ov, 0));
CheckEventState(ov.hEvent, err);
// fail with ERROR_INSUFFICIENT_BUFFER (STATUS_BUFFER_TOO_SMALL)
// NT_ERROR(c0000023) - event will be not set
err = BOOL_TO_ERROR(DeviceIoControl(hFile,
FSCTL_FILESYSTEM_GET_STATISTICS, 0, 0, 0, 0, 0, &ov));
CheckEventState(ov.hEvent, err);
// ERROR_MORE_DATA (STATUS_BUFFER_OVERFLOW)
// !NT_ERROR(80000005) - event will be set
// note - win 32 api return false and error != ERROR_IO_PENDING
err = BOOL_TO_ERROR(DeviceIoControl(hFile,
FSCTL_FILESYSTEM_GET_STATISTICS, 0, 0, &fs, sizeof(fs), 0, &ov));
CheckEventState(ov.hEvent, err);
if (err == ERROR_MORE_DATA)
{
SYSTEM_INFO si;
GetSystemInfo(&si);
ULONG cb = si.dwNumberOfProcessors * fs.SizeOfCompleteStructure;
union {
PVOID pv;
PBYTE pb;
PFILESYSTEM_STATISTICS pfs;
};
pv = alloca(cb);
// must be NOERROR(0) here
// !NT_ERROR(0) - event will be set
err = BOOL_TO_ERROR(DeviceIoControl(hFile, FSCTL_FILESYSTEM_GET_STATISTICS, 0, 0,
pv, cb, 0, &ov));
CheckEventState(ov.hEvent, err);
if (!err && GetOverlappedResult(hFile, &ov, &cb, FALSE))
{
do
{
// use pfs here
} while (pb += fs.SizeOfCompleteStructure, --si.dwNumberOfProcessors);
}
}
CloseHandle(ov.hEvent);
}
CloseHandle(hFile);
}
}
and output:
error = 998(80000002)NON signaled
EventState = 0
error = 0(0)NON signaled
EventState = 0
error = 122(c0000023)NON signaled
EventState = 0
error = 234(80000005)Signaled
EventState = 1
error = 0(0)Signaled
EventState = 1

Related

Windows WFP Driver: Getting BSOD when processing packets in ClassifyFn callback

I am trying to code a simple firewall application which can allow or block network connection attempts made from userlevel processes.
To do so, following the WFPStarterKit tutorial, I created a WFP Driver which is set to intercept data at FWPM_LAYER_OUTBOUND_TRANSPORT_V4 layer.
The ClassifyFn callback function is responsible for intercepting the connection attempt, and either allow or deny it.
Once the ClassifyFn callback gets hit, the ProcessID of the packet is sent, along with a few other info, to a userlevel process through the FltSendMessage function.
The userlevel process receives the message, checks the ProcessID, and replies a boolean allow/deny command to the driver.
While this approach works when blocking a first packet, in some cases (expecially when allowing multiple packets) the code generates a BSOD with the INVALID_PROCESS_ATTACH_ATTEMPT error code.
The error is triggered at the call to FltSendMessage.
While I am still unable to pinpoint the exact problem,
it seems that making the callout thread wait (through FltSendMessage) for a reply from userlevel can generate this BSOD error on some conditions.
I would be very grateful if you can point me to the right direction.
Here is the function where I register the callout:
NTSTATUS register_example_callout(DEVICE_OBJECT * wdm_device)
{
NTSTATUS status = STATUS_SUCCESS;
FWPS_CALLOUT s_callout = { 0 };
FWPM_CALLOUT m_callout = { 0 };
FWPM_DISPLAY_DATA display_data = { 0 };
if (filter_engine_handle == NULL)
return STATUS_INVALID_HANDLE;
display_data.name = EXAMPLE_CALLOUT_NAME;
display_data.description = EXAMPLE_CALLOUT_DESCRIPTION;
// Register a new Callout with the Filter Engine using the provided callout functions
s_callout.calloutKey = EXAMPLE_CALLOUT_GUID;
s_callout.classifyFn = example_classify;
s_callout.notifyFn = example_notify;
s_callout.flowDeleteFn = example_flow_delete;
status = FwpsCalloutRegister((void *)wdm_device, &s_callout, &example_callout_id);
if (!NT_SUCCESS(status)) {
DbgPrint("Failed to register callout functions for example callout, status 0x%08x", status);
goto Exit;
}
// Setup a FWPM_CALLOUT structure to store/track the state associated with the FWPS_CALLOUT
m_callout.calloutKey = EXAMPLE_CALLOUT_GUID;
m_callout.displayData = display_data;
m_callout.applicableLayer = FWPM_LAYER_OUTBOUND_TRANSPORT_V4;
m_callout.flags = 0;
status = FwpmCalloutAdd(filter_engine_handle, &m_callout, NULL, NULL);
if (!NT_SUCCESS(status)) {
DbgPrint("Failed to register example callout, status 0x%08x", status);
}
else {
DbgPrint("Example Callout Registered");
}
Exit:
return status;
}
Here is the callout function:
/*************************
ClassifyFn Function
**************************/
void example_classify(
const FWPS_INCOMING_VALUES * inFixedValues,
const FWPS_INCOMING_METADATA_VALUES * inMetaValues,
void * layerData,
const void * classifyContext,
const FWPS_FILTER * filter,
UINT64 flowContext,
FWPS_CLASSIFY_OUT * classifyOut)
{
UNREFERENCED_PARAMETER(layerData);
UNREFERENCED_PARAMETER(classifyContext);
UNREFERENCED_PARAMETER(flowContext);
UNREFERENCED_PARAMETER(filter);
UNREFERENCED_PARAMETER(inMetaValues);
NETWORK_ACCESS_QUERY AccessQuery;
BOOLEAN SafeToOpen = TRUE;
classifyOut->actionType = FWP_ACTION_PERMIT;
AccessQuery.remote_address = inFixedValues->incomingValue[FWPS_FIELD_OUTBOUND_TRANSPORT_V4_IP_REMOTE_ADDRESS].value.uint32;
AccessQuery.remote_port = inFixedValues->incomingValue[FWPS_FIELD_OUTBOUND_TRANSPORT_V4_IP_REMOTE_PORT].value.uint16;
// Get Process ID
AccessQuery.ProcessId = (UINT64)PsGetCurrentProcessId();
if (!AccessQuery.ProcessId)
{
return;
}
// Here we connect to our userlevel application using FltSendMessage.
// Some checks are done and the SafeToOpen variable is populated with a BOOLEAN which indicates if to allow or block the packet.
// However, sometimes, a BSOD is generated with an INVALID_PROCESS_ATTACH_ATTEMPT error on the FltSendMessage call
QueryUserLevel(QUERY_NETWORK, &AccessQuery, sizeof(NETWORK_ACCESS_QUERY), &SafeToOpen, NULL, 0);
if (!SafeToOpen) {
classifyOut->actionType = FWP_ACTION_BLOCK;
}
return;
}
WFP drivers communicate to user-mode applications using the inverted call model. In this method, you keep an IRP from the user-mode pending at your kernel-mode driver instance and whenever you want to send data back to the user-mode you complete the IRP along with the data you want to send back.
The problem was that sometimes the ClassifyFn callback function can be called at IRQL DISPATCH_LEVEL.
FltSendMessage does not support DISPATCH_LEVEL, as it can only be run at IRQL <= APC_LEVEL.
Running at DISPATCH_LEVEL can cause this function to generate a BSOD.
I solved the problem by invoking FltSendMessage from a worker thread which runs at IRQL PASSIVE_LEVEL.
The worker thread can be created using IoQueueWorkItem.

MmCopyVirtualMemory failing, code is correct

My situation is that MmCopyVirtualMemory almost always (%99 of the time) returns STATUS_PARTIAL_COPY.
(Im operating in a Ring0 Driver)
I've tried so many different things, like using different variables sizes and types, different addresses etc... It always returns STATUS_PARTIAL_COPY.
Nothing online has helped either, its not a really common error.
Error description:
{Partial Copy} Due to protection conflicts not all the requested bytes could be copied.
My way of reading a processes memory:
DWORD64 Read(DWORD64 SourceAddress, SIZE_T Size)
{
SIZE_T Bytes;
NTSTATUS Status = STATUS_SUCCESS;
DWORD64 TempRead;
DbgPrintEx(0, 0, "\nRead Address:%p\n", SourceAddress); // Always Prints Correct Address
DbgPrintEx(0, 0, "Read szAddress:%x\n", Size); // Prints Correct Size 8 bytes
Status = MmCopyVirtualMemory(Process, SourceAddress, PsGetCurrentProcess(), &TempRead, Size, KernelMode, &Bytes);
DbgPrintEx(0, 0, "Read Bytes:%x\n", Bytes); // Copied bytes - prints 0
DbgPrintEx(0, 0, "Read Output:%p\n", TempRead); // prints 0 as expected since it failed
if (!NT_SUCCESS(Status))
{
DbgPrintEx(0, 0, "Read Failed:%p\n", Status);
return NULL;
}
return TempRead;
}
Example of how I use it:
NetMan = Read(BaseAddr + NET_MAN, sizeof(DWORD64));
//BaseAddr is a DWORD64 and NetMan is also a DWORD64
I've tripped checked my code to many times, all of it appears to be right.
After investigating MiDoPoolCopy (MmCopyVirtualMemoy calls this) it seems that its failing during the move operation:
// MiDoPoolCopy function
// Probe to make sure that the specified buffer is accessible in
// the target process.
//Wont execute (supplied KernelMode)
if ((InVa == FromAddress) && (PreviousMode != KernelMode)){
Probing = TRUE;
ProbeForRead (FromAddress, BufferSize, sizeof(CHAR));
Probing = FALSE;
}
//Failing either here, copying inside the target process's address space to the buffer
RtlCopyMemory (PoolArea, InVa, AmountToMove);
KeUnstackDetachProcess (&ApcState);
KeStackAttachProcess (&ToProcess->Pcb, &ApcState);
//
// Now operating in the context of the ToProcess.
//
//Wont execute (supplied KernelMode)
if ((InVa == FromAddress) && (PreviousMode != KernelMode)){
Probing = TRUE;
ProbeForWrite (ToAddress, BufferSize, sizeof(CHAR));
Probing = FALSE;
}
Moving = TRUE;
//or failing here - moving from the Target Process to Source (target process->Kernel)
RtlCopyMemory (OutVa, PoolArea, AmountToMove);
Here's the SEH returning STATUS_PARTIAL_COPY (wrapped in try and except)
//(wrapped in try and except)
//
// If the failure occurred during the move operation, determine
// which move failed, and calculate the number of bytes
// actually moved.
//
*NumberOfBytesRead = BufferSize - LeftToMove;
if (Moving == TRUE) {
//
// The failure occurred writing the data.
//
if (ExceptionAddressConfirmed == TRUE) {
*NumberOfBytesRead = (SIZE_T)((ULONG_PTR)(BadVa - (ULONG_PTR)FromAddress));
}
}
return STATUS_PARTIAL_COPY;
MmCopyVirtualMemory (undocumented struct)
NTSTATUS NTAPI MmCopyVirtualMemory
(
PEPROCESS SourceProcess,
PVOID SourceAddress,
PEPROCESS TargetProcess,
PVOID TargetAddress,
SIZE_T BufferSize,
KPROCESSOR_MODE PreviousMode,
PSIZE_T ReturnSize
);
Here is the source for both MmCopyVirtualMemory and MiDoPoolCopy:
https://lacicloud.net/custom/open/leaks/Windows%20Leaked%20Source/wrk-v1.2/base/ntos/mm/readwrt.c
Any help would be greatly appreciated, I've been stuck on this for to long...
I know this is old, but because I had the same issue and fixed it, maybe someone else will stumble on this in the future.
Reading your code the issue is the target address you want to copy the data. You are using a single defined DWORD64, which is when running the function a simple variable (register or on stack) and not a memory region where you can write to.
The solution is to provide a correct buffer for which you allocated memory before with malloc.
Example (you want to read a DWORD64, based on your code with some adjustments):
DWORD64* buffer = malloc(sizeof(DWORD64))
Status = MmCopyVirtualMemory(Process, (void*)SourceAddress, PsGetCurrentProcess(), (void*)buffer, sizeof(DWORD64), UserMode, &Bytes);
Do not forget to free your buffer after using to prevent a memory leak.

What is the purpose of WSA_WAIT_EVENT_0 in overlapped IO?

All my experience in networking has been on linux so I'm an absolute beginner at windows networking. This is probably a stupid question but I can't seem to find the answer anywhere. Consider the following code snippet:
DWORD Index = WSAWaitForMultipleEvents(EventTotal, EventArray, FALSE, WSA_INFINITE, FALSE);
WSAResetEvent( EventArray[Index - WSA_WAIT_EVENT_0]);
Every time an event is selected from the EventArray WSA_WAIT_EVENT_0 is subtracted from the index but WSA_WAIT_EVENT_0 is defined in winsock2.h as being equal to zero.
Why is code cluttered with this seemingly needless subtraction? Obviously the compiler will optimize it out but still don't understand why it's there.
The fact that WSA_WAIT_EVENT_0 is defined as 0 is irrelevant (it is just an alias for WAIT_OBJECT_0 from the WaitFor(Single|Multiple)Object(s)() API, which is also defined as 0 - WSAWaitForMultipleEvents() is itself just a wrapper for WaitForMultipleObjectsEx(), though Microsoft reserves the right to change the implementation in the future without breaking existing user code).
WSAWaitForMultipleEvents() can operate on multiple events at a time, and its return value will be one of the following possibilities:
WSA_WAIT_EVENT_0 .. (WSA_WAIT_EVENT_0 + cEvents - 1)
A specific event object was signaled.
WSA_WAIT_IO_COMPLETION
One or more alertable I/O completion routines were executed.
WSA_WAIT_TIMEOUT
A timeout occurred.
WSA_WAIT_FAILED
The function failed.
Typically, code should be looking at the return value and act accordingly, eg:
DWORD ReturnValue = WSAWaitForMultipleEvents(...);
if ((ReturnValue >= WSA_WAIT_EVENT_0) && (ReturnValue < (WSA_WAIT_EVENT_0 + EventTotal))
{
DWORD Index = ReturnValue - WSA_WAIT_EVENT_0;
// handle event at Index as needed...
}
else if (ReturnValue == WSA_WAIT_IO_COMPLETION)
{
// handle I/O as needed...
}
else if (RetunValue == WSA_WAIT_TIMEOUT)
{
// handle timeout as needed...
}
else
{
// handle error as needed...
}
Which can be simplified given the fact that bAlertable is FALSE (no I/O routines can be called) and dwTimeout is WSA_INFINITE (no timeout can elapse), so there are only 2 possible outcomes - an event is signaled or an error occurred:
DWORD ReturnValue = WSAWaitForMultipleEvents(EventTotal, EventArray, FALSE, WSA_INFINITE, FALSE);
if (ReturnValue != WSA_WAIT_FAILED)
{
DWORD Index = ReturnValue - WSA_WAIT_EVENT_0;
WSAResetEvent(EventArray[Index]);
}
else
{
// handle error as needed...
}
The documentation says it will return WSA_WAIT_EVENT_0 if event 0 was signaled, WSA_WAIT_EVENT_0 + 1 if event 1 was signaled, and so on.
Sure, they set WSA_WAIT_EVENT_0 to 0 in this version of Windows, but what if it's 1 in the next version, or 100?

SetSecurityInfo returns access denied

Using C, I'm trying to establish a pipe connection between a process and it's child process, while the child process has a lower mandatory(integrity) level (low, while the parent process is high).
I wrote the following program (it's a simplified version if it), but it fails with: ERROR_ACCESS_DENIED (0x5)
INT wmain(IN SIZE_T nArgc, IN PWSTR *pArgv)
{
SECURITY_ATTRIBUTES securityArrtibutes = { 0 };
HANDLE hPipeRead = NULL;
HANDLE hPipeWrite = NULL;
tSecurityArrtibutes.nLength = sizeof(tSecurityArrtibutes);
tSecurityArrtibutes.bInheritHandle = TRUE;
SetSeSecurityNamePrivilege();
CreatePipe(&hPipeRead, &hPipeWrite, &securityArrtibutes, 0);
ChangeMandatoryLabelHandle(hPipeRead);
}
VOID ChangeMandatoryLabelHandle(HANDLE hObject)
{
BOOL bRetval = FALSE;
DWORD dwError = 0;
PSECURITY_DESCRIPTOR pSecurityDescriptor = NULL;
PACL ptSacl = NULL;
BOOL bSaclPresent = FALSE;
BOOL bSaclDefaulted = FALSE;
PWSTR pSDDL = NULL;
SDDL = L"S:(ML;;LW;;;NW)";
bRetval = ConvertStringSecurityDescriptorToSecurityDescriptorW(pSDDL, SDDL_REVISION_1, &pSecurityDescriptor, NULL);
if (FALSE == bRetval)
... // Handle failure
bRetval = GetSecurityDescriptorSacl(pSecurityDescriptor, &bSaclPresent, &ptSacl, &bSaclDefaulted);
if (FALSE == bRetval)
... // Handle failure
// getting ERROR_ACCESS_DENIED (0x5)
dwErr = SetSecurityInfo(hObject, SE_KERNEL_OBJECT, LABEL_SECURITY_INFORMATION, NULL, NULL, NULL, ptSacl);
if (ERROR_SUCCESS != dwErr)
... // Handle failure
... // Cleanup
}
I followed https://msdn.microsoft.com/en-us/library/windows/desktop/aa379588(v=vs.85).aspx and the remark that
To set the SACL of an object, the caller must have the SE_SECURITY_NAME privilege enabled. :
BOOL SetSeSecurityNamePrivilege()
{
HANDLE hToken;
TOKEN_PRIVILEGES tp;
LUID luid;
if(!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY | TOKEN_IMPERSONATE, &hToken)
return FALSE
if (!LookupPrivilegeValue(NULL, SE_SECURITY_NAME, &luid))
return FALSE;
tp.PrivilegeCount = 1;
tp.Privileges[0].Luid = luid;
if (bEnablePrivilege)
tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
else
tp.Privileges[0].Attributes = 0;
if (!AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(TOKEN_PRIVILEGES), (PTOKEN_PRIVILEGES)NULL, (PDWORD)NULL))
return FALSE;
if (GetLastError() == ERROR_NOT_ALL_ASSIGNED)
return FALSE;
return TRUE;
}
note: I get the same result when I try to execute it with files, with CreateFile instead of CreatePipe.
In addition if I try to do that with files, and I replace SetSecurityInfo with SetNamedSecurityInfoW, and give it the full path of the file, it works great.
Does anyone have an idea how to make it work? Thanks!
A few notes before addressing the cause of your immediate problem.
First and foremost, you do not need to change the security descriptor at all, and doing so is unlikely to help you achieve your ultimate goal. The security descriptor is only checked when you attempt to open a handle to an object; if you already have a handle, the security descriptor has no effect. Since you are creating an unnamed pipe, you must be passing the handle, not the pipe name, to the child, so you do not need the ChangeMandatoryLabelHandle function at all.
Secondly, the SE_SECURITY_NAME privilege is not needed when setting LABEL_SECURITY_INFORMATION. The mandatory label is logically distinct from the rest of the SACL, and is treated as a special case.
Thirdly, your "S:(ML;;LW;;;NW)" is invalid.
I tried to use it in ConvertStringSecurityDescriptorToSecurityDescriptorW and got error 1336, The access control list (ACL) structure is invalid. Instead, use"D:NO_ACCESS_CONTROLS:(ML;;;;;LW)" or better still use the following code to create a security descriptor with a low label and no DACL:
ULONG cb = MAX_SID_SIZE;
PSID LowLabelSid = (PSID)alloca(MAX_SID_SIZE);
ULONG dwError = NOERROR;
if (CreateWellKnownSid(WinLowLabelSid, 0, LowLabelSid, &cb))
{
PACL Sacl = (PACL)alloca(cb += sizeof(ACL) + sizeof(ACE_HEADER) + sizeof(ACCESS_MASK));
if (InitializeAcl(Sacl, cb, ACL_REVISION) &&
AddMandatoryAce(Sacl, ACL_REVISION, 0, 0, LowLabelSid))
{
SECURITY_DESCRIPTOR sd;
InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION);
SetSecurityDescriptorDacl(&sd, TRUE, NULL, FALSE);
SetSecurityDescriptorSacl(&sd, TRUE, Sacl, FALSE);
SECURITY_ATTRIBUTES sa = { sizeof(sa), &sd, TRUE };
// todo something here
}
else
{
dwError = GetLastError();
}
}
else
{
dwError = GetLastError();
}
But again, you need to understand that there is (almost) never any sense in creating a security descriptor for an unnamed object. The security descriptor is only checked when opening an object, and (in user mode) you cannot open an object that does not have a name.
(From kernel mode we can open an object by pointer using ObOpenObjectByPointer.)
(In older versions of Windows, CreatePipe actually created a pipe with a random name, but starting from Windows 7 the pipe really is unnamed, so it cannot be opened with CreateFile or any similar method.)
In any case, it is my opinion that using CreatePipe in this context was a bad choice. This function is not well designed and has too few parameters. There is no option to create a bidirectional pipe or to open the pipe in asynchronous mode. I think it is better to use CreateNamedPipeW and CreateFileW.
(Alternatively, from Windows 7 onwards, you can use ZwCreateNamedPipeFile and ZwOpenFile to create and open an unnamed pipe.)
The proximate problem with the code as posted is that SetSecurityInfo and SetKernelObjectSecurity return ERROR_ACCESS_DENIED when called with the handle returned by CreatePipe. This is because, as described in the documentation for LABEL_SECURITY_INFORMATION:
Right required to set: WRITE_OWNER
Since CreatePipe does not give you the option to select the access rights that the handles are opened with, you have no way of doing this. If you instead use CreateNamedPipe you can set WRITE_OWNER in dwOpenMode.
However, you should note that if you wish to create an object with a special security descriptor, it is preferable to provide that security descriptor when the object is created. There is no point in creating the object with a default security descriptor and then changing it; why do in two operations what you can do in one? In this case, the SECURITY_ATTRIBUTES structure you pass to CreatePipe or CreateNamedPipe can be used to specify the security descriptor, providing another way of addressing your immediate problem, although as previously mentioned this will not in fact be useful.

Launch an application and wait until it has finished without blocking redraw

I have an interactive Win32 application and at some point I need to launch another application and wait until that other application has finished. During the time the other application runs, the interactive application shouldn't be responsive expect for resizing and moving the window (this implies of course that the interactive application still should continue redrawing).
My current approach is this:
Create a Thread T
Disable the main window (with EnableWindow(handle, FALSE))
Continue message loop
Special WM_APP message sent from thread T will enable the main window again (EnableWindow(handle, TRUE));
Thread T:
launch the application with CreateProcess
wait until application has terminated using WaitForSingleObject
post a special WM_APP message to the main window using PostMessage.
thread terminates here
This works fine, but I'm not sure if this is the correct approach.
Is there a better approach or should I continue like this?
Your approach is fine, just make sure to use PostMessage() for
send a special WM_APP message to the main window
to avoid deadlock if the main thread happens to wait for thread T.
As commenter noted, an alternative for creating a thread is to use a message loop with MsgWaitForMultipleObjectsEx.
The advantages I see:
There is no additional thread required, so there will be no worries about thread-related problems such as race conditions and deadlocks. These problems are often hard to find and debug (typically they only happen on customer machines), so I try to avoid threads as much as possible.
The program flow for creating the process and waiting for it is simpler. It can be sequential instead of event-based like the thread solution.
You have to judge for yourself if this is a better approach for your scenario.
A function that can be used to wait for a process (or any other waitable handle) while processing messages could be as follows. It's implementation is pretty involved (for background info see the links at the end of my answer) but usage is quite easy (see example afterwards).
// Function to process messages until the state of any of the given objects is signaled,
// the timeout is reached or a WM_QUIT message is received.
// Parameter hDialog can be nullptr, if there is no dialog.
//
// Returns ERROR_SUCCESS if any of the given handles is signaled.
// Returns ERROR_TIMEOUT in case of timeout.
// Returns ERROR_CANCELLED if the WM_QUIT message has been received.
// Returns the value of GetLastError() if MsgWaitForMultipleObjectsEx() fails.
DWORD WaitForObjectsWithMsgLoop(
HWND hDialog, const std::vector<HANDLE>& handles, DWORD timeOutMillis = INFINITE )
{
if( handles.empty() )
return ERROR_INVALID_PARAMETER;
DWORD handleCount = static_cast<DWORD>( handles.size() );
DWORD startTime = GetTickCount();
DWORD duration = 0;
do
{
DWORD status = MsgWaitForMultipleObjectsEx(
handleCount, handles.data(),
timeOutMillis - duration,
QS_ALLINPUT,
MWMO_INPUTAVAILABLE );
if( status == WAIT_FAILED )
{
// MsgWaitForMultipleObjectsEx() has failed.
return GetLastError();
}
else if( status >= WAIT_OBJECT_0 && status < WAIT_OBJECT_0 + handleCount )
{
// Any of the handles is signaled.
return ERROR_SUCCESS;
}
else if( status == WAIT_OBJECT_0 + handleCount )
{
// New input is available, process it.
MSG msg;
while( PeekMessage( &msg, NULL, 0, 0, PM_REMOVE ) )
{
if( msg.message == WM_QUIT )
{
// End the message loop because of quit message.
PostQuitMessage( static_cast<int>( msg.wParam ) );
return ERROR_CANCELLED;
}
// Enable message filter hooks (that's what the system does in it's message loops).
// You may use a custom code >= MSGF_USER.
// https://blogs.msdn.microsoft.com/oldnewthing/20050428-00/?p=35753
if( ! CallMsgFilter( &msg, MSGF_USER ) )
{
// Optionally process dialog messages.
if( ! hDialog || ! IsDialogMessage( hDialog, &msg ) )
{
// Standard message processing.
TranslateMessage( &msg );
DispatchMessage( &msg );
}
}
}
}
duration = GetTickCount() - startTime;
}
while( duration < timeOutMillis );
// Timeout reached.
return ERROR_TIMEOUT;
}
The function could be used in a dialog box procedure as follows. Error handling omitted for brevity.
INT_PTR CALLBACK YourDialogProc( HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam )
{
static bool s_childProcessRunning = false;
switch( message )
{
case YourMessageToLaunchProcess:
{
// prevent reentrancy in case the process is already running
if( s_childProcessRunning )
{
MessageBoxW( hDlg, L"Process already running", L"Error", MB_ICONERROR );
return TRUE;
}
// Prepare CreateProcess() arguments
STARTUPINFO si{ sizeof(si) };
PROCESS_INFORMATION pi{};
wchar_t command[] = L"notepad.exe"; // string must be writable!
// Launch the process
if( CreateProcessW( NULL, command, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi ) )
{
// Set flag to prevent reentrancy.
s_childProcessRunning = true;
// Wait until the child process exits while processing messages
// to keep the window responsive.
DWORD waitRes = WaitForObjectsWithMsgLoop( hDlg, { pi.hProcess } );
// TODO: Check waitRes for error
s_childProcessRunning = false;
// Cleanup
CloseHandle( pi.hThread );
CloseHandle( pi.hProcess );
}
return TRUE;
}
// more message handlers...
}
return FALSE;
}
Obligatory Old New Thing links:
Pumping messages while waiting for a period of time
The dialog manager, part 4: The dialog loop
Rescuing thread messages from modal loops via message filters

Resources