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