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