• 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.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