• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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