1 // Copyright 2014 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 "components/metrics/net/net_metrics_log_uploader.h"
6
7 #include <sstream>
8
9 #include "base/base64.h"
10 #include "base/feature_list.h"
11 #include "base/functional/bind.h"
12 #include "base/metrics/histogram_macros.h"
13 #include "base/metrics/statistics_recorder.h"
14 #include "base/strings/strcat.h"
15 #include "base/strings/string_number_conversions.h"
16 #include "components/encrypted_messages/encrypted_message.pb.h"
17 #include "components/encrypted_messages/message_encrypter.h"
18 #include "components/metrics/metrics_log.h"
19 #include "components/metrics/metrics_log_uploader.h"
20 #include "net/base/load_flags.h"
21 #include "net/base/url_util.h"
22 #include "net/traffic_annotation/network_traffic_annotation.h"
23 #include "services/network/public/cpp/resource_request.h"
24 #include "services/network/public/cpp/shared_url_loader_factory.h"
25 #include "services/network/public/cpp/simple_url_loader.h"
26 #include "services/network/public/mojom/url_response_head.mojom.h"
27 #include "third_party/metrics_proto/chrome_user_metrics_extension.pb.h"
28 #include "third_party/metrics_proto/reporting_info.pb.h"
29 #include "third_party/zlib/google/compression_utils.h"
30 #include "url/gurl.h"
31
32 namespace {
33
34 // Constants used for encrypting logs that are sent over HTTP. The
35 // corresponding private key is used by the metrics server to decrypt logs.
36 const char kEncryptedMessageLabel[] = "metrics log";
37
38 const uint8_t kServerPublicKey[] = {
39 0x51, 0xcc, 0x52, 0x67, 0x42, 0x47, 0x3b, 0x10, 0xe8, 0x63, 0x18,
40 0x3c, 0x61, 0xa7, 0x96, 0x76, 0x86, 0x91, 0x40, 0x71, 0x39, 0x5f,
41 0x31, 0x1a, 0x39, 0x5b, 0x76, 0xb1, 0x6b, 0x3d, 0x6a, 0x2b};
42
43 const uint32_t kServerPublicKeyVersion = 1;
44
45 constexpr char kNoUploadUrlsReasonMsg[] =
46 "No server upload URLs specified. Will not attempt to retransmit.";
47
GetNetworkTrafficAnnotation(const metrics::MetricsLogUploader::MetricServiceType & service_type,const metrics::LogMetadata & log_metadata)48 net::NetworkTrafficAnnotationTag GetNetworkTrafficAnnotation(
49 const metrics::MetricsLogUploader::MetricServiceType& service_type,
50 const metrics::LogMetadata& log_metadata) {
51 // The code in this function should remain so that we won't need a default
52 // case that does not have meaningful annotation.
53 // Structured Metrics is an UMA consented metric service.
54 if (service_type == metrics::MetricsLogUploader::UMA ||
55 service_type == metrics::MetricsLogUploader::STRUCTURED_METRICS) {
56 return net::DefineNetworkTrafficAnnotation("metrics_report_uma", R"(
57 semantics {
58 sender: "Metrics UMA Log Uploader"
59 description:
60 "Report of usage statistics and crash-related data about Chromium. "
61 "Usage statistics contain information such as preferences, button "
62 "clicks, and memory usage and do not include web page URLs or "
63 "personal information. See more at "
64 "https://www.google.com/chrome/browser/privacy/ under 'Usage "
65 "statistics and crash reports'. Usage statistics are tied to a "
66 "pseudonymous machine identifier and not to your email address."
67 trigger:
68 "Reports are automatically generated on startup and at intervals "
69 "while Chromium is running."
70 data:
71 "A protocol buffer with usage statistics and crash related data."
72 destination: GOOGLE_OWNED_SERVICE
73 }
74 policy {
75 cookies_allowed: NO
76 setting:
77 "Users can enable or disable this feature by disabling "
78 "'Automatically send usage statistics and crash reports to Google' "
79 "in Chromium's settings under Advanced Settings, Privacy. The "
80 "feature is enabled by default."
81 chrome_policy {
82 MetricsReportingEnabled {
83 policy_options {mode: MANDATORY}
84 MetricsReportingEnabled: false
85 }
86 }
87 })");
88 }
89 DCHECK_EQ(service_type, metrics::MetricsLogUploader::UKM);
90
91 // TODO(b/308003806) Create an annotation for AppKM.
92
93 return net::DefineNetworkTrafficAnnotation("metrics_report_ukm", R"(
94 semantics {
95 sender: "Metrics UKM Log Uploader"
96 description:
97 "Report of usage statistics that are keyed by URLs to Chromium. This "
98 "includes information about the web pages you visit and your usage "
99 "of them, such as page load speed. This will also include URLs and "
100 "statistics related to downloaded files. These statistics may also "
101 "include information about the extensions that have been installed "
102 "from Chrome Web Store. Google only stores usage statistics "
103 "associated with published extensions, and URLs that are known by "
104 "Google’s search index. Usage statistics are tied to a "
105 "pseudonymous machine identifier and not to your email address."
106 trigger:
107 "Reports are automatically generated on startup and at intervals "
108 "while Chromium is running with Sync enabled."
109 data:
110 "A protocol buffer with usage statistics and associated URLs."
111 destination: GOOGLE_OWNED_SERVICE
112 }
113 policy {
114 cookies_allowed: NO
115 setting:
116 "Users can enable or disable this feature by disabling 'Make "
117 "searches and browsing better' in Chrome's settings under Advanced "
118 "Settings, Privacy. This has to be enabled for all active profiles. "
119 "This is only enabled if the user has 'Help improve Chrome's "
120 "features and performance' enabled in the same settings menu. "
121 "Information about the installed extensions is sent only if "
122 "Extension Sync is enabled."
123 chrome_policy {
124 MetricsReportingEnabled {
125 policy_options {mode: MANDATORY}
126 MetricsReportingEnabled: false
127 }
128 }
129 })");
130 }
131
132 std::string SerializeReportingInfo(
133 const metrics::ReportingInfo& reporting_info) {
134 std::string result;
135 std::string bytes;
136 bool success = reporting_info.SerializeToString(&bytes);
137 DCHECK(success);
138 base::Base64Encode(bytes, &result);
139 return result;
140 }
141
142 // Encrypts a |plaintext| string, using the encrypted_messages component,
143 // returns |encrypted| which is a serialized EncryptedMessage object. Returns
144 // false if there was a problem encrypting.
145 bool EncryptString(const std::string& plaintext, std::string* encrypted) {
146 encrypted_messages::EncryptedMessage encrypted_message;
147 if (!encrypted_messages::EncryptSerializedMessage(
148 kServerPublicKey, kServerPublicKeyVersion, kEncryptedMessageLabel,
149 plaintext, &encrypted_message)) {
150 NOTREACHED() << "Error encrypting string.";
151 return false;
152 }
153 if (!encrypted_message.SerializeToString(encrypted)) {
154 NOTREACHED() << "Error serializing encrypted string.";
155 return false;
156 }
157 return true;
158 }
159
160 // Encrypts a |plaintext| string and returns |encoded|, which is a base64
161 // encoded serialized EncryptedMessage object. Returns false if there was a
162 // problem encrypting or serializing.
163 bool EncryptAndBase64EncodeString(const std::string& plaintext,
164 std::string* encoded) {
165 std::string encrypted_text;
166 if (!EncryptString(plaintext, &encrypted_text)) {
167 return false;
168 }
169
170 base::Base64Encode(encrypted_text, encoded);
171 return true;
172 }
173
174 #ifndef NDEBUG
175 void LogUploadingHistograms(const std::string& compressed_log_data) {
176 if (!VLOG_IS_ON(2)) {
177 return;
178 }
179
180 std::string uncompressed;
181 if (!compression::GzipUncompress(compressed_log_data, &uncompressed)) {
182 DVLOG(2) << "failed to uncompress log";
183 return;
184 }
185 metrics::ChromeUserMetricsExtension proto;
186 if (!proto.ParseFromString(uncompressed)) {
187 DVLOG(2) << "failed to parse uncompressed log";
188 return;
189 };
190 DVLOG(2) << "Uploading histograms...";
191
192 const base::StatisticsRecorder::Histograms histograms =
193 base::StatisticsRecorder::GetHistograms();
194 auto get_histogram_name = [&](uint64_t name_hash) -> std::string {
195 for (base::HistogramBase* histogram : histograms) {
196 if (histogram->name_hash() == name_hash) {
197 return histogram->histogram_name();
198 }
199 }
200 return base::StrCat({"unnamed ", base::NumberToString(name_hash)});
201 };
202
203 for (int i = 0; i < proto.histogram_event_size(); i++) {
204 const metrics::HistogramEventProto& event = proto.histogram_event(i);
205
206 std::stringstream summary;
207 summary << " sum=" << event.sum();
208 for (int j = 0; j < event.bucket_size(); j++) {
209 const metrics::HistogramEventProto::Bucket& b = event.bucket(j);
210 // Empty fields have a specific meaning, see
211 // third_party/metrics_proto/histogram_event.proto.
212 summary << " bucket["
213 << (b.has_min() ? base::NumberToString(b.min()) : "..") << '-'
214 << (b.has_max() ? base::NumberToString(b.max()) : "..") << ")="
215 << (b.has_count() ? base::NumberToString(b.count()) : "(1)");
216 }
217 DVLOG(2) << get_histogram_name(event.name_hash()) << summary.str();
218 }
219 }
220 #endif
221
222 } // namespace
223
224 namespace metrics {
225
226 NetMetricsLogUploader::NetMetricsLogUploader(
227 scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
228 const GURL& server_url,
229 base::StringPiece mime_type,
230 MetricsLogUploader::MetricServiceType service_type,
231 const MetricsLogUploader::UploadCallback& on_upload_complete)
232 : NetMetricsLogUploader(url_loader_factory,
233 server_url,
234 /*insecure_server_url=*/GURL(),
235 mime_type,
236 service_type,
237 on_upload_complete) {}
238
239 NetMetricsLogUploader::NetMetricsLogUploader(
240 scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
241 const GURL& server_url,
242 const GURL& insecure_server_url,
243 base::StringPiece mime_type,
244 MetricsLogUploader::MetricServiceType service_type,
245 const MetricsLogUploader::UploadCallback& on_upload_complete)
246 : url_loader_factory_(std::move(url_loader_factory)),
247 server_url_(server_url),
248 insecure_server_url_(insecure_server_url),
249 mime_type_(mime_type.data(), mime_type.size()),
250 service_type_(service_type),
251 on_upload_complete_(on_upload_complete) {}
252
253 NetMetricsLogUploader::~NetMetricsLogUploader() = default;
254
255 void NetMetricsLogUploader::UploadLog(const std::string& compressed_log_data,
256 const LogMetadata& log_metadata,
257 const std::string& log_hash,
258 const std::string& log_signature,
259 const ReportingInfo& reporting_info) {
260 // If this attempt is a retry, there was a network error, the last attempt was
261 // over HTTPS, and there is an insecure URL set, then attempt this upload over
262 // HTTP.
263 if (reporting_info.attempt_count() > 1 &&
264 reporting_info.last_error_code() != 0 &&
265 reporting_info.last_attempt_was_https() &&
266 !insecure_server_url_.is_empty()) {
267 UploadLogToURL(compressed_log_data, log_metadata, log_hash, log_signature,
268 reporting_info, insecure_server_url_);
269 return;
270 }
271 UploadLogToURL(compressed_log_data, log_metadata, log_hash, log_signature,
272 reporting_info, server_url_);
273 }
274
275 void NetMetricsLogUploader::UploadLogToURL(
276 const std::string& compressed_log_data,
277 const LogMetadata& log_metadata,
278 const std::string& log_hash,
279 const std::string& log_signature,
280 const ReportingInfo& reporting_info,
281 const GURL& url) {
282 DCHECK(!log_hash.empty());
283
284 #ifndef NDEBUG
285 // For debug builds, you can use -vmodule=net_metrics_log_uploader=2
286 // to enable logging of uploaded histograms. You probably also want to use
287 // --force-enable-metrics-reporting, or metrics reporting may not be enabled.
288 LogUploadingHistograms(compressed_log_data);
289 #endif
290
291 auto resource_request = std::make_unique<network::ResourceRequest>();
292 resource_request->url = url;
293 // Drop cookies and auth data.
294 resource_request->credentials_mode = network::mojom::CredentialsMode::kOmit;
295 resource_request->method = "POST";
296
297 std::string reporting_info_string = SerializeReportingInfo(reporting_info);
298 // If we are not using HTTPS for this upload, encrypt it. We do not encrypt
299 // requests to localhost to allow testing with a local collector that doesn't
300 // have decryption enabled.
301 bool should_encrypt =
302 !url.SchemeIs(url::kHttpsScheme) && !net::IsLocalhost(url);
303 if (should_encrypt) {
304 std::string base64_encoded_hash;
305 if (!EncryptAndBase64EncodeString(log_hash, &base64_encoded_hash)) {
306 HTTPFallbackAborted();
307 return;
308 }
309 resource_request->headers.SetHeader("X-Chrome-UMA-Log-SHA1",
310 base64_encoded_hash);
311
312 std::string base64_encoded_signature;
313 if (!EncryptAndBase64EncodeString(log_signature,
314 &base64_encoded_signature)) {
315 HTTPFallbackAborted();
316 return;
317 }
318 resource_request->headers.SetHeader("X-Chrome-UMA-Log-HMAC-SHA256",
319 base64_encoded_signature);
320
321 std::string base64_reporting_info;
322 if (!EncryptAndBase64EncodeString(reporting_info_string,
323 &base64_reporting_info)) {
324 HTTPFallbackAborted();
325 return;
326 }
327 resource_request->headers.SetHeader("X-Chrome-UMA-ReportingInfo",
328 base64_reporting_info);
329 } else {
330 resource_request->headers.SetHeader("X-Chrome-UMA-Log-SHA1", log_hash);
331 resource_request->headers.SetHeader("X-Chrome-UMA-Log-HMAC-SHA256",
332 log_signature);
333 resource_request->headers.SetHeader("X-Chrome-UMA-ReportingInfo",
334 reporting_info_string);
335 // Tell the server that we're uploading gzipped protobufs only if we are not
336 // encrypting, since encrypted messages have to be decrypted server side
337 // after decryption, not before.
338 resource_request->headers.SetHeader("content-encoding", "gzip");
339 }
340
341 net::NetworkTrafficAnnotationTag traffic_annotation =
342 GetNetworkTrafficAnnotation(service_type_, log_metadata);
343 url_loader_ = network::SimpleURLLoader::Create(std::move(resource_request),
344 traffic_annotation);
345
346 if (should_encrypt) {
347 std::string encrypted_message;
348 if (!EncryptString(compressed_log_data, &encrypted_message)) {
349 url_loader_.reset();
350 HTTPFallbackAborted();
351 return;
352 }
353 url_loader_->AttachStringForUpload(encrypted_message, mime_type_);
354 } else {
355 url_loader_->AttachStringForUpload(compressed_log_data, mime_type_);
356 }
357
358 // It's safe to use |base::Unretained(this)| here, because |this| owns
359 // the |url_loader_|, and the callback will be cancelled if the |url_loader_|
360 // is destroyed.
361 url_loader_->DownloadToStringOfUnboundedSizeUntilCrashAndDie(
362 url_loader_factory_.get(),
363 base::BindOnce(&NetMetricsLogUploader::OnURLLoadComplete,
364 base::Unretained(this)));
365 }
366
367 void NetMetricsLogUploader::HTTPFallbackAborted() {
368 // The callback is called with: a response code of 0 to indicate no upload was
369 // attempted, a generic net error, and false to indicate it wasn't a secure
370 // connection. If no server URLs were specified, discard the log and do not
371 // attempt retransmission.
372 bool force_discard =
373 server_url_.is_empty() && insecure_server_url_.is_empty();
374 base::StringPiece force_discard_reason =
375 force_discard ? kNoUploadUrlsReasonMsg : "";
376 on_upload_complete_.Run(/*response_code=*/0, net::ERR_FAILED,
377 /*was_https=*/false, force_discard,
378 force_discard_reason);
379 }
380
381 // The callback is only invoked if |url_loader_| it was bound against is alive.
382 void NetMetricsLogUploader::OnURLLoadComplete(
383 std::unique_ptr<std::string> response_body) {
384 int response_code = -1;
385 if (url_loader_->ResponseInfo() && url_loader_->ResponseInfo()->headers) {
386 response_code = url_loader_->ResponseInfo()->headers->response_code();
387 }
388
389 int error_code = url_loader_->NetError();
390
391 bool was_https = url_loader_->GetFinalURL().SchemeIs(url::kHttpsScheme);
392 url_loader_.reset();
393
394 // If no server URLs were specified, discard the log and do not attempt
395 // retransmission.
396 bool force_discard =
397 server_url_.is_empty() && insecure_server_url_.is_empty();
398 base::StringPiece force_discard_reason =
399 force_discard ? kNoUploadUrlsReasonMsg : "";
400 on_upload_complete_.Run(response_code, error_code, was_https, force_discard,
401 force_discard_reason);
402 }
403
404 } // namespace metrics
405