From 8bf60bbd1de2a329340e88d3fb31ea30e83eed8f Mon Sep 17 00:00:00 2001 From: Leah Neukirchen Date: Thu, 13 Jan 2022 18:42:06 +0100 Subject: detect and refuse pipelining Previously, two HTTP requests within the same read() would both be parsed by http_parser_execute (and corrupted state). We don't support this, since we distinguish between reading and writing parts of the server action, and there's no way to let poll perform the writing part in this case. Detect pipelining by pausing the parser in on_message_complete and checking if we parsed fewer bytes than we passed. Then handle the first request and drop the connection; a compliant HTTP client must retry without pipelining. Found by @duncaen. Signed-off-by: Leah Neukirchen --- FEATURES | 2 +- LICENSE | 2 +- hittpd.c | 19 +++++++++++++++++-- 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/FEATURES b/FEATURES index 5524dd8..8afda8a 100644 --- a/FEATURES +++ b/FEATURES @@ -6,7 +6,7 @@ Features of hittpd: - HTTP 1.0 and HTTP 1.1 support - Ranges (basic variants only) - If-Modified-Since -- Keep-Alive +- Keep-Alive (but no pipelining) - IPv6 - sendfile(2) on Linux - Virtual Hosts diff --git a/LICENSE b/LICENSE index 5d980d9..cb12f14 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright 2020, 2021 Leah Neukirchen +Copyright 2020, 2021, 2022 Leah Neukirchen Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to diff --git a/hittpd.c b/hittpd.c index e5114d6..9ddd2c4 100644 --- a/hittpd.c +++ b/hittpd.c @@ -1,6 +1,6 @@ /* hittpd - efficient, no-frills HTTP 1.1 server */ -/* Copyright 2020, 2021 Leah Neukirchen +/* Copyright 2020, 2021, 2022 Leah Neukirchen * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to @@ -516,6 +516,7 @@ on_message_complete(http_parser *p) { struct conn_data *data = p->data; data->state = SENDING; + http_parser_pause(p, 1); // can't do more than one request at a time if (p->http_major == 0 && p->http_minor == 9) return send_error(p, 400, "Bad Request"); @@ -919,7 +920,21 @@ read_client(int i) } else if (n == 0) { close_connection(i); } else { - http_parser_execute(&parsers[i], &settings, buf, n); + size_t r = http_parser_execute(&parsers[i], &settings, buf, n); + + if ((size_t)n == r && + HTTP_PARSER_ERRNO(&parsers[i]) == HPE_PAUSED) { + // we handled a complete request, we can reuse + // the parser + http_parser_pause(&parsers[i], 0); + } else { + // the read data was longer than a single request + // drop the rest and make sure we close the connection, + // so the client tries without pipelining + // (this is conformant) + http_parser_pause(&parsers[i], 0); + parsers[i].flags |= F_CONNECTION_CLOSE; + } if (parsers[i].http_errno) { printf("err=%s\n", -- cgit 1.4.1