1 // Copyright 2021 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "net/spdy/alps_decoder.h"
6
7 #include <string_view>
8
9 #include "base/feature_list.h"
10 #include "base/metrics/histogram_functions.h"
11 #include "net/base/features.h"
12
13 namespace net {
14 namespace {
15
ReadUint16PrefixedStringPiece(std::string_view * payload,std::string_view * output)16 bool ReadUint16PrefixedStringPiece(std::string_view* payload,
17 std::string_view* output) {
18 if (payload->size() < 2) {
19 return false;
20 }
21 const uint16_t length = (static_cast<uint16_t>((*payload)[0]) << 8) +
22 (static_cast<uint8_t>((*payload)[1]));
23 payload->remove_prefix(2);
24
25 if (payload->size() < length) {
26 return false;
27 }
28 *output = payload->substr(0, length);
29 payload->remove_prefix(length);
30
31 return true;
32 }
33
34 } // anonymous namespace
35
AlpsDecoder()36 AlpsDecoder::AlpsDecoder() {
37 decoder_adapter_.set_visitor(&settings_parser_);
38 decoder_adapter_.set_extension_visitor(&accept_ch_parser_);
39 }
40
41 AlpsDecoder::~AlpsDecoder() = default;
42
Decode(base::span<const char> data)43 AlpsDecoder::Error AlpsDecoder::Decode(base::span<const char> data) {
44 decoder_adapter_.ProcessInput(data.data(), data.size());
45
46 // Log if any errors were bypassed.
47 base::UmaHistogramEnumeration(
48 "Net.SpdySession.AlpsDecoderStatus.Bypassed",
49 accept_ch_parser_.error_bypass());
50
51 if (decoder_adapter_.HasError()) {
52 return Error::kFramingError;
53 }
54
55 if (settings_parser_.forbidden_frame_received()) {
56 return Error::kForbiddenFrame;
57 }
58
59 if (settings_parser_.settings_ack_received()) {
60 return Error::kSettingsWithAck;
61 }
62
63 if (decoder_adapter_.state() !=
64 http2::Http2DecoderAdapter::SPDY_READY_FOR_FRAME) {
65 return Error::kNotOnFrameBoundary;
66 }
67
68 return accept_ch_parser_.error();
69 }
70
settings_frame_count() const71 int AlpsDecoder::settings_frame_count() const {
72 return settings_parser_.settings_frame_count();
73 }
74
75 AlpsDecoder::SettingsParser::SettingsParser() = default;
76 AlpsDecoder::SettingsParser::~SettingsParser() = default;
77
OnCommonHeader(spdy::SpdyStreamId,size_t,uint8_t type,uint8_t)78 void AlpsDecoder::SettingsParser::OnCommonHeader(
79 spdy::SpdyStreamId /*stream_id*/,
80 size_t /*length*/,
81 uint8_t type,
82 uint8_t /*flags*/) {
83 if (type == static_cast<uint8_t>(http2::Http2FrameType::DATA) ||
84 type == static_cast<uint8_t>(http2::Http2FrameType::HEADERS) ||
85 type == static_cast<uint8_t>(http2::Http2FrameType::PRIORITY) ||
86 type == static_cast<uint8_t>(http2::Http2FrameType::RST_STREAM) ||
87 type == static_cast<uint8_t>(http2::Http2FrameType::PUSH_PROMISE) ||
88 type == static_cast<uint8_t>(http2::Http2FrameType::PING) ||
89 type == static_cast<uint8_t>(http2::Http2FrameType::GOAWAY) ||
90 type == static_cast<uint8_t>(http2::Http2FrameType::WINDOW_UPDATE) ||
91 type == static_cast<uint8_t>(http2::Http2FrameType::CONTINUATION)) {
92 forbidden_frame_received_ = true;
93 }
94 }
95
OnSettings()96 void AlpsDecoder::SettingsParser::OnSettings() {
97 settings_frame_count_++;
98 }
OnSetting(spdy::SpdySettingsId id,uint32_t value)99 void AlpsDecoder::SettingsParser::OnSetting(spdy::SpdySettingsId id,
100 uint32_t value) {
101 settings_[id] = value;
102 }
103
OnSettingsAck()104 void AlpsDecoder::SettingsParser::OnSettingsAck() {
105 settings_ack_received_ = true;
106 }
107
108 AlpsDecoder::AcceptChParser::AcceptChParser() = default;
109 AlpsDecoder::AcceptChParser::~AcceptChParser() = default;
110
OnFrameHeader(spdy::SpdyStreamId stream_id,size_t length,uint8_t type,uint8_t flags)111 bool AlpsDecoder::AcceptChParser::OnFrameHeader(spdy::SpdyStreamId stream_id,
112 size_t length,
113 uint8_t type,
114 uint8_t flags) {
115 // Ignore data after an error has occurred.
116 if (error_ != Error::kNoError)
117 return false;
118 // Stop all alps parsing if it's disabled.
119 if (!base::FeatureList::IsEnabled(features::kAlpsParsing))
120 return false;
121 // Handle per-type parsing.
122 switch (type) {
123 case static_cast<uint8_t>(spdy::SpdyFrameType::ACCEPT_CH): {
124 // Stop alps client hint parsing if it's disabled.
125 if (!base::FeatureList::IsEnabled(features::kAlpsClientHintParsing))
126 return false;
127 // Check for issues with the frame.
128 if (stream_id != 0) {
129 error_ = Error::kAcceptChInvalidStream;
130 return false;
131 }
132 if (flags != 0) {
133 error_ = Error::kAcceptChWithFlags;
134 return false;
135 }
136 // This frame can be parsed in OnFramePayload.
137 return true;
138 }
139 default:
140 // Ignore all other types.
141 return false;
142 }
143 }
144
OnFramePayload(const char * data,size_t len)145 void AlpsDecoder::AcceptChParser::OnFramePayload(const char* data, size_t len) {
146 DCHECK_EQ(Error::kNoError, error_);
147
148 std::string_view payload(data, len);
149
150 while (!payload.empty()) {
151 std::string_view origin;
152 std::string_view value;
153 if (!ReadUint16PrefixedStringPiece(&payload, &origin) ||
154 !ReadUint16PrefixedStringPiece(&payload, &value)) {
155 if (base::FeatureList::IsEnabled(
156 features::kShouldKillSessionOnAcceptChMalformed)) {
157 // This causes a session termination.
158 error_ = Error::kAcceptChMalformed;
159 return;
160 } else {
161 // This logs that a session termination was bypassed.
162 error_bypass_ = Error::kAcceptChMalformed;
163 return;
164 }
165 }
166 accept_ch_.push_back(
167 spdy::AcceptChOriginValuePair{std::string(origin), std::string(value)});
168 }
169 }
170
171 } // namespace net
172