/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 | 5.02k | void TrimRepeated(RepeatedField* field, std::size_t limit) { |
24 | 5.02k | const std::size_t size = static_cast<std::size_t>(field->size()); |
25 | 5.02k | if (size > limit) { |
26 | 0 | field->DeleteSubrange(static_cast<int>(limit), static_cast<int>(size - limit)); |
27 | 0 | } |
28 | 5.02k | } |
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 | 8.33k | bool BoundTelnetFragment(std::string* fragment, std::size_t* bytes_left, std::size_t* controls_left) { |
34 | 8.33k | std::size_t retained = 0; |
35 | 1.20M | while (retained < fragment->size() && retained < *bytes_left) { |
36 | 1.19M | if (static_cast<unsigned char>((*fragment)[retained]) == 0xff) { |
37 | 12.7k | if (*controls_left == 0) { |
38 | 55 | break; |
39 | 55 | } |
40 | 12.6k | --*controls_left; |
41 | 12.6k | } |
42 | 1.19M | ++retained; |
43 | 1.19M | } |
44 | | |
45 | 8.33k | const bool complete = retained == fragment->size(); |
46 | 8.33k | fragment->resize(retained); |
47 | 8.33k | *bytes_left -= retained; |
48 | 8.33k | return complete; |
49 | 8.33k | } |
50 | | |
51 | | } // namespace |
52 | | |
53 | 5.02k | void BoundTelnetResponse(curl::fuzzer::proto::Connection* connection) { |
54 | 5.02k | if (connection == nullptr) { |
55 | 0 | return; |
56 | 0 | } |
57 | | |
58 | 5.02k | TrimRepeated(connection->mutable_on_readable(), scenario_limits::kMaxTelnetResponseChunks); |
59 | | |
60 | 5.02k | std::size_t bytes_left = scenario_limits::kMaxTelnetResponseBytes; |
61 | 5.02k | std::size_t controls_left = scenario_limits::kMaxTelnetControlBytes; |
62 | 5.02k | if (!BoundTelnetFragment(connection->mutable_initial_response(), &bytes_left, &controls_left)) { |
63 | 15 | connection->clear_on_readable(); |
64 | 15 | return; |
65 | 15 | } |
66 | | |
67 | 5.01k | int retained_chunks = 0; |
68 | 8.28k | for (; retained_chunks < connection->on_readable_size(); ++retained_chunks) { |
69 | 3.30k | std::string* fragment = connection->mutable_on_readable(retained_chunks); |
70 | 3.30k | if (!BoundTelnetFragment(fragment, &bytes_left, &controls_left)) { |
71 | | // A genuinely partial prefix is observable and worth retaining. When a |
72 | | // previous fragment exhausted the budget, however, keeping the empty |
73 | | // truncation would give LPM a field curl can never distinguish. |
74 | 40 | if (!fragment->empty()) { |
75 | 34 | ++retained_chunks; |
76 | 34 | } |
77 | 40 | break; |
78 | 40 | } |
79 | 3.30k | } |
80 | | |
81 | 5.01k | auto* chunks = connection->mutable_on_readable(); |
82 | 5.01k | if (retained_chunks < chunks->size()) { |
83 | 22 | chunks->DeleteSubrange(retained_chunks, chunks->size() - retained_chunks); |
84 | 22 | } |
85 | 5.01k | } |
86 | | |
87 | | } // namespace proto_fuzzer |