Coverage Report

Created: 2026-09-01 07:00

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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