On 5/25/22 22:30, Alexandre Julliard wrote:
We can wrap the IOSB writes in `set_async_iosb` inside a `__TRY` block from `wine/unixlib.h`.
That would only be hiding the bug.
Handling the exception does not necessarily mean silencing the bug; we can flag the error condition via ERR(). Also, letting the IOSB leak won't make debugging much more pleasurable either IMHO:
1. There's no way to detect pulling the rug out under a pending I/O operation. 2. It causes hard-to-trace memory leak.
--- As a side note, Windows does ignore invalid memory accesses when writing IOSB. On Windows, running the test program below results in the following:
Test 1. Valid Status, Valid Information iosb->Status = 0 iosb->Information = 0x1
Test 2. Valid Status, Faulting Information iosb->Status = 0xcccccccc iosb->Information = 0xcccccccccccccccc
Test 3. Faulting Status, Valid Information iosb->Status = 0xcccccccc iosb->Information = 0x1
Test 4. Faulting Status, Faulting Information iosb->Status = 0xcccccccc iosb->Information = 0xcccccccccccccccc
From the output above, we can infer two things:
1. Windows gives up writing IOSB and let the program continue as usual when it encounters invalid memory accesses. 2. Even if the Status field is invalid, Windows still successfully writes the Information field.
--- #define WIN32_LEAN_AND_MEAN #define _UNICODE #include <windows.h> #include <winternl.h> #include <stdio.h> #include <stdlib.h>
NTSYSAPI NTSTATUS WINAPI NtReadFile(HANDLE,HANDLE,PIO_APC_ROUTINE,PVOID,PIO_STATUS_BLOCK,PVOID,ULONG,PLARGE_INTEGER,PULONG);;
void die_(int line, unsigned int errcode) { fprintf(stderr, "error @ %d: %u (%#x)\n", line, errcode, errcode); exit(EXIT_FAILURE); } #define die() die_(__LINE__, GetLastError())
static void subtest(HANDLE read_pipe, HANDLE write_pipe, IO_STATUS_BLOCK *iosb, void *fault_page);
int wmain(void) { HANDLE read_pipe, write_pipe; void *page_0, *page_1, *fault_page; const WCHAR pipe_name[] = L"\\.\PIPE\LOCAL\faulty-iosb-test"; IO_STATUS_BLOCK *iosb;
read_pipe = CreateNamedPipeW( pipe_name, PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED | FILE_FLAG_FIRST_PIPE_INSTANCE, PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT | PIPE_ACCEPT_REMOTE_CLIENTS, 1, 4096, 4096, 0, NULL ); if (read_pipe == INVALID_HANDLE_VALUE) die();
write_pipe = CreateFile( pipe_name, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, 0, NULL ); if (write_pipe == INVALID_HANDLE_VALUE) die();
page_0 = VirtualAlloc(NULL, 4096 * 2, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); if (!page_0) die(); page_1 = (char *)page_0 + 4096;
puts("Test 1. Valid Status, Valid Information"); iosb = (IO_STATUS_BLOCK *)page_0; fault_page = page_1; subtest(read_pipe, write_pipe, iosb, fault_page);
puts("Test 2. Valid Status, Faulting Information"); iosb = CONTAINING_RECORD((ULONG_PTR *)page_1, IO_STATUS_BLOCK, Information); fault_page = page_1; subtest(read_pipe, write_pipe, iosb, fault_page);
puts("Test 3. Faulting Status, Valid Information"); iosb = CONTAINING_RECORD((ULONG_PTR *)page_1, IO_STATUS_BLOCK, Information); fault_page = page_0; subtest(read_pipe, write_pipe, iosb, fault_page);
puts("Test 4. Faulting Status, Faulting Information"); iosb = (IO_STATUS_BLOCK *)page_0; fault_page = page_0; subtest(read_pipe, write_pipe, iosb, fault_page);
return 0; }
static void subtest(HANDLE read_pipe, HANDLE write_pipe, IO_STATUS_BLOCK *iosb, void *fault_page) { DWORD old_prot; DWORD result; NTSTATUS status; char buffer[1];
memset(iosb, 0xcc, sizeof(*iosb)); status = NtReadFile(read_pipe, NULL, NULL, NULL, iosb, buffer, sizeof(buffer), NULL, NULL); if (status != STATUS_PENDING) die_(__LINE__, status);
if (!VirtualProtect(fault_page, 4096, PAGE_NOACCESS, &old_prot)) die();
if (!WriteFile(write_pipe, "", 1, &result, NULL)) die(); if (WaitForSingleObject(read_pipe, INFINITE) != WAIT_OBJECT_0) die();
if (!VirtualProtect(fault_page, 4096, old_prot, &old_prot)) die();
printf("iosb->Status = %#lx\n", (unsigned long)iosb->Status); printf("iosb->Information = %#Ix\n", iosb->Information); puts(""); }