Coverage Report

Created: 2026-10-15 06:25

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/curl_fuzzer/proto_fuzzer/option_model.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 Generated option semantics without curl execution or handle state.
9
10
#include "proto_fuzzer/option_model.h"
11
12
#include <curl/curl.h>
13
14
namespace proto_fuzzer {
15
16
namespace {
17
18
// Only the generated constants require curl headers. This translation unit
19
// makes no libcurl calls and can be linked into normalization-only tests.
20
namespace generated {
21
#include "curl_fuzzer_option_manifest.inc"
22
}  // namespace generated
23
24
}  // namespace
25
26
/// Decode using an already-resolved descriptor, avoiding another lookup when
27
/// the runtime has selected the option's application path.
28
/// @param descriptor Generated description of the option being decoded.
29
/// @param option Structured option supplying the integral value.
30
/// @return Integral value interpreted according to the descriptor's family.
31
std::uint64_t DecodeIntegralOptionValue(const OptionDescriptor& descriptor,
32
585k
                                        const curl::fuzzer::proto::SetOption& option) {
33
585k
  if (descriptor.kind == OptionValueKind::kString) {
34
0
    return 0;
35
0
  }
36
585k
  switch (option.value_case()) {
37
165k
    case curl::fuzzer::proto::SetOption::kBoolValue:
38
165k
      return option.bool_value() ? 1U : 0U;
39
397k
    case curl::fuzzer::proto::SetOption::kUintValue:
40
397k
      return descriptor.kind == OptionValueKind::kBool ? (option.uint_value() != 0 ? 1U : 0U) : option.uint_value();
41
1.76k
    case curl::fuzzer::proto::SetOption::kStringValue:
42
21.5k
    case curl::fuzzer::proto::SetOption::VALUE_NOT_SET:
43
21.5k
      return 0;
44
585k
  }
45
0
  return 0;
46
585k
}
47
48
/// Return the generated description, or nullptr for an unsupported option.
49
/// @param id Protobuf option identifier to look up.
50
/// @return Immutable generated descriptor, or nullptr when unsupported.
51
1.78M
const OptionDescriptor* LookupOptionDescriptor(curl::fuzzer::proto::CurlOptionId id) {
52
1.78M
  return generated::LookupOptionDescriptor(id);
53
1.78M
}
54
55
/// Whether curl will interpret this pin as an in-memory digest expression.
56
/// @param value Public-key pin expression to inspect.
57
/// @return True when the value begins with curl's in-memory digest prefix.
58
17.8k
bool UsesInMemoryPublicKeyPin(const std::string& value) { return value.rfind("sha256//", 0) == 0; }
59
60
/// Prefix filename-like pins with in-memory digest syntax. This helper does
61
/// not trim bytes; fixed-target normalization applies its final budget after
62
/// canonicalization, while compatibility execution preserves its input size.
63
/// @param value Pin expression to constrain in place; nullptr is harmless.
64
7.86k
void ConstrainPinnedPublicKeyValue(std::string* value) {
65
7.86k
  if (value != nullptr && !UsesInMemoryPublicKeyPin(*value)) {
66
3.77k
    value->insert(0, "sha256//");
67
3.77k
  }
68
7.86k
}
69
70
/// Decode a recognized integral option. Numeric options preserve magnitude;
71
/// boolean options use truthiness. Incompatible or unknown values yield zero.
72
/// @param option Structured option whose descriptor and value will be read.
73
/// @return Descriptor-aware integral value, or zero for incompatible values.
74
4.96k
std::uint64_t DecodeIntegralOptionValue(const curl::fuzzer::proto::SetOption& option) {
75
4.96k
  const OptionDescriptor* descriptor = LookupOptionDescriptor(option.option_id());
76
4.96k
  return descriptor == nullptr ? 0 : DecodeIntegralOptionValue(*descriptor, option);
77
4.96k
}
78
79
/// Restore each recognized option's consumed oneof member without calling
80
/// libcurl. Public-key pins are constrained to curl's in-memory digest syntax.
81
/// Unknown ids remain available for later mutation into a supported option.
82
/// @param scenario Scenario to canonicalize in place; nullptr is harmless.
83
322k
void CanonicalizeOptionValueCases(curl::fuzzer::proto::Scenario* scenario) {
84
322k
  if (scenario == nullptr) {
85
0
    return;
86
0
  }
87
797k
  for (auto& option : *scenario->mutable_options()) {
88
797k
    const OptionDescriptor* descriptor = LookupOptionDescriptor(option.option_id());
89
797k
    if (descriptor == nullptr) {
90
24.9k
      continue;
91
24.9k
    }
92
772k
    switch (descriptor->kind) {
93
315k
      case OptionValueKind::kString:
94
315k
        if (option.value_case() != curl::fuzzer::proto::SetOption::kStringValue) {
95
3.57k
          option.set_string_value("");
96
3.57k
        }
97
315k
        if (descriptor->id == curl::fuzzer::proto::CURLOPT_PINNEDPUBLICKEY) {
98
7.23k
          ConstrainPinnedPublicKeyValue(option.mutable_string_value());
99
7.23k
        }
100
315k
        break;
101
317k
      case OptionValueKind::kUint:
102
317k
        if (option.value_case() != curl::fuzzer::proto::SetOption::kUintValue) {
103
4.80k
          option.set_uint_value(DecodeIntegralOptionValue(*descriptor, option));
104
4.80k
        }
105
317k
        break;
106
139k
      case OptionValueKind::kBool:
107
139k
        if (option.value_case() != curl::fuzzer::proto::SetOption::kBoolValue) {
108
5.92k
          option.set_bool_value(DecodeIntegralOptionValue(*descriptor, option) != 0);
109
5.92k
        }
110
139k
        break;
111
772k
    }
112
772k
  }
113
322k
}
114
115
}  // namespace proto_fuzzer