diff options
| -rw-r--r-- | executor/cov_filter.h | 53 | ||||
| -rw-r--r-- | executor/cover_filter.h | 137 | ||||
| -rw-r--r-- | executor/executor.cc | 51 | ||||
| -rw-r--r-- | executor/shmem.h | 75 | ||||
| -rw-r--r-- | executor/test.h | 97 | ||||
| -rw-r--r-- | pkg/flatrpc/flatrpc.fbs | 3 | ||||
| -rw-r--r-- | pkg/flatrpc/flatrpc.go | 36 | ||||
| -rw-r--r-- | pkg/flatrpc/flatrpc.h | 59 | ||||
| -rw-r--r-- | pkg/ipc/ipc.go | 29 | ||||
| -rw-r--r-- | syz-fuzzer/fuzzer.go | 7 | ||||
| -rw-r--r-- | syz-manager/covfilter.go | 38 | ||||
| -rw-r--r-- | syz-manager/covfilter_test.go | 72 | ||||
| -rw-r--r-- | syz-manager/rpc.go | 6 |
13 files changed, 393 insertions, 270 deletions
diff --git a/executor/cov_filter.h b/executor/cov_filter.h deleted file mode 100644 index 1119a837a..000000000 --- a/executor/cov_filter.h +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright 2020 syzkaller project authors. All rights reserved. -// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file. - -#include <fcntl.h> -#include <sys/mman.h> -#include <sys/stat.h> - -struct cov_filter_t { - uint64 pcstart; - uint64 pcsize; - uint8 bitmap[]; -}; - -static cov_filter_t* cov_filter; - -static void init_coverage_filter(char* filename) -{ - int f = open(filename, O_RDONLY); - if (f < 0) { - // We don't fail here because we don't know yet if we should use coverage filter or not. - // We will receive the flag only in execute flags and will fail in coverage_filter if necessary. - debug("bitmap is not found, coverage filter disabled\n"); - return; - } - struct stat st; - if (fstat(f, &st)) - fail("faied to stat coverage filter"); - // A random address for bitmap. Don't corrupt output_data. - void* preferred = (void*)0x110f230000ull; - cov_filter = (cov_filter_t*)mmap(preferred, st.st_size, PROT_READ, MAP_PRIVATE, f, 0); - if (cov_filter != preferred) - failmsg("failed to mmap coverage filter bitmap", "want=%p, got=%p", preferred, cov_filter); - if ((uint32)st.st_size != sizeof(uint64) * 2 + ((cov_filter->pcsize >> 4) / 8 + 2)) - fail("bad coverage filter bitmap size"); - close(f); -} - -static bool coverage_filter(uint64 pc) -{ - if (!flag_coverage_filter) - return true; - if (cov_filter == NULL) - fail("coverage filter was enabled but bitmap initialization failed"); - // Prevent out of bound while searching bitmap. - if (pc < cov_filter->pcstart || pc > cov_filter->pcstart + cov_filter->pcsize) - return false; - // For minimizing the size of bitmap, the lowest 4-bit will be dropped. - pc -= cov_filter->pcstart; - pc = pc >> 4; - uint64 idx = pc / 8; - uint64 shift = pc % 8; - return (cov_filter->bitmap[idx] & (1 << shift)) > 0; -} diff --git a/executor/cover_filter.h b/executor/cover_filter.h new file mode 100644 index 000000000..672e9fbec --- /dev/null +++ b/executor/cover_filter.h @@ -0,0 +1,137 @@ +// Copyright 2024 syzkaller project authors. All rights reserved. +// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file. + +// CoverFilter is PC hash set that can be placed in shared memory. +// +// The set can cover up to 4 distinct 1GB regions of PCs. +// This restriction allows for efficient, simple and shared memory compatible representation, +// but should be enough to cover any reasonable combination of kernel/modules mapping. +// +// Low 3 bits of PCs are discarded. This reduces memory consumption 8x, but allows for some false positives. +// However, in practice false positives should be very rare. A typical coverage call instruction is 4/5 bytes, +// and there must be at least 1 other instruction in between them to make them different basic blocks, +// so it's practically impossible to place 2 of them in the same 8-byte region. +// For signal with hashed low 12 bits the probability is also low b/c overall density of coverage callbacks +// is relatively low, a KASAN Linux kernel contains 1 callback per 88 bytes of code on average. +// So even if we discard low 3 bits, average densitiy is still 1/11. +// For gVisor with dense coverage IDs special care must be taken to avoid collisions. +// +// The set is organized as a 3 level table. +// The top "region" level is linear lookup, but contains at most 4 entries, each covering 1GB. +// Most likely the first entry is the right one. This level allows to cover unconnected regions of PCs. +// The next "L1" level splits 1GB chunks into 1MB chunks, and allows to allocate memory only +// for a subset of these 1MB chunks. +// The last "L2" level covers 1MB chunks with 16KB bitmaps (1MB divided by 8 for 3 discarded PC bits, +// and divided by 8 again for 8 bits in a byte). +class CoverFilter +{ +public: + CoverFilter(const char* file, void* preferred = nullptr) + : shmem_(file, preferred, kMemSize), tab_(static_cast<Table*>(shmem_.Mem())) + { + } + + CoverFilter(int fd, void* preferred = nullptr) + : shmem_(fd, preferred, kMemSize, false), tab_(static_cast<Table*>(shmem_.Mem())) + { + } + + void Insert(uint64 pc) + { + auto [byte, bit] = FindByte(pc, true); + byte |= bit; + } + + void Remove(uint64 pc) + { + auto [byte, bit] = FindByte(pc, true); + byte &= ~bit; + } + + bool Contains(uint64 pc) + { + auto [byte, bit] = FindByte(pc, false); + return byte & bit; + } + + // Prevents any future modifications to the filter. + void Seal() + { + shmem_.Seal(); + } + + int FD() const + { + return shmem_.FD(); + } + +private: + static constexpr size_t kNumRegions = 4; + static constexpr size_t kL1Size = 1 << 30; + static constexpr size_t kL2Size = 1 << 20; + static constexpr size_t kPCDivider = 8; + static constexpr size_t kByteBits = 8; + // Approximately how much .text we can cover (2GB of PCs require 32MB shmem region). + static constexpr size_t kMaxCovered = 2ull << 30; + static constexpr size_t kCompression = kPCDivider * kByteBits; + static constexpr size_t kMemSize = kMaxCovered / kCompression; + static constexpr size_t kNoRegion = static_cast<size_t>(-1); + + struct Table { + uint64 regions[kNumRegions]; + uint16 l1[kNumRegions][kL1Size / kL2Size]; + uint8 l2[][kL2Size / kCompression]; + }; + + ShmemFile shmem_; + Table* tab_ = nullptr; + uint16 alloc_ = 0; + + std::pair<uint8&, uint8> FindByte(uint64 pc, bool add = false) + { + static const uint8 empty = 0; + size_t reg = FindRegion(pc, add); + if (reg == kNoRegion) + return {const_cast<uint8&>(empty), 0}; + size_t l1 = (pc % kL1Size) / kL2Size; + size_t l2 = tab_->l1[reg][l1]; + if (l2 == 0) { + if (!add) + return {const_cast<uint8&>(empty), 0}; + l2 = ++alloc_; + tab_->l1[reg][l1] = l2; + if ((tab_->l2[l2 - 1] + 1) > reinterpret_cast<uint8*>(tab_) + kMemSize) + Overflow(pc); + } + size_t off = (pc % kL2Size) / kCompression; + size_t shift = (pc / kPCDivider) % kByteBits; + return {tab_->l2[l2 - 1][off], 1 << shift}; + } + + size_t FindRegion(uint64 pc, bool add = false) + { + const uint64 reg = pc | (kL1Size - 1); + for (size_t r = 0; r < kNumRegions; r++) { + if (tab_->regions[r] == reg) + return r; + } + if (!add) + return kNoRegion; + for (size_t r = 0; r < kNumRegions; r++) { + if (tab_->regions[r] == 0) { + tab_->regions[r] = reg; + return r; + } + } + Overflow(pc); + } + + NORETURN void Overflow(uint64 pc) + { + failmsg("coverage filter is full", "pc=0x%llx regions=[0x%llx 0x%llx 0x%llx 0x%llx] alloc=%u", + pc, tab_->regions[0], tab_->regions[1], tab_->regions[2], tab_->regions[3], alloc_); + } + + CoverFilter(const CoverFilter&) = delete; + CoverFilter& operator=(const CoverFilter&) = delete; +}; diff --git a/executor/executor.cc b/executor/executor.cc index cb2245c69..ca728a6aa 100644 --- a/executor/executor.cc +++ b/executor/executor.cc @@ -177,7 +177,6 @@ static bool flag_collect_cover; static bool flag_collect_signal; static bool flag_dedup_cover; static bool flag_threaded; -static bool flag_coverage_filter; // If true, then executor should write the comparisons data to fuzzer. static bool flag_comparisons; @@ -302,6 +301,8 @@ struct handshake_req { uint64 flags; // env flags uint64 pid; uint64 sandbox_arg; + uint64 cover_filter_size; + // Followed by uint64[cover_filter_size] filter. }; struct handshake_reply { @@ -390,6 +391,7 @@ static void copyin(char* addr, uint64 val, uint64 size, uint64 bf, uint64 bf_off static bool copyout(char* addr, uint64 size, uint64* res); static void setup_control_pipes(); static void setup_features(char** enable, int n); +static bool coverage_filter(uint64 pc); #include "syscalls.h" @@ -413,10 +415,14 @@ static void setup_features(char** enable, int n); static feature_t features[] = {}; #endif -#include "cov_filter.h" +#include "shmem.h" + +#include "cover_filter.h" #include "test.h" +static std::optional<CoverFilter> cover_filter; + #if SYZ_HAVE_SANDBOX_ANDROID static uint64 sandbox_arg = 0; #endif @@ -509,17 +515,6 @@ int main(int argc, char** argv) // Don't enable comps because we don't use them in the fuzzer yet. cover_enable(&extra_cov, false, true); } - char sep = '/'; -#if GOOS_windows - sep = '\\'; -#endif - char filename[1024] = {0}; - char* end = strrchr(argv[0], sep); - size_t len = end - argv[0]; - strncpy(filename, argv[0], len + 1); - strncat(filename, "syz-cover-bitmap", 17); - filename[sizeof(filename) - 1] = '\0'; - init_coverage_filter(filename); } int status = 0; @@ -644,9 +639,9 @@ void parse_env_flags(uint64 flags) void receive_handshake() { handshake_req req = {}; - int n = read(kInPipeFd, &req, sizeof(req)); + ssize_t n = read(kInPipeFd, &req, sizeof(req)); if (n != sizeof(req)) - failmsg("handshake read failed", "read=%d", n); + failmsg("handshake read failed", "read=%zu", n); if (req.magic != kInMagic) failmsg("bad handshake magic", "magic=0x%llx", req.magic); #if SYZ_HAVE_SANDBOX_ANDROID @@ -654,6 +649,18 @@ void receive_handshake() #endif parse_env_flags(req.flags); procid = req.pid; + if (!req.cover_filter_size) + return; + // A random address for bitmap. Don't corrupt output_data. + cover_filter.emplace("syz-cover-filer", reinterpret_cast<void*>(0x110f230000ull)); + std::vector<uint64> pcs(req.cover_filter_size); + const ssize_t filter_size = req.cover_filter_size * sizeof(uint64); + n = read(kInPipeFd, &pcs[0], filter_size); + if (n != filter_size) + failmsg("failed to read cover filter", "read=%zu", n); + for (auto pc : pcs) + cover_filter->Insert(pc); + cover_filter->Seal(); } void reply_handshake() @@ -684,13 +691,12 @@ void receive_execute() flag_dedup_cover = req.exec_flags & (1 << 2); flag_comparisons = req.exec_flags & (1 << 3); flag_threaded = req.exec_flags & (1 << 4); - flag_coverage_filter = req.exec_flags & (1 << 5); - debug("[%llums] exec opts: procid=%llu threaded=%d cover=%d comps=%d dedup=%d signal=%d" - " timeouts=%llu/%llu/%llu filter=%d\n", + debug("[%llums] exec opts: procid=%llu threaded=%d cover=%d comps=%d dedup=%d signal=%d " + " timeouts=%llu/%llu/%llu\n", current_time_ms() - start_time_ms, procid, flag_threaded, flag_collect_cover, flag_comparisons, flag_dedup_cover, flag_collect_signal, syscall_timeout_ms, - program_timeout_ms, slowdown_scale, flag_coverage_filter); + program_timeout_ms, slowdown_scale); if (syscall_timeout_ms == 0 || program_timeout_ms <= syscall_timeout_ms || slowdown_scale == 0) failmsg("bad timeouts", "syscall=%llu, program=%llu, scale=%llu", syscall_timeout_ms, program_timeout_ms, slowdown_scale); @@ -1036,6 +1042,13 @@ void write_coverage_signal(cover_t* cov, uint32* signal_count_pos, uint32* cover } } +bool coverage_filter(uint64 pc) +{ + if (!cover_filter) + return true; + return cover_filter->Contains(pc); +} + void handle_completion(thread_t* th) { if (event_isset(&th->ready) || !event_isset(&th->done) || !th->executing) diff --git a/executor/shmem.h b/executor/shmem.h new file mode 100644 index 000000000..ab9d17300 --- /dev/null +++ b/executor/shmem.h @@ -0,0 +1,75 @@ +// Copyright 2024 syzkaller project authors. All rights reserved. +// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file. + +#include <fcntl.h> +#include <stddef.h> +#include <sys/mman.h> +#include <unistd.h> + +// ShmemFile is shared memory region wrapper. +class ShmemFile +{ +public: + // Maps shared memory region of size 'size' from a new file 'file', preferably at the address 'preferred'. + ShmemFile(const char* file, void* preferred, size_t size) + { + fd_ = open(file, O_RDWR | O_CREAT | O_TRUNC, 0600); + if (fd_ == -1) + failmsg("shmem open failed", "file=%s", file); + if (fallocate(fd_, 0, 0, size)) + failmsg("shmem fallocate failed", "size=%zu", size); + Mmap(fd_, preferred, size, true); + if (unlink(file)) + fail("shmem unlink failed"); + } + + // Maps shared memory region from the file 'fd' in read/write or write-only mode. + ShmemFile(int fd, void* preferred, size_t size, bool write) + { + Mmap(fd, preferred, size, write); + } + + ~ShmemFile() + { + if (munmap(mem_, size_)) + fail("shmem munmap failed"); + if (fd_ != -1) + close(fd_); + } + + // Prevents any future modifications to the region. + void Seal() + { + if (mprotect(mem_, size_, PROT_READ)) + fail("shmem mprotect failed"); + if (fd_ != -1) + close(fd_); + fd_ = -1; + } + + int FD() const + { + return fd_; + } + + void* Mem() const + { + return mem_; + } + +private: + void* mem_ = nullptr; + size_t size_ = 0; + int fd_ = -1; + + void Mmap(int fd, void* preferred, size_t size, bool write) + { + size_ = size; + mem_ = mmap(preferred, size, PROT_READ | (write ? PROT_WRITE : 0), MAP_SHARED, fd, 0); + if (mem_ == MAP_FAILED) + failmsg("shmem mmap failed", "size=%zu", size); + } + + ShmemFile(const ShmemFile&) = delete; + ShmemFile& operator=(const ShmemFile&) = delete; +}; diff --git a/executor/test.h b/executor/test.h index bb1291f97..c49459033 100644 --- a/executor/test.h +++ b/executor/test.h @@ -201,37 +201,82 @@ static int test_csum_inet_acc() return 0; } -static int test_coverage_filter() +static int test_cover_filter() { - struct tmp_cov_filter_t { - uint64 pcstart; - uint64 pcsize; - uint8 bitmap[((0x1000 >> 4) + 7) / 8]; + char* tmp = tempnam(nullptr, "syz-test-cover-filter"); + CoverFilter filter(tmp); + CoverFilter child(filter.FD()); + free(tmp); + + std::vector<uint64> pcs = { + 100, + 111, + 200, + (1 << 20) - 1, + 1 << 20, + (1 << 30) - 1, + 100ull << 30, + (100ull << 30) + 100, + 200ull << 30, + (1ull << 62) + 100, }; - static struct tmp_cov_filter_t tmp_cov_filter; - tmp_cov_filter.pcstart = 0xffffffff81000000; - tmp_cov_filter.pcsize = 0x1000; - cov_filter = (cov_filter_t*)&tmp_cov_filter; - flag_coverage_filter = true; - uint64 enable_pc = 0xffffffff81000765; - uint64 disable_pc = 0xffffffff81000627; - uint64 out_pc = 0xffffffff82000000; + // These we don't insert, but they are also present due to truncation of low 3 bits. + std::vector<uint64> also_contain = { + 96, + 103, + 104, + 207, + (1 << 20) - 7, + (1 << 20) + 7, + (1ull << 62) + 96, + (1ull << 62) + 103, + }; - uint32 idx = ((enable_pc - cov_filter->pcstart) >> 4) / 8; - uint32 shift = ((enable_pc - cov_filter->pcstart) >> 4) % 8; - cov_filter->bitmap[idx] |= (1 << shift); + std::vector<uint64> dont_contain = { + 0, + 1, + 95, + 112, + 199, + 208, + 100 << 10, + (1 << 20) - 9, + (1 << 20) + 8, + (2ull << 30) - 1, + 2ull << 30, + (2ull << 30) + 1, + (100ull << 30) + 108, + 150ull << 30, + 1ull << 40, + 1ull << 63, + ~0ull, + }; - if (!coverage_filter(enable_pc)) - return 1; - if (coverage_filter(disable_pc)) - return 1; - if (coverage_filter(out_pc)) - return 1; + int ret = 0; + for (auto pc : pcs) + filter.Insert(pc); + pcs.insert(pcs.end(), also_contain.begin(), also_contain.end()); + for (auto pc : pcs) { + if (!filter.Contains(pc) || !child.Contains(pc)) { + printf("filter doesn't contain %llu (0x%llx)\n", pc, pc); + ret = 1; + } + } + for (auto pc : dont_contain) { + if (filter.Contains(pc) || child.Contains(pc)) { + printf("filter contains %llu (0x%llx)\n", pc, pc); + ret = 1; + } + } - cov_filter = NULL; - flag_coverage_filter = false; - return 0; + filter.Remove(105); + if (filter.Contains(104) || filter.Contains(105) || filter.Contains(111)) + printf("filter contains 105 after removal\n"); + if (!filter.Contains(103)) + printf("filter doesn't contains 103 after 105 removal\n"); + + return ret; } static struct { @@ -244,7 +289,7 @@ static struct { #if GOOS_linux && (GOARCH_amd64 || GOARCH_ppc64 || GOARCH_ppc64le) {"test_kvm", test_kvm}, #endif - {"test_coverage_filter", test_coverage_filter}, + {"test_cover_filter", test_cover_filter}, }; static int run_tests(const char* test) diff --git a/pkg/flatrpc/flatrpc.fbs b/pkg/flatrpc/flatrpc.fbs index 7800d6779..78adc8ec5 100644 --- a/pkg/flatrpc/flatrpc.fbs +++ b/pkg/flatrpc/flatrpc.fbs @@ -54,7 +54,7 @@ table InfoRequestRaw { } table InfoReplyRaw { - cover_filter :[uint8]; + cover_filter :[uint64]; } table FileInfoRaw { @@ -134,7 +134,6 @@ enum ExecFlag : uint64 (bit_flags) { DedupCover, // deduplicate coverage in executor CollectComps, // collect KCOV comparisons Threaded, // use multiple threads to mitigate blocked syscalls - CoverFilter, // setup and use bitmap to do coverage filter } struct ExecOptsRaw { diff --git a/pkg/flatrpc/flatrpc.go b/pkg/flatrpc/flatrpc.go index 9fbd2d0ae..b561334fe 100644 --- a/pkg/flatrpc/flatrpc.go +++ b/pkg/flatrpc/flatrpc.go @@ -312,7 +312,6 @@ const ( ExecFlagDedupCover ExecFlag = 4 ExecFlagCollectComps ExecFlag = 8 ExecFlagThreaded ExecFlag = 16 - ExecFlagCoverFilter ExecFlag = 32 ) var EnumNamesExecFlag = map[ExecFlag]string{ @@ -321,7 +320,6 @@ var EnumNamesExecFlag = map[ExecFlag]string{ ExecFlagDedupCover: "DedupCover", ExecFlagCollectComps: "CollectComps", ExecFlagThreaded: "Threaded", - ExecFlagCoverFilter: "CoverFilter", } var EnumValuesExecFlag = map[string]ExecFlag{ @@ -330,7 +328,6 @@ var EnumValuesExecFlag = map[string]ExecFlag{ "DedupCover": ExecFlagDedupCover, "CollectComps": ExecFlagCollectComps, "Threaded": ExecFlagThreaded, - "CoverFilter": ExecFlagCoverFilter, } func (v ExecFlag) String() string { @@ -1005,7 +1002,7 @@ func InfoRequestRawEnd(builder *flatbuffers.Builder) flatbuffers.UOffsetT { } type InfoReplyRawT struct { - CoverFilter []byte `json:"cover_filter"` + CoverFilter []uint64 `json:"cover_filter"` } func (t *InfoReplyRawT) Pack(builder *flatbuffers.Builder) flatbuffers.UOffsetT { @@ -1014,7 +1011,12 @@ func (t *InfoReplyRawT) Pack(builder *flatbuffers.Builder) flatbuffers.UOffsetT } coverFilterOffset := flatbuffers.UOffsetT(0) if t.CoverFilter != nil { - coverFilterOffset = builder.CreateByteString(t.CoverFilter) + coverFilterLength := len(t.CoverFilter) + InfoReplyRawStartCoverFilterVector(builder, coverFilterLength) + for j := coverFilterLength - 1; j >= 0; j-- { + builder.PrependUint64(t.CoverFilter[j]) + } + coverFilterOffset = builder.EndVector(coverFilterLength) } InfoReplyRawStart(builder) InfoReplyRawAddCoverFilter(builder, coverFilterOffset) @@ -1022,7 +1024,11 @@ func (t *InfoReplyRawT) Pack(builder *flatbuffers.Builder) flatbuffers.UOffsetT } func (rcv *InfoReplyRaw) UnPackTo(t *InfoReplyRawT) { - t.CoverFilter = rcv.CoverFilterBytes() + coverFilterLength := rcv.CoverFilterLength() + t.CoverFilter = make([]uint64, coverFilterLength) + for j := 0; j < coverFilterLength; j++ { + t.CoverFilter[j] = rcv.CoverFilter(j) + } } func (rcv *InfoReplyRaw) UnPack() *InfoReplyRawT { @@ -1061,11 +1067,11 @@ func (rcv *InfoReplyRaw) Table() flatbuffers.Table { return rcv._tab } -func (rcv *InfoReplyRaw) CoverFilter(j int) byte { +func (rcv *InfoReplyRaw) CoverFilter(j int) uint64 { o := flatbuffers.UOffsetT(rcv._tab.Offset(4)) if o != 0 { a := rcv._tab.Vector(o) - return rcv._tab.GetByte(a + flatbuffers.UOffsetT(j*1)) + return rcv._tab.GetUint64(a + flatbuffers.UOffsetT(j*8)) } return 0 } @@ -1078,19 +1084,11 @@ func (rcv *InfoReplyRaw) CoverFilterLength() int { return 0 } -func (rcv *InfoReplyRaw) CoverFilterBytes() []byte { - o := flatbuffers.UOffsetT(rcv._tab.Offset(4)) - if o != 0 { - return rcv._tab.ByteVector(o + rcv._tab.Pos) - } - return nil -} - -func (rcv *InfoReplyRaw) MutateCoverFilter(j int, n byte) bool { +func (rcv *InfoReplyRaw) MutateCoverFilter(j int, n uint64) bool { o := flatbuffers.UOffsetT(rcv._tab.Offset(4)) if o != 0 { a := rcv._tab.Vector(o) - return rcv._tab.MutateByte(a+flatbuffers.UOffsetT(j*1), n) + return rcv._tab.MutateUint64(a+flatbuffers.UOffsetT(j*8), n) } return false } @@ -1102,7 +1100,7 @@ func InfoReplyRawAddCoverFilter(builder *flatbuffers.Builder, coverFilter flatbu builder.PrependUOffsetTSlot(0, flatbuffers.UOffsetT(coverFilter), 0) } func InfoReplyRawStartCoverFilterVector(builder *flatbuffers.Builder, numElems int) flatbuffers.UOffsetT { - return builder.StartVector(1, numElems, 1) + return builder.StartVector(8, numElems, 8) } func InfoReplyRawEnd(builder *flatbuffers.Builder) flatbuffers.UOffsetT { return builder.EndObject() diff --git a/pkg/flatrpc/flatrpc.h b/pkg/flatrpc/flatrpc.h index aa02046b1..d430e48f2 100644 --- a/pkg/flatrpc/flatrpc.h +++ b/pkg/flatrpc/flatrpc.h @@ -509,34 +509,49 @@ enum class ExecFlag : uint64_t { DedupCover = 4ULL, CollectComps = 8ULL, Threaded = 16ULL, - CoverFilter = 32ULL, NONE = 0, - ANY = 63ULL + ANY = 31ULL }; FLATBUFFERS_DEFINE_BITMASK_OPERATORS(ExecFlag, uint64_t) -inline const ExecFlag (&EnumValuesExecFlag())[6] { +inline const ExecFlag (&EnumValuesExecFlag())[5] { static const ExecFlag values[] = { ExecFlag::CollectSignal, ExecFlag::CollectCover, ExecFlag::DedupCover, ExecFlag::CollectComps, - ExecFlag::Threaded, - ExecFlag::CoverFilter + ExecFlag::Threaded }; return values; } +inline const char * const *EnumNamesExecFlag() { + static const char * const names[17] = { + "CollectSignal", + "CollectCover", + "", + "DedupCover", + "", + "", + "", + "CollectComps", + "", + "", + "", + "", + "", + "", + "", + "Threaded", + nullptr + }; + return names; +} + inline const char *EnumNameExecFlag(ExecFlag e) { - switch (e) { - case ExecFlag::CollectSignal: return "CollectSignal"; - case ExecFlag::CollectCover: return "CollectCover"; - case ExecFlag::DedupCover: return "DedupCover"; - case ExecFlag::CollectComps: return "CollectComps"; - case ExecFlag::Threaded: return "Threaded"; - case ExecFlag::CoverFilter: return "CoverFilter"; - default: return ""; - } + if (flatbuffers::IsOutRange(e, ExecFlag::CollectSignal, ExecFlag::Threaded)) return ""; + const size_t index = static_cast<size_t>(e) - static_cast<size_t>(ExecFlag::CollectSignal); + return EnumNamesExecFlag()[index]; } enum class CallFlag : uint8_t { @@ -1018,7 +1033,7 @@ flatbuffers::Offset<InfoRequestRaw> CreateInfoRequestRaw(flatbuffers::FlatBuffer struct InfoReplyRawT : public flatbuffers::NativeTable { typedef InfoReplyRaw TableType; - std::vector<uint8_t> cover_filter{}; + std::vector<uint64_t> cover_filter{}; }; struct InfoReplyRaw FLATBUFFERS_FINAL_CLASS : private flatbuffers::Table { @@ -1027,8 +1042,8 @@ struct InfoReplyRaw FLATBUFFERS_FINAL_CLASS : private flatbuffers::Table { enum FlatBuffersVTableOffset FLATBUFFERS_VTABLE_UNDERLYING_TYPE { VT_COVER_FILTER = 4 }; - const flatbuffers::Vector<uint8_t> *cover_filter() const { - return GetPointer<const flatbuffers::Vector<uint8_t> *>(VT_COVER_FILTER); + const flatbuffers::Vector<uint64_t> *cover_filter() const { + return GetPointer<const flatbuffers::Vector<uint64_t> *>(VT_COVER_FILTER); } bool Verify(flatbuffers::Verifier &verifier) const { return VerifyTableStart(verifier) && @@ -1045,7 +1060,7 @@ struct InfoReplyRawBuilder { typedef InfoReplyRaw Table; flatbuffers::FlatBufferBuilder &fbb_; flatbuffers::uoffset_t start_; - void add_cover_filter(flatbuffers::Offset<flatbuffers::Vector<uint8_t>> cover_filter) { + void add_cover_filter(flatbuffers::Offset<flatbuffers::Vector<uint64_t>> cover_filter) { fbb_.AddOffset(InfoReplyRaw::VT_COVER_FILTER, cover_filter); } explicit InfoReplyRawBuilder(flatbuffers::FlatBufferBuilder &_fbb) @@ -1061,7 +1076,7 @@ struct InfoReplyRawBuilder { inline flatbuffers::Offset<InfoReplyRaw> CreateInfoReplyRaw( flatbuffers::FlatBufferBuilder &_fbb, - flatbuffers::Offset<flatbuffers::Vector<uint8_t>> cover_filter = 0) { + flatbuffers::Offset<flatbuffers::Vector<uint64_t>> cover_filter = 0) { InfoReplyRawBuilder builder_(_fbb); builder_.add_cover_filter(cover_filter); return builder_.Finish(); @@ -1069,8 +1084,8 @@ inline flatbuffers::Offset<InfoReplyRaw> CreateInfoReplyRaw( inline flatbuffers::Offset<InfoReplyRaw> CreateInfoReplyRawDirect( flatbuffers::FlatBufferBuilder &_fbb, - const std::vector<uint8_t> *cover_filter = nullptr) { - auto cover_filter__ = cover_filter ? _fbb.CreateVector<uint8_t>(*cover_filter) : 0; + const std::vector<uint64_t> *cover_filter = nullptr) { + auto cover_filter__ = cover_filter ? _fbb.CreateVector<uint64_t>(*cover_filter) : 0; return rpc::CreateInfoReplyRaw( _fbb, cover_filter__); @@ -2351,7 +2366,7 @@ inline InfoReplyRawT *InfoReplyRaw::UnPack(const flatbuffers::resolver_function_ inline void InfoReplyRaw::UnPackTo(InfoReplyRawT *_o, const flatbuffers::resolver_function_t *_resolver) const { (void)_o; (void)_resolver; - { auto _e = cover_filter(); if (_e) { _o->cover_filter.resize(_e->size()); std::copy(_e->begin(), _e->end(), _o->cover_filter.begin()); } } + { auto _e = cover_filter(); if (_e) { _o->cover_filter.resize(_e->size()); for (flatbuffers::uoffset_t _i = 0; _i < _e->size(); _i++) { _o->cover_filter[_i] = _e->Get(_i); } } } } inline flatbuffers::Offset<InfoReplyRaw> InfoReplyRaw::Pack(flatbuffers::FlatBufferBuilder &_fbb, const InfoReplyRawT* _o, const flatbuffers::rehasher_function_t *_rehasher) { diff --git a/pkg/ipc/ipc.go b/pkg/ipc/ipc.go index 103ae5c37..c09137e3b 100644 --- a/pkg/ipc/ipc.go +++ b/pkg/ipc/ipc.go @@ -33,6 +33,8 @@ type Config struct { RateLimit bool // rate limit start of new processes for host fuzzer mode Timeouts targets.Timeouts + + CoverFilter []uint64 } type Env struct { @@ -495,10 +497,12 @@ const ( ) type handshakeReq struct { - magic uint64 - flags uint64 // env flags - pid uint64 - sandboxArg uint64 + magic uint64 + flags uint64 // env flags + pid uint64 + sandboxArg uint64 + coverFilterSize uint64 + // Followed by [coverFilterSize]uint64 filter. } type handshakeReply struct { @@ -676,15 +680,24 @@ func (c *command) close() { // handshake sends handshakeReq and waits for handshakeReply. func (c *command) handshake() error { req := &handshakeReq{ - magic: inMagic, - flags: uint64(c.flags), - pid: uint64(c.pid), - sandboxArg: uint64(c.sandboxArg), + magic: inMagic, + flags: uint64(c.flags), + pid: uint64(c.pid), + sandboxArg: uint64(c.sandboxArg), + coverFilterSize: uint64(len(c.config.CoverFilter)), } reqData := (*[unsafe.Sizeof(*req)]byte)(unsafe.Pointer(req))[:] if _, err := c.outwp.Write(reqData); err != nil { return c.handshakeError(fmt.Errorf("failed to write control pipe: %w", err)) } + if req.coverFilterSize != 0 { + ptr := (*byte)(unsafe.Pointer(&c.config.CoverFilter[0])) + size := uintptr(req.coverFilterSize) * unsafe.Sizeof(c.config.CoverFilter[0]) + coverFilter := unsafe.Slice(ptr, size) + if _, err := c.outwp.Write(coverFilter); err != nil { + return c.handshakeError(fmt.Errorf("failed to write control pipe: %w", err)) + } + } read := make(chan error, 1) go func() { diff --git a/syz-fuzzer/fuzzer.go b/syz-fuzzer/fuzzer.go index 8527330e0..93259cc03 100644 --- a/syz-fuzzer/fuzzer.go +++ b/syz-fuzzer/fuzzer.go @@ -135,12 +135,7 @@ func main() { log.SyzFatal(err) } infoReply := infoReplyRaw.UnPack() - - if len(infoReply.CoverFilter) != 0 { - if err := osutil.WriteFile("syz-cover-bitmap", infoReply.CoverFilter); err != nil { - log.SyzFatalf("failed to write syz-cover-bitmap: %v", err) - } - } + config.CoverFilter = infoReply.CoverFilter fuzzerTool := &FuzzerTool{ conn: conn, diff --git a/syz-manager/covfilter.go b/syz-manager/covfilter.go index ee2eb715f..8b7d54cd0 100644 --- a/syz-manager/covfilter.go +++ b/syz-manager/covfilter.go @@ -128,44 +128,6 @@ func covFilterAddRawPCs(pcs map[uint64]struct{}, rawPCsFiles []string) error { return nil } -func createCoverageBitmap(cfg *mgrconfig.Config, pcs []uint64) []byte { - // Return nil if filtering is not used. - if len(pcs) == 0 { - return nil - } - start, size := coverageFilterRegion(pcs) - log.Logf(2, "coverage filter from 0x%x to 0x%x, size 0x%x, pcs %v", start, start+size, size, len(pcs)) - // The file starts with two uint64: covFilterStart and covFilterSize, - // and a bitmap with size ((covFilterSize>>4)/8+2 bytes follow them. - // 8-bit = 1-byte - data := make([]byte, 16+((size>>4)/8+2)) - order := cfg.SysTarget.HostEndian - order.PutUint64(data, start) - order.PutUint64(data[8:], size) - - bitmap := data[16:] - for _, pc := range pcs { - // The lowest 4-bit is dropped. - pc = backend.NextInstructionPC(cfg.SysTarget, cfg.Type, pc) - pc = (pc - start) >> 4 - bitmap[pc/8] |= (1 << (pc % 8)) - } - return data -} - -func coverageFilterRegion(pcs []uint64) (uint64, uint64) { - start, end := ^uint64(0), uint64(0) - for _, pc := range pcs { - if start > pc { - start = pc - } - if end < pc { - end = pc - } - } - return start, end - start -} - func compileRegexps(regexpStrings []string) ([]*regexp.Regexp, error) { var regexps []*regexp.Regexp for _, rs := range regexpStrings { diff --git a/syz-manager/covfilter_test.go b/syz-manager/covfilter_test.go deleted file mode 100644 index 78d61edd0..000000000 --- a/syz-manager/covfilter_test.go +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright 2020 syzkaller project authors. All rights reserved. -// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file. - -package main - -import ( - "testing" - - "github.com/google/syzkaller/pkg/mgrconfig" - "github.com/google/syzkaller/sys/targets" -) - -func TestCreateBitmap(t *testing.T) { - pcs := []uint64{ - 0x81000002, - 0x8120001d, - } - target := targets.Get("test", "64") - order := target.HostEndian - cfg := &mgrconfig.Config{ - Derived: mgrconfig.Derived{ - SysTarget: target, - }, - } - bitmap := createCoverageBitmap(cfg, pcs) - start := order.Uint64(bitmap[0:]) - size := order.Uint64(bitmap[8:]) - if start != 0x81000002 || size != 0x20001b { - t.Fatalf("bad region 0x%x/0x%x", start, size) - } - for i, byte := range bitmap[16:] { - var expect uint8 - switch i { - case 0: - expect = 0x1 - case 0x20001 / 0x8: - expect = 1 << (0x20001 % 0x8) - } - if byte != expect { - t.Errorf("bad bitmap byte 0x%x: 0x%x, expect 0x%x", i, byte, expect) - } - } - pcs = []uint64{ - 0, - 0xffffffff, - } - createCoverageBitmap(cfg, pcs) - pcs = []uint64{ - 0x81000000, - 0x81000100, - } - createCoverageBitmap(cfg, pcs) - pcs = []uint64{ - 0x81000002, - 0x81000010, - 0x81000102, - } - createCoverageBitmap(cfg, pcs) -} - -func TestNilCoverageBitmap(t *testing.T) { - var pcs []uint64 - cfg := &mgrconfig.Config{ - Derived: mgrconfig.Derived{ - SysTarget: targets.Get("test", "64"), - }, - } - bitmap := createCoverageBitmap(cfg, pcs) - if bitmap != nil { - t.Errorf("created a bitmap on nil pcs") - } -} diff --git a/syz-manager/rpc.go b/syz-manager/rpc.go index 422a6be00..6e7e3b710 100644 --- a/syz-manager/rpc.go +++ b/syz-manager/rpc.go @@ -251,9 +251,8 @@ func (serv *RPCServer) handshake(conn *flatrpc.Conn) (string, []byte, *cover.Can }) canonicalizer := serv.canonicalModules.NewInstance(modules) - instCoverFilter := canonicalizer.Decanonicalize(serv.execCoverFilter) infoReply := &flatrpc.InfoReply{ - CoverFilter: createCoverageBitmap(serv.cfg, instCoverFilter), + CoverFilter: canonicalizer.Decanonicalize(serv.execCoverFilter), } if err := flatrpc.Send(conn, infoReply); err != nil { return "", nil, nil, err @@ -659,9 +658,6 @@ func (serv *RPCServer) execOpts() flatrpc.ExecOpts { if !serv.cfg.RawCover { exec |= flatrpc.ExecFlagDedupCover } - if serv.cfg.HasCovFilter() { - exec |= flatrpc.ExecFlagCoverFilter - } return flatrpc.ExecOpts{ EnvFlags: env, ExecFlags: exec, |
