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