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

Commit e7b2985

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 a62b7d5 commit e7b2985

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
@@ -709,7 +709,7 @@ exports.TLSSocket = TLSSocket;
709709
const proxiedMethods = [
710710
'ref', 'unref', 'open', 'bind', 'listen', 'connect', 'bind6',
711711
'connect6', 'getsockname', 'getpeername', 'setNoDelay', 'setKeepAlive',
712-
'setSimultaneousAccepts', 'setBlocking',
712+
'setSimultaneousAccepts', 'setBlocking', 'hasPendingRead',
713713

714714
// PipeWrap
715715
'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

@@ -115,6 +115,7 @@ void LibuvStreamWrap::RegisterExternalReferences(
115115
registry->Register(IsConstructCallCallback);
116116
registry->Register(GetWriteQueueSize);
117117
registry->Register(SetBlocking);
118+
registry->Register(HasPendingRead);
118119
StreamBase::RegisterExternalReferences(registry);
119120
}
120121

@@ -153,6 +154,7 @@ Local<FunctionTemplate> LibuvStreamWrap::GetConstructorTemplate(
153154
Local<FunctionTemplate>(),
154155
static_cast<PropertyAttribute>(ReadOnly | DontDelete));
155156
SetProtoMethod(isolate, tmpl, "setBlocking", SetBlocking);
157+
SetProtoMethod(isolate, tmpl, "hasPendingRead", HasPendingRead);
156158
StreamBase::AddMethods(env, tmpl);
157159
env->set_libuv_stream_wrap_ctor_template(tmpl);
158160
}
@@ -320,6 +322,67 @@ void LibuvStreamWrap::SetBlocking(const FunctionCallbackInfo<Value>& args) {
320322
args.GetReturnValue().Set(uv_stream_set_blocking(wrap->stream(), enable));
321323
}
322324

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

‎src/stream_wrap.h‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ class LibuvStreamWrap : public HandleWrap, public StreamBase {
103103
static void GetWriteQueueSize(
104104
const v8::FunctionCallbackInfo<v8::Value>& info);
105105
static void SetBlocking(const v8::FunctionCallbackInfo<v8::Value>& args);
106+
static void HasPendingRead(const v8::FunctionCallbackInfo<v8::Value>& args);
106107

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

0 commit comments

Comments
 (0)