• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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