aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDmitry Vyukov <dvyukov@google.com>2017-10-20 11:30:59 +0200
committerDmitry Vyukov <dvyukov@google.com>2017-10-23 09:59:39 +0200
commitb71450d9fbb6e1d07d83b01d2f2fe4b41c5cdefb (patch)
tree1cfe7f7330e15879d6cfcb58e44a318c5b36e5d2
parent3c6fe803958431367baa476f5e591cf6d749640a (diff)
executor: prevent executor from messing with output region
When comparisons are enabled fuzzer somehow manages to discover the output region and corrupt it. It seems to fetch the address from some memory operations (mmap/munmap). Don't leak the output region address.
-rw-r--r--executor/executor.h38
-rw-r--r--executor/executor_akaros.cc5
-rw-r--r--executor/executor_freebsd.cc5
-rw-r--r--executor/executor_fuchsia.cc5
-rw-r--r--executor/executor_linux.cc39
-rw-r--r--executor/executor_windows.cc5
6 files changed, 78 insertions, 19 deletions
diff --git a/executor/executor.h b/executor/executor.h
index e4bafc21e..669851e90 100644
--- a/executor/executor.h
+++ b/executor/executor.h
@@ -26,6 +26,7 @@ const int kOutPipeFd = 251; // remapped from stdout
const int kMaxInput = 2 << 20;
const int kMaxOutput = 16 << 20;
+const int kCoverSize = 64 << 10;
const int kMaxArgs = 9;
const int kMaxThreads = 16;
const int kMaxCommands = 16 << 10;
@@ -164,9 +165,8 @@ struct kcov_comparison_t {
uint64_t arg2;
uint64_t pc;
- // Writes the structure using the write_one function for each field.
- // Inspired by write_output() function.
- void write(uint32_t* (*write_one)(uint32_t));
+ bool ignore() const;
+ void write();
bool operator==(const struct kcov_comparison_t& other) const;
bool operator<(const struct kcov_comparison_t& other) const;
};
@@ -520,13 +520,19 @@ void handle_completion(thread_t* th)
if (flag_collect_comps) {
// Collect only the comparisons
- comps_size = th->cover_size;
+ uint32_t ncomps = th->cover_size;
kcov_comparison_t* start = (kcov_comparison_t*)th->cover_data;
- kcov_comparison_t* end = start + comps_size;
+ kcov_comparison_t* end = start + ncomps;
+ if ((uint64_t*)end >= th->cover_data + kCoverSize)
+ fail("too many comparisons %u", ncomps);
std::sort(start, end);
- comps_size = std::unique(start, end) - start;
- for (uint32_t i = 0; i < comps_size; ++i)
- start[i].write(write_output);
+ ncomps = std::unique(start, end) - start;
+ for (uint32_t i = 0; i < ncomps; ++i) {
+ if (start[i].ignore())
+ continue;
+ comps_size++;
+ start[i].write();
+ }
} else {
// Write out feedback signals.
// Currently it is code edges computed as xor of
@@ -761,10 +767,10 @@ uint64_t read_input(uint64_t** input_posp, bool peek)
return *input_pos;
}
-void kcov_comparison_t::write(uint32_t* (*write_one)(uint32_t))
+void kcov_comparison_t::write()
{
// Write order: type arg1 arg2 pc.
- write_one((uint32_t)type);
+ write_output((uint32_t)type);
// KCOV converts all arguments of size x first to uintx_t and then to
// uint64_t. We want to properly extend signed values, e.g we want
@@ -788,15 +794,15 @@ void kcov_comparison_t::write(uint32_t* (*write_one)(uint32_t))
}
bool is_size_8 = (type & KCOV_CMP_SIZE_MASK) == KCOV_CMP_SIZE8;
if (!is_size_8) {
- write_one((uint32_t)arg1);
- write_one((uint32_t)arg2);
+ write_output((uint32_t)arg1);
+ write_output((uint32_t)arg2);
return;
}
// If we have 64 bits arguments then write them in Little-endian.
- write_one((uint32_t)(arg1 & 0xFFFFFFFF));
- write_one((uint32_t)(arg1 >> 32));
- write_one((uint32_t)(arg2 & 0xFFFFFFFF));
- write_one((uint32_t)(arg2 >> 32));
+ write_output((uint32_t)(arg1 & 0xFFFFFFFF));
+ write_output((uint32_t)(arg1 >> 32));
+ write_output((uint32_t)(arg2 & 0xFFFFFFFF));
+ write_output((uint32_t)(arg2 >> 32));
}
bool kcov_comparison_t::operator==(const struct kcov_comparison_t& other) const
diff --git a/executor/executor_akaros.cc b/executor/executor_akaros.cc
index f70e83807..8cb87d3d7 100644
--- a/executor/executor_akaros.cc
+++ b/executor/executor_akaros.cc
@@ -98,3 +98,8 @@ uint32_t* write_output(uint32_t v)
void write_completed(uint32_t completed)
{
}
+
+bool kcov_comparison_t::ignore() const
+{
+ return false;
+}
diff --git a/executor/executor_freebsd.cc b/executor/executor_freebsd.cc
index b0d5acdec..925bb3814 100644
--- a/executor/executor_freebsd.cc
+++ b/executor/executor_freebsd.cc
@@ -166,3 +166,8 @@ void write_completed(uint32_t completed)
{
__atomic_store_n(output_data, completed, __ATOMIC_RELEASE);
}
+
+bool kcov_comparison_t::ignore() const
+{
+ return false;
+}
diff --git a/executor/executor_fuchsia.cc b/executor/executor_fuchsia.cc
index a93811928..05ded7857 100644
--- a/executor/executor_fuchsia.cc
+++ b/executor/executor_fuchsia.cc
@@ -61,3 +61,8 @@ uint32_t* write_output(uint32_t v)
void write_completed(uint32_t completed)
{
}
+
+bool kcov_comparison_t::ignore() const
+{
+ return false;
+}
diff --git a/executor/executor_linux.cc b/executor/executor_linux.cc
index d49e7e22b..77316fa12 100644
--- a/executor/executor_linux.cc
+++ b/executor/executor_linux.cc
@@ -34,8 +34,8 @@ const unsigned long KCOV_TRACE_CMP = 1;
const int kInFd = 3;
const int kOutFd = 4;
-const int kCoverSize = 64 << 10;
-const int kPageSize = 4 << 10;
+
+void* const kOutputDataAddr = (void*)0x1bdbc20000ull;
uint32_t* output_data;
uint32_t* output_pos;
@@ -54,7 +54,6 @@ int main(int argc, char** argv)
// If it is corrupted ipc package will fail to parse its contents and panic.
// But fuzzer constantly invents new ways of how to currupt the region,
// so we map the region at a (hopefully) hard to guess address surrounded by unmapped pages.
- void* const kOutputDataAddr = (void*)0x1ddbc20000;
output_data = (uint32_t*)mmap(kOutputDataAddr, kMaxOutput, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED, kOutFd, 0);
if (output_data != kOutputDataAddr)
fail("mmap of output file failed");
@@ -292,3 +291,37 @@ void write_completed(uint32_t completed)
{
__atomic_store_n(output_data, completed, __ATOMIC_RELEASE);
}
+
+bool kcov_comparison_t::ignore() const
+{
+ // Comparisons with 0 are not interesting, fuzzer should be able to guess 0's without help.
+ if (arg1 == 0 && (arg2 == 0 || (type & KCOV_CMP_CONST)))
+ return true;
+ if ((type & KCOV_CMP_SIZE_MASK) == KCOV_CMP_SIZE8) {
+ // This can be a pointer (assuming 64-bit kernel).
+ // First of all, we want avert fuzzer from our output region.
+ // Without this fuzzer manages to discover and corrupt it.
+ uint64_t out_start = (uint64_t)kOutputDataAddr;
+ uint64_t out_end = out_start + kMaxOutput;
+ if (arg1 >= out_start && arg1 <= out_end)
+ return true;
+ if (arg2 >= out_start && arg2 <= out_end)
+ return true;
+#if defined(__i386__) || defined(__x86_64__)
+ // Filter out kernel physical memory addresses.
+ // These are internal kernel comparisons and should not be interesting.
+ // The range covers first 1TB of physical mapping.
+ uint64_t kmem_start = (uint64_t)0xffff880000000000ull;
+ uint64_t kmem_end = (uint64_t)0xffff890000000000ull;
+ bool kptr1 = arg1 >= kmem_start && arg1 <= kmem_end;
+ bool kptr2 = arg2 >= kmem_start && arg2 <= kmem_end;
+ if (kptr1 && kptr2)
+ return true;
+ if (kptr1 && arg2 == 0)
+ return true;
+ if (kptr2 && arg1 == 0)
+ return true;
+#endif
+ }
+ return false;
+}
diff --git a/executor/executor_windows.cc b/executor/executor_windows.cc
index 04f6fe6aa..90091c833 100644
--- a/executor/executor_windows.cc
+++ b/executor/executor_windows.cc
@@ -63,3 +63,8 @@ uint32_t* write_output(uint32_t v)
void write_completed(uint32_t completed)
{
}
+
+bool kcov_comparison_t::ignore() const
+{
+ return false;
+}