/src/curl_fuzzer/proto_fuzzer/telnet_scenario.cc
Line | Count | Source |
1 | | /* |
2 | | * Copyright (C) Max Dymond, <cmeister2@gmail.com>, et al. |
3 | | * |
4 | | * SPDX-License-Identifier: curl |
5 | | */ |
6 | | |
7 | | /// @file |
8 | | /// @brief Implementation of shared TELNET response normalization. |
9 | | |
10 | | #include "proto_fuzzer/telnet_scenario.h" |
11 | | |
12 | | #include <cstddef> |
13 | | #include <string> |
14 | | |
15 | | #include "proto_fuzzer/scenario_limits.h" |
16 | | |
17 | | namespace proto_fuzzer { |
18 | | |
19 | | namespace { |
20 | | |
21 | | /// Remove a repeated-field suffix that the TELNET runtime cannot observe. |
22 | | template <typename RepeatedField> |
23 | 8.79k | void TrimRepeated(RepeatedField* field, std::size_t limit) { |
24 | 8.79k | const std::size_t size = static_cast<std::size_t>(field->size()); |
25 | 8.79k | if (size > limit) { |
26 | 13 | field->DeleteSubrange(static_cast<int>(limit), static_cast<int>(size - limit)); |
27 | 13 | } |
28 | 8.79k | } |
29 | | |
30 | | /// Retain one contiguous fragment without crossing either shared budget. |
31 | | /// Counting IAC bytes conservatively covers negotiation and subnegotiation |
32 | | /// replies without duplicating curl's state machine in the harness. |
33 | 11.1k | bool BoundTelnetFragment(std::string* fragment, std::size_t* bytes_left, std::size_t* controls_left) { |
34 | 11.1k | std::size_t retained = 0; |
35 | 1.19M | while (retained < fragment->size() && retained < *bytes_left) { |
36 | 1.18M | if (static_cast<unsigned char>((*fragment)[retained]) == 0xff) { |
37 | 11.6k | if (*controls_left == 0) { |
38 | 42 | break; |
39 | 42 | } |
40 | 11.5k | --*controls_left; |
41 | 11.5k | } |
42 | 1.18M | ++retained; |
43 | 1.18M | } |
44 | | |
45 | 11.1k | const bool complete = retained == fragment->size(); |
46 | 11.1k | fragment->resize(retained); |
47 | 11.1k | *bytes_left -= retained; |
48 | 11.1k | return complete; |
49 | 11.1k | } |
50 | | |
51 | | } // namespace |
52 | | |
53 | | /// Retain the response prefix that TelnetMockServer can preload safely. |
54 | | /// |
55 | | /// TELNET runs synchronously inside curl_multi_perform(), so the harness |
56 | | /// cannot deliver later fragments or drain an arbitrary number of replies |
57 | | /// between parser transitions. This function applies both the byte budget and |
58 | | /// the stricter IAC budget shared by the LPM postprocessor and runtime. Once a |
59 | | /// fragment is truncated, every later fragment is removed because it can no |
60 | | /// longer affect curl. |
61 | | /// @param connection Response script to normalize in place. |
62 | 8.79k | void BoundTelnetResponse(curl::fuzzer::proto::Connection* connection) { |
63 | 8.79k | if (connection == nullptr) { |
64 | 0 | return; |
65 | 0 | } |
66 | | |
67 | 8.79k | TrimRepeated(connection->mutable_on_readable(), scenario_limits::kMaxTelnetResponseChunks); |
68 | | |
69 | 8.79k | std::size_t bytes_left = scenario_limits::kMaxTelnetResponseBytes; |
70 | 8.79k | std::size_t controls_left = scenario_limits::kMaxTelnetControlBytes; |
71 | 8.79k | if (!BoundTelnetFragment(connection->mutable_initial_response(), &bytes_left, &controls_left)) { |
72 | 6 | connection->clear_on_readable(); |
73 | 6 | return; |
74 | 6 | } |
75 | | |
76 | 8.78k | int retained_chunks = 0; |
77 | 11.1k | for (; retained_chunks < connection->on_readable_size(); ++retained_chunks) { |
78 | 2.38k | std::string* fragment = connection->mutable_on_readable(retained_chunks); |
79 | 2.38k | if (!BoundTelnetFragment(fragment, &bytes_left, &controls_left)) { |
80 | | // A genuinely partial prefix is observable and worth retaining. When a |
81 | | // previous fragment exhausted the budget, however, keeping the empty |
82 | | // truncation would give LPM a field curl can never distinguish. |
83 | 37 | if (!fragment->empty()) { |
84 | 25 | ++retained_chunks; |
85 | 25 | } |
86 | 37 | break; |
87 | 37 | } |
88 | 2.38k | } |
89 | | |
90 | 8.78k | auto* chunks = connection->mutable_on_readable(); |
91 | 8.78k | if (retained_chunks < chunks->size()) { |
92 | 23 | chunks->DeleteSubrange(retained_chunks, chunks->size() - retained_chunks); |
93 | 23 | } |
94 | 8.78k | } |
95 | | |
96 | | } // namespace proto_fuzzer |