http: ensure agent data guard works without I/O · nodejs/node@96c3ccb · GitHub
Skip to content

Commit 96c3ccb

Browse files
jasnelladuh95
authored andcommitted
http: ensure agent data guard works without I/O
Before a kept-alive socket is given to a request, now ask the OS whether anything is waiting to be read on it. If there is, the Agent destroys the socket and uses another one or opens a new connection. The check doesn't consume any bytes. Signed-off-by: James M Snell <jasnell@gmail.com> Assisted-by: Opencode PR-URL: nodejs-private/node-private#983 Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com> CVE-ID: CVE-2026-105481
1 parent fca511a commit 96c3ccb

5 files changed

Lines changed: 213 additions & 3 deletions

File tree

‎lib/_http_agent.js‎

Lines changed: 25 additions & 0 deletions

‎lib/internal/tls/wrap.js‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -733,7 +733,7 @@ exports.TLSSocket = TLSSocket;
733733
const proxiedMethods = [
734734
'ref', 'unref', 'open', 'bind', 'listen', 'connect', 'bind6',
735735
'connect6', 'getsockname', 'getpeername', 'setNoDelay', 'setKeepAlive',
736-
'setSimultaneousAccepts', 'setBlocking',
736+
'setSimultaneousAccepts', 'setBlocking', 'hasPendingRead',
737737

738738
// PipeWrap
739739
'setPendingInstances',

‎src/stream_wrap.cc‎

Lines changed: 65 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,9 @@
3333
#include "udp_wrap.h"
3434
#include "util-inl.h"
3535

36-
#include <cstring> // memcpy()
36+
#include <cerrno> // errno
3737
#include <climits> // INT_MAX
38-
38+
#include <cstring> // memcpy()
3939

4040
namespace node {
4141

@@ -130,6 +130,7 @@ void LibuvStreamWrap::RegisterExternalReferences(
130130
registry->Register(IsConstructCallCallback);
131131
registry->Register(GetWriteQueueSize);
132132
registry->Register(SetBlocking);
133+
registry->Register(HasPendingRead);
133134
StreamBase::RegisterExternalReferences(registry);
134135
}
135136

@@ -168,6 +169,7 @@ Local<FunctionTemplate> LibuvStreamWrap::GetConstructorTemplate(
168169
Local<FunctionTemplate>(),
169170
static_cast<PropertyAttribute>(ReadOnly | DontDelete));
170171
SetProtoMethod(isolate, tmpl, "setBlocking", SetBlocking);
172+
SetProtoMethod(isolate, tmpl, "hasPendingRead", HasPendingRead);
171173
StreamBase::AddMethods(env, tmpl);
172174
env->set_libuv_stream_wrap_ctor_template(tmpl);
173175
}
@@ -335,6 +337,67 @@ void LibuvStreamWrap::SetBlocking(const FunctionCallbackInfo<Value>& args) {
335337
args.GetReturnValue().Set(uv_stream_set_blocking(wrap->stream(), enable));
336338
}
337339

340+
// Returns true if a read on the underlying socket would complete right now,
341+
// i.e. the OS has data, EOF, or a socket error queued that libuv has not
342+
// delivered yet because the event loop has not polled for I/O since it
343+
// arrived. Nothing is consumed. Returns false when there is nothing pending
344+
// or when the handle type does not support the check.
345+
void LibuvStreamWrap::HasPendingRead(const FunctionCallbackInfo<Value>& args) {
346+
LibuvStreamWrap* wrap;
347+
ASSIGN_OR_RETURN_UNWRAP(&wrap, args.This());
348+
args.GetReturnValue().Set(false);
349+
350+
if (!wrap->IsAlive() || wrap->IsClosing()) return;
351+
352+
#ifdef _WIN32
353+
// Only TCP sockets can be probed with recv() on Windows.
354+
if (!wrap->is_tcp()) return;
355+
#else
356+
if (!wrap->is_tcp() && !wrap->is_named_pipe()) return;
357+
#endif
358+
359+
uv_os_fd_t fd;
360+
if (uv_fileno(reinterpret_cast<uv_handle_t*>(wrap->stream()), &fd) != 0)
361+
return;
362+
363+
char c;
364+
#ifdef _WIN32
365+
// libuv puts TCP sockets in non-blocking mode and reads with zero-byte
366+
// overlapped reads, so unread data stays in the kernel buffer.
367+
int r = recv(reinterpret_cast<SOCKET>(fd), &c, 1, MSG_PEEK);
368+
if (r == SOCKET_ERROR) {
369+
switch (WSAGetLastError()) {
370+
case WSAEWOULDBLOCK:
371+
case WSAENOTCONN:
372+
case WSAENOTSOCK:
373+
return;
374+
}
375+
}
376+
#else
377+
int flags = MSG_PEEK;
378+
#ifdef MSG_DONTWAIT
379+
flags |= MSG_DONTWAIT;
380+
#endif
381+
ssize_t r;
382+
do {
383+
r = recv(fd, &c, 1, flags);
384+
} while (r == -1 && errno == EINTR);
385+
if (r == -1) {
386+
switch (errno) {
387+
case EAGAIN:
388+
#if EWOULDBLOCK != EAGAIN
389+
case EWOULDBLOCK:
390+
#endif
391+
case ENOTCONN:
392+
case ENOTSOCK:
393+
return;
394+
}
395+
}
396+
#endif
397+
398+
args.GetReturnValue().Set(true);
399+
}
400+
338401
typedef SimpleShutdownWrap<ReqWrap<uv_shutdown_t>> LibuvShutdownWrap;
339402
typedef SimpleWriteWrap<ReqWrap<uv_write_t>> LibuvWriteWrap;
340403

‎src/stream_wrap.h‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ class LibuvStreamWrap : public HandleWrap, public StreamBase {
108108
static void GetWriteQueueSize(
109109
const v8::FunctionCallbackInfo<v8::Value>& info);
110110
static void SetBlocking(const v8::FunctionCallbackInfo<v8::Value>& args);
111+
static void HasPendingRead(const v8::FunctionCallbackInfo<v8::Value>& args);
111112

112113
// Callbacks for libuv
113114
void OnUvAlloc(size_t suggested_size, uv_buf_t* buf);
Lines changed: 121 additions & 0 deletions

0 commit comments

Comments
 (0)