diff --git a/fuzzing/EpollHelloWorld.cpp b/fuzzing/EpollHelloWorld.cpp index 831029e..f7ef339 100644 --- a/fuzzing/EpollHelloWorld.cpp +++ b/fuzzing/EpollHelloWorld.cpp @@ -141,8 +141,8 @@ void test() { }); /* Trigger some context functions */ - app.addServerName("", {}); - app.removeServerName(""); + app.addServerName("servername", {}); + app.removeServerName("servername"); app.missingServerName(nullptr); app.getNativeHandle(); diff --git a/src/BloomFilter.h b/src/BloomFilter.h index 95ced77..0d0671f 100644 --- a/src/BloomFilter.h +++ b/src/BloomFilter.h @@ -44,12 +44,14 @@ private: public: bool mightHave(std::string_view key) { - return filter.test(hash1(key)) && filter.test(hash2(key)) && (key.length() < 2 || filter.test(hash3(key))); + return !key.length() || (filter.test(hash1(key)) && filter.test(hash2(key)) && (key.length() < 2 || filter.test(hash3(key)))); } void add(std::string_view key) { - filter.set(hash1(key)); - filter.set(hash2(key)); + if (key.length()) { + filter.set(hash1(key)); + filter.set(hash2(key)); + } if (key.length() >= 2) { filter.set(hash3(key)); } @@ -62,4 +64,4 @@ public: } -#endif // UWS_BLOOMFILTER_H \ No newline at end of file +#endif // UWS_BLOOMFILTER_H diff --git a/src/HttpParser.h b/src/HttpParser.h index 3a1e7af..0cfa7bc 100644 --- a/src/HttpParser.h +++ b/src/HttpParser.h @@ -162,26 +162,19 @@ private: return unsignedIntegerValue; } - static void *memchr_r(const char *p, const char *end) { - uint64_t mask = *(uint64_t *)"\r\r\r\r\r\r\r\r"; - if (p <= end - 8) { - for (; p <= end - 8; p += 8) { - uint64_t val = *(uint64_t *)p ^ mask; - if ((val + 0xfefefefefefefeffull) & (~val & 0x8080808080808080ull)) { - break; - } - } - } - - for (; p < end; p++) { - if (*(unsigned char *)p == '\r') { + /* Find carriage return will scan forever. But we "fence" the end margin part of the receive buffer, + * by putting a CR there in case one isn't found before it, so this optimizing assumption is fine. */ + static inline void *find_cr(char *p, char */*end*/) { + for (uint64_t mask = 0x0d0d0d0d0d0d0d0d; true; p += 8) { + uint64_t val = *(uint64_t *)p ^ mask; + if ((val + 0xfefefefefefefeffull) & (~val & 0x8080808080808080ull)) { + while (*(unsigned char *)p != 0x0d) p++; return (void *)p; } } + } - return nullptr; - } - + /* 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) { char *preliminaryKey, *preliminaryValue, *start = postPaddedBuffer; @@ -210,29 +203,45 @@ private: * for PROXY means we can end up succeeding, yet leaving bytes in the fallback buffer * which is then removed, and our counters to flip due to overflow and we end up with a crash */ - for (unsigned int i = 0; i < HttpRequest::MAX_HEADERS; i++) { - for (preliminaryKey = postPaddedBuffer; (*postPaddedBuffer != ':') & (*(unsigned char *)postPaddedBuffer > 32); *(postPaddedBuffer++) |= 32); - if (*postPaddedBuffer == '\r') { - if ((postPaddedBuffer != end) & (postPaddedBuffer[1] == '\n') & (i > 0)) { - headers->key = std::string_view(nullptr, 0); - return (unsigned int) ((postPaddedBuffer + 2) - start); - } else { - return 0; + for (unsigned int i = 0; i < HttpRequest::MAX_HEADERS - 1; i++) { + /* Lower case and short scan until ':', or stop at \r (from previous scan) */ + for (preliminaryKey = postPaddedBuffer; (*postPaddedBuffer != ':') && (*(unsigned char *)postPaddedBuffer > 32); *(postPaddedBuffer++) |= 32); + headers->key = std::string_view(preliminaryKey, (size_t) (postPaddedBuffer - preliminaryKey)); + /* Assume colon, space follows (this is fine as we have at least 2 bytes past) */ + if (postPaddedBuffer[0] == ':' && postPaddedBuffer[1] == ' ') { + postPaddedBuffer += 2; + } else { + /* Trim until value starts */ + for (; (*postPaddedBuffer == ':' || *(unsigned char *)postPaddedBuffer < 33) && *postPaddedBuffer != '\r'; postPaddedBuffer++); + } + preliminaryValue = postPaddedBuffer; + /* The goal of this call is to find next "\r\n", fast */ + postPaddedBuffer = (char *) find_cr(postPaddedBuffer, end); + /* We fence end[0] with \r, followed by end[1] being something that is "not \n", to signify "not found". + * This way we can have this one single check to see if we found \r\n WITHIN our allowed search space. */ + if (postPaddedBuffer[1] == '\n') { + /* Store this header, it is valid */ + headers->value = std::string_view(preliminaryValue, (size_t) (postPaddedBuffer - preliminaryValue)); + postPaddedBuffer += 2; + headers++; + + /* We definitely have at least one header (or request line), so check if we are done */ + if (*postPaddedBuffer == '\r') { + if (postPaddedBuffer[1] == '\n') { + /* This cann take the very last header space */ + headers->key = std::string_view(nullptr, 0); + return (unsigned int) ((postPaddedBuffer + 2) - start); + } else { + /* \r\n\r plus non-\n letter is malformed request, or simply out of search space */ + return 0; + } } } else { - headers->key = std::string_view(preliminaryKey, (size_t) (postPaddedBuffer - preliminaryKey)); - for (postPaddedBuffer++; (*postPaddedBuffer == ':' || *(unsigned char *)postPaddedBuffer < 33) && *postPaddedBuffer != '\r'; postPaddedBuffer++); - preliminaryValue = postPaddedBuffer; - postPaddedBuffer = (char *) memchr_r(postPaddedBuffer, end); - if (postPaddedBuffer && postPaddedBuffer[1] == '\n') { - headers->value = std::string_view(preliminaryValue, (size_t) (postPaddedBuffer - preliminaryValue)); - postPaddedBuffer += 2; - headers++; - } else { - return 0; - } + /* We are either out of search space or this is a malformed request */ + return 0; } } + /* We ran out of header space, too large request */ return 0; } @@ -243,8 +252,10 @@ private: /* How much data we CONSUMED (to throw away) */ unsigned int consumedTotal = 0; - /* Fence one byte 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. */ data[length] = '\r'; + 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)); ) { data += consumed; diff --git a/tests/HttpParser.cpp b/tests/HttpParser.cpp index c7342f7..cd8e254 100644 --- a/tests/HttpParser.cpp +++ b/tests/HttpParser.cpp @@ -4,8 +4,9 @@ #include "../src/HttpParser.h" int main() { - unsigned char data[] = {0x47, 0x45, 0x54, 0x20, 0x2f, 0x20, 0x48, 0x54, 0x54, 0x50, 0x2f, 0x31, 0x2e, 0x31, 0xd, 0xa, 0x61, 0x73, 0x63, 0x69, 0x69, 0x3a, 0x20, 0x74, 0x65, 0x73, 0x74, 0xd, 0xa, 0x75, 0x74, 0x66, 0x38, 0x3a, 0x20, 0xd1, 0x82, 0xd0, 0xb5, 0xd1, 0x81, 0xd1, 0x82, 0xd, 0xa, 0x48, 0x6f, 0x73, 0x74, 0x3a, 0x20, 0x31, 0x32, 0x37, 0x2e, 0x30, 0x2e, 0x30, 0x2e, 0x31, 0xd, 0xa, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x3a, 0x20, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0xd, 0xa, 0xd, 0xa, 'E'}; - int size = sizeof(data) - 1; + /* Parser needs at least 8 bytes post padding */ + unsigned char data[] = {0x47, 0x45, 0x54, 0x20, 0x2f, 0x20, 0x48, 0x54, 0x54, 0x50, 0x2f, 0x31, 0x2e, 0x31, 0xd, 0xa, 0x61, 0x73, 0x63, 0x69, 0x69, 0x3a, 0x20, 0x74, 0x65, 0x73, 0x74, 0xd, 0xa, 0x75, 0x74, 0x66, 0x38, 0x3a, 0x20, 0xd1, 0x82, 0xd0, 0xb5, 0xd1, 0x81, 0xd1, 0x82, 0xd, 0xa, 0x48, 0x6f, 0x73, 0x74, 0x3a, 0x20, 0x31, 0x32, 0x37, 0x2e, 0x30, 0x2e, 0x30, 0x2e, 0x31, 0xd, 0xa, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x3a, 0x20, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0xd, 0xa, 0xd, 0xa, 'E', 'E', 'E', 'E', 'E', 'E', 'E', 'E'}; + int size = sizeof(data) - 8; void *user = nullptr; void *reserved = nullptr;