1 // Copyright 2023 gRPC authors.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 #include <inttypes.h>
16 #include <stdio.h>
17 #include <stdlib.h>
18
19 #include <algorithm>
20 #include <memory>
21 #include <tuple>
22 #include <utility>
23 #include <vector>
24
25 #include "absl/random/bit_gen_ref.h"
26 #include "absl/status/status.h"
27 #include "absl/strings/escaping.h"
28 #include "absl/strings/match.h"
29 #include "src/core/ext/transport/chttp2/transport/hpack_encoder.h"
30 #include "src/core/ext/transport/chttp2/transport/hpack_encoder_table.h"
31 #include "src/core/ext/transport/chttp2/transport/hpack_parser.h"
32 #include "src/core/ext/transport/chttp2/transport/hpack_parser_table.h"
33 #include "src/core/lib/experiments/config.h"
34 #include "src/core/lib/iomgr/error.h"
35 #include "src/core/lib/iomgr/exec_ctx.h"
36 #include "src/core/lib/resource_quota/arena.h"
37 #include "src/core/lib/resource_quota/memory_quota.h"
38 #include "src/core/lib/resource_quota/resource_quota.h"
39 #include "src/core/lib/slice/slice.h"
40 #include "src/core/lib/slice/slice_buffer.h"
41 #include "src/core/lib/transport/metadata_batch.h"
42 #include "src/core/util/ref_counted_ptr.h"
43 #include "src/core/util/status_helper.h"
44 #include "src/libfuzzer/libfuzzer_macro.h"
45 #include "test/core/test_util/fuzz_config_vars.h"
46 #include "test/core/test_util/proto_bit_gen.h"
47 #include "test/core/test_util/test_config.h"
48 #include "test/core/transport/chttp2/hpack_sync_fuzzer.pb.h"
49
50 bool squelch = true;
51 bool leak_check = true;
52
53 namespace grpc_core {
54 namespace {
55
IsStreamError(const absl::Status & status)56 bool IsStreamError(const absl::Status& status) {
57 intptr_t stream_id;
58 return grpc_error_get_int(status, StatusIntProperty::kStreamId, &stream_id);
59 }
60
FuzzOneInput(const hpack_sync_fuzzer::Msg & msg)61 void FuzzOneInput(const hpack_sync_fuzzer::Msg& msg) {
62 ProtoBitGen proto_bit_src(msg.random_numbers());
63
64 // STAGE 1: Encode the fuzzing input into a buffer (encode_output)
65 HPackCompressor compressor;
66 SliceBuffer encode_output;
67 hpack_encoder_detail::Encoder encoder(
68 &compressor, msg.use_true_binary_metadata(), encode_output);
69 for (const auto& header : msg.headers()) {
70 switch (header.ty_case()) {
71 case hpack_sync_fuzzer::Header::TY_NOT_SET:
72 break;
73 case hpack_sync_fuzzer::Header::kIndexed:
74 if (header.indexed() == 0) continue; // invalid encoding
75 encoder.EmitIndexed(header.indexed());
76 break;
77 case hpack_sync_fuzzer::Header::kLiteralIncIdx:
78 if (header.literal_inc_idx().key().length() +
79 header.literal_inc_idx().value().length() >
80 HPackEncoderTable::MaxEntrySize() / 2) {
81 // Not an interesting case to fuzz
82 continue;
83 }
84 if (msg.check_ab_preservation() &&
85 header.literal_inc_idx().key() == "a") {
86 continue;
87 }
88 if (absl::EndsWith(header.literal_inc_idx().value(), "-bin")) {
89 std::ignore = encoder.EmitLitHdrWithBinaryStringKeyIncIdx(
90 Slice::FromCopiedString(header.literal_inc_idx().key()),
91 Slice::FromCopiedString(header.literal_inc_idx().value()));
92 } else {
93 std::ignore = encoder.EmitLitHdrWithNonBinaryStringKeyIncIdx(
94 Slice::FromCopiedString(header.literal_inc_idx().key()),
95 Slice::FromCopiedString(header.literal_inc_idx().value()));
96 }
97 break;
98 case hpack_sync_fuzzer::Header::kLiteralNotIdx:
99 if (msg.check_ab_preservation() &&
100 header.literal_not_idx().key() == "a") {
101 continue;
102 }
103 if (absl::EndsWith(header.literal_not_idx().value(), "-bin")) {
104 encoder.EmitLitHdrWithBinaryStringKeyNotIdx(
105 Slice::FromCopiedString(header.literal_not_idx().key()),
106 Slice::FromCopiedString(header.literal_not_idx().value()));
107 } else {
108 encoder.EmitLitHdrWithNonBinaryStringKeyNotIdx(
109 Slice::FromCopiedString(header.literal_not_idx().key()),
110 Slice::FromCopiedString(header.literal_not_idx().value()));
111 }
112 break;
113 case hpack_sync_fuzzer::Header::kLiteralNotIdxFromIdx:
114 if (header.literal_not_idx_from_idx().index() == 0) continue;
115 encoder.EmitLitHdrWithBinaryStringKeyNotIdx(
116 header.literal_not_idx_from_idx().index(),
117 Slice::FromCopiedString(header.literal_not_idx_from_idx().value()));
118 break;
119 }
120 }
121 if (msg.check_ab_preservation()) {
122 std::ignore = encoder.EmitLitHdrWithNonBinaryStringKeyIncIdx(
123 Slice::FromCopiedString("a"), Slice::FromCopiedString("b"));
124 }
125
126 // STAGE 2: Decode the buffer (encode_output) into a list of headers
127 HPackParser parser;
128 ExecCtx exec_ctx;
129 grpc_metadata_batch read_metadata;
130 parser.BeginFrame(
131 &read_metadata, 1024, 1024, HPackParser::Boundary::EndOfHeaders,
132 HPackParser::Priority::None,
133 HPackParser::LogInfo{1, HPackParser::LogInfo::kHeaders, false});
134 std::vector<std::pair<size_t, absl::Status>> seen_errors;
135 for (size_t i = 0; i < encode_output.Count(); i++) {
136 auto err = parser.Parse(
137 encode_output.c_slice_at(i), i == (encode_output.Count() - 1),
138 absl::BitGenRef(proto_bit_src), /*call_tracer=*/nullptr);
139 if (!err.ok()) {
140 seen_errors.push_back(std::make_pair(i, err));
141 // If we get a connection error (i.e. not a stream error), stop parsing,
142 // return.
143 if (!IsStreamError(err)) return;
144 }
145 }
146
147 if (seen_errors.empty() && msg.check_ab_preservation()) {
148 std::string backing;
149 auto a_value = read_metadata.GetStringValue("a", &backing);
150 if (!a_value.has_value()) {
151 fprintf(stderr, "Expected 'a' header to be present: %s\n",
152 read_metadata.DebugString().c_str());
153 abort();
154 }
155 if (a_value != "b") {
156 fprintf(stderr, "Expected 'a' header to be 'b', got '%s'\n",
157 std::string(*a_value).c_str());
158 abort();
159 }
160 }
161
162 // STAGE 3: If we reached here we either had a stream error or no error
163 // parsing.
164 // Either way, the hpack tables should be of the same size between client and
165 // server.
166 const auto encoder_size = encoder.hpack_table().test_only_table_size();
167 const auto parser_size = parser.hpack_table()->test_only_table_size();
168 const auto encoder_elems = encoder.hpack_table().test_only_table_elems();
169 const auto parser_elems = parser.hpack_table()->num_entries();
170 if (encoder_size != parser_size || encoder_elems != parser_elems) {
171 fprintf(stderr, "Encoder size: %d Parser size: %d\n", encoder_size,
172 parser_size);
173 fprintf(stderr, "Encoder elems: %d Parser elems: %d\n", encoder_elems,
174 parser_elems);
175 if (!seen_errors.empty()) {
176 fprintf(stderr, "Seen errors during parse:\n");
177 for (const auto& error : seen_errors) {
178 fprintf(stderr, " [slice %" PRIdPTR "] %s\n", error.first,
179 error.second.ToString().c_str());
180 }
181 }
182 fprintf(stderr, "Encoded data:\n");
183 for (size_t i = 0; i < encode_output.Count(); i++) {
184 fprintf(
185 stderr, " [slice %" PRIdPTR "]: %s\n", i,
186 absl::BytesToHexString(encode_output[i].as_string_view()).c_str());
187 }
188 abort();
189 }
190
191 if (msg.check_ab_preservation()) {
192 SliceBuffer encode_output_2;
193 hpack_encoder_detail::Encoder encoder_2(
194 &compressor, msg.use_true_binary_metadata(), encode_output_2);
195 encoder_2.EmitIndexed(62);
196 CHECK_EQ(encode_output_2.Count(), 1);
197 grpc_metadata_batch read_metadata_2;
198 parser.BeginFrame(
199 &read_metadata_2, 1024, 1024, HPackParser::Boundary::EndOfHeaders,
200 HPackParser::Priority::None,
201 HPackParser::LogInfo{3, HPackParser::LogInfo::kHeaders, false});
202 auto err = parser.Parse(encode_output_2.c_slice_at(0), true,
203 absl::BitGenRef(proto_bit_src),
204 /*call_tracer=*/nullptr);
205 if (!err.ok()) {
206 fprintf(stderr, "Error parsing preservation encoded data: %s\n",
207 err.ToString().c_str());
208 abort();
209 }
210 std::string backing;
211 auto a_value = read_metadata_2.GetStringValue("a", &backing);
212 if (!a_value.has_value()) {
213 fprintf(stderr,
214 "Expected 'a' header to be present: %s\nfirst metadata: %s\n",
215 read_metadata_2.DebugString().c_str(),
216 read_metadata.DebugString().c_str());
217 abort();
218 }
219 if (a_value != "b") {
220 fprintf(stderr, "Expected 'a' header to be 'b', got '%s'\n",
221 std::string(*a_value).c_str());
222 abort();
223 }
224 }
225 }
226
227 } // namespace
228 } // namespace grpc_core
229
DEFINE_PROTO_FUZZER(const hpack_sync_fuzzer::Msg & msg)230 DEFINE_PROTO_FUZZER(const hpack_sync_fuzzer::Msg& msg) {
231 if (squelch) {
232 grpc_disable_all_absl_logs();
233 }
234 grpc_core::ApplyFuzzConfigVars(msg.config_vars());
235 grpc_core::TestOnlyReloadExperimentsFromConfigVariables();
236 grpc_core::FuzzOneInput(msg);
237 }
238