Index: lib/cfi/cfi.cc =================================================================== --- lib/cfi/cfi.cc +++ lib/cfi/cfi.cc @@ -28,9 +28,13 @@ #include "interception/interception.h" #include "sanitizer_common/sanitizer_common.h" #include "sanitizer_common/sanitizer_flag_parser.h" +#include "sanitizer_common/sanitizer_platform_limits_posix.h" +#include "sanitizer_common/sanitizer_readonlyupdate.h" #include "ubsan/ubsan_init.h" #include "ubsan/ubsan_flags.h" +typedef __sanitizer::ReadOnlyUpdate ShadowWrite; + static uptr __cfi_shadow; static constexpr uptr kShadowGranularity = 12; static constexpr uptr kShadowAlign = 1UL << kShadowGranularity; // 4096 @@ -71,7 +75,10 @@ assert(v == kInvalidShadow || v == kUncheckedShadow); uint16_t *shadow_begin = mem_to_shadow(begin); uint16_t *shadow_end = mem_to_shadow(end - 1) + 1; - memset(shadow_begin, v, (shadow_end - shadow_begin) * sizeof(*shadow_begin)); + + const auto sw = ShadowWrite::start(shadow_begin, shadow_end); + memset(sw.begin(), v, (sw.end() - sw.begin()) * sizeof(uint16_t)); + sw.commit(); } static void fill_shadow(uptr begin, uptr end, uptr cfi_check) { @@ -84,8 +91,11 @@ uint16_t *s = mem_to_shadow(p); uint16_t *s_end = mem_to_shadow(end - 1) + 1; uint16_t sv = ((p - cfi_check) >> kShadowGranularity) + 1; - for (; s < s_end; s++, sv++) - *s = sv; + + const auto sw = ShadowWrite::start(s, s_end); + for (uint16_t *dst = sw.begin(); dst < sw.end(); ++dst, ++sv) + *dst = sv; + sw.commit(); // Sanity checks. uptr q = p & ~(kShadowAlign - 1); @@ -236,6 +246,20 @@ } } +static void map_shadow() { + uptr vma = GetMaxVirtualAddress(); + // Shadow is 2 -> 2**kShadowGranularity. + uptr shadow_size = (vma >> (kShadowGranularity - 1)) + 1; + VReport(1, "CFI: VMA size %zx, shadow size %zx\n", vma, shadow_size); + void *shadow = MmapNoReserveOrDie(shadow_size, "CFI shadow"); + VReport(1, "CFI: shadow at %zx .. %zx\n", shadow, + reinterpret_cast(shadow) + shadow_size); + + MprotectReadOnly((uptr)shadow, shadow_size); + + __cfi_shadow = (uptr)shadow; +} + extern "C" SANITIZER_INTERFACE_ATTRIBUTE #if !SANITIZER_CAN_USE_PREINIT_ARRAY // On ELF platforms, the constructor is invoked using .preinit_array (see below) @@ -245,14 +269,7 @@ SanitizerToolName = "CFI"; InitializeFlags(); - uptr vma = GetMaxVirtualAddress(); - // Shadow is 2 -> 2**kShadowGranularity. - uptr shadow_size = (vma >> (kShadowGranularity - 1)) + 1; - VReport(1, "CFI: VMA size %zx, shadow size %zx\n", vma, shadow_size); - void *shadow = MmapNoReserveOrDie(shadow_size, "CFI shadow"); - VReport(1, "CFI: shadow at %zx .. %zx\n", shadow, - reinterpret_cast(shadow) + shadow_size); - __cfi_shadow = (uptr)shadow; + map_shadow(); init_shadow(); #ifdef CFI_ENABLE_DIAG Index: lib/sanitizer_common/CMakeLists.txt =================================================================== --- lib/sanitizer_common/CMakeLists.txt +++ lib/sanitizer_common/CMakeLists.txt @@ -90,6 +90,7 @@ sanitizer_platform_limits_posix.h sanitizer_posix.h sanitizer_procmaps.h + sanitizer_readonlyupdate.h sanitizer_quarantine.h sanitizer_report_decorator.h sanitizer_stackdepot.h Index: lib/sanitizer_common/sanitizer_common.h =================================================================== --- lib/sanitizer_common/sanitizer_common.h +++ lib/sanitizer_common/sanitizer_common.h @@ -93,6 +93,8 @@ // Disallow access to a memory range. Use MmapNoAccess to allocate an // unaccessible memory. bool MprotectNoAccess(uptr addr, uptr size); +bool MprotectReadOnly(uptr addr, uptr size); +bool MprotectReadWrite(uptr addr, uptr size); // Used to check if we can map shadow memory to a fixed location. bool MemoryRangeIsAvailable(uptr range_start, uptr range_end); Index: lib/sanitizer_common/sanitizer_posix.cc =================================================================== --- lib/sanitizer_common/sanitizer_posix.cc +++ lib/sanitizer_common/sanitizer_posix.cc @@ -168,7 +168,15 @@ } bool MprotectNoAccess(uptr addr, uptr size) { - return 0 == internal_mprotect((void*)addr, size, PROT_NONE); + return 0 == internal_mprotect((void *)addr, size, PROT_NONE); +} + +bool MprotectReadOnly(uptr addr, uptr size) { + return 0 == internal_mprotect((void *)addr, size, PROT_READ); +} + +bool MprotectReadWrite(uptr addr, uptr size) { + return 0 == internal_mprotect((void *)addr, size, PROT_READ | PROT_WRITE); } fd_t OpenFile(const char *filename, FileAccessMode mode, error_t *errno_p) { Index: lib/sanitizer_common/sanitizer_readonlyupdate.h =================================================================== --- /dev/null +++ lib/sanitizer_common/sanitizer_readonlyupdate.h @@ -0,0 +1,90 @@ +//===-------- sanitizer_readonlyupdate.h ------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// A helper class for updating a read-only memory region via mremap. +//===----------------------------------------------------------------------===// +#ifndef CFI_SHADOW_H +#define CFI_SHADOW_H + +#include + +#include "sanitizer_common.h" + +namespace __sanitizer { + +// Update the read-only shadow w/o ever making it writable. +// Works by preparing the data in a writable buffer on the side, then +// moving it in place with mprotect + mremap. +template +struct ReadOnlyUpdate { + // The updated region. + uptr shadow_start, shadow_end; + // The updated region, extended to the nearest page boundaries. + uptr aligned_start, aligned_end; + // Temporary write buffer matching the extended shadow region. + uptr copy_start, copy_size; + + // Get the start of the temporary buffer. + T *begin() const { + return (T *)(copy_start + (shadow_start - aligned_start)); + } + + // Get the end of the temporary buffer. + T *end() const { return (T *)(copy_start + (shadow_end - aligned_start)); } + +// Finalize the operation, atomically (when possible) moving the temporary +// buffer to the requested shadow region. +#if SANITIZER_LINUX + void commit() const { + MprotectReadOnly(copy_start, copy_size); + void *res = mremap((void *)copy_start, copy_size, copy_size, + MREMAP_MAYMOVE | MREMAP_FIXED, (void *)aligned_start); + CHECK(res != MAP_FAILED); + } +#else + void commit() const { + MprotectReadWrite(aligned_start, copy_size); + internal_memcpy((void *)aligned_start, (void *)copy_start, copy_size); + MprotectReadOnly(aligned_start, copy_size); + CHECK_EQ(0, internal_memcmp((void *)aligned_start, (void *)copy_start, + copy_size)); + UnmapOrDie((void *)copy_start, copy_size); + } +#endif + + // Prepare to update a shadow region. + // Current contents of the region are not preserved. + static ReadOnlyUpdate start(T *start, T *end) { + CHECK(end >= start); + return ReadOnlyUpdate((uptr)start, (uptr)end); + } + + private: + ReadOnlyUpdate(uptr start, uptr end) { + shadow_start = start; + shadow_end = end; + + uptr page_size = GetPageSizeCached(); + aligned_start = shadow_start & ~(page_size - 1); + aligned_end = (shadow_end + page_size - 1) & ~(page_size - 1); + + copy_size = aligned_end - aligned_start; + copy_start = (uptr)MmapOrDie(copy_size, "CFI shadow update"); + uptr copy_end = copy_start + copy_size; + + uptr left_copy_size = shadow_start - aligned_start; + uptr right_copy_size = aligned_end - shadow_end; + internal_memcpy((void *)copy_start, (void *)aligned_start, left_copy_size); + internal_memcpy((void *)(copy_end - right_copy_size), + (void *)(aligned_end - right_copy_size), right_copy_size); + } +}; + +} // namespace __sanitizer + +#endif // CFI_SHADOW_H Index: lib/sanitizer_common/tests/CMakeLists.txt =================================================================== --- lib/sanitizer_common/tests/CMakeLists.txt +++ lib/sanitizer_common/tests/CMakeLists.txt @@ -26,6 +26,7 @@ sanitizer_posix_test.cc sanitizer_printf_test.cc sanitizer_procmaps_test.cc + sanitizer_readonlyupdate_test.cc sanitizer_stackdepot_test.cc sanitizer_stacktrace_printer_test.cc sanitizer_stacktrace_test.cc Index: lib/sanitizer_common/tests/sanitizer_readonlyupdate_test.cc =================================================================== --- /dev/null +++ lib/sanitizer_common/tests/sanitizer_readonlyupdate_test.cc @@ -0,0 +1,81 @@ +//===-- sanitizer_readonlyupdate_test.cc ----------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +#include "sanitizer_readonlyupdate.h" + +#include "gtest/gtest.h" + +class ReadOnlyUpdateTest : public ::testing::Test { + protected: + virtual void SetUp() { + page = GetPageSizeCached(); + sz = page * 10; + p = (char *)MmapOrDie(sz, nullptr); + ASSERT_NE(p, (char *)-1); + } + + virtual void TearDown() { + UnmapOrDie(p, sz); + } + + uptr page; + uptr sz; + char *p; +}; + +TEST_F(ReadOnlyUpdateTest, PageAligned) { + p[page + 10] = 1; + + const auto w = ReadOnlyUpdate::start(p + page, p + page * 2); + ASSERT_EQ(w.end() - w.begin(), (sptr)page); + for (char *q = (char *)w.begin(); q != (char *)w.end(); ++q) EXPECT_EQ(*q, 0); + *w.begin() = 42; + *(w.end() - 1) = 43; + w.commit(); + + EXPECT_EQ(p[page - 1], 0); + EXPECT_EQ(p[page], 42); + EXPECT_EQ(p[page + 1], 0); + EXPECT_EQ(p[page + 10], 0); // original contents lost + EXPECT_EQ(p[2 * page - 1], 43); + EXPECT_EQ(p[2 * page], 0); + + // Still read only. + EXPECT_DEATH(p[page + 14] = 0, ""); +} + +TEST_F(ReadOnlyUpdateTest, Unaligned) { + p[page + 110] = 1; + + const auto w = + ReadOnlyUpdate::start(p + page + 100, p + page * 3 - 200); + for (char *q = (char *)w.begin(); q != (char *)w.end(); ++q) EXPECT_EQ(*q, 0); + *w.begin() = 42; + *(w.end() - 1) = 43; + w.commit(); + + EXPECT_EQ(p[page - 1], 0); + EXPECT_EQ(p[page + 99], 0); + EXPECT_EQ(p[page + 100], 42); + EXPECT_EQ(p[page + 101], 0); + EXPECT_EQ(p[page + 110], 0); + EXPECT_EQ(p[page * 3 - 202], 0); + EXPECT_EQ(p[page * 3 - 201], 43); + EXPECT_EQ(p[page * 3 - 200], 0); + + EXPECT_DEATH(p[page + 105] = 0, ""); + EXPECT_DEATH(p[page * 3 - 207] = 0, ""); +} + +TEST_F(ReadOnlyUpdateTest, Empty) { + const auto w = ReadOnlyUpdate::start(p + page + 100, p + page + 100); + CHECK(w.begin() == w.end()); + w.commit(); + + for (char *q = p; q != p + sz; ++q) EXPECT_EQ(*q, 0); +}