curl-fuzzer C++ reference
Loading...
Searching...
No Matches
telnet_mock_server.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 <memory>
15#include <string>
16
21
22namespace proto_fuzzer {
23
24namespace {
25
26// BoundTelnetResponse limits worst-case negotiation output to well below this
27// floor. Rejecting a surprising platform socket configuration is preferable
28// to letting curl's blocking POLLOUT path become a fuzzer timeout.
29constexpr std::size_t kMinClientSendBufferBytes = 64 * 1024;
30
31// curl currently builds each TTYPE, XDISPLOC, or NEW_ENV reply in a fixed
32// 2048-byte buffer. Charging that whole buffer to every retained IAC is more
33// conservative than curl's protocol grammar, while doubled upload bytes and a
34// generous allowance for initial/small negotiation frames cover the remaining
35// writes that can accumulate before the pre-read drain runs.
36constexpr std::size_t kCurlTelnetSuboptionReplyBytes = 2048;
37constexpr std::size_t kSmallNegotiationHeadroomBytes = 4 * 1024;
38constexpr std::size_t kMaxBufferedClientBytes =
39 scenario_limits::kMaxTelnetControlBytes * kCurlTelnetSuboptionReplyBytes +
40 2 * scenario_limits::kMaxTelnetUploadBytes + kSmallNegotiationHeadroomBytes;
41static_assert(kMaxBufferedClientBytes <= kMinClientSendBufferBytes,
42 "TELNET mutation caps exceed the verified client send buffer");
43
48void CopyTelnetResponsePrefix(const curl::fuzzer::proto::Connection& source,
49 curl::fuzzer::proto::Connection* destination) {
50 std::size_t bytes_left = scenario_limits::kMaxTelnetResponseBytes;
51 const auto copy_fragment = [&bytes_left](const std::string& fragment, std::string* output) {
52 const std::size_t retained = std::min(fragment.size(), bytes_left);
53 output->assign(fragment.data(), retained);
54 bytes_left -= retained;
55 return retained == fragment.size();
56 };
57
58 if (!copy_fragment(source.initial_response(), destination->mutable_initial_response())) {
59 return;
60 }
61
62 const std::size_t chunk_count =
63 std::min<std::size_t>(scenario_limits::kMaxTelnetResponseChunks, source.on_readable_size());
64 for (std::size_t index = 0; index < chunk_count && bytes_left != 0; ++index) {
65 if (!copy_fragment(source.on_readable(static_cast<int>(index)), destination->add_on_readable())) {
66 break;
67 }
68 }
69}
70
71} // namespace
72
75TelnetMockServer::TelnetMockServer() : scenario_(nullptr), socket_opened_(false) {}
76
79
86 if (request_data != nullptr) {
87 request_data->SetBeforeUploadReadCallback(&DrainBeforeUploadRead, this);
88 }
89}
90
92void TelnetMockServer::DrainBeforeUploadRead(void* userdata) {
93 auto* server = static_cast<TelnetMockServer*>(userdata);
94 if (server != nullptr && server->connection_) {
95 (void)server->connection_->DrainIncoming();
96 }
97}
98
111curl_socket_t TelnetMockServer::HandleOpenSocket(curlsocktype purpose, struct curl_sockaddr* address) {
112 (void)purpose;
113 (void)address;
114 if (scenario_ == nullptr || socket_opened_) {
115 return CURL_SOCKET_BAD;
116 }
117 socket_opened_ = true;
118
119 connection_ = std::make_unique<MockConnection>();
120 if (!connection_->ok()) {
121 connection_.reset();
122 return CURL_SOCKET_BAD;
123 }
124
125 if (!connection_->EnsureClientSendBufferSize(kMinClientSendBufferBytes)) {
126 connection_.reset();
127 return CURL_SOCKET_BAD;
128 }
129
130 const auto write_bytes = [this](const std::string& bytes) {
131 return bytes.empty() || connection_->WriteAll(reinterpret_cast<const unsigned char*>(bytes.data()), bytes.size());
132 };
133
134 // Compatibility corpus entries bypass the LPM postprocessor. Copy only the
135 // raw prefix TELNET can consume, then normalize it so direct replay gets the
136 // same byte/control budgets without traversing HTTP/WS-only fields.
137 const auto& source = scenario_->connection();
138 curl::fuzzer::proto::Connection script;
139 CopyTelnetResponsePrefix(source, &script);
140 BoundTelnetResponse(&script);
141 bool complete = write_bytes(script.initial_response());
142 const std::size_t chunk_count = static_cast<std::size_t>(script.on_readable_size());
143 for (std::size_t index = 0; complete && index < chunk_count; ++index) {
144 complete = write_bytes(script.on_readable(static_cast<int>(index)));
145 }
146 (void)complete;
147
148 // Do not apply BackpressureConfig here. curl's TELNET driver sends from
149 // inside its blocking protocol loop; tightening the socket would invalidate
150 // the minimum-capacity invariant used by the pre-read drain.
151 connection_->ShutdownWrite();
152 return connection_->take_client_fd();
153}
154
162void TelnetMockServer::RunLoop(CURLM* multi, CURL* easy, const curl::fuzzer::proto::Scenario& scenario) {
163 (void)easy;
164 scenario_ = &scenario;
165 socket_opened_ = false;
166 connection_.reset();
167
168 int still_running = 1;
169 for (int iteration = 0; still_running && iteration < kMaxIdleIterations; ++iteration) {
170 const CURLMcode result = curl_multi_perform(multi, &still_running);
171 if (result != CURLM_OK) {
172 break;
173 }
174 if (connection_) {
175 (void)connection_->DrainIncoming();
176 }
177 }
178
179 scenario_ = nullptr;
180}
181
182} // namespace proto_fuzzer
std::unique_ptr< MockConnection > connection_
The per-scenario MockConnection, lazily created by HandleOpenSocket().
static constexpr int kMaxIdleIterations
Consecutive no-progress budget for ordinary scenarios.
Builds HTTP headers or TELNET options, MIME state, and upload callbacks whose storage or userdata lib...
void SetBeforeUploadReadCallback(UploadScriptState::BeforeReadCallback callback, void *userdata)
Arrange protocol-specific work immediately before every upload read.
Preloads a complete bounded TELNET response before curl enters its protocol loop.
curl_socket_t HandleOpenSocket(curlsocktype purpose=CURLSOCKTYPE_IPCXN, struct curl_sockaddr *address=nullptr) override
Create one socketpair, preload every raw response fragment curl can observe, then half-close the peer...
void ConfigureRequestData(ScenarioRequestData *request_data) override
Attach the synchronous peer drain to TELNET's upload callback.
void RunLoop(CURLM *multi, CURL *easy, const curl::fuzzer::proto::Scenario &scenario) override
Drive a small number of non-waiting multi transitions.
TelnetMockServer()
Start without a borrowed scenario.
~TelnetMockServer() override
The base owns and closes the server half after curl releases its client fd.
MockConnection and MockServer — the in-process peer that feeds canned response bytes to libcurl over ...
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 kMaxTelnetControlBytes
Bound reply amplification as well as input bytes.
constexpr std::size_t kMaxTelnetResponseBytes
TELNET consumes the peer synchronously, so only this bounded prefix can be preloaded before curl ente...
constexpr std::size_t kMaxTelnetUploadBytes
TELNET writes upload bytes while its blocking driver owns the thread, so keep total callback work sma...
constexpr std::size_t kMaxTelnetResponseChunks
Raw fragments are all preloaded before TELNET starts.
Non-blocking in-process peer for curl's blocking TELNET driver.
void BoundTelnetResponse(curl::fuzzer::proto::Connection *connection)
Retain the response prefix that TelnetMockServer can preload safely.
Shared TELNET response normalization.