curl-fuzzer C++ reference
Loading...
Searching...
No Matches
transfer_session.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 <algorithm>
13#include <cstddef>
14#include <cstdint>
15#include <utility>
16
22
23namespace proto_fuzzer {
24namespace {
25
26constexpr char kDevNullPath[] = "/dev/null";
27constexpr char kAltSvcOrigin[] = "altsvc-origin.test";
28constexpr char kAltSvcLoopbackResolve[] = "*:80:127.0.1.127";
29
31int AbortUnexpectedAltSvcResolve(void*, void*, void*) { return 1; }
32
33bool HasCanonicalAltSvcAuthority(const std::string& host_path) {
34 const std::size_t host_size = sizeof(kAltSvcOrigin) - 1;
35 return host_path.compare(0, host_size, kAltSvcOrigin) == 0 &&
36 (host_path.size() == host_size || host_path[host_size] == '/' || host_path[host_size] == '?' ||
37 host_path[host_size] == '#');
38}
39
41void PrepareInputFile(const std::string& contents, std::size_t* remaining_bytes, BoundedAnonymousInputFile* input_file,
42 bool guard_netrc = false) {
43 const std::size_t size = std::min(contents.size(), std::min(scenario_limits::kMaxFileInputBytes, *remaining_bytes));
44 if (size == 0) {
45 return;
46 }
47 if (guard_netrc) {
48 // Retain a parser-neutral newline when curl's loader drops comment lines.
49 // Charge only protobuf bytes to the shared budget, as before.
50 std::string guarded_contents(1, '\n');
51 guarded_contents.append(contents.data(), size);
52 if (input_file->Write(reinterpret_cast<const std::uint8_t*>(guarded_contents.data()), guarded_contents.size())) {
53 *remaining_bytes -= size;
54 }
55 } else if (input_file->Write(reinterpret_cast<const std::uint8_t*>(contents.data()), size)) {
56 *remaining_bytes -= size;
57 }
58}
59
60} // namespace
61
64struct TransferSession::ParserInputs {
65 BoundedAnonymousInputFile file_source{scenario_limits::kMaxFileInputBytes};
66 BoundedAnonymousInputFile cookie{scenario_limits::kMaxFileInputBytes};
67 BoundedAnonymousInputFile altsvc{scenario_limits::kMaxFileInputBytes};
68 BoundedAnonymousInputFile hsts{scenario_limits::kMaxFileInputBytes};
69 BoundedAnonymousInputFile netrc{scenario_limits::kMaxFileInputBytes + 1};
70 BoundedAnonymousInputFile crl{scenario_limits::kMaxFileInputBytes};
71 BoundedAnonymousInputFile pinned_public_key{scenario_limits::kMaxFileInputBytes};
72};
73
74TransferSession::TransferSession() = default;
75TransferSession::~TransferSession() { Close(); }
76
80 if (!easy_) {
81 easy_.reset(curl_easy_init());
82 }
83 return easy_ != nullptr;
84}
85
88CURL* TransferSession::easy() const { return easy_.get(); }
89
93void TransferSession::ApplyBaseline(const curl::fuzzer::proto::Scenario& scenario) {
94 if (easy_) {
95 // Apply the new pointer before releasing the previous list.
96 connect_to_ = ApplyBaselineOptions(easy_.get(), scenario.scheme(), scenario.trace_ids(), scenario.trace_config());
97 }
98}
99
103 if (!easy_ || request_data_ || api_lifecycle_) {
104 return false;
105 }
106 curl_easy_reset(easy_.get());
107 connect_to_.reset();
108 altsvc_resolve_.reset();
109 return true;
110}
111
116bool TransferSession::PrepareInputFiles(const curl::fuzzer::proto::Scenario& scenario, ScenarioRunMode mode) {
117 if (easy_) {
118 return false;
119 }
122 return true;
123 }
124 parser_inputs_ = std::make_unique<ParserInputs>();
125 if (mode == ScenarioRunMode::kFileCoverage) {
126 const std::size_t size = std::min(scenario.file_source().size(), scenario_limits::kMaxFileInputBytes);
127 return parser_inputs_->file_source.Write(reinterpret_cast<const std::uint8_t*>(scenario.file_source().data()),
128 size);
129 }
130 std::size_t remaining_bytes = scenario_limits::kMaxFileInputTotalBytes;
132 PrepareInputFile(scenario.cookie_file(), &remaining_bytes, &parser_inputs_->cookie);
133 PrepareInputFile(scenario.altsvc_file(), &remaining_bytes, &parser_inputs_->altsvc);
134 PrepareInputFile(scenario.hsts_file(), &remaining_bytes, &parser_inputs_->hsts);
135 PrepareInputFile(scenario.netrc_file(), &remaining_bytes, &parser_inputs_->netrc, true);
136 } else {
137 PrepareInputFile(scenario.crl_file(), &remaining_bytes, &parser_inputs_->crl);
138 PrepareInputFile(scenario.pinned_public_key_file(), &remaining_bytes, &parser_inputs_->pinned_public_key);
139 }
140 return true;
141}
142
147 return parser_inputs_ ? parser_inputs_->file_source.path() : nullptr;
148}
149
152 if (!easy_ || !parser_inputs_) {
153 return;
154 }
155 if (const char* path = parser_inputs_->cookie.path()) {
156 (void)curl_easy_setopt(easy_.get(), CURLOPT_COOKIEFILE, path);
157 }
158 // Alt-Svc/HSTS use one option for input and cleanup output. Load first,
159 // then restore the fixed sink; both loaders retain earlier file entries.
160 if (const char* path = parser_inputs_->altsvc.path()) {
161 (void)curl_easy_setopt(easy_.get(), CURLOPT_ALTSVC, path);
162 (void)curl_easy_setopt(easy_.get(), CURLOPT_ALTSVC, kDevNullPath);
163 }
164 if (const char* path = parser_inputs_->hsts.path()) {
165 (void)curl_easy_setopt(easy_.get(), CURLOPT_HSTS, path);
166 (void)curl_easy_setopt(easy_.get(), CURLOPT_HSTS, kDevNullPath);
167 }
168 if (const char* path = parser_inputs_->netrc.path()) {
169 (void)curl_easy_setopt(easy_.get(), CURLOPT_NETRC_FILE, path);
170 (void)curl_easy_setopt(easy_.get(), CURLOPT_NETRC, CURL_NETRC_REQUIRED);
171 }
172 if (const char* path = parser_inputs_->crl.path()) {
173 (void)curl_easy_setopt(easy_.get(), CURLOPT_CRLFILE, path);
174 }
175}
176
181 if (!easy_ || !parser_inputs_) {
182 return;
183 }
184 if (const char* path = parser_inputs_->pinned_public_key.path()) {
185 (void)curl_easy_setopt(easy_.get(), CURLOPT_PINNEDPUBLICKEY, path);
186 }
187}
188
191void TransferSession::ConfigureAltSvcRouting(const std::string& host_path) {
192 if (!easy_ || !parser_inputs_ || parser_inputs_->altsvc.path() == nullptr ||
193 !HasCanonicalAltSvcAuthority(host_path)) {
194 return;
195 }
196 CurlSlistPtr resolve(curl_slist_append(nullptr, kAltSvcLoopbackResolve));
197 if (!resolve || curl_easy_setopt(easy_.get(), CURLOPT_RESOLVE, resolve.get()) != CURLE_OK) {
198 return;
199 }
200 // Adopt only after installation; failures leave the previous list alive.
201 altsvc_resolve_ = std::move(resolve);
202 if (curl_easy_setopt(easy_.get(), CURLOPT_RESOLVER_START_FUNCTION, &AbortUnexpectedAltSvcResolve) == CURLE_OK) {
203 // CONNECT_TO takes precedence over Alt-Svc. Only detach it when the
204 // wildcard host cache and fail-closed resolver callback are installed.
205 (void)curl_easy_setopt(easy_.get(), CURLOPT_CONNECT_TO, nullptr);
206 }
207}
208
213ScenarioRequestData* TransferSession::InstallRequestData(const curl::fuzzer::proto::Scenario& scenario,
214 bool apply_resolve_entries) {
215 if (!easy_ || request_data_) {
216 return nullptr;
217 }
218 request_data_.emplace(easy_.get(), scenario, apply_resolve_entries);
219 return &*request_data_;
220}
221
226ApiLifecycle* TransferSession::InstallApiLifecycle(const curl::fuzzer::proto::ApiPlan& plan, std::string_view url) {
227 if (!easy_ || api_lifecycle_) {
228 return nullptr;
229 }
230 api_lifecycle_ = std::make_unique<ApiLifecycle>(easy_.get(), plan, url);
231 return api_lifecycle_.get();
232}
233
236 request_data_.reset();
237 easy_.reset();
238 api_lifecycle_.reset();
239 altsvc_resolve_.reset();
240 connect_to_.reset();
241 parser_inputs_.reset();
242}
243
244} // namespace proto_fuzzer
Safe ownership and typed probes for the dedicated public-API lane.
Owns bounded bytes exposed to filename-only APIs through procfs.
Exercises public easy/share/query APIs while preserving all caller- owned option and callback lifetim...
A reusable anonymous regular file whose descriptor remains reachable as a pathname.
Builds HTTP headers or TELNET options, MIME state, and upload callbacks whose storage or userdata lib...
bool Initialize()
Allocate the easy lazily so unused concurrent-transfer slots stay empty.
CURL * easy() const
Borrow the owned easy; callers must not clean it themselves.
const char * FileSourcePath() const
Return the pathname of the prepared FILE source/sink.
void ApplyInputFiles()
Apply prepared parser paths after baseline and peer installation.
ScenarioRequestData * InstallRequestData(const curl::fuzzer::proto::Scenario &scenario, bool apply_resolve_entries=false)
Install owned request attachments.
void Close()
Release all resources in their required order. Safe to call repeatedly.
bool ResetConfiguration()
Reset pre-transfer configuration while preserving prepared parser files.
void ConfigureAltSvcRouting(const std::string &host_path)
Enable isolated Alt-Svc routing for the canonical deep-HTTP authority.
void ApplyPinnedPublicKeyFile()
Install the prepared public-key pin path after generic scalar options.
void ApplyBaseline(const curl::fuzzer::proto::Scenario &scenario)
Apply baseline options and retain their CONNECT_TO storage.
bool PrepareInputFiles(const curl::fuzzer::proto::Scenario &scenario, ScenarioRunMode mode)
Copy bounded parser inputs into lazily allocated anonymous files.
ApiLifecycle * InstallApiLifecycle(const curl::fuzzer::proto::ApiPlan &plan, std::string_view url)
Install API/share state that survives easy cleanup.
std::unique_ptr< curl_slist, CurlSlistDeleter > CurlSlistPtr
Unique ownership of a caller-owned curl_slist.
Definition curl_raii.h:60
CurlSlistPtr ApplyBaselineOptions(CURL *easy, curl::fuzzer::proto::Scheme scheme, bool trace_ids, const std::string &trace_config)
Apply deterministic routing, timeout, output, and persistence defaults.
Translates Scenario SetOption messages into curl_easy_setopt calls, plus the fixed baseline setopts t...
Owns pointer-valued HTTP headers, TELNET options, MIME state, and upload callback state derived from ...
Shared complexity limits for structured scenarios.
constexpr std::size_t kMaxFileInputBytes
One filename-only parser input can span many lines and cross curl's normal transfer buffer boundary w...
constexpr std::size_t kMaxFileInputTotalBytes
Share a ceiling across filename-backed fixtures so populating several in one scenario cannot multiply...
ScenarioRunMode
Selects one of the complete, valid RunScenario behaviours.
@ kFileCoverage
Drive file:// without a peer, reading only the canonical local target.
@ kTlsCoverage
Drive HTTPS through a real TLS peer and inspect live TLS result state.
@ kDeepHttpCoverage
Drive deep HTTP plus its bounded filename-backed parser inputs.
Owns an easy handle and the backing storage retained by its options.