0

Randomize HTTP/2 grease setting parameter per session.

Previously NetworkSessionConfiguration chose a random setting identifier
and value, which were propagated to every SpdySession instance as part
of HttpNetworkSession::Params::http2_settings.  Since a browser session
sent the same setting identifier and value on every HTTP/2 connection,
it could be fingerprinted and identified across different servers.

This CL adds a new Boolean
HttpNetworkSession::Params::enable_http2_settings_grease, plumbs it from
NetworkSessionConfiguration through SpdySessionPool to SpdySession, and
makes SpdySession do the random drawing independently of other
SpdySession instances.  This way each HTTP/2 connection will have an
independent identifier, making fingerprinting and tracking impossible.

Bug: 841264
Change-Id: I1df37a5ac33b9cb05f927566d40dc147d46fbf0a
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3251582
Reviewed-by: Renjie Tang <renjietang@chromium.org>
Commit-Queue: Bence Béky <bnc@chromium.org>
Cr-Commit-Position: refs/heads/main@{#936381}
This commit is contained in:
Bence Béky
2021-10-29 14:45:51 +00:00
committed by Chromium LUCI CQ
parent 0541a7e2ab
commit d228fb2c4a
11 changed files with 116 additions and 24 deletions

@ -128,11 +128,7 @@ void ConfigureHttp2Params(const base::CommandLine& command_line,
if (command_line.HasSwitch(switches::kHttp2GreaseSettings) ||
GetVariationParam(http2_trial_params, "http2_grease_settings") ==
"true") {
spdy::SpdySettingsId id = 0x0a0a + 0x1000 * base::RandGenerator(0xf + 1) +
0x0010 * base::RandGenerator(0xf + 1);
uint32_t value = base::RandGenerator(
static_cast<uint64_t>(std::numeric_limits<uint32_t>::max()) + 1);
params->http2_settings.insert(std::make_pair(id, value));
params->enable_http2_settings_grease = true;
}
// Optionally define a frame of reserved type to "grease" frame types, see

@ -68,6 +68,7 @@ TEST_F(NetworkSessionConfiguratorTest, Defaults) {
EXPECT_TRUE(params_.enable_http2);
EXPECT_TRUE(params_.http2_settings.empty());
EXPECT_FALSE(params_.enable_http2_settings_grease);
EXPECT_FALSE(params_.greased_http2_frame);
EXPECT_FALSE(params_.http2_end_stream_with_data_frame);
EXPECT_TRUE(params_.enable_websocket_over_http2);
@ -825,14 +826,7 @@ TEST_F(NetworkSessionConfiguratorTest, Http2GreaseSettingsFromCommandLine) {
ParseCommandLineAndFieldTrials(command_line);
bool greased_setting_found = false;
for (const auto& setting : params_.http2_settings) {
if ((setting.first & 0x0f0f) == 0x0a0a) {
greased_setting_found = true;
break;
}
}
EXPECT_TRUE(greased_setting_found);
EXPECT_TRUE(params_.enable_http2_settings_grease);
}
TEST_F(NetworkSessionConfiguratorTest, Http2GreaseSettingsFromFieldTrial) {
@ -843,14 +837,7 @@ TEST_F(NetworkSessionConfiguratorTest, Http2GreaseSettingsFromFieldTrial) {
ParseFieldTrials();
bool greased_setting_found = false;
for (const auto& setting : params_.http2_settings) {
if ((setting.first & 0x0f0f) == 0x0a0a) {
greased_setting_found = true;
break;
}
}
EXPECT_TRUE(greased_setting_found);
EXPECT_TRUE(params_.enable_http2_settings_grease);
}
TEST_F(NetworkSessionConfiguratorTest, Http2GreaseFrameTypeFromCommandLine) {

@ -96,6 +96,7 @@ HttpNetworkSessionParams::HttpNetworkSessionParams()
#else
spdy_go_away_on_ip_change(false),
#endif // defined(OS_ANDROID) || defined(OS_WIN) || defined(OS_IOS)
enable_http2_settings_grease(false),
http2_end_stream_with_data_frame(false),
time_func(&base::TimeTicks::Now),
enable_http2_alternative_service(false),
@ -192,6 +193,7 @@ HttpNetworkSession::HttpNetworkSession(const HttpNetworkSessionParams& params,
params.spdy_session_max_recv_window_size,
params.spdy_session_max_queued_capped_frames,
AddDefaultHttp2Settings(params.http2_settings),
params.enable_http2_settings_grease,
params.greased_http2_frame,
params.http2_end_stream_with_data_frame,
params.enable_priority_update,

@ -111,6 +111,12 @@ struct NET_EXPORT HttpNetworkSessionParams {
// The same setting will be sent on every connection to prevent the retry
// logic from hiding broken servers.
spdy::SettingsMap http2_settings;
// If true, a setting parameter with reserved identifier will be sent in every
// initial SETTINGS frame, see
// https://tools.ietf.org/html/draft-bishop-httpbis-grease-00.
// The setting identifier and value will be drawn independently for each
// connection to prevent tracking of the client.
bool enable_http2_settings_grease;
// If set, an HTTP/2 frame with a reserved frame type will be sent after
// every HTTP/2 SETTINGS frame and before every HTTP/2 DATA frame.
// https://tools.ietf.org/html/draft-bishop-httpbis-grease-00.

@ -13,6 +13,7 @@
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/run_loop.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_piece.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
@ -10440,6 +10441,71 @@ TEST_F(SpdyNetworkTransactionTest, ZeroRTTConfirmErrorAsync) {
EXPECT_THAT(out.rv, IsError(ERR_SSL_PROTOCOL_ERROR));
}
TEST_F(SpdyNetworkTransactionTest, GreaseSettings) {
RecordingNetLogObserver net_log_observer;
auto session_deps = std::make_unique<SpdySessionDependencies>();
session_deps->enable_http2_settings_grease = true;
NormalSpdyTransactionHelper helper(
request_, DEFAULT_PRIORITY,
NetLogWithSource::Make(NetLogSourceType::NONE), std::move(session_deps));
SpdySessionPool* spdy_session_pool = helper.session()->spdy_session_pool();
SpdySessionPoolPeer pool_peer(spdy_session_pool);
pool_peer.SetEnableSendingInitialData(true);
// Greased setting parameter is random. Hang writes instead of trying to
// construct matching mock data. Extra write and read is needed because mock
// data cannot end on ERR_IO_PENDING. Writes or reads will not actually be
// resumed.
MockWrite writes[] = {MockWrite(ASYNC, ERR_IO_PENDING, 0),
MockWrite(ASYNC, OK, 1)};
MockRead reads[] = {MockRead(ASYNC, ERR_IO_PENDING, 2),
MockRead(ASYNC, OK, 3)};
SequencedSocketData data(reads, writes);
helper.RunPreTestSetup();
helper.AddData(&data);
int rv = helper.trans()->Start(&request_, CompletionOnceCallback{}, log_);
EXPECT_THAT(rv, IsError(ERR_IO_PENDING));
base::RunLoop().RunUntilIdle();
helper.ResetTrans();
EXPECT_FALSE(data.AllReadDataConsumed());
EXPECT_FALSE(data.AllWriteDataConsumed());
const auto entries = net_log_observer.GetEntries();
size_t pos = ExpectLogContainsSomewhere(
entries, 0, NetLogEventType::HTTP2_SESSION_SEND_SETTINGS,
NetLogEventPhase::NONE);
ASSERT_LT(pos, entries.size());
const base::Value& params = entries[pos].params;
const base::Value* const settings = params.FindKey("settings");
ASSERT_TRUE(settings);
ASSERT_TRUE(settings->is_list());
base::Value::ConstListView list = settings->GetList();
ASSERT_FALSE(list.empty());
// Get last setting parameter.
const base::Value& greased_setting = list[list.size() - 1];
ASSERT_TRUE(greased_setting.is_string());
base::StringPiece greased_setting_string(greased_setting.GetString());
const std::string kExpectedPrefix = "[id:";
EXPECT_EQ(kExpectedPrefix,
greased_setting_string.substr(0, kExpectedPrefix.size()));
int setting_identifier = 0;
base::StringToInt(greased_setting_string.substr(kExpectedPrefix.size()),
&setting_identifier);
// The setting identifier must be of format 0x?a?a.
EXPECT_EQ(0xa, setting_identifier % 16);
EXPECT_EQ(0xa, (setting_identifier / 256) % 16);
}
// If |http2_end_stream_with_data_frame| is false, then the HEADERS frame of a
// GET request will close the stream using the END_STREAM flag. Test that
// |greased_http2_frame| is ignored and no reserved frames are sent on a closed

@ -16,6 +16,7 @@
#include "base/logging.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/rand_util.h"
#include "base/strings/abseil_string_conversions.h"
#include "base/strings/strcat.h"
#include "base/strings/string_number_conversions.h"
@ -919,6 +920,7 @@ SpdySession::SpdySession(
size_t session_max_recv_window_size,
int session_max_queued_capped_frames,
const spdy::SettingsMap& initial_settings,
bool enable_http2_settings_grease,
const absl::optional<SpdySessionPool::GreasedHttp2Frame>&
greased_http2_frame,
bool http2_end_stream_with_data_frame,
@ -948,6 +950,7 @@ SpdySession::SpdySession(
write_state_(WRITE_STATE_IDLE),
error_on_close_(OK),
initial_settings_(initial_settings),
enable_http2_settings_grease_(enable_http2_settings_grease),
greased_http2_frame_(greased_http2_frame),
http2_end_stream_with_data_frame_(http2_end_stream_with_data_frame),
enable_priority_update_(enable_priority_update),
@ -2623,6 +2626,16 @@ void SpdySession::SendInitialData() {
settings_map.insert(setting);
}
}
if (enable_http2_settings_grease_) {
spdy::SpdySettingsId greased_id = 0x0a0a +
0x1000 * base::RandGenerator(0xf + 1) +
0x0010 * base::RandGenerator(0xf + 1);
uint32_t greased_value = base::RandGenerator(
static_cast<uint64_t>(std::numeric_limits<uint32_t>::max()) + 1);
// Let insertion silently fail if `settings_map` already contains
// `greased_id`.
settings_map.insert(std::make_pair(greased_id, greased_value));
}
net_log_.AddEvent(NetLogEventType::HTTP2_SESSION_SEND_SETTINGS, [&] {
return NetLogSpdySendSettingsParams(&settings_map);
});

@ -345,6 +345,7 @@ class NET_EXPORT SpdySession : public BufferedSpdyFramerVisitorInterface,
size_t session_max_recv_window_size,
int session_max_queued_capped_frames,
const spdy::SettingsMap& initial_settings,
bool enable_http2_settings_grease,
const absl::optional<SpdySessionPool::GreasedHttp2Frame>&
greased_http2_frame,
bool http2_end_stream_with_data_frame,
@ -1128,6 +1129,13 @@ class NET_EXPORT SpdySession : public BufferedSpdyFramerVisitorInterface,
// and maximum HPACK dynamic table size.
const spdy::SettingsMap initial_settings_;
// If true, a setting parameter with reserved identifier will be sent in every
// initial SETTINGS frame, see
// https://tools.ietf.org/html/draft-bishop-httpbis-grease-00.
// The setting identifier and value will be drawn independently for each
// connection to prevent tracking of the client.
const bool enable_http2_settings_grease_;
// If set, an HTTP/2 frame with a reserved frame type will be sent after
// every HTTP/2 SETTINGS frame and before every HTTP/2 DATA frame. See
// https://tools.ietf.org/html/draft-bishop-httpbis-grease-00.

@ -85,6 +85,7 @@ SpdySessionPool::SpdySessionPool(
size_t session_max_recv_window_size,
int session_max_queued_capped_frames,
const spdy::SettingsMap& initial_settings,
bool enable_http2_settings_grease,
const absl::optional<GreasedHttp2Frame>& greased_http2_frame,
bool http2_end_stream_with_data_frame,
bool enable_priority_update,
@ -104,6 +105,7 @@ SpdySessionPool::SpdySessionPool(
session_max_recv_window_size_(session_max_recv_window_size),
session_max_queued_capped_frames_(session_max_queued_capped_frames),
initial_settings_(initial_settings),
enable_http2_settings_grease_(enable_http2_settings_grease),
greased_http2_frame_(greased_http2_frame),
http2_end_stream_with_data_frame_(http2_end_stream_with_data_frame),
enable_priority_update_(enable_priority_update),
@ -661,9 +663,9 @@ std::unique_ptr<SpdySession> SpdySessionPool::CreateSession(
enable_ping_based_connection_checking_, is_http2_enabled_,
is_quic_enabled_, session_max_recv_window_size_,
session_max_queued_capped_frames_, initial_settings_,
greased_http2_frame_, http2_end_stream_with_data_frame_,
enable_priority_update_, time_func_, push_delegate_,
network_quality_estimator_, net_log);
enable_http2_settings_grease_, greased_http2_frame_,
http2_end_stream_with_data_frame_, enable_priority_update_, time_func_,
push_delegate_, network_quality_estimator_, net_log);
}
base::WeakPtr<SpdySession> SpdySessionPool::InsertSession(

@ -139,6 +139,7 @@ class NET_EXPORT SpdySessionPool
size_t session_max_recv_window_size,
int session_max_queued_capped_frames,
const spdy::SettingsMap& initial_settings,
bool enable_http2_settings_grease,
const absl::optional<GreasedHttp2Frame>& greased_http2_frame,
bool http2_end_stream_with_data_frame,
bool enable_priority_update,
@ -454,6 +455,13 @@ class NET_EXPORT SpdySessionPool
// and maximum HPACK dynamic table size.
const spdy::SettingsMap initial_settings_;
// If true, a setting parameter with reserved identifier will be sent in every
// initial SETTINGS frame, see
// https://tools.ietf.org/html/draft-bishop-httpbis-grease-00.
// The setting identifier and value will be drawn independently for each
// connection to prevent tracking of the client.
const bool enable_http2_settings_grease_;
// If set, an HTTP/2 frame with a reserved frame type will be sent after
// every HTTP/2 SETTINGS frame and before every HTTP/2 DATA frame. See
// https://tools.ietf.org/html/draft-bishop-httpbis-grease-00.

@ -312,6 +312,7 @@ SpdySessionDependencies::SpdySessionDependencies(
time_func(&base::TimeTicks::Now),
enable_http2_alternative_service(false),
enable_websocket_over_http2(false),
enable_http2_settings_grease(false),
http2_end_stream_with_data_frame(false),
net_log(nullptr),
disable_idle_sockets_close_on_memory_pressure(false),
@ -372,6 +373,8 @@ HttpNetworkSessionParams SpdySessionDependencies::CreateSessionParams(
session_deps->enable_http2_alternative_service;
params.enable_websocket_over_http2 =
session_deps->enable_websocket_over_http2;
params.enable_http2_settings_grease =
session_deps->enable_http2_settings_grease;
params.greased_http2_frame = session_deps->greased_http2_frame;
params.http2_end_stream_with_data_frame =
session_deps->http2_end_stream_with_data_frame;

@ -200,6 +200,7 @@ struct SpdySessionDependencies {
SpdySession::TimeFunc time_func;
bool enable_http2_alternative_service;
bool enable_websocket_over_http2;
bool enable_http2_settings_grease;
absl::optional<SpdySessionPool::GreasedHttp2Frame> greased_http2_frame;
bool http2_end_stream_with_data_frame;
NetLog* net_log;