@@ -43,12 +43,22 @@ const {
4343 filterEnvForProxies,
4444} = require ( 'internal/http' ) ;
4545const { AsyncResource } = require ( 'async_hooks' ) ;
46- const { async_id_symbol } = require ( 'internal/async_hooks' ) . symbols ;
46+ const {
47+ async_id_symbol,
48+ owner_symbol,
49+ } = require ( 'internal/async_hooks' ) . symbols ;
4750const {
4851 getLazy,
4952 kEmptyObject,
5053 once,
5154} = require ( 'internal/util' ) ;
55+ const {
56+ onStreamRead,
57+ } = require ( 'internal/stream_base_commons' ) ;
58+ const {
59+ kReadBytesOrError,
60+ streamBaseState,
61+ } = internalBinding ( 'stream_wrap' ) ;
5262const {
5363 validateNumber,
5464 validateOneOf,
@@ -60,6 +70,7 @@ const { getOptionValue } = require('internal/options');
6070const kOnKeylog = Symbol ( 'onkeylog' ) ;
6171const kRequestOptions = Symbol ( 'requestOptions' ) ;
6272const kRequestAsyncResource = Symbol ( 'requestAsyncResource' ) ;
73+ const kFreeSocketDataGuard = Symbol ( 'freeSocketDataGuard' ) ;
6374
6475// New Agent code.
6576
@@ -92,9 +103,51 @@ function freeSocketErrorListener(err) {
92103// in the TCP buffer and be silently consumed as the response for the
93104// *next* request that reuses the socket (response-queue poisoning).
94105// See: https://hackerone.com/reports/3582376
95- function freeSocketDataGuard ( ) {
96- debug ( 'DATA on FREE socket - destroying poisoned socket' ) ;
97- this . destroy ( ) ;
106+ function freeSocketOnReadGuard ( ) {
107+ const nread = streamBaseState [ kReadBytesOrError ] ;
108+ if ( nread === 0 ) return ;
109+
110+ debug ( 'READ on FREE socket - destroying poisoned socket' ) ;
111+ this [ owner_symbol ] . destroy ( ) ;
112+ }
113+
114+ function installFreeSocketDataGuard ( socket ) {
115+ if ( socket . readableLength > 0 ) {
116+ debug ( 'BUFFERED DATA on FREE socket - destroying poisoned socket' ) ;
117+ socket . destroy ( ) ;
118+ return ;
119+ }
120+
121+ if ( socket . connecting ) {
122+ socket [ kFreeSocketDataGuard ] = function onConnect ( ) {
123+ socket [ kFreeSocketDataGuard ] = null ;
124+ installFreeSocketDataGuard ( socket ) ;
125+ } ;
126+ socket . once ( 'connect' , socket [ kFreeSocketDataGuard ] ) ;
127+ return ;
128+ }
129+
130+ const handle = socket . _handle ;
131+ if ( handle ) {
132+ handle . onread = freeSocketOnReadGuard ;
133+ if ( ! handle . reading ) {
134+ handle . reading = true ;
135+ const err = handle . readStart ( ) ;
136+ if ( err ) socket . destroy ( ) ;
137+ }
138+ }
139+ }
140+
141+ function removeFreeSocketDataGuard ( socket ) {
142+ if ( socket [ kFreeSocketDataGuard ] ) {
143+ socket . removeListener ( 'connect' , socket [ kFreeSocketDataGuard ] ) ;
144+ socket [ kFreeSocketDataGuard ] = null ;
145+ }
146+
147+ const handle = socket . _handle ;
148+ if ( handle ?. onread === freeSocketOnReadGuard ) {
149+ handle . onread = onStreamRead ;
150+ }
98151}
99152
100153function Agent ( options ) {
@@ -206,8 +259,7 @@ function Agent(options) {
206259 this . removeSocket ( socket , options ) ;
207260
208261 socket . once ( 'error' , freeSocketErrorListener ) ;
209- socket . on ( 'data' , freeSocketDataGuard ) ;
210- socket . resume ( ) ;
262+ installFreeSocketDataGuard ( socket ) ;
211263 freeSockets . push ( socket ) ;
212264 } ) ;
213265
@@ -599,7 +651,7 @@ Agent.prototype.keepSocketAlive = function keepSocketAlive(socket) {
599651Agent . prototype . reuseSocket = function reuseSocket ( socket , req ) {
600652 debug ( 'have free socket' ) ;
601653 socket . removeListener ( 'error' , freeSocketErrorListener ) ;
602- socket . removeListener ( 'data' , freeSocketDataGuard ) ;
654+ removeFreeSocketDataGuard ( socket ) ;
603655 req . reusedSocket = true ;
604656 socket . ref ( ) ;
605657} ;
0 commit comments