Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 5198573

Browse filesBrowse files
Flarnaaduh95
authored andcommitted
http: fix use-after-free when freeParser is called during llhttp_execute
When pipelined requests arrive in one TCP segment, llhttp_execute() parses them all in a single call. If a synchronous 'close' event handler invokes freeParser() mid-execution, cleanParser() nulls out parser state while llhttp_execute() is still on the stack, crashing on the next callback. Add an is_being_freed_ flag that freeParser() sets via parser.markFreed() before cleaning state. Proxy::Raw checks the flag before every callback and returns HPE_USER to abort execution early if set. PR-URL: #62095 Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Colin Ihrig <cjihrig@gmail.com> Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com>
1 parent a5b1be2 commit 5198573
Copy full SHA for 5198573

3 files changed

+31-1Lines changed: 31 additions & 1 deletion

File tree

Expand file treeCollapse file tree
Open diff view settings
Filter options
Expand file treeCollapse file tree
Open diff view settings
Collapse file

‎lib/_http_common.js‎

Copy file name to clipboardExpand all lines: lib/_http_common.js
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -189,8 +189,8 @@ function freeParser(parser, req, socket) {
189189
if (parser) {
190190
if (parser._consumed)
191191
parser.unconsume();
192-
cleanParser(parser);
193192
parser.remove();
193+
cleanParser(parser);
194194
if (parsers.free(parser) === false) {
195195
// Make sure the parser's stack has unwound before deleting the
196196
// corresponding C++ object through .close().
Collapse file

‎src/node_http_parser.cc‎

Copy file name to clipboardExpand all lines: src/node_http_parser.cc
+7Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -628,6 +628,8 @@ class Parser : public AsyncWrap, public StreamListener {
628628
Parser* parser;
629629
ASSIGN_OR_RETURN_UNWRAP(&parser, args.This());
630630

631+
parser->is_being_freed_ = true;
632+
631633
if (parser->connectionsList_ != nullptr) {
632634
parser->connectionsList_->Pop(parser);
633635
parser->connectionsList_->PopActive(parser);
@@ -1012,6 +1014,7 @@ class Parser : public AsyncWrap, public StreamListener {
10121014
num_values_ = 0;
10131015
have_flushed_ = false;
10141016
got_exception_ = false;
1017+
is_being_freed_ = false;
10151018
headers_completed_ = false;
10161019
max_http_header_size_ = max_http_header_size;
10171020
}
@@ -1056,6 +1059,7 @@ class Parser : public AsyncWrap, public StreamListener {
10561059
size_t num_values_;
10571060
bool have_flushed_;
10581061
bool got_exception_;
1062+
bool is_being_freed_ = false;
10591063
size_t current_buffer_len_;
10601064
const char* current_buffer_data_;
10611065
bool headers_completed_ = false;
@@ -1075,6 +1079,9 @@ class Parser : public AsyncWrap, public StreamListener {
10751079
struct Proxy<int (Parser::*)(Args...), Member> {
10761080
static int Raw(llhttp_t* p, Args ... args) {
10771081
Parser* parser = ContainerOf(&Parser::parser_, p);
1082+
if (parser->is_being_freed_) {
1083+
return 0;
1084+
}
10781085
int rv = (parser->*Member)(std::forward<Args>(args)...);
10791086
if (rv == 0) {
10801087
rv = parser->MaybePause();
Collapse file
+23Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
const { createServer } = require('http');
5+
const { connect } = require('net');
6+
7+
// Regression test: ensure llhttp_execute() is aborted when freeParser() is
8+
// called synchronously during parsing of pipelined requests.
9+
const server = createServer(common.mustCall((req, res) => {
10+
req.socket.emit('close');
11+
res.end();
12+
}, 1));
13+
14+
server.unref();
15+
16+
server.listen(0, common.mustCall(() => {
17+
// Two pipelined requests in one write, processed by a single llhttp_execute().
18+
const client = connect(server.address().port);
19+
client.end(
20+
'GET / HTTP/1.1\r\nHost: localhost\r\nConnection: keep-alive\r\n\r\n' +
21+
'GET / HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n',
22+
);
23+
}));

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.