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
30 #include <grpc/support/log.h>
31
32 #include "src/core/ext/transport/chttp2/transport/hpack_encoder.h"
33 #include "src/core/ext/transport/chttp2/transport/hpack_encoder_table.h"
34 #include "src/core/ext/transport/chttp2/transport/hpack_parser.h"
35 #include "src/core/ext/transport/chttp2/transport/hpack_parser_table.h"
36 #include "src/core/lib/experiments/config.h"
37 #include "src/core/lib/gprpp/ref_counted_ptr.h"
38 #include "src/core/lib/gprpp/status_helper.h"
39 #include "src/core/lib/iomgr/error.h"
40 #include "src/core/lib/iomgr/exec_ctx.h"
41 #include "src/core/lib/resource_quota/arena.h"
42 #include "src/core/lib/resource_quota/memory_quota.h"
43 #include "src/core/lib/resource_quota/resource_quota.h"
44 #include "src/core/lib/slice/slice.h"
45 #include "src/core/lib/slice/slice_buffer.h"
46 #include "src/core/lib/transport/metadata_batch.h"
47 #include "src/libfuzzer/libfuzzer_macro.h"
48 #include "test/core/transport/chttp2/hpack_sync_fuzzer.pb.h"
49 #include "test/core/util/fuzz_config_vars.h"
50 #include "test/core/util/proto_bit_gen.h"
51
52 bool squelch = true;
53 bool leak_check = true;
54
dont_log(gpr_log_func_args *)55 static void dont_log(gpr_log_func_args* /*args*/) {}
56
57 namespace grpc_core {
58 namespace {
59
IsStreamError(const absl::Status & status)60 bool IsStreamError(const absl::Status& status) {
61 intptr_t stream_id;
62 return grpc_error_get_int(status, StatusIntProperty::kStreamId, &stream_id);
63 }
64
FuzzOneInput(const hpack_sync_fuzzer::Msg & msg)65 void FuzzOneInput(const hpack_sync_fuzzer::Msg& msg) {
66 ProtoBitGen proto_bit_src(msg.random_numbers());
67
68 // STAGE 1: Encode the fuzzing input into a buffer (encode_output)
69 HPackCompressor compressor;
70 SliceBuffer encode_output;
71 hpack_encoder_detail::Encoder encoder(
72 &compressor, msg.use_true_binary_metadata(), encode_output);
73 for (const auto& header : msg.headers()) {
74 switch (header.ty_case()) {
75 case hpack_sync_fuzzer::Header::TY_NOT_SET:
76 break;
77 case hpack_sync_fuzzer::Header::kIndexed:
78 if (header.indexed() == 0) continue; // invalid encoding
79 encoder.EmitIndexed(header.indexed());
80 break;
81 case hpack_sync_fuzzer::Header::kLiteralIncIdx:
82 if (header.literal_inc_idx().key().length() +
83 header.literal_inc_idx().value().length() >
84 HPackEncoderTable::MaxEntrySize() / 2) {
85 // Not an interesting case to fuzz
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 (absl::EndsWith(header.literal_not_idx().value(), "-bin")) {
100 encoder.EmitLitHdrWithBinaryStringKeyNotIdx(
101 Slice::FromCopiedString(header.literal_not_idx().key()),
102 Slice::FromCopiedString(header.literal_not_idx().value()));
103 } else {
104 encoder.EmitLitHdrWithNonBinaryStringKeyNotIdx(
105 Slice::FromCopiedString(header.literal_not_idx().key()),
106 Slice::FromCopiedString(header.literal_not_idx().value()));
107 }
108 break;
109 case hpack_sync_fuzzer::Header::kLiteralNotIdxFromIdx:
110 if (header.literal_not_idx_from_idx().index() == 0) continue;
111 encoder.EmitLitHdrWithBinaryStringKeyNotIdx(
112 header.literal_not_idx_from_idx().index(),
113 Slice::FromCopiedString(header.literal_not_idx_from_idx().value()));
114 break;
115 }
116 }
117
118 // STAGE 2: Decode the buffer (encode_output) into a list of headers
119 HPackParser parser;
120 auto memory_allocator =
121 ResourceQuota::Default()->memory_quota()->CreateMemoryAllocator(
122 "test-allocator");
123 auto arena = MakeScopedArena(1024, &memory_allocator);
124 ExecCtx exec_ctx;
125 grpc_metadata_batch read_metadata;
126 parser.BeginFrame(
127 &read_metadata, 1024, 1024, HPackParser::Boundary::EndOfHeaders,
128 HPackParser::Priority::None,
129 HPackParser::LogInfo{1, HPackParser::LogInfo::kHeaders, false});
130 std::vector<std::pair<size_t, absl::Status>> seen_errors;
131 for (size_t i = 0; i < encode_output.Count(); i++) {
132 auto err = parser.Parse(
133 encode_output.c_slice_at(i), i == (encode_output.Count() - 1),
134 absl::BitGenRef(proto_bit_src), /*call_tracer=*/nullptr);
135 if (!err.ok()) {
136 seen_errors.push_back(std::make_pair(i, err));
137 // If we get a connection error (i.e. not a stream error), stop parsing,
138 // return.
139 if (!IsStreamError(err)) return;
140 }
141 }
142
143 // STAGE 3: If we reached here we either had a stream error or no error
144 // parsing.
145 // Either way, the hpack tables should be of the same size between client and
146 // server.
147 const auto encoder_size = encoder.hpack_table().test_only_table_size();
148 const auto parser_size = parser.hpack_table()->test_only_table_size();
149 const auto encoder_elems = encoder.hpack_table().test_only_table_elems();
150 const auto parser_elems = parser.hpack_table()->num_entries();
151 if (encoder_size != parser_size || encoder_elems != parser_elems) {
152 fprintf(stderr, "Encoder size: %d Parser size: %d\n", encoder_size,
153 parser_size);
154 fprintf(stderr, "Encoder elems: %d Parser elems: %d\n", encoder_elems,
155 parser_elems);
156 if (!seen_errors.empty()) {
157 fprintf(stderr, "Seen errors during parse:\n");
158 for (const auto& error : seen_errors) {
159 fprintf(stderr, " [slice %" PRIdPTR "] %s\n", error.first,
160 error.second.ToString().c_str());
161 }
162 }
163 fprintf(stderr, "Encoded data:\n");
164 for (size_t i = 0; i < encode_output.Count(); i++) {
165 fprintf(
166 stderr, " [slice %" PRIdPTR "]: %s\n", i,
167 absl::BytesToHexString(encode_output[i].as_string_view()).c_str());
168 }
169 abort();
170 }
171 }
172
173 } // namespace
174 } // namespace grpc_core
175
DEFINE_PROTO_FUZZER(const hpack_sync_fuzzer::Msg & msg)176 DEFINE_PROTO_FUZZER(const hpack_sync_fuzzer::Msg& msg) {
177 if (squelch) gpr_set_log_function(dont_log);
178 grpc_core::ApplyFuzzConfigVars(msg.config_vars());
179 grpc_core::TestOnlyReloadExperimentsFromConfigVariables();
180 grpc_core::FuzzOneInput(msg);
181 }
182