/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 |