Add http errors

This commit is contained in:
Alex Hultman
2023-05-28 10:12:50 +02:00
parent 7999d09286
commit 919a3bbeba
4 changed files with 48 additions and 32 deletions
+1 -5
View File
@@ -102,7 +102,7 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
} }
/* Parse it */ /* Parse it */
void *returnedUser = httpParser.consumePostPadded((char *) data, size, user, reserved, [reserved](void *s, uWS::HttpRequest *httpRequest) -> void * { auto [err, returnedUser] = httpParser.consumePostPadded((char *) data, size, user, reserved, [reserved](void *s, uWS::HttpRequest *httpRequest) -> void * {
readBytes(httpRequest->getHeader(httpRequest->getUrl())); readBytes(httpRequest->getHeader(httpRequest->getUrl()));
readBytes(httpRequest->getMethod()); readBytes(httpRequest->getMethod());
@@ -135,10 +135,6 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
/* Return ok */ /* Return ok */
return user; return user;
}, [](void *user) -> void * {
/* Return break */
return nullptr;
}); });
if (!returnedUser) { if (!returnedUser) {
+4 -5
View File
@@ -136,7 +136,7 @@ private:
#endif #endif
/* The return value is entirely up to us to interpret. The HttpParser only care for whether the returned value is DIFFERENT or not from passed user */ /* The return value is entirely up to us to interpret. The HttpParser only care for whether the returned value is DIFFERENT or not from passed user */
void *returnedSocket = httpResponseData->consumePostPadded(data, (unsigned int) length, s, proxyParser, [httpContextData](void *s, HttpRequest *httpRequest) -> void * { auto [err, returnedSocket] = httpResponseData->consumePostPadded(data, (unsigned int) length, s, proxyParser, [httpContextData](void *s, HttpRequest *httpRequest) -> void * {
/* For every request we reset the timeout and hang until user makes action */ /* For every request we reset the timeout and hang until user makes action */
/* Warning: if we are in shutdown state, resetting the timer is a security issue! */ /* Warning: if we are in shutdown state, resetting the timer is a security issue! */
us_socket_timeout(SSL, (us_socket_t *) s, 0); us_socket_timeout(SSL, (us_socket_t *) s, 0);
@@ -245,10 +245,6 @@ private:
} }
} }
return user; return user;
}, [](void *user) {
/* Close any socket on HTTP errors */
us_socket_close(SSL, (us_socket_t *) user, 0, nullptr);
return nullptr;
}); });
/* Mark that we are no longer parsing Http */ /* Mark that we are no longer parsing Http */
@@ -256,6 +252,9 @@ private:
/* If we got fullptr that means the parser wants us to close the socket from error (same as calling the errorHandler) */ /* If we got fullptr that means the parser wants us to close the socket from error (same as calling the errorHandler) */
if (returnedSocket == FULLPTR) { if (returnedSocket == FULLPTR) {
/* For errors, we only deliver them "at most once". We don't care if they get halfways delivered or not. */
us_socket_write(SSL, s, httpErrorResponses[err].data(), (int) httpErrorResponses[err].length(), false);
us_socket_shutdown(SSL, s);
/* Close any socket on HTTP errors */ /* Close any socket on HTTP errors */
us_socket_close(SSL, s, 0, nullptr); us_socket_close(SSL, s, 0, nullptr);
/* This just makes the following code act as if the socket was closed from error inside the parser. */ /* This just makes the following code act as if the socket was closed from error inside the parser. */
+42 -21
View File
@@ -26,6 +26,7 @@
#include <cstring> #include <cstring>
#include <algorithm> #include <algorithm>
#include <climits> #include <climits>
#include <string_view>
#include "MoveOnlyFunction.h" #include "MoveOnlyFunction.h"
#include "ChunkedEncoding.h" #include "ChunkedEncoding.h"
@@ -35,6 +36,21 @@
namespace uWS { namespace uWS {
/* Possible errors from http parsing */
enum HttpError {
HTTP_ERROR_505_HTTP_VERSION_NOT_SUPPORTED = 1,
HTTP_ERROR_431_REQUEST_HEADER_FIELDS_TOO_LARGE = 2,
HTTP_ERROR_400_BAD_REQUEST = 3
};
/* Returned parser errors match this LUT. */
static const std::string_view httpErrorResponses[] = {
"", /* Zeroth place is no error so don't use it */
"HTTP/1.1 505 HTTP Version Not Supported\r\n\r\n<h1>505 HTTP Version Not Supported</h1>",
"HTTP/1.1 431 Request Header Fields Too Large\r\n\r\n<h1>431 Request Header Fields Too Large</h1>",
"HTTP/1.1 400 Bad Request\r\n\r\n<h1>400 Bad Request</h1>"
};
/* We require at least this much post padding */ /* We require at least this much post padding */
static const unsigned int MINIMUM_HTTP_POST_PADDING = 32; static const unsigned int MINIMUM_HTTP_POST_PADDING = 32;
static void *FULLPTR = (void *)~(uintptr_t)0; static void *FULLPTR = (void *)~(uintptr_t)0;
@@ -281,7 +297,7 @@ private:
} }
/* End is only used for the proxy parser. The HTTP parser recognizes "\ra" as invalid "\r\n" scan and breaks. */ /* End is only used for the proxy parser. The HTTP parser recognizes "\ra" as invalid "\r\n" scan and breaks. */
static unsigned int getHeaders(char *postPaddedBuffer, char *end, struct HttpRequest::Header *headers, void *reserved) { static unsigned int getHeaders(char *postPaddedBuffer, char *end, struct HttpRequest::Header *headers, void *reserved, unsigned int &err) {
char *preliminaryKey, *preliminaryValue, *start = postPaddedBuffer; char *preliminaryKey, *preliminaryValue, *start = postPaddedBuffer;
#ifdef UWS_WITH_PROXY #ifdef UWS_WITH_PROXY
@@ -313,6 +329,8 @@ private:
/* The request line is different from the field names / field values */ /* The request line is different from the field names / field values */
if (!(postPaddedBuffer = consumeRequestLine(postPaddedBuffer, headers[0]))) { if (!(postPaddedBuffer = consumeRequestLine(postPaddedBuffer, headers[0]))) {
/* Error - invalid request line */ /* Error - invalid request line */
/* Assuming it is 505 HTTP Version Not Supported */
err = HTTP_ERROR_505_HTTP_VERSION_NOT_SUPPORTED;
return 0; return 0;
} }
headers++; headers++;
@@ -394,13 +412,14 @@ private:
/* How much data we CONSUMED (to throw away) */ /* How much data we CONSUMED (to throw away) */
unsigned int consumedTotal = 0; unsigned int consumedTotal = 0;
unsigned int err = 0;
/* Fence two bytes past end of our buffer (buffer has post padded margins). /* Fence two bytes past end of our buffer (buffer has post padded margins).
* This is to always catch scan for \r but not for \r\n. */ * This is to always catch scan for \r but not for \r\n. */
data[length] = '\r'; data[length] = '\r';
data[length + 1] = 'a'; /* Anything that is not \n, to trigger "invalid request" */ data[length + 1] = 'a'; /* Anything that is not \n, to trigger "invalid request" */
for (unsigned int consumed; length && (consumed = getHeaders(data, data + length, req->headers, reserved)); ) { for (unsigned int consumed; length && (consumed = getHeaders(data, data + length, req->headers, reserved, err)); ) {
data += consumed; data += consumed;
length -= consumed; length -= consumed;
consumedTotal += consumed; consumedTotal += consumed;
@@ -416,7 +435,7 @@ private:
/* Break if no host header (but we can have empty string which is different from nullptr) */ /* Break if no host header (but we can have empty string which is different from nullptr) */
if (!req->getHeader("host").data()) { if (!req->getHeader("host").data()) {
return {0, FULLPTR}; return {HTTP_ERROR_400_BAD_REQUEST, FULLPTR};
} }
/* RFC 9112 6.3 /* RFC 9112 6.3
@@ -430,7 +449,7 @@ private:
/* Returning fullptr is the same as calling the errorHandler */ /* Returning fullptr is the same as calling the errorHandler */
/* We could be smart and set an error in the context along with this, to indicate what /* We could be smart and set an error in the context along with this, to indicate what
* http error response we might want to return */ * http error response we might want to return */
return {0, FULLPTR}; return {HTTP_ERROR_400_BAD_REQUEST, FULLPTR};
} }
/* Parse query */ /* Parse query */
@@ -478,7 +497,7 @@ private:
dataHandler(user, chunk, chunk.length() == 0); dataHandler(user, chunk, chunk.length() == 0);
} }
if (isParsingInvalidChunkedEncoding(remainingStreamingBytes)) { if (isParsingInvalidChunkedEncoding(remainingStreamingBytes)) {
return {0, FULLPTR}; return {HTTP_ERROR_400_BAD_REQUEST, FULLPTR};
} }
unsigned int consumed = (length - (unsigned int) dataToConsume.length()); unsigned int consumed = (length - (unsigned int) dataToConsume.length());
data = (char *) dataToConsume.data(); data = (char *) dataToConsume.data();
@@ -489,7 +508,7 @@ private:
remainingStreamingBytes = toUnsignedInteger(contentLengthString); remainingStreamingBytes = toUnsignedInteger(contentLengthString);
if (remainingStreamingBytes == UINT_MAX) { if (remainingStreamingBytes == UINT_MAX) {
/* Parser error */ /* Parser error */
return {0, FULLPTR}; return {HTTP_ERROR_400_BAD_REQUEST, FULLPTR};
} }
if (!CONSUME_MINIMALLY) { if (!CONSUME_MINIMALLY) {
@@ -511,11 +530,15 @@ private:
break; break;
} }
} }
/* Whenever we return FULLPTR, the interpretation of "consumed" should be the HttpError enum. */
if (err) {
return {err, FULLPTR};
}
return {consumedTotal, user}; return {consumedTotal, user};
} }
public: public:
void *consumePostPadded(char *data, unsigned int length, void *user, void *reserved, MoveOnlyFunction<void *(void *, HttpRequest *)> &&requestHandler, MoveOnlyFunction<void *(void *, std::string_view, bool)> &&dataHandler, MoveOnlyFunction<void *(void *)> &&errorHandler) { std::pair<unsigned int, void *> consumePostPadded(char *data, unsigned int length, void *user, void *reserved, MoveOnlyFunction<void *(void *, HttpRequest *)> &&requestHandler, MoveOnlyFunction<void *(void *, std::string_view, bool)> &&dataHandler) {
/* This resets BloomFilter by construction, but later we also reset it again. /* This resets BloomFilter by construction, but later we also reset it again.
* Optimize this to skip resetting twice (req could be made global) */ * Optimize this to skip resetting twice (req could be made global) */
@@ -530,7 +553,7 @@ public:
dataHandler(user, chunk, chunk.length() == 0); dataHandler(user, chunk, chunk.length() == 0);
} }
if (isParsingInvalidChunkedEncoding(remainingStreamingBytes)) { if (isParsingInvalidChunkedEncoding(remainingStreamingBytes)) {
return FULLPTR; return {HTTP_ERROR_400_BAD_REQUEST, FULLPTR};
} }
data = (char *) dataToConsume.data(); data = (char *) dataToConsume.data();
length = (unsigned int) dataToConsume.length(); length = (unsigned int) dataToConsume.length();
@@ -540,7 +563,7 @@ public:
if (remainingStreamingBytes >= length) { if (remainingStreamingBytes >= length) {
void *returnedUser = dataHandler(user, std::string_view(data, length), remainingStreamingBytes == length); void *returnedUser = dataHandler(user, std::string_view(data, length), remainingStreamingBytes == length);
remainingStreamingBytes -= length; remainingStreamingBytes -= length;
return returnedUser; return {0, returnedUser};
} else { } else {
void *returnedUser = dataHandler(user, std::string_view(data, remainingStreamingBytes), true); void *returnedUser = dataHandler(user, std::string_view(data, remainingStreamingBytes), true);
@@ -550,7 +573,7 @@ public:
remainingStreamingBytes = 0; remainingStreamingBytes = 0;
if (returnedUser != user) { if (returnedUser != user) {
return returnedUser; return {0, returnedUser};
} }
} }
} }
@@ -567,7 +590,7 @@ public:
// break here on break // break here on break
std::pair<unsigned int, void *> consumed = fenceAndConsumePostPadded<true>(fallback.data(), (unsigned int) fallback.length(), user, reserved, &req, requestHandler, dataHandler); std::pair<unsigned int, void *> consumed = fenceAndConsumePostPadded<true>(fallback.data(), (unsigned int) fallback.length(), user, reserved, &req, requestHandler, dataHandler);
if (consumed.second != user) { if (consumed.second != user) {
return consumed.second; return consumed;
} }
if (consumed.first) { if (consumed.first) {
@@ -587,7 +610,7 @@ public:
dataHandler(user, chunk, chunk.length() == 0); dataHandler(user, chunk, chunk.length() == 0);
} }
if (isParsingInvalidChunkedEncoding(remainingStreamingBytes)) { if (isParsingInvalidChunkedEncoding(remainingStreamingBytes)) {
return FULLPTR; return {HTTP_ERROR_400_BAD_REQUEST, FULLPTR};
} }
data = (char *) dataToConsume.data(); data = (char *) dataToConsume.data();
length = (unsigned int) dataToConsume.length(); length = (unsigned int) dataToConsume.length();
@@ -596,7 +619,7 @@ public:
if (remainingStreamingBytes >= (unsigned int) length) { if (remainingStreamingBytes >= (unsigned int) length) {
void *returnedUser = dataHandler(user, std::string_view(data, length), remainingStreamingBytes == (unsigned int) length); void *returnedUser = dataHandler(user, std::string_view(data, length), remainingStreamingBytes == (unsigned int) length);
remainingStreamingBytes -= length; remainingStreamingBytes -= length;
return returnedUser; return {0, returnedUser};
} else { } else {
void *returnedUser = dataHandler(user, std::string_view(data, remainingStreamingBytes), true); void *returnedUser = dataHandler(user, std::string_view(data, remainingStreamingBytes), true);
@@ -606,7 +629,7 @@ public:
remainingStreamingBytes = 0; remainingStreamingBytes = 0;
if (returnedUser != user) { if (returnedUser != user) {
return returnedUser; return {0, returnedUser};
} }
} }
} }
@@ -614,17 +637,15 @@ public:
} else { } else {
if (fallback.length() == MAX_FALLBACK_SIZE) { if (fallback.length() == MAX_FALLBACK_SIZE) {
// note: you don't really need error handler, just return something strange! return {HTTP_ERROR_431_REQUEST_HEADER_FIELDS_TOO_LARGE, FULLPTR};
// we could have it return a constant pointer to denote error!
return errorHandler(user);
} }
return user; return {0, user};
} }
} }
std::pair<unsigned int, void *> consumed = fenceAndConsumePostPadded<false>(data, length, user, reserved, &req, requestHandler, dataHandler); std::pair<unsigned int, void *> consumed = fenceAndConsumePostPadded<false>(data, length, user, reserved, &req, requestHandler, dataHandler);
if (consumed.second != user) { if (consumed.second != user) {
return consumed.second; return consumed;
} }
data += consumed.first; data += consumed.first;
@@ -634,12 +655,12 @@ public:
if (length < MAX_FALLBACK_SIZE) { if (length < MAX_FALLBACK_SIZE) {
fallback.append(data, length); fallback.append(data, length);
} else { } else {
return errorHandler(user); return {HTTP_ERROR_431_REQUEST_HEADER_FIELDS_TOO_LARGE, FULLPTR};
} }
} }
// added for now // added for now
return user; return {0, user};
} }
}; };