Improve update request privacy (#31127)

* Don't send any contents of the hw object
 * Never send app/lang
 * Don't send download_time_ms

The motivation is that these could otherwise potentially be used to
fingerprint users.
This commit is contained in:
Michael Herrmann
2025-09-29 16:54:35 +02:00
committed by GitHub
parent dfe42b9171
commit c9f6debc71
27 changed files with 530 additions and 3 deletions
+3
View File
@@ -87,6 +87,9 @@ if (!is_ios) {
if (!is_ios && !is_android) {
brave_all_unit_tests_deps += [ "//brave/test:brave_installer_unittests" ]
}
if (is_win || is_mac || is_linux) {
brave_all_unit_tests_deps += [ "//brave/updater:brave_updater_unittests" ]
}
# This group is for unit tests that run as part of brave_all_unit_tests. Other
# tests that you only want to build should be added directly to `all` group
+1 -1
View File
@@ -98,7 +98,7 @@ hooks = [
'pattern': '.',
'condition': 'checkout_mac',
'action': ['vpython3', 'build/download_dep.py',
'omaha4/BraveUpdater-136.1.79.71.zip',
'omaha4/BraveUpdater-141.1.84.102.zip',
'//brave/third_party/updater/mac',
'BraveUpdater.app/'],
},
+3
View File
@@ -0,0 +1,3 @@
include_rules = [
"+brave/components/update_client/privacy_preserving_protocol_handler.h",
]
@@ -0,0 +1,10 @@
/* Copyright (c) 2025 The Brave Authors. All rights reserved.
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at https://mozilla.org/MPL/2.0/. */
#include "brave/components/update_client/privacy_preserving_protocol_handler.h"
#define ProtocolHandlerFactoryJSON PrivacyPreservingProtocolHandlerFactory
#include <chrome/updater/configurator.cc>
#undef ProtocolHandlerFactoryJSON
@@ -0,0 +1,10 @@
/* Copyright (c) 2025 The Brave Authors. All rights reserved.
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at https://mozilla.org/MPL/2.0/. */
#include "brave/components/update_client/privacy_preserving_protocol_handler.h"
#define ProtocolHandlerFactoryJSON PrivacyPreservingProtocolHandlerFactory
#include <chrome/updater/ping_configurator.cc>
#undef ProtocolHandlerFactoryJSON
@@ -1,4 +1,5 @@
include_rules = [
"+brave/components/update_client/privacy_preserving_protocol_handler.h",
"+components/crx_file",
"+components/update_client",
]
@@ -5,9 +5,13 @@
#include "components/component_updater/configurator_impl.h"
#include "brave/components/update_client/privacy_preserving_protocol_handler.h"
#define EnabledBackgroundDownloader EnabledBackgroundDownloader_Unused
#define EnabledCupSigning EnabledCupSigning_Unused
#define ProtocolHandlerFactoryJSON PrivacyPreservingProtocolHandlerFactory
#include <components/component_updater/configurator_impl.cc>
#undef ProtocolHandlerFactoryJSON
#undef EnabledCupSigning
#undef EnabledBackgroundDownloader
+2
View File
@@ -31,9 +31,11 @@ test("brave_components_unittests") {
"//brave/components/brave_shields/core/browser:unit_tests",
"//brave/components/brave_shields/core/common:unit_tests",
"//brave/components/brave_wallet/common:unit_tests",
"//brave/components/component_updater:unit_tests",
"//brave/components/email_aliases:unit_tests",
"//brave/components/json:unit_tests",
"//brave/components/static_redirect_helper:unit_tests",
"//brave/components/update_client:unit_tests",
"//components/test:run_all_unittests",
]
+16
View File
@@ -0,0 +1,16 @@
# Copyright (c) 2025 The Brave Authors. All rights reserved.
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
# You can obtain one at https://mozilla.org/MPL/2.0/.
source_set("unit_tests") {
testonly = true
sources = [ "configurator_impl_unittest.cc" ]
deps = [
"//base",
"//brave/components/update_client:test_util",
"//components/component_updater",
"//components/update_client",
"//testing/gtest",
]
}
+4
View File
@@ -0,0 +1,4 @@
include_rules = [
"+components/component_updater",
"+components/update_client",
]
@@ -0,0 +1,30 @@
/* Copyright (c) 2025 The Brave Authors. All rights reserved.
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at https://mozilla.org/MPL/2.0/. */
#include "components/component_updater/configurator_impl.h"
#include "brave/components/update_client/test_util.h"
#include "components/update_client/command_line_config_policy.h"
#include "components/update_client/protocol_handler.h"
#include "components/update_client/protocol_serializer.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace component_updater {
TEST(BraveComponentUpdaterConfiguratorImplTest,
UsesPrivacyPreservingProtocolSerializer) {
auto configurator = std::make_unique<ConfiguratorImpl>(
update_client::CommandLineConfigPolicy(), false);
auto factory = configurator->GetProtocolHandlerFactory();
ASSERT_TRUE(factory);
auto serializer = factory->CreateSerializer();
ASSERT_TRUE(serializer);
EXPECT_TRUE(update_client::StripsPrivacySensitiveData(*serializer));
}
} // namespace component_updater
+28
View File
@@ -22,3 +22,31 @@ buildflag_header("buildflags") {
"UPDATER_PROD_ENDPOINT=\"$updater_prod_endpoint\"",
]
}
source_set("unit_tests") {
testonly = true
sources = [ "privacy_preserving_protocol_handler_unittest.cc" ]
deps = [
":test_util",
"//components/update_client",
"//testing/gtest",
# The build fails without this dep, even though our source doesn't use it.
"//base",
]
}
source_set("test_util") {
testonly = true
sources = [
"test_util.cc",
"test_util.h",
]
deps = [
"//base",
"//components/prefs",
"//components/prefs:test_support",
"//components/update_client",
"//third_party/re2",
]
}
+10
View File
@@ -0,0 +1,10 @@
include_rules = [
"+components/update_client",
]
specific_include_rules = {
"test_util.cc": [
"+components/prefs/pref_service.h",
"+components/prefs/testing_pref_service.h",
"+third_party/re2",
]
}
@@ -0,0 +1,27 @@
/* Copyright (c) 2025 The Brave Authors. All rights reserved.
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at https://mozilla.org/MPL/2.0/. */
#include "brave/components/update_client/privacy_preserving_protocol_handler.h"
#include <memory>
#include "brave/components/update_client/privacy_preserving_protocol_serializer.h"
#include "components/update_client/protocol_handler.h"
#include "components/update_client/protocol_parser.h"
namespace update_client {
std::unique_ptr<ProtocolParser>
PrivacyPreservingProtocolHandlerFactory::CreateParser() const {
// We're not interested in changing this behavior. Mirror upstream.
return ProtocolHandlerFactoryJSON().CreateParser();
}
std::unique_ptr<ProtocolSerializer>
PrivacyPreservingProtocolHandlerFactory::CreateSerializer() const {
return std::make_unique<PrivacyPreservingProtocolSerializer>();
}
} // namespace update_client
@@ -0,0 +1,29 @@
/* Copyright (c) 2025 The Brave Authors. All rights reserved.
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at https://mozilla.org/MPL/2.0/. */
#ifndef BRAVE_COMPONENTS_UPDATE_CLIENT_PRIVACY_PRESERVING_PROTOCOL_HANDLER_H_
#define BRAVE_COMPONENTS_UPDATE_CLIENT_PRIVACY_PRESERVING_PROTOCOL_HANDLER_H_
#include <memory>
#include "components/update_client/protocol_handler.h"
#include "components/update_client/protocol_parser.h"
#include "components/update_client/protocol_serializer.h"
namespace update_client {
// This class returns the same parser as upstream but returns a different
// serializer (PrivacyPreservingProtocolSerializer) to remove values from update
// requests that could be used to fingerprint users.
class PrivacyPreservingProtocolHandlerFactory : public ProtocolHandlerFactory {
public:
// Overrides for ProtocolHandlerFactory.
std::unique_ptr<ProtocolParser> CreateParser() const override;
std::unique_ptr<ProtocolSerializer> CreateSerializer() const override;
};
} // namespace update_client
#endif // BRAVE_COMPONENTS_UPDATE_CLIENT_PRIVACY_PRESERVING_PROTOCOL_HANDLER_H_
@@ -0,0 +1,17 @@
/* Copyright (c) 2025 The Brave Authors. All rights reserved.
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at https://mozilla.org/MPL/2.0/. */
#include "brave/components/update_client/privacy_preserving_protocol_serializer.h"
#include "brave/components/update_client/test_util.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace update_client {
TEST(PrivacyPreservingProtocolSerializer, StripsPrivacySensitiveData) {
EXPECT_TRUE(
StripsPrivacySensitiveData(PrivacyPreservingProtocolSerializer()));
}
} // namespace update_client
@@ -0,0 +1,65 @@
/* Copyright (c) 2025 The Brave Authors. All rights reserved.
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at https://mozilla.org/MPL/2.0/. */
#include "brave/components/update_client/privacy_preserving_protocol_serializer.h"
#include <string>
#include <utility>
#include "base/json/json_reader.h"
#include "base/json/json_writer.h"
#include "base/values.h"
#include "components/update_client/protocol_definition.h"
#include "components/update_client/protocol_serializer_json.h"
namespace update_client {
// This method returns the same result as upstream's ProtocolSerializerJSON,
// but with some fields removed that could be used to fingerprint users.
std::string PrivacyPreservingProtocolSerializer::Serialize(
const protocol_request::Request& request) const {
std::string upstream_result = ProtocolSerializerJSON().Serialize(request);
std::optional<base::Value> root = base::JSONReader::Read(upstream_result);
if (!root.has_value() || !root->is_dict()) {
return upstream_result;
}
base::Value::Dict& root_dict = root->GetDict();
base::Value::Dict* request_dict = root_dict.FindDict("request");
if (!request_dict) {
return upstream_result;
}
// We don't want to send the information in the hw dictionary, but the
// protocol specification requires it to be present. All its fields have
// default values and are therefore optional. We therefore remain
// spec-compliant by simply sending an empty hw dictionary.
if (base::Value::Dict* hw_dict = request_dict->FindDict("hw")) {
hw_dict->clear();
}
if (base::Value::List* apps = request_dict->FindList("apps")) {
for (auto& app : *apps) {
if (!app.is_dict()) {
continue;
}
base::Value::Dict& app_dict = app.GetDict();
app_dict.Remove("lang");
if (base::Value::List* events = app_dict.FindList("events")) {
for (auto& event : *events) {
if (event.is_dict()) {
event.GetDict().Remove("download_time_ms");
}
}
}
}
}
return base::WriteJson(*root).value_or(std::move(upstream_result));
}
} // namespace update_client
@@ -0,0 +1,34 @@
/* Copyright (c) 2025 The Brave Authors. All rights reserved.
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at https://mozilla.org/MPL/2.0/. */
#ifndef BRAVE_COMPONENTS_UPDATE_CLIENT_PRIVACY_PRESERVING_PROTOCOL_SERIALIZER_H_
#define BRAVE_COMPONENTS_UPDATE_CLIENT_PRIVACY_PRESERVING_PROTOCOL_SERIALIZER_H_
#include <string>
#include "components/update_client/protocol_definition.h"
#include "components/update_client/protocol_serializer.h"
namespace update_client {
// PrivacyPreservingProtocolSerializer wraps around upstream's
// ProtocolSerializerJSON, removing values from update requests that could be
// used to fingerprint users.
class PrivacyPreservingProtocolSerializer : public ProtocolSerializer {
public:
PrivacyPreservingProtocolSerializer() = default;
PrivacyPreservingProtocolSerializer(
const PrivacyPreservingProtocolSerializer&) = delete;
PrivacyPreservingProtocolSerializer& operator=(
const PrivacyPreservingProtocolSerializer&) = delete;
// Overrides for ProtocolSerializer.
std::string Serialize(
const protocol_request::Request& request) const override;
};
} // namespace update_client
#endif // BRAVE_COMPONENTS_UPDATE_CLIENT_PRIVACY_PRESERVING_PROTOCOL_SERIALIZER_H_
+7
View File
@@ -3,6 +3,13 @@
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
# You can obtain one at http://mozilla.org/MPL/2.0/.
brave_components_update_client_sources = [
"//brave/components/update_client/privacy_preserving_protocol_handler.cc",
"//brave/components/update_client/privacy_preserving_protocol_handler.h",
"//brave/components/update_client/privacy_preserving_protocol_serializer.cc",
"//brave/components/update_client/privacy_preserving_protocol_serializer.h",
]
brave_components_update_client_deps = [
"//brave/components/constants",
"//brave/components/widevine:constants",
+86
View File
@@ -0,0 +1,86 @@
/* Copyright (c) 2025 The Brave Authors. All rights reserved.
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at https://mozilla.org/MPL/2.0/. */
#include "brave/components/update_client/test_util.h"
#include <memory>
#include <optional>
#include <utility>
#include <vector>
#include "base/functional/bind.h"
#include "base/values.h"
#include "base/version.h"
#include "components/prefs/pref_service.h"
#include "components/prefs/testing_pref_service.h"
#include "components/update_client/persisted_data.h"
#include "components/update_client/protocol_definition.h"
#include "components/update_client/protocol_serializer.h"
#include "third_party/re2/src/re2/re2.h"
namespace update_client {
// This function checks two things. First, that the serializer faithfully
// encodes the necessary data for update requests. Second, that it does not
// encode the following fields, which could be used to fingerprint users:
// - hw[*]
// - apps[*].lang
// - apps[*].events[*].download_time_ms
bool StripsPrivacySensitiveData(const ProtocolSerializer& serializer) {
// Much of this code was copied from protocol_serializer_json_unittest.cc.
auto pref = std::make_unique<TestingPrefServiceSimple>();
RegisterPersistedDataPrefs(pref->registry());
auto metadata = CreatePersistedData(
base::BindRepeating([](PrefService* pref) { return pref; }, pref.get()),
nullptr);
std::vector<base::Value::Dict> events(2);
events[0].Set("download_time_ms", 9965);
events[0].Set("eventresult", 1);
events[1].Set("eventtype", 63);
std::vector<protocol_request::App> apps;
apps.push_back(MakeProtocolApp(
"id1", base::Version("1.0"), "ap1", "BRND", "ins_id", "lang", -1,
"source1", "location1", {{"attr", "1"}}, "c1", "ch1", "cn1", "test",
{9384}, /*cached_hashes=*/{},
MakeProtocolUpdateCheck(true, "33.12", true, false),
{{"install", "foobar_install_data_index", ""}},
MakeProtocolPing("id1", metadata.get(), {}), std::move(events)));
const auto request = MakeProtocolRequest(
false, "{15160585-8ADE-4D3C-839B-1281A6035D1F}", "prod_id", "1.0",
"channel", "OS", "cacheable", std::nullopt, {{"extra", "params"}}, {},
std::move(apps));
const auto request_str = serializer.Serialize(request);
static constexpr char regex[] =
R"({"request":{"@os":"\w+","@updater":"prod_id",)"
R"("acceptformat":"[^"]+",)"
R"("apps":\[{"ap":"ap1","appid":"id1","attr":"1",)"
R"("brand":"BRND","cohort":"c1","cohorthint":"ch1","cohortname":"cn1",)"
R"("data":\[{"index":"foobar_install_data_index","name":"install"}],)"
R"("disabled":\[{"reason":9384}],"enabled":false,)"
R"("events":\[{"eventresult":1},{"eventtype":63}],)"
R"("iid":"ins_id",)"
R"("installdate":-1,)"
R"("installedby":"location1","installsource":"source1",)"
R"("ping":{[^}]*},)"
R"("release_channel":"test",)"
R"("updatecheck":{"rollback_allowed":true,)"
R"("targetversionprefix":"33.12",)"
R"("updatedisabled":true},"version":"1.0"}],"arch":"\w+","dedup":"cr",)"
R"("dlpref":"cacheable","extra":"params",)"
R"("hw":{},)"
R"("ismachine":false,)"
R"("os":{"arch":"[_,-.\w]+","platform":"OS",)"
R"(("sp":"[\s\w]+",)?"version":"[+-.\w]+"},"prodchannel":"channel",)"
R"("prodversion":"1.0","protocol":"4.0","requestid":"{[-\w]{36}}",)"
R"("sessionid":"{[-\w]{36}}","updaterchannel":"channel",)"
R"("updaterversion":"1.0"(,"wow64":true)?}})";
return RE2::FullMatch(request_str, regex);
}
} // namespace update_client
+17
View File
@@ -0,0 +1,17 @@
/* Copyright (c) 2025 The Brave Authors. All rights reserved.
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at https://mozilla.org/MPL/2.0/. */
#ifndef BRAVE_COMPONENTS_UPDATE_CLIENT_TEST_UTIL_H_
#define BRAVE_COMPONENTS_UPDATE_CLIENT_TEST_UTIL_H_
#include "components/update_client/protocol_serializer.h"
namespace update_client {
bool StripsPrivacySensitiveData(const ProtocolSerializer& serializer);
} // namespace update_client
#endif // BRAVE_COMPONENTS_UPDATE_CLIENT_TEST_UTIL_H_
@@ -1,12 +1,12 @@
diff --git a/components/update_client/BUILD.gn b/components/update_client/BUILD.gn
index a0f35874277a25cb7d091dafa54730915962439d..d15b797053c03a1c2d192d12158c2524d09510f7 100644
index a0f35874277a25cb7d091dafa54730915962439d..fb179a65da5712323ea1df316a5653706db55f1b 100644
--- a/components/update_client/BUILD.gn
+++ b/components/update_client/BUILD.gn
@@ -199,6 +199,7 @@ static_library("update_client") {
"background_downloader_win.h",
]
}
+ deps += brave_components_update_client_deps public_deps = brave_components_update_client_public_deps
+ sources += brave_components_update_client_sources deps += brave_components_update_client_deps public_deps = brave_components_update_client_public_deps
}
if (is_mac) {
+27
View File
@@ -0,0 +1,27 @@
# Copyright (c) 2025 The Brave Authors. All rights reserved.
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
# You can obtain one at https://mozilla.org/MPL/2.0/.
import("//testing/test.gni")
if (is_win || is_mac || is_linux) {
test("brave_updater_unittests") {
testonly = true
sources = [
"configurator_unittest.cc",
"ping_configurator_unittest.cc",
"run_all_unittests.cc",
]
deps = [
"//base",
"//base/test:test_support",
"//brave/components/update_client:test_util",
"//chrome/updater:base",
"//chrome/updater:constants_test",
"//components/prefs:test_support",
"//components/update_client",
"//testing/gtest",
]
}
}
+6
View File
@@ -0,0 +1,6 @@
include_rules = [
"+brave/components/update_client",
"+chrome/updater",
"+components/prefs",
"+components/update_client",
]
+49
View File
@@ -0,0 +1,49 @@
/* Copyright (c) 2025 The Brave Authors. All rights reserved.
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at https://mozilla.org/MPL/2.0/. */
#include "chrome/updater/configurator.h"
#include <utility>
#include "base/files/file_path.h"
#include "base/memory/scoped_refptr.h"
#include "base/test/task_environment.h"
#include "brave/components/update_client/test_util.h"
#include "chrome/updater/external_constants.h"
#include "chrome/updater/persisted_data.h"
#include "chrome/updater/prefs_impl.h"
#include "chrome/updater/updater_scope.h"
#include "components/prefs/pref_registry_simple.h"
#include "components/prefs/testing_pref_service.h"
#include "components/update_client/protocol_handler.h"
#include "components/update_client/protocol_serializer.h"
#include "components/update_client/update_client.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace updater {
TEST(Configurator, UsesPrivacyPreservingProtocolSerializer) {
base::test::TaskEnvironment task_environment;
auto pref_service = std::make_unique<TestingPrefServiceSimple>();
update_client::RegisterPrefs(pref_service->registry());
RegisterPersistedDataPrefs(pref_service->registry());
auto prefs = base::MakeRefCounted<UpdaterPrefsImpl>(base::FilePath(), nullptr,
std::move(pref_service));
auto external_constants = CreateExternalConstants();
auto configurator = base::MakeRefCounted<Configurator>(
prefs, external_constants, UpdaterScope::kUser);
auto factory = configurator->GetProtocolHandlerFactory();
ASSERT_TRUE(factory);
auto serializer = factory->CreateSerializer();
ASSERT_TRUE(serializer);
EXPECT_TRUE(update_client::StripsPrivacySensitiveData(*serializer));
}
} // namespace updater
+27
View File
@@ -0,0 +1,27 @@
/* Copyright (c) 2025 The Brave Authors. All rights reserved.
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at https://mozilla.org/MPL/2.0/. */
#include "chrome/updater/ping_configurator.h"
#include "brave/components/update_client/test_util.h"
#include "components/update_client/protocol_handler.h"
#include "components/update_client/protocol_serializer.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace updater {
TEST(PingConfigurator, UsesPrivacyPreservingProtocolSerializer) {
auto configurator = CreatePingConfigurator();
auto factory = configurator->GetProtocolHandlerFactory();
ASSERT_TRUE(factory);
auto serializer = factory->CreateSerializer();
ASSERT_TRUE(serializer);
EXPECT_TRUE(update_client::StripsPrivacySensitiveData(*serializer));
}
} // namespace updater
+15
View File
@@ -0,0 +1,15 @@
/* Copyright (c) 2025 The Brave Authors. All rights reserved.
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at https://mozilla.org/MPL/2.0/. */
#include "base/functional/bind.h"
#include "base/test/launcher/unit_test_launcher.h"
#include "base/test/test_suite.h"
int main(int argc, char** argv) {
base::TestSuite test_suite(argc, argv);
return base::LaunchUnitTests(
argc, argv,
base::BindOnce(&base::TestSuite::Run, base::Unretained(&test_suite)));
}