Line | Count | Source |
1 | | /* |
2 | | * Copyright (C) 2026 Emweb bv, Herent, Belgium. |
3 | | * |
4 | | * See the LICENSE file for terms of use. |
5 | | */ |
6 | | |
7 | | #include <stdint.h> |
8 | | #include <stddef.h> |
9 | | #include <sstream> |
10 | | #include <string> |
11 | | #include <vector> |
12 | | |
13 | | #include "Wt/WSslInfo.h" |
14 | | #include "web/CgiParser.h" |
15 | | #include "web/WebRequest.h" |
16 | | |
17 | 2.45k | #define kMaxInputLength 65536 |
18 | | |
19 | | // Minimal WebRequest used to drive CgiParser without a server/socket; modelled |
20 | | // on the MockRequest in test/private/EventDecodeTest.C. The fuzz bytes are the |
21 | | // request body fed through in(). |
22 | | class FuzzRequest : public Wt::WebRequest { |
23 | | public: |
24 | 0 | bool supportsTransferWebSocketResourceSocket() override { return false; } |
25 | 0 | void flush(WT_MAYBE_UNUSED ResponseState state, WT_MAYBE_UNUSED const WriteCallback& callback) override { } |
26 | 5.47k | std::istream &in() override { return in_; } |
27 | 0 | std::ostream &out() override { return out_; } |
28 | 0 | std::ostream &err() override { return err_; } |
29 | 0 | void setRedirect(WT_MAYBE_UNUSED const std::string& url) override { } |
30 | 0 | void setStatus(WT_MAYBE_UNUSED int status) override { } |
31 | 0 | int status() override { return 0; } |
32 | 0 | void setContentType(const std::string &value) override { contentType_ = value; } |
33 | 2.44k | const char *contentType() const override { return contentType_.c_str(); } |
34 | 0 | void setContentLength(int64_t length) override { contentLength_ = length; } |
35 | 2.44k | int64_t contentLength() const override { return contentLength_; } |
36 | 0 | void addHeader(WT_MAYBE_UNUSED const std::string& name, WT_MAYBE_UNUSED const std::string& value) override { } |
37 | 0 | void insertHeader(WT_MAYBE_UNUSED const std::string &name, WT_MAYBE_UNUSED const std::string &value) override { } |
38 | 0 | const char *envValue(WT_MAYBE_UNUSED const char *name) const override { return nullptr; } |
39 | 0 | const std::string &serverName() const override { return s_; } |
40 | 0 | const std::string &serverPort() const override { return s_; } |
41 | 0 | const std::string &scriptName() const override { return s_; } |
42 | 2.44k | const char *requestMethod() const override { return requestMethod_.c_str(); } |
43 | 2.44k | const std::string &queryString() const override { return s_; } |
44 | 0 | const std::string &pathInfo() const override { return s_; } |
45 | 0 | const std::string &remoteAddr() const override { return s_; } |
46 | 0 | const char *urlScheme() const override { return s_.c_str(); } |
47 | 0 | const char *headerValue(WT_MAYBE_UNUSED const char* name) const override { return nullptr; } |
48 | 0 | std::vector<Wt::Http::Message::Header> headers() const override { return {}; } |
49 | 0 | std::unique_ptr<Wt::WSslInfo> sslInfo(const Wt::Configuration &) const override { return nullptr; } |
50 | | |
51 | | std::string contentType_; |
52 | | int64_t contentLength_ = 0; |
53 | | std::stringstream in_; |
54 | | std::stringstream out_; |
55 | | std::stringstream err_; |
56 | | std::string requestMethod_ = "POST"; |
57 | | std::string s_; |
58 | | }; |
59 | | |
60 | | // Fuzzes CgiParser (src/web/CgiParser.C): the multipart/form-data and |
61 | | // x-www-form-urlencoded request-body parser. |
62 | 2.45k | extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) { |
63 | 2.45k | if (Size < 1 || Size > kMaxInputLength) { |
64 | 10 | return 0; |
65 | 10 | } |
66 | | |
67 | 2.44k | const uint8_t selector = Data[0]; |
68 | 2.44k | const char *body = reinterpret_cast<const char *>(Data + 1); |
69 | 2.44k | const size_t bodyLen = Size - 1; |
70 | | |
71 | 2.44k | FuzzRequest request; |
72 | 2.44k | request.in_.write(body, bodyLen); |
73 | 2.44k | request.contentLength_ = static_cast<int64_t>(bodyLen); |
74 | | |
75 | 2.44k | if ((selector & 1) == 0) { |
76 | 1.23k | request.contentType_ = "multipart/form-data; boundary=AaB03x"; |
77 | 1.23k | } else { |
78 | 1.21k | request.contentType_ = "application/x-www-form-urlencoded"; |
79 | 1.21k | } |
80 | | |
81 | 2.44k | try { |
82 | 2.44k | Wt::CgiParser parser(/*maxRequestSize=*/1 << 20, /*maxFormData=*/1 << 20); |
83 | 2.44k | parser.parse(request, Wt::CgiParser::ReadDefault); |
84 | 2.44k | } catch (...) { |
85 | 1.22k | } |
86 | | |
87 | 2.44k | return 0; |
88 | 2.44k | } |