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);
+}