curl-fuzzer C++ reference
Loading...
Searching...
No Matches
h2_runtime.cc
Go to the documentation of this file.
1/*
2 * Copyright (C) Max Dymond, <cmeister2@gmail.com>, et al.
3 *
4 * SPDX-License-Identifier: curl
5 */
6
9
11
12#include <curl/multi.h>
13
14#include <algorithm>
15#include <limits>
16#include <utility>
17
19
20namespace proto_fuzzer {
21
22namespace {
23
24// Bound header inspection independently of curl's PUSH_PROMISE limits.
25constexpr std::size_t kMaxInspectedPushHeaders = 16;
26// One extra easy handle exercises accepted push without multiplying work.
27constexpr std::size_t kMaxAcceptedPushes = 1;
28
29} // namespace
30
32H2Runtime::H2Runtime() = default;
33
38void H2Runtime::PrepareMulti(CURLM* multi, bool accept_push) {
39 accept_push_ = accept_push;
40 accepted_push_count_ = 0;
41 pushed_body_bytes_ = 0;
42 (void)curl_multi_setopt(multi, CURLMOPT_PIPELINING, CURLPIPE_MULTIPLEX);
43 (void)curl_multi_setopt(multi, CURLMOPT_PUSHFUNCTION, &H2Runtime::PushCallback);
44 (void)curl_multi_setopt(multi, CURLMOPT_PUSHDATA, this);
45}
46
51 if (connection != nullptr) {
52 connection->SetIncomingDataObserver(&plan_driver_);
53 }
54}
55
60void H2Runtime::ResetPlan(const curl::fuzzer::proto::Http2Plan& plan, std::string scheme, std::string authority) {
61 plan_driver_.Reset(plan, std::move(scheme), std::move(authority));
62 stream_weight_after_headers_ = plan.stream_weight_after_headers();
63 post_headers_transition_applied_ = false;
64 stream_weight_setopt_result_ = CURLE_FAILED_INIT;
65 stream_weight_egress_result_ = CURLE_FAILED_INIT;
66}
67
73 if (stream_weight_after_headers_ == 0 || post_headers_transition_applied_ ||
74 plan_driver_.observed_request_count() == 0) {
75 return;
76 }
77 post_headers_transition_applied_ = true;
78 stream_weight_setopt_result_ =
79 curl_easy_setopt(easy, CURLOPT_STREAM_WEIGHT, static_cast<long>(stream_weight_after_headers_));
80 // Unpausing an already-running H2 stream is a documented no-op at the
81 // transfer layer, but it deliberately asks the live filter to flush its
82 // updated flow/priority state. This public call provides a deterministic
83 // egress boundary without sleeping or depending on socket readiness.
84 stream_weight_egress_result_ = curl_easy_pause(easy, CURLPAUSE_CONT);
85}
86
95void H2Runtime::DrivePlan(CURLM* multi, MockServer& server, int max_idle_iterations, int max_drive_iterations,
96 const std::function<void()>& after_perform) {
97 int still_running = 1;
98 int idle_iterations = 0;
99 int drive_iterations = 0;
100 while (still_running && idle_iterations < max_idle_iterations && drive_iterations++ < max_drive_iterations) {
101 const int running_before = still_running;
102 if (curl_multi_perform(multi, &still_running) != CURLM_OK) {
103 break;
104 }
105 after_perform();
106 if (!still_running) {
107 break;
108 }
109
110 bool made_progress = still_running != running_before;
111 if (MockConnection* connection = server.connection(); connection != nullptr) {
112 made_progress = connection->DrainIncoming() != 0 || made_progress;
113 std::string chunk;
114 if (plan_driver_.NextChunk(&chunk)) {
115 made_progress = true;
116 if (!chunk.empty()) {
117 (void)connection->WriteAll(reinterpret_cast<const unsigned char*>(chunk.data()), chunk.size());
118 }
119 made_progress = connection->DrainIncoming() != 0 || made_progress;
120 }
121 }
122 if (made_progress) {
123 idle_iterations = 0;
124 } else {
125 ++idle_iterations;
126 }
127 }
128}
129
133void H2Runtime::FinishTransfer(CURL* easy, MockConnection* connection) {
134 upkeep_result_ = curl_easy_upkeep(easy);
135 if (connection != nullptr) {
136 (void)connection->DrainIncoming();
137 }
138
139#if defined(PROTO_FUZZER_OBSERVE_STATE)
140 // The carrier still owns every callback and plan observation here. Retain
141 // the completed state in profiling builds without changing the H2 exchange
142 // or adding per-input work to ordinary fuzz targets.
143 const volatile std::size_t observation[] = {push_callback_count(),
148 static_cast<std::size_t>(upkeep_result()),
151 plan_driver_.has_pending_work()};
152 (void)observation;
153#endif
154}
155
157std::size_t H2Runtime::push_callback_count() const { return push_callback_count_; }
158
160std::size_t H2Runtime::push_header_count() const { return push_header_count_; }
161
163bool H2Runtime::saw_push_path() const { return saw_push_path_; }
164
166std::size_t H2Runtime::accepted_push_count() const { return accepted_push_count_; }
167
169std::size_t H2Runtime::pushed_body_bytes() const { return pushed_body_bytes_; }
170
172CURLcode H2Runtime::upkeep_result() const { return upkeep_result_; }
173
175std::size_t H2Runtime::observed_request_count() const { return plan_driver_.observed_request_count(); }
176
178std::size_t H2Runtime::observed_client_settings_count() const { return plan_driver_.observed_client_settings_count(); }
179
181std::size_t H2Runtime::observed_client_priority_count() const { return plan_driver_.observed_client_priority_count(); }
182
184bool H2Runtime::post_headers_transition_applied() const { return post_headers_transition_applied_; }
185
187CURLcode H2Runtime::stream_weight_setopt_result() const { return stream_weight_setopt_result_; }
188
190CURLcode H2Runtime::stream_weight_egress_result() const { return stream_weight_egress_result_; }
191
192int H2Runtime::PushCallback(CURL* /*parent*/, CURL* pushed, std::size_t header_count, struct curl_pushheaders* headers,
193 void* userdata) {
194 auto* self = static_cast<H2Runtime*>(userdata);
195 if (self == nullptr) {
196 return CURL_PUSH_DENY;
197 }
198
199 ++self->push_callback_count_;
200 self->push_header_count_ += header_count;
201 const std::size_t inspected = std::min(header_count, kMaxInspectedPushHeaders);
202 for (std::size_t index = 0; index < inspected; ++index) {
203 (void)curl_pushheader_bynum(headers, index);
204 }
205 self->saw_push_path_ = curl_pushheader_byname(headers, ":path") != nullptr;
206 (void)curl_pushheader_byname(headers, "x-fuzzer-missing");
207
208 if (!self->accept_push_ || self->accepted_push_count_ >= kMaxAcceptedPushes) {
209 return CURL_PUSH_DENY;
210 }
211 // The new pushed handle needs an application-owned sink before acceptance.
212 if (curl_easy_setopt(pushed, CURLOPT_WRITEFUNCTION, &H2Runtime::PushedWriteCallback) != CURLE_OK ||
213 curl_easy_setopt(pushed, CURLOPT_WRITEDATA, self) != CURLE_OK) {
214 return CURL_PUSH_DENY;
215 }
216 ++self->accepted_push_count_;
217 return CURL_PUSH_OK;
218}
219
220std::size_t H2Runtime::PushedWriteCallback(char* /*contents*/, std::size_t size, std::size_t nmemb, void* userdata) {
221 auto* self = static_cast<H2Runtime*>(userdata);
222 const std::size_t maximum = std::numeric_limits<std::size_t>::max();
223 if (self == nullptr || (size != 0 && nmemb > maximum / size)) {
224 return 0;
225 }
226 const std::size_t bytes = size * nmemb;
227 self->pushed_body_bytes_ += std::min(bytes, maximum - self->pushed_body_bytes_);
228 return bytes;
229}
230
231} // namespace proto_fuzzer
Protocol state shared by TLS and plaintext HTTP/2 origin peers.
Definition h2_runtime.h:33
bool post_headers_transition_applied() const
H2Runtime()
Construct detached protocol state without observing a connection.
std::size_t observed_request_count() const
void FinishTransfer(CURL *easy, MockConnection *connection)
Probe upkeep while curl's connection cache and the peer are still live.
std::size_t push_callback_count() const
void ResetPlan(const curl::fuzzer::proto::Http2Plan &plan, std::string scheme, std::string authority="tls.test")
Borrow a structured plan for the ensuing synchronous drive.
Definition h2_runtime.cc:60
std::size_t observed_client_priority_count() const
void AttachConnection(MockConnection *connection)
Observe application bytes after the carrier has decoded its transport.
Definition h2_runtime.cc:50
std::size_t accepted_push_count() const
CURLcode stream_weight_setopt_result() const
std::size_t observed_client_settings_count() const
void ApplyPostHeadersTransition(CURL *easy)
Apply the plan's one state transition only after a real request stream has been observed.
Definition h2_runtime.cc:72
CURLcode stream_weight_egress_result() const
CURLcode upkeep_result() const
void DrivePlan(CURLM *multi, MockServer &server, int max_idle_iterations, int max_drive_iterations, const std::function< void()> &after_perform)
Alternate curl and one bounded H2 plan boundary.
Definition h2_runtime.cc:95
std::size_t push_header_count() const
void PrepareMulti(CURLM *multi, bool accept_push)
Install the bounded push policy before curl emits its initial SETTINGS.
Definition h2_runtime.cc:38
std::size_t pushed_body_bytes() const
bool saw_push_path() const
Owns one half of a socketpair used to feed canned responses to libcurl.
Definition mock_server.h:29
virtual std::size_t DrainIncoming()
Advance incoming transport work according to the configured per-call limit.
void SetIncomingDataObserver(IncomingDataObserver *observer)
Register a non-owning sink for bytes consumed from curl.
Orchestrates a bounded sequence of mock HTTP exchanges: installs socket callbacks on an easy handle,...
Definition mock_server.h:72
Shared HTTP/2 plan, push, and upkeep behavior for origin peers.
MockConnection and MockServer — the in-process peer that feeds canned response bytes to libcurl over ...