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 <grpc/support/port_platform.h>
16 
17 #include "src/core/ext/transport/chttp2/transport/hpack_parse_result.h"
18 
19 #include <stddef.h>
20 
21 #include <initializer_list>
22 
23 #include "absl/strings/str_format.h"
24 
25 #include "src/core/ext/transport/chttp2/transport/hpack_constants.h"
26 #include "src/core/lib/gprpp/status_helper.h"
27 #include "src/core/lib/iomgr/error.h"
28 #include "src/core/lib/slice/slice.h"
29 
30 namespace grpc_core {
31 
32 namespace {
33 class MetadataSizeLimitExceededEncoder {
34  public:
MetadataSizeLimitExceededEncoder(std::string & summary)35   explicit MetadataSizeLimitExceededEncoder(std::string& summary)
36       : summary_(summary) {}
37 
Encode(const Slice & key,const Slice & value)38   void Encode(const Slice& key, const Slice& value) {
39     AddToSummary(key.as_string_view(), value.size());
40   }
41 
42   template <typename Key, typename Value>
Encode(Key,const Value & value)43   void Encode(Key, const Value& value) {
44     AddToSummary(Key::key(), EncodedSizeOfKey(Key(), value));
45   }
46 
47  private:
AddToSummary(absl::string_view key,size_t value_length)48   void AddToSummary(absl::string_view key,
49                     size_t value_length) GPR_ATTRIBUTE_NOINLINE {
50     absl::StrAppend(&summary_, " ", key, ":",
51                     hpack_constants::SizeForEntry(key.size(), value_length),
52                     "B");
53   }
54   std::string& summary_;
55 };
56 
MakeStreamError(absl::Status error)57 absl::Status MakeStreamError(absl::Status error) {
58   GPR_DEBUG_ASSERT(!error.ok());
59   return grpc_error_set_int(std::move(error), StatusIntProperty::kStreamId, 0);
60 }
61 }  // namespace
62 
Materialize() const63 absl::Status HpackParseResult::Materialize() const {
64   if (materialized_status_.has_value()) return *materialized_status_;
65   materialized_status_ = BuildMaterialized();
66   return *materialized_status_;
67 }
68 
BuildMaterialized() const69 absl::Status HpackParseResult::BuildMaterialized() const {
70   switch (status_.get()) {
71     case HpackParseStatus::kOk:
72       return absl::OkStatus();
73     case HpackParseStatus::kEof:
74       Crash("Materialize() called on EOF");
75       break;
76     case HpackParseStatus::kMovedFrom:
77       Crash("Materialize() called on moved-from object");
78       break;
79     case HpackParseStatus::kInvalidMetadata:
80       if (key_.empty()) {
81         return MakeStreamError(absl::InternalError(
82             ValidateMetadataResultToString(validate_metadata_result_)));
83       } else {
84         return MakeStreamError(absl::InternalError(absl::StrCat(
85             ValidateMetadataResultToString(validate_metadata_result_), ": ",
86             key_)));
87       }
88     case HpackParseStatus::kSoftMetadataLimitExceeded:
89     case HpackParseStatus::kHardMetadataLimitExceeded: {
90       const auto& e = metadata_limit_exceeded_;
91       // Collect a summary of sizes so far for debugging
92       // Do not collect contents, for fear of exposing PII.
93       std::string summary;
94       if (e.prior != nullptr) {
95         MetadataSizeLimitExceededEncoder encoder(summary);
96         e.prior->Encode(&encoder);
97       }
98       return MakeStreamError(absl::ResourceExhaustedError(absl::StrCat(
99           "received metadata size exceeds ",
100           status_.get() == HpackParseStatus::kSoftMetadataLimitExceeded
101               ? "soft"
102               : "hard",
103           " limit (", e.frame_length, " vs. ", e.limit, ")",
104           summary.empty() ? "" : "; ", summary)));
105     }
106     case HpackParseStatus::kHardMetadataLimitExceededByKey: {
107       const auto& e = metadata_limit_exceeded_by_atom_;
108       return MakeStreamError(absl::ResourceExhaustedError(
109           absl::StrCat("received metadata size exceeds hard limit (key length ",
110                        e.atom_length, " vs. ", e.limit, ")")));
111     }
112     case HpackParseStatus::kHardMetadataLimitExceededByValue: {
113       const auto& e = metadata_limit_exceeded_by_atom_;
114       return MakeStreamError(absl::ResourceExhaustedError(absl::StrCat(
115           "received metadata size exceeds hard limit (value length ",
116           e.atom_length, " vs. ", e.limit, ")")));
117     }
118     case HpackParseStatus::kMetadataParseError:
119       if (!key_.empty()) {
120         return MakeStreamError(absl::InternalError(
121             absl::StrCat("Error parsing '", key_, "' metadata")));
122       } else {
123         return MakeStreamError(absl::InternalError("Error parsing metadata"));
124       }
125     case HpackParseStatus::kUnbase64Failed:
126       if (!key_.empty()) {
127         return MakeStreamError(absl::InternalError(absl::StrCat(
128             "Error parsing '", key_, "' metadata: illegal base64 encoding")));
129       } else {
130         return MakeStreamError(absl::InternalError(
131             absl::StrCat("Failed base64 decoding metadata")));
132       }
133     case HpackParseStatus::kIncompleteHeaderAtBoundary:
134       return absl::InternalError(
135           "Incomplete header at the end of a header/continuation sequence");
136     case HpackParseStatus::kVarintOutOfRange:
137       return absl::InternalError(absl::StrFormat(
138           "integer overflow in hpack integer decoding: have 0x%08x, "
139           "got byte 0x%02x",
140           varint_out_of_range_.value, varint_out_of_range_.last_byte));
141     case HpackParseStatus::kIllegalTableSizeChange:
142       return absl::InternalError(absl::StrCat(
143           "Attempt to make hpack table ", illegal_table_size_change_.new_size,
144           " bytes when max is ", illegal_table_size_change_.max_size,
145           " bytes"));
146     case HpackParseStatus::kAddBeforeTableSizeUpdated:
147       return absl::InternalError(
148           absl::StrCat("HPACK max table size reduced to ",
149                        illegal_table_size_change_.new_size,
150                        " but not reflected by hpack stream (still at ",
151                        illegal_table_size_change_.max_size, ")"));
152     case HpackParseStatus::kParseHuffFailed:
153       if (!key_.empty()) {
154         return absl::InternalError(
155             absl::StrCat("Failed huffman decoding '", key_, "' metadata"));
156       } else {
157         return absl::InternalError(
158             absl::StrCat("Failed huffman decoding metadata"));
159       }
160       break;
161     case HpackParseStatus::kTooManyDynamicTableSizeChanges:
162       return absl::InternalError(
163           "More than two max table size changes in a single frame");
164     case HpackParseStatus::kMaliciousVarintEncoding:
165       return absl::InternalError(
166           "Malicious varint encoding detected in HPACK stream");
167     case HpackParseStatus::kInvalidHpackIndex:
168       return absl::InternalError(absl::StrFormat(
169           "Invalid HPACK index received (%d)", invalid_hpack_index_));
170     case HpackParseStatus::kIllegalHpackOpCode:
171       return absl::InternalError("Illegal hpack op code");
172   }
173   GPR_UNREACHABLE_CODE(return absl::UnknownError("Should never reach here"));
174 }
175 
176 }  // namespace grpc_core
177