metrics: Use unsigned type for length field

This brings the changes from the Chrome OS CL
https://chromium-review.googlesource.com/c/chromiumos/platform2/+/1341794
over to the Chrome repository.

BUG=chromium:906622
TEST=Compiles and passes tests.

Change-Id: I36b4c6d156322ca56d8678b10ac7b46103e3c670
Reviewed-on: https://chromium-review.googlesource.com/c/1346397
Commit-Queue: Mattias Nissler <mnissler@chromium.org>
Reviewed-by: Ilya Sherman <isherman@chromium.org>
Cr-Commit-Position: refs/heads/master@{#619913}
diff --git a/components/metrics/serialization/serialization_utils.cc b/components/metrics/serialization/serialization_utils.cc
index 7214b5d..21931d0 100644
--- a/components/metrics/serialization/serialization_utils.cc
+++ b/components/metrics/serialization/serialization_utils.cc
@@ -14,6 +14,7 @@
 #include "base/files/file_util.h"
 #include "base/files/scoped_file.h"
 #include "base/logging.h"
+#include "base/numerics/safe_math.h"
 #include "base/strings/string_split.h"
 #include "base/strings/string_util.h"
 #include "components/metrics/serialization/metric_sample.h"
@@ -35,11 +36,11 @@
   CHECK(message);
 
   int result;
-  int32_t message_size;
-  const int32_t message_header_size = sizeof(message_size);
+  uint32_t encoded_size;
+  const size_t message_header_size = sizeof(uint32_t);
   // The file containing the metrics does not leave the device so the writer and
   // the reader will always have the same endianness.
-  result = HANDLE_EINTR(read(fd, &message_size, message_header_size));
+  result = HANDLE_EINTR(read(fd, &encoded_size, message_header_size));
   if (result < 0) {
     DPLOG(ERROR) << "reading metrics message header";
     return false;
@@ -48,17 +49,19 @@
     // This indicates a normal EOF.
     return false;
   }
-  if (result < message_header_size) {
+  if (base::checked_cast<size_t>(result) < message_header_size) {
     DLOG(ERROR) << "bad read size " << result << ", expecting "
-                << sizeof(message_size);
+                << message_header_size;
     return false;
   }
 
   // kMessageMaxLength applies to the entire message: the 4-byte
   // length field and the content.
+  size_t message_size = base::checked_cast<size_t>(encoded_size);
   if (message_size > SerializationUtils::kMessageMaxLength) {
     DLOG(ERROR) << "message too long : " << message_size;
-    if (HANDLE_EINTR(lseek(fd, message_size - 4, SEEK_CUR)) == -1) {
+    if (HANDLE_EINTR(lseek(fd, message_size - message_header_size, SEEK_CUR)) ==
+        -1) {
       DLOG(ERROR) << "error while skipping message. abort";
       return false;
     }
@@ -192,17 +195,19 @@
   }
 
   std::string msg = sample.ToString();
-  int32_t size = msg.length() + sizeof(int32_t);
-  if (size > kMessageMaxLength) {
+  size_t size = 0;
+  if (!base::CheckAdd(msg.length(), sizeof(uint32_t)).AssignIfValid(&size) ||
+      size > kMessageMaxLength) {
     DPLOG(ERROR) << "cannot write message: too long: " << filename;
     return false;
   }
 
   // The file containing the metrics samples will only be read by programs on
   // the same device so we do not check endianness.
+  uint32_t encoded_size = base::checked_cast<uint32_t>(size);
   if (!base::WriteFileDescriptor(file_descriptor.get(),
-                                 reinterpret_cast<char*>(&size),
-                                 sizeof(size))) {
+                                 reinterpret_cast<char*>(&encoded_size),
+                                 sizeof(uint32_t))) {
     DPLOG(ERROR) << "error writing message length: " << filename;
     return false;
   }
diff --git a/components/metrics/serialization/serialization_utils.h b/components/metrics/serialization/serialization_utils.h
index c741cb2f..2052686 100644
--- a/components/metrics/serialization/serialization_utils.h
+++ b/components/metrics/serialization/serialization_utils.h
@@ -40,7 +40,7 @@
 bool WriteMetricToFile(const MetricSample& sample, const std::string& filename);
 
 // Maximum length of a serialized message
-static const int kMessageMaxLength = 1024;
+static const size_t kMessageMaxLength = 1024;
 
 }  // namespace SerializationUtils
 }  // namespace metrics
diff --git a/components/metrics/serialization/serialization_utils_unittest.cc b/components/metrics/serialization/serialization_utils_unittest.cc
index 5685a1f..6bf3c8c 100644
--- a/components/metrics/serialization/serialization_utils_unittest.cc
+++ b/components/metrics/serialization/serialization_utils_unittest.cc
@@ -135,6 +135,50 @@
   EXPECT_TRUE(crash->IsEqual(*samples[0]));
 }
 
+TEST_F(SerializationUtilsTest, NegativeLengthTest) {
+  // This input is specifically constructed to yield a single crash sample when
+  // parsed by a buggy version of the code but fails to parse and doesn't yield
+  // samples when parsed by a correct implementation.
+  constexpr uint8_t kInput[] = {
+      // Length indicating that next length field is the negative one below.
+      // This sample is invalid as it contains more than three null bytes.
+      0x14,
+      0x00,
+      0x00,
+      0x00,
+      // Encoding of a valid crash sample.
+      0x0c,
+      0x00,
+      0x00,
+      0x00,
+      0x63,
+      0x72,
+      0x61,
+      0x73,
+      0x68,
+      0x00,
+      0x61,
+      0x00,
+      // Invalid sample that jumps past the negative length bytes below.
+      0x08,
+      0x00,
+      0x00,
+      0x00,
+      // This is -16 in two's complement interpretation, pointing to the valid
+      // crash sample before.
+      0xf0,
+      0xff,
+      0xff,
+      0xff,
+  };
+  CHECK(base::WriteFile(filepath, reinterpret_cast<const char*>(kInput),
+                        sizeof(kInput)));
+
+  std::vector<std::unique_ptr<MetricSample>> samples;
+  SerializationUtils::ReadAndTruncateMetricsFromFile(filename, &samples);
+  ASSERT_EQ(0U, samples.size());
+}
+
 TEST_F(SerializationUtilsTest, WriteReadTest) {
   std::unique_ptr<MetricSample> hist =
       MetricSample::HistogramSample("myhist", 1, 2, 3, 4);