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