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:

committed by
Chromium LUCI CQ

parent
0541a7e2ab
commit
d228fb2c4a
components/network_session_configurator/browser
net
@ -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;
|
||||
|
Reference in New Issue
Block a user