mirror of
https://github.com/DeusData/codebase-memory-mcp.git
synced 2026-11-04 08:13:37 +08:00
fix(daemon): wait for the client's first frame before impersonating it
Root cause of the Windows client-rejection loop, named by the accept classifier: ImpersonateNamedPipeClient fails with ERROR_CANNOT_IMPERSONATE (1368) - "unable to impersonate using a named pipe until data has been read from that pipe" - because the daemon authenticated the connecting client the instant ConnectNamedPipe returned, before the client had put any bytes on the wire. Every client was refused and the accept loop spun (116 rejections in one smoke window). The client always sends its HELLO request immediately after connecting and only then waits for the response (cbm_daemon_runtime_client_connect: ipc_connect -> send_frame -> receive), so the server can safely wait for the client's first bytes before impersonating. win_pipe_wait_for_client_data polls PeekNamedPipe - which inspects the buffer WITHOUT consuming it, so the HELLO frame stays intact for the runtime's own read - and gates only on data PRESENCE, the exact precondition the impersonation API requires. Bounded by a finite deadline so a connect-but-never-send peer is refused, not hung. The identity check itself is unchanged; it now simply runs after the client is impersonatable. Windows-only path; the unix matrix (green at 6742/0 on this tree) is unaffected. Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
This commit is contained in:
@@ -26,6 +26,13 @@ enum {
|
||||
CBM_DAEMON_IPC_RETRY_INTERVAL_MS = 10,
|
||||
CBM_DAEMON_IPC_COORDINATION_CLEANUP_MS = 500,
|
||||
CBM_DAEMON_IPC_WINDOWS_RENDEZVOUS_RETRY_MS = 250,
|
||||
/* After ConnectNamedPipe the server must see the client's first bytes
|
||||
* before it can impersonate it (ImpersonateNamedPipeClient fails with
|
||||
* ERROR_CANNOT_IMPERSONATE until data has been read from the pipe). The
|
||||
* client always sends its HELLO request immediately after connecting, so
|
||||
* this only bounds how long a connect-but-never-send peer is tolerated;
|
||||
* generous for starved CI runners, still finite. */
|
||||
CBM_DAEMON_IPC_FIRST_FRAME_TIMEOUT_MS = 15000,
|
||||
};
|
||||
|
||||
/* Last private-namespace validation refusal, set at the exact failing check.
|
||||
@@ -5128,6 +5135,26 @@ static int win_overlapped_wait(HANDLE handle, OVERLAPPED *overlapped, DWORD time
|
||||
return result;
|
||||
}
|
||||
|
||||
/* Wait until the connected client has placed its first bytes in the pipe.
|
||||
* PeekNamedPipe inspects the buffer without consuming it, so the client's
|
||||
* HELLO frame stays intact for the runtime's own read; we only gate on its
|
||||
* PRESENCE, which is the precondition ImpersonateNamedPipeClient requires.
|
||||
* Returns false on a broken pipe or if the deadline passes with no data. */
|
||||
static bool win_pipe_wait_for_client_data(HANDLE pipe, uint64_t deadline_ms) {
|
||||
for (;;) {
|
||||
DWORD available = 0;
|
||||
if (!PeekNamedPipe(pipe, NULL, 0, NULL, &available, NULL)) {
|
||||
return false;
|
||||
}
|
||||
if (available > 0) {
|
||||
return true;
|
||||
}
|
||||
if (!win_retry_pause(deadline_ms)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool win_pipe_client_is_current_user(HANDLE pipe) {
|
||||
win_security_t security;
|
||||
if (!win_security_init(&security)) {
|
||||
@@ -5223,6 +5250,17 @@ int cbm_daemon_ipc_accept(cbm_daemon_ipc_listener_t *listener, uint32_t timeout_
|
||||
listener->pipe = INVALID_HANDLE_VALUE;
|
||||
return result;
|
||||
}
|
||||
/* The client sends its HELLO immediately after connecting; wait for those
|
||||
* bytes before impersonating, or ImpersonateNamedPipeClient fails with
|
||||
* ERROR_CANNOT_IMPERSONATE (1368) and every client is refused. */
|
||||
if (!win_pipe_wait_for_client_data(listener->pipe,
|
||||
ipc_deadline_after(CBM_DAEMON_IPC_FIRST_FRAME_TIMEOUT_MS))) {
|
||||
cbm_log_warn("daemon.accept.client_rejected", "stage", "first_frame");
|
||||
(void)DisconnectNamedPipe(listener->pipe);
|
||||
(void)CloseHandle(listener->pipe);
|
||||
listener->pipe = INVALID_HANDLE_VALUE;
|
||||
return -1;
|
||||
}
|
||||
if (!win_pipe_client_is_current_user(listener->pipe)) {
|
||||
/* The daemon-side twin of the client's server_identity log: without
|
||||
* it a server-initiated disconnect is indistinguishable from a
|
||||
|
||||
Reference in New Issue
Block a user