Bitcoin Core 32.99.0
P2P Digital Currency
http_request.cpp
Go to the documentation of this file.
1// Copyright (c) 2020-present The Bitcoin Core developers
2// Distributed under the MIT software license, see the accompanying
3// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4
5#include <httpserver.h>
6#include <netaddress.h>
8#include <test/fuzz/fuzz.h>
9#include <test/fuzz/util.h>
10#include <test/util/net.h>
11#include <test/util/time.h>
12#include <util/check.h>
14#include <util/strencodings.h>
15
16#include <cassert>
17#include <cstdint>
18#include <memory>
19#include <string>
20#include <string_view>
21#include <vector>
22
23std::string_view RequestMethodString(HTTPRequestMethod m);
24
25using namespace bitcoin_http;
26
27namespace {
28
33void CheckBodyMatchesFraming(const HTTPRequest& req)
34{
35 const std::string body{req.ReadBody()};
36 const auto transfer_encoding{req.GetHeader("Transfer-Encoding")};
37 const auto content_length{req.GetHeader("Content-Length")};
38 if (transfer_encoding && ToLower(*transfer_encoding) == "chunked") {
39 assert(body.size() <= MAX_BODY_SIZE);
40 } else if (content_length) {
41 const auto parsed_length{ToIntegral<uint64_t>(*content_length)};
42 assert(parsed_length);
43 assert(body.size() == *parsed_length);
44 } else {
45 assert(body.empty());
46 }
47}
48
50void SingleShotParse(const std::string& http_buffer, FuzzedDataProvider& provider)
51{
52 using util::LineReader;
53
54 HTTPRequest http_request;
55 LineReader reader(http_buffer, MAX_HEADERS_SIZE);
56 try {
57 if (!http_request.LoadControlData(reader)) return;
58 if (!http_request.LoadHeaders(reader)) return;
59 if (!http_request.LoadBody(reader)) return;
60 } catch (const std::runtime_error&) {
61 return;
62 }
63
64 const HTTPRequestMethod request_method = http_request.GetRequestMethod();
65 (void)RequestMethodString(request_method);
66 (void)http_request.GetURI();
67 (void)http_request.GetHeader("Host");
68 std::string header = provider.ConsumeRandomLengthString(16);
69 (void)http_request.GetHeader(header);
70 (void)http_request.WriteHeader(std::string(header), provider.ConsumeRandomLengthString(16));
71 (void)http_request.GetHeader(header);
72 CheckBodyMatchesFraming(http_request);
73}
74
76class FuzzClient : public HTTPRemoteClient
77{
78public:
79 FuzzClient() : HTTPRemoteClient{/*id=*/0, /*addr=*/CService(), /*socket=*/std::make_unique<ZeroSock>()} {}
80 void Receive(std::string_view s) { MutateRecvBuffer().append(s); }
81};
82
83int StateRank(HTTPRequest::State s)
84{
85 switch (s) {
86 case HTTPRequest::State::Init: return 0;
88 case HTTPRequest::State::NeedsBody: return 2;
89 case HTTPRequest::State::Complete: return 3;
90 case HTTPRequest::State::Error: return 4;
91 }
92 assert(false);
93}
94
95struct ParsedRequest {
96 HTTPRequestMethod method;
97 std::string uri;
98 std::string host;
99 std::string body;
100 bool operator==(const ParsedRequest&) const = default;
101};
102
103struct RunResult {
104 std::vector<ParsedRequest> requests;
105 bool errored{false};
106 std::string remainder;
108};
109
112void Drain(const std::shared_ptr<FuzzClient>& client, RunResult& out)
113{
114 while (true) {
115 // A failed request is inert: further reads consume nothing.
116 if (const HTTPRequest* cur{client->GetRequest()};
117 cur && cur->GetState() == HTTPRequest::State::Error) {
118 const size_t buffered{client->GetRecvBuffer().size()};
120 assert(client->GetRecvBuffer().size() == buffered);
121 out.errored = true;
122 break;
123 }
124
125 std::unique_ptr<HTTPRequest> req{HTTPRemoteClient::TryReadRequest(client)};
126
127 if (!req) {
128 if (const HTTPRequest* cur{client->GetRequest()}) {
129 // Complete is always handed back, never left behind.
130 assert(cur->GetState() != HTTPRequest::State::Complete);
131 assert(StateRank(cur->GetState()) >= StateRank(out.last_state));
132 out.last_state = cur->GetState();
133 if (cur->GetState() == HTTPRequest::State::Error) out.errored = true;
134 if (const auto chunk_size{cur->GetChunkSize()}) {
135 assert(cur->GetChunkProgress() <= *chunk_size);
136 }
137 assert(cur->ReadBody().size() <= MAX_BODY_SIZE);
138 }
139 break;
140 }
141
143 CheckBodyMatchesFraming(*req);
144 out.requests.emplace_back(req->GetRequestMethod(), req->GetURI(),
145 req->GetHeader("Host").value_or(""), req->ReadBody());
146
147 // While a request is with a worker, nothing new is parsed or consumed.
148 const size_t buffered{client->GetRecvBuffer().size()};
150 assert(client->GetRecvBuffer().size() == buffered);
151
152 req->WriteReply(HTTP_OK, ""); // clears m_req_busy
153 out.last_state = HTTPRequest::State::Init;
154 }
155}
156
160void CheckSegmentationIndependence(const std::string& input, FuzzedDataProvider& provider)
161{
162 // WriteReply() stamps a wall-clock Date header and the client stamps a
163 // steady-clock idle time, both of which the fuzz determinism check rejects.
164 FakeNodeClock clock{1610000000s};
165 FakeSteadyClock steady_clock;
166
167 // The whole stream arrives in one I/O cycle. This is similar to
168 // SingleShotParse(), although here we pipe it through the client and also
169 // parse multiple requests.
170 RunResult one_shot;
171 {
172 auto client{std::make_shared<FuzzClient>()};
173 client->Receive(input);
174 Drain(client, one_shot);
175 one_shot.remainder = client->GetRecvBuffer();
176 }
177
178 // The same stream arrives in arbitrary pieces. Once the provider runs dry
179 // this becomes one byte per cycle, the maximally fragmented case.
180 RunResult sliced;
181 {
182 auto client{std::make_shared<FuzzClient>()};
183 std::string_view remaining{input};
184 while (!remaining.empty()) {
185 const size_t n{provider.ConsumeIntegralInRange<size_t>(1, remaining.size())};
186 client->Receive(remaining.substr(0, n));
187 remaining = remaining.substr(n);
188 Drain(client, sliced);
189 }
190 sliced.remainder = client->GetRecvBuffer();
191 }
192
193 assert(one_shot.requests == sliced.requests);
194 assert(one_shot.errored == sliced.errored);
195 // The whole receive buffer is discarded on a parse error, so in the sliced case
196 // what is left over depends on how much had arrived when the error fired.
197 if (!one_shot.errored) assert(one_shot.remainder == sliced.remainder);
198}
199
200} // namespace
201
202FUZZ_TARGET(http_request)
203{
204 FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()};
205 // MAX_HEADERS_SIZE is 8192: leave room for a headers section that can
206 // reach the limit, plus a body.
207 const std::string http_buffer{fuzzed_data_provider.ConsumeRandomLengthString(2 * MAX_HEADERS_SIZE)};
208
209 SingleShotParse(http_buffer, fuzzed_data_provider);
210 CheckSegmentationIndependence(http_buffer, fuzzed_data_provider);
211}
#define Assert(val)
Identity function.
Definition: check.h:116
A combination of a network address (CNetAddr) and a (TCP) port.
Definition: netaddress.h:531
Helper to initialize the global NodeClock, let a duration elapse, and reset it after use in a test.
Definition: time.h:54
Helper to initialize the global MockableSteadyClock, let a duration elapse, and reset it after use in...
Definition: time.h:29
std::string ConsumeRandomLengthString(size_t max_length)
T ConsumeIntegralInRange(T min, T max)
std::string & MutateRecvBuffer()
Used for tests.
Definition: httpserver.h:539
static std::unique_ptr< HTTPRequest > TryReadRequest(const std::shared_ptr< HTTPRemoteClient > &client) EXCLUSIVE_LOCKS_REQUIRED(!client -> m_send_mutex)
Try to read an HTTPRequest from a client's receive buffer.
void Receive() EXCLUSIVE_LOCKS_REQUIRED(!m_sock_mutex)
Definition: httpserver.cpp:931
void WriteHeader(std::string &&hdr, std::string &&value)
Definition: httpserver.cpp:703
std::string GetURI() const
Definition: httpserver.h:186
bool LoadHeaders(util::LineReader &reader)
Definition: httpserver.cpp:426
bool LoadControlData(util::LineReader &reader)
Methods that attempt to parse HTTP request fields line-by-line from a receive buffer.
Definition: httpserver.cpp:378
std::optional< std::string > GetHeader(std::string_view hdr) const
Definition: httpserver.cpp:698
HTTPRequestMethod GetRequestMethod() const
Definition: httpserver.h:188
std::string ReadBody() const
Definition: httpserver.h:191
bool LoadBody(util::LineReader &reader)
Definition: httpserver.cpp:431
void WriteReply(HTTPStatusCode status, std::span< const std::byte > reply_body={})
Definition: httpserver.cpp:535
State GetState() const
Definition: httpserver.h:203
A mocked Sock alternative that succeeds on all operations.
Definition: net.h:169
std::string_view RequestMethodString(HTTPRequestMethod m)
HTTP request method as string - use for logging only.
Definition: httpserver.cpp:117
FUZZ_TARGET(http_request)
HTTPRequestMethod
Definition: httpserver.h:49
util::LineReader reader
std::unique_ptr< ProxyClient< messages::FooInterface > > client
constexpr uint64_t MAX_BODY_SIZE
Maximum size of an HTTP request body received from a client.
Definition: httpserver.h:82
constexpr size_t MAX_HEADERS_SIZE
Maximum size of each headers line in an HTTP request, also the maximum size of all headers total.
Definition: httpserver.h:78
bool operator==(const CNetAddr &a, const CNetAddr &b)
Definition: netaddress.cpp:609
@ HTTP_OK
Definition: protocol.h:14
FuzzedDataProvider provider
Definition: dbwrapper.cpp:366
std::string ToLower(std::string_view str)
Returns the lowercase equivalent of the given string.
assert(!tx.IsCoinBase())
FuzzedDataProvider & fuzzed_data_provider
Definition: fees.cpp:46
FakeNodeClock clock