Add http errors
This commit is contained in:
+1
-5
@@ -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
@@ -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
@@ -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};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
+1
-1
Submodule uSockets updated: 8cd4cb66eb...57252c4a27
Reference in New Issue
Block a user