Breaking Readfile() Blocking - Named Pipe (Windows API)

Windows named pipe partial read

PIPE_TYPE_BYTE is set at side which create the named pipe.

At reading side, set PIPE_READMODE_BYTE mode for reading partial data.

In partial reading mode, you can use PIPE_READMODE_BYTE and PIPE_NOWAIT combination to avoid the ReadFile function hangs. lpNumberOfBytesRead parameter can be used to detect if there is more data to read. When there is no data to be read the lpNumberOfBytesRead will be zero if the ReadFile function call success. Otherwise, check the error by calling the GetLastError function.

Based on official sample: Named Pipe Client and Multithreaded Pipe Server.

The client reading related code will be change to:

dwMode = PIPE_READMODE_BYTE | PIPE_NOWAIT;// PIPE_READMODE_MESSAGE;
fSuccess = SetNamedPipeHandleState(
hPipe, // pipe handle
&dwMode, // new pipe mode
NULL, // don't set maximum bytes
NULL); // don't set maximum time

// ...

WCHAR rdWChar;
do
{
// Read from the pipe.

fSuccess = ReadFile(
hPipe, // pipe handle
&rdWChar, // buffer to receive reply
2, // test size, read two bytes per read operation
&cbRead, // number of bytes read
NULL); // not overlapped

if (!fSuccess && GetLastError() != ERROR_MORE_DATA)
break;

wprintf(L"%c", rdWChar);
} while (cbRead); // repeat loop if there is more bytes

How is it possible to repeatedly read from a NamedPipe in Windows?

of course this is possible and need do after your pipe is connected and until disconnect. 109 this is ERROR_BROKEN_PIPE - you got this error in ReadFile when another end is close pipe handle, by call CloseHandle. in this case you need call DisconnectNamedPipe and then wait for new client by call ConnectNamedPipe. after connection is complete - you need just call ReadFile , in read completion again call ReadFile and so on until disconnect - some error returned. if you got error ERROR_PIPE_NOT_CONNECTED in ReadFile (just or in completion) this mean that remote end call DisconnectNamedPipe - your pipe already disconnected, so you can skip call to DisconnectNamedPipe and just call ConnectNamedPipe.

WriteFile and ReadFile on child process's stdio (using overlapped named pipe)

After search on Github, it's quite easy.
Create a Named Pipe for current process(read), and create a File for child process(write).

Github links:

https://github.com/parasol-framework/parasol/blob/c5ad64188c7496e3ff54eb75fd03c9e3124fe08b/src/core/microsoft/processes.c

https://github.com/dinamsky/malware-botnets/blob/4cd142d10f971cb93c334b6f48c12c85bcc8f63a/Phatbot-stoney/Phatbot-stoney/cmdshell.cpp

https://github.com/grimjoey/jlstudio/blob/e5d2d81f0a94d2020f2e912e43a487d9cb6f7c33/src/jls/process.cpp

https://github.com/Ai-Thinker-Open/Ai-Thinker-Open_ESP32-S2_SDK/blob/7d75213674b4572f90c68162ad6fe9b16dae65ad/tools/windows/tool_setup/cmdlinerunner/cmdlinerunner.c

https://github.com/LeonColt/skripsi/blob/141af593ec65cd7adaedf7a90fc4cd7cde5cc602/Maschinen/RedirectIOChildProcess.cpp

redirect stdout and stderr

#include <windows.h>
#include <assert.h>
#include <stdio.h>

struct Stdio;

typedef void (*Callback)(struct Stdio* self, DWORD len);

struct Stdio {
OVERLAPPED ol;
HANDLE pipe;
BYTE buf[100];
Callback callback;
};

typedef struct CTX {
struct Stdio Stdout, Stderr, Stdin;
} CTX, *LCTX, *LPCTX;

HANDLE Hstdout=INVALID_HANDLE_VALUE;
HANDLE Hstderr=INVALID_HANDLE_VALUE;

DWORD WINAPI Worker(LPVOID iocp){
struct Stdio *stdio;
OVERLAPPED *ol;
DWORD dwIoSize;
for(;;){
if (!GetQueuedCompletionStatus(iocp, &dwIoSize, (PDWORD_PTR)&stdio, &ol, INFINITE) || dwIoSize==0 || ol==NULL || stdio==NULL){
switch (GetLastError()){
case ERROR_BROKEN_PIPE:
puts("the process has been exited, exit thread...");
break;
default:
printf("error = %d, exit thread\n", GetLastError());
}
break;
}
stdio->callback(stdio, dwIoSize);
}
return 0;
}
void OnStdoutRead(struct Stdio *self, DWORD len){
WriteConsoleA(Hstdout, self->buf, len, NULL, NULL);
ReadFile(self->pipe, self->buf, sizeof(self->buf), NULL, &self->ol);
}
void OnStderrRead(struct Stdio *self, DWORD len){
WriteConsoleA(Hstderr, self->buf, len, NULL, NULL);
ReadFile(self->pipe, self->buf, sizeof(self->buf), NULL, &self->ol);
}

int wmain(){
assert(Hstdout = GetStdHandle(STD_OUTPUT_HANDLE));
assert(Hstderr = GetStdHandle(STD_ERROR_HANDLE));
HANDLE Pstdout, Pstderr; // child process's
CTX ctx{.Stdout=Stdio{.callback=OnStdoutRead}, .Stderr=Stdio{.callback=OnStderrRead}};

HANDLE iocp = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);
HANDLE hThread = CreateThread(NULL, 0, Worker, iocp, 0, NULL); // Worker thread

SECURITY_ATTRIBUTES sa{.nLength=sizeof(SECURITY_ATTRIBUTES), .bInheritHandle=TRUE};

const WCHAR* pipe_name1 = L"\\\\.\\Pipe\\child-1";

assert((ctx.Stdout.pipe = CreateNamedPipeW(
pipe_name1,
PIPE_ACCESS_INBOUND | FILE_FLAG_OVERLAPPED,
PIPE_TYPE_BYTE,
1,
4096,
4096,
5000,
NULL))!=INVALID_HANDLE_VALUE);

assert(INVALID_HANDLE_VALUE != (Pstdout = CreateFileW(
pipe_name1,
GENERIC_WRITE,
0,
&sa,
OPEN_EXISTING,
FILE_FLAG_OVERLAPPED,
NULL)));

const WCHAR *pipe_name2 = L"\\\\.\\Pipe\\child-2";

assert((ctx.Stderr.pipe = CreateNamedPipeW(
pipe_name2,
PIPE_ACCESS_INBOUND | FILE_FLAG_OVERLAPPED,
PIPE_TYPE_BYTE,
1,
4096,
4096,
5000,
NULL))!=INVALID_HANDLE_VALUE);

assert((Pstderr = CreateFileW(
pipe_name2,
GENERIC_WRITE,
0,
&sa,
OPEN_EXISTING,
FILE_FLAG_OVERLAPPED,
NULL))!=INVALID_HANDLE_VALUE);

STARTUPINFOW si{.cb = sizeof(si),
.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW,
.wShowWindow = SW_HIDE,
.hStdInput = GetStdHandle(STD_INPUT_HANDLE), // use current stdin
.hStdOutput = Pstdout,
.hStdError = Pstderr};

WCHAR cmd[] = L"powershell"; // or cmd, py, bash...
PROCESS_INFORMATION pInfo{};

assert(CreateProcessW(
NULL, cmd, NULL, NULL,
TRUE, 0, NULL, NULL,
&si, &pInfo));

assert(CloseHandle(Pstdout)); // we don't need this
assert(CloseHandle(Pstderr)); // we don't need this

assert(CreateIoCompletionPort(ctx.Stdout.pipe, iocp, (ULONG_PTR)&ctx.Stdout, 0));
assert(CreateIoCompletionPort(ctx.Stderr.pipe, iocp, (ULONG_PTR)&ctx.Stderr, 0));

ReadFile(ctx.Stdout.pipe, ctx.Stdout.buf, sizeof(ctx.Stdout.buf), NULL, &ctx.Stdout.ol);
ReadFile(ctx.Stderr.pipe, ctx.Stdout.buf, sizeof(ctx.Stderr.buf), NULL, &ctx.Stderr.ol);

WaitForSingleObject(pInfo.hProcess, INFINITE); // wait for process exit
PostQueuedCompletionStatus(iocp, 0, 0, NULL); // tell IOCP Worker exit
WaitForSingleObject(hThread, INFINITE); // wait for thread exit

assert(CloseHandle(hThread));
assert(CloseHandle(ctx.Stderr.pipe));
assert(CloseHandle(ctx.Stdout.pipe));
assert(CloseHandle(pInfo.hProcess));
assert(CloseHandle(iocp));
assert(CloseHandle(Hstderr));
puts("exit main function..."); // !!important: before close stdout
assert(CloseHandle(Hstdout));
}

Redirect stdin, stdout, stderr

#include <windows.h>
#include <assert.h>
#include <stdio.h>

static HANDLE Hstdout, Hstderr, Hstdin, HstopEvent;

struct Stdio;

typedef void (*Callback)(struct Stdio* self, DWORD len);

struct Stdio {
OVERLAPPED ol;
HANDLE pipe;
BYTE buf[100];
Callback callback;
};

typedef struct CTX {
struct Stdio Stdout, Stderr, Stdin;
} CTX, *LCTX, *LPCTX;

DWORD WINAPI Worker(LPVOID iocp){
struct Stdio *stdio;
OVERLAPPED *ol;
DWORD dwIoSize;
for(;;){
if (!GetQueuedCompletionStatus(iocp, &dwIoSize, (PDWORD_PTR)&stdio, &ol, INFINITE) || dwIoSize==0 || ol==NULL || stdio==NULL){
switch (GetLastError()){
case ERROR_BROKEN_PIPE:
SetEvent(HstopEvent);
puts("the process has been exited, exit thread...");
break;
default:
printf("error = %d, exit thread\n", GetLastError());
}
break;
}
stdio->callback(stdio, dwIoSize);
}
return 0;
}
void OnStdoutRead(struct Stdio *self, DWORD len){
WriteConsoleA(Hstdout, self->buf, len, NULL, NULL);
ReadFile(self->pipe, self->buf, sizeof(self->buf), NULL, &self->ol);
}
void OnStderrRead(struct Stdio *self, DWORD len){
WriteConsoleA(Hstderr, self->buf, len, NULL, NULL);
ReadFile(self->pipe, self->buf, sizeof(self->buf), NULL, &self->ol);
}
void OnStdinWriteComplete(struct Stdio *self, DWORD len){
printf("[%u bytes write to stdin]\n", len);
}
int wmain(){
assert((Hstdout = GetStdHandle(STD_OUTPUT_HANDLE))!=INVALID_HANDLE_VALUE);
assert((Hstderr = GetStdHandle(STD_ERROR_HANDLE))!=INVALID_HANDLE_VALUE);
assert((Hstdin = GetStdHandle(STD_INPUT_HANDLE))!=INVALID_HANDLE_VALUE);
assert((HstopEvent=CreateEventW(NULL, FALSE, FALSE, NULL))!=INVALID_HANDLE_VALUE);
CTX ctx{.Stdout=Stdio{.callback=OnStdoutRead}, .Stderr=Stdio{.callback=OnStderrRead}, .Stdin=Stdio{.callback=OnStdinWriteComplete}};
STARTUPINFOW si{.cb = sizeof(si), .dwFlags = STARTF_USESTDHANDLES};
HANDLE iocp = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);
HANDLE hThread = CreateThread(NULL, 0, Worker, iocp, 0, NULL); // Worker thread

SECURITY_ATTRIBUTES sa{.nLength=sizeof(SECURITY_ATTRIBUTES), .bInheritHandle=TRUE};

const WCHAR* pipe_name1 = L"\\\\.\\Pipe\\child-1";

assert((ctx.Stdout.pipe = CreateNamedPipeW(
pipe_name1,
PIPE_ACCESS_INBOUND | FILE_FLAG_OVERLAPPED,
PIPE_TYPE_BYTE,
1,
4096,
4096,
5000,
NULL))!=INVALID_HANDLE_VALUE);

assert((si.hStdOutput = CreateFileW(
pipe_name1,
GENERIC_WRITE,
0,
&sa,
OPEN_EXISTING,
FILE_FLAG_OVERLAPPED,
NULL))!=INVALID_HANDLE_VALUE);

const WCHAR *pipe_name2 = L"\\\\.\\Pipe\\child-2";

assert((ctx.Stderr.pipe = CreateNamedPipeW(
pipe_name2,
PIPE_ACCESS_INBOUND | FILE_FLAG_OVERLAPPED,
PIPE_TYPE_BYTE,
1,
4096,
4096,
5000,
NULL))!=INVALID_HANDLE_VALUE);

assert((si.hStdError = CreateFileW(
pipe_name2,
GENERIC_WRITE,
0,
&sa,
OPEN_EXISTING,
FILE_FLAG_OVERLAPPED,
NULL))!=INVALID_HANDLE_VALUE);

const WCHAR *pipe_name3 = L"\\\\.\\Pipe\\child-3";

assert((ctx.Stdin.pipe = CreateNamedPipeW(
pipe_name3,
PIPE_ACCESS_OUTBOUND,
PIPE_TYPE_BYTE,
1,
4096,
4096,
5000,
NULL))!=INVALID_HANDLE_VALUE);

assert((si.hStdInput = CreateFileW(
pipe_name3,
GENERIC_READ,
0,
&sa,
OPEN_EXISTING,
FILE_FLAG_OVERLAPPED,
NULL))!=INVALID_HANDLE_VALUE);

WCHAR cmd[] = L"powershell"; // or cmd, py, bash...

PROCESS_INFORMATION pInfo{};
assert(CreateProcessW(
NULL, cmd, NULL, NULL,
TRUE, 0, NULL, NULL,
&si, &pInfo));

assert(CloseHandle(si.hStdError)); // we don't need this
assert(CloseHandle(si.hStdInput)); // we don't need this
assert(CloseHandle(si.hStdOutput)); // we don't need this
assert(CreateIoCompletionPort(ctx.Stdout.pipe, iocp, (ULONG_PTR)&ctx.Stdout, 0));
assert(CreateIoCompletionPort(ctx.Stderr.pipe, iocp, (ULONG_PTR)&ctx.Stderr, 0));

ReadFile(ctx.Stdout.pipe, ctx.Stdout.buf, sizeof(ctx.Stdout.buf), NULL, &ctx.Stdout.ol);
ReadFile(ctx.Stderr.pipe, ctx.Stdout.buf, sizeof(ctx.Stderr.buf), NULL, &ctx.Stderr.ol);

DWORD dwIoSize;
for(;;){
CHAR buf[100];
ReadConsoleA(Hstdin, buf, sizeof(buf), &dwIoSize, NULL);
if (WaitForSingleObject(HstopEvent, 0)==WAIT_OBJECT_0)
break;
WriteFile(ctx.Stdin.pipe, buf, dwIoSize, NULL, &ctx.Stdin.ol);
if (WaitForSingleObject(HstopEvent, 0)==WAIT_OBJECT_0)
break;
}
PostQueuedCompletionStatus(iocp, 0, 0, NULL); // tell IOCP Worker exit
WaitForSingleObject(hThread, INFINITE); // wait for thread exit

assert(CloseHandle(hThread));
assert(CloseHandle(ctx.Stderr.pipe));
assert(CloseHandle(ctx.Stdout.pipe));
assert(CloseHandle(pInfo.hProcess));
assert(CloseHandle(iocp));
assert(CloseHandle(Hstderr));
assert(CloseHandle(HstopEvent));
puts("exit main function..."); // !!important: before close stdout
assert(CloseHandle(Hstdout));
}

win32: how stop ReadFile (stdin|pipe)

When reading from a console's actual STDIN, you can use PeekConsoleInput() and ReadConsoleInfo() instead of ReadFile().

When reading from an (un)named pipe, use PeekNamedPipe() before calling ReadFile().

This will allow you to poll STDIN for new input before actually reading it, and then you can check your thread termination status in between polls.

You can use GetFileType() to detect what kind of device is attached to your STD_INPUT_HANDLE handle.

Named pipe: ReadFile after ConnectNamedPipe return ERROR_BROKEN_PIPE

PathFileExists is the pipe killer! After hours of trying I finally found what breaks the pipe: a simple call to PathFileExists on the pipe name! This was added recently on the client side to check whether the pipe is already created. I had a look at the code changes but I totally missed that. PathFileExists correctly returns true or false but seems to mess up the pipe (as I told it did not help to allow more than one client to connect). Argh!!!



Related Topics



Leave a reply



Submit