Add fuzzer for writing to shared memory buffer.

Change-Id: Ie1aecc827b9c3ceee6d7c82d90e8082dd060965b
Bug: 69150303
diff --git a/BUILD.gn b/BUILD.gn
index 242eb47..ee9ed13 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -278,6 +278,7 @@
     deps = [
       "src/ipc:buffered_frame_deserializer_fuzzer",
       "src/profiling/memory:shared_ring_buffer_fuzzer",
+      "src/profiling/memory:shared_ring_buffer_write_fuzzer",
       "src/profiling/memory:unwinding_fuzzer",
       "src/protozero:protozero_decoder_fuzzer",
       "src/trace_processor:trace_processor_fuzzer",
diff --git a/src/profiling/memory/BUILD.gn b/src/profiling/memory/BUILD.gn
index e6ce5d4..70ae093 100644
--- a/src/profiling/memory/BUILD.gn
+++ b/src/profiling/memory/BUILD.gn
@@ -229,3 +229,15 @@
     "../../base",
   ]
 }
+
+perfetto_fuzzer_test("shared_ring_buffer_write_fuzzer") {
+  testonly = true
+  sources = [
+    "shared_ring_buffer_write_fuzzer.cc",
+  ]
+  deps = [
+    ":ring_buffer",
+    "../../../gn:default_deps",
+    "../../base",
+  ]
+}
diff --git a/src/profiling/memory/shared_ring_buffer.cc b/src/profiling/memory/shared_ring_buffer.cc
index 956cdd2..257d5eb 100644
--- a/src/profiling/memory/shared_ring_buffer.cc
+++ b/src/profiling/memory/shared_ring_buffer.cc
@@ -182,6 +182,13 @@
 
   const uint64_t size_with_header =
       base::AlignUp<kAlignment>(size + kHeaderSize);
+
+  // size_with_header < size is for catching overflow of size_with_header.
+  if (PERFETTO_UNLIKELY(size_with_header < size)) {
+    errno = EINVAL;
+    return result;
+  }
+
   if (size_with_header > write_avail(pos)) {
     meta_->stats.num_writes_overflow++;
     errno = EAGAIN;
diff --git a/src/profiling/memory/shared_ring_buffer.h b/src/profiling/memory/shared_ring_buffer.h
index d0f3d2f..0e68661 100644
--- a/src/profiling/memory/shared_ring_buffer.h
+++ b/src/profiling/memory/shared_ring_buffer.h
@@ -121,8 +121,8 @@
     return lock;
   }
 
- private:
-  struct alignas(base::kPageSize) MetadataPage {
+  // Exposed for fuzzers.
+  struct MetadataPage {
     alignas(uint64_t) std::atomic<bool> spinlock;
     uint64_t read_pos;
     uint64_t write_pos;
@@ -131,6 +131,7 @@
     Stats stats;
   };
 
+ private:
   struct PointerPositions {
     uint64_t read_pos;
     uint64_t write_pos;
diff --git a/src/profiling/memory/shared_ring_buffer_fuzzer.cc b/src/profiling/memory/shared_ring_buffer_fuzzer.cc
index 2fe7554..a874a0f 100644
--- a/src/profiling/memory/shared_ring_buffer_fuzzer.cc
+++ b/src/profiling/memory/shared_ring_buffer_fuzzer.cc
@@ -25,12 +25,6 @@
 namespace profiling {
 namespace {
 
-struct MetadataHeader {
-  alignas(uint64_t) std::atomic<bool> spinlock;
-  uint64_t read_pos;
-  uint64_t write_pos;
-};
-
 size_t RoundToPow2(size_t v) {
   uint64_t x = static_cast<uint64_t>(v);
   if (x < 2)
@@ -48,17 +42,17 @@
 }
 
 int FuzzRingBuffer(const uint8_t* data, size_t size) {
-  if (size <= sizeof(MetadataHeader))
+  if (size <= sizeof(SharedRingBuffer::MetadataPage))
     return 0;
 
   auto fd = base::TempFile::CreateUnlinked().ReleaseFD();
   PERFETTO_CHECK(fd);
 
-  // Use fuzzer input to first fill the MetadataHeader in the first page, and
-  // then put the remainder into the data portion of the ring buffer (2nd+
-  // pages).
-  size_t payload_size = size - sizeof(MetadataHeader);
-  const uint8_t* payload = data + sizeof(MetadataHeader);
+  // Use fuzzer input to first fill the SharedRingBuffer::MetadataPage in the
+  // first page, and then put the remainder into the data portion of the ring
+  // buffer (2nd+ pages).
+  size_t payload_size = size - sizeof(SharedRingBuffer::MetadataPage);
+  const uint8_t* payload = data + sizeof(SharedRingBuffer::MetadataPage);
   size_t payload_size_pages =
       (payload_size + base::kPageSize - 1) / base::kPageSize;
   // Upsize test buffer to be 2^n data pages (precondition of the impl) + 1 page
@@ -67,7 +61,7 @@
 
   // Clear spinlock field, as otherwise the read will wait indefinitely (it
   // defaults to indefinite blocking mode).
-  MetadataHeader header = {};
+  SharedRingBuffer::MetadataPage header = {};
   memcpy(&header, data, sizeof(header));
   header.spinlock = 0;
 
diff --git a/src/profiling/memory/shared_ring_buffer_write_fuzzer.cc b/src/profiling/memory/shared_ring_buffer_write_fuzzer.cc
new file mode 100644
index 0000000..1bdd925
--- /dev/null
+++ b/src/profiling/memory/shared_ring_buffer_write_fuzzer.cc
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include "perfetto/base/file_utils.h"
+#include "perfetto/base/temp_file.h"
+#include "src/profiling/memory/shared_ring_buffer.h"
+
+namespace perfetto {
+namespace profiling {
+namespace {
+
+struct FuzzingInputHeader {
+  size_t write_size;
+  SharedRingBuffer::MetadataPage metadata_page;
+};
+
+size_t RoundToPow2(size_t v) {
+  uint64_t x = static_cast<uint64_t>(v);
+  if (x < 2)
+    return 2;
+
+  x--;
+  x |= x >> 1;
+  x |= x >> 2;
+  x |= x >> 4;
+  x |= x >> 8;
+  x |= x >> 16;
+  x |= x >> 32;
+  x++;
+  return static_cast<size_t>(x);
+}
+
+int FuzzRingBufferWrite(const uint8_t* data, size_t size) {
+  if (size <= sizeof(FuzzingInputHeader))
+    return 0;
+
+  auto fd = base::TempFile::CreateUnlinked().ReleaseFD();
+  PERFETTO_CHECK(fd);
+
+  // Prefill shared buffer with fuzzer input, then attempt to write.
+  // TODO(fmayer): Do we actually need to fill the buffer with payload, or
+  // should we only fuzz the metadata?
+  size_t payload_size = size - sizeof(FuzzingInputHeader);
+  const uint8_t* payload = data + sizeof(FuzzingInputHeader);
+  size_t payload_size_pages =
+      (payload_size + base::kPageSize - 1) / base::kPageSize;
+  // Upsize test buffer to be 2^n data pages (precondition of the impl) + 1 page
+  // for the metadata.
+  size_t total_size_pages = 1 + RoundToPow2(payload_size_pages);
+
+  // Clear spinlock field, as otherwise we will fail acquiring the lock below.
+  FuzzingInputHeader header = {};
+  memcpy(&header, data, sizeof(header));
+  SharedRingBuffer::MetadataPage& metadata_page = header.metadata_page;
+  metadata_page.spinlock = 0;
+
+  PERFETTO_CHECK(ftruncate(*fd, static_cast<off_t>(total_size_pages *
+                                                   base::kPageSize)) == 0);
+  PERFETTO_CHECK(base::WriteAll(*fd, &metadata_page, sizeof(metadata_page)) !=
+                 -1);
+  PERFETTO_CHECK(lseek(*fd, base::kPageSize, SEEK_SET) != -1);
+  PERFETTO_CHECK(base::WriteAll(*fd, payload, payload_size) != -1);
+
+  auto buf = SharedRingBuffer::Attach(std::move(fd));
+  PERFETTO_CHECK(!!buf);
+
+  SharedRingBuffer::Buffer write_buf;
+  {
+    auto lock = buf->AcquireLock(ScopedSpinlock::Mode::Try);
+    PERFETTO_CHECK(lock.locked());
+    write_buf = buf->BeginWrite(lock, header.write_size);
+  }
+  if (!write_buf)
+    return 0;
+
+  memset(write_buf.data, '\0', write_buf.size);
+  buf->EndWrite(std::move(write_buf));
+  return 0;
+}
+
+}  // namespace
+}  // namespace profiling
+}  // namespace perfetto
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size);
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+  return perfetto::profiling::FuzzRingBufferWrite(data, size);
+}