| // Copyright 2020 The Fuchsia Authors |
| // |
| // Use of this source code is governed by a MIT-style |
| // license that can be found in the LICENSE file or at |
| // https://opensource.org/licenses/MIT |
| |
| #include "vm/pmm_checker.h" |
| |
| #include <assert.h> |
| #include <lib/cmdline.h> |
| #include <lib/counters.h> |
| #include <lib/instrumentation/asan.h> |
| #include <platform.h> |
| #include <string.h> |
| #include <sys/types.h> |
| |
| #include <pretty/hexdump.h> |
| #include <vm/physmap.h> |
| |
| #include "debug.h" |
| |
| namespace { |
| |
| // The number of times the pmm checker has detected corruption. |
| KCOUNTER(counter_pattern_validation_failed, "vm.pmm.checker.validation_failed") |
| |
| // The value 0x43 was chosen because it stands out when interpreted as ASCII ('C') and is an odd |
| // value that is less likely to occur natually (e.g. arm64 instructions are 4-byte aligned). |
| constexpr uint8_t kPatternOneByte = 0x43u; |
| constexpr uint64_t kPattern = 0x4343434343434343ull; |
| |
| // This is a macro because it's passed to KERNEL_OOPS and KERNEL_OOPS requires a literal. |
| #define FORMAT_MESSAGE "pmm checker found unexpected pattern in page at %p; fill size is %lu\n" |
| |
| void DumpPage(size_t fill_size, void* kvaddr) { |
| printf("dump of page follows\n"); |
| hexdump8(kvaddr, PAGE_SIZE); |
| } |
| |
| void DumpPageAndOops(vm_page_t* page, size_t fill_size, void* kvaddr) { |
| KERNEL_OOPS(FORMAT_MESSAGE, kvaddr, fill_size); |
| DumpPage(fill_size, kvaddr); |
| } |
| |
| void DumpPageAndPanic(vm_page_t* page, size_t fill_size, void* kvaddr) { |
| platform_panic_start(); |
| printf(FORMAT_MESSAGE, kvaddr, fill_size); |
| DumpPage(fill_size, kvaddr); |
| panic("pmm free list corruption suspected\n"); |
| } |
| |
| } // namespace |
| |
| // static |
| ktl::optional<PmmChecker::Action> PmmChecker::ActionFromString(const char* action_string) { |
| if (!strcmp(action_string, "oops")) { |
| return PmmChecker::Action::OOPS; |
| } |
| if (!strcmp(action_string, "panic")) { |
| return PmmChecker::Action::PANIC; |
| } |
| return ktl::nullopt; |
| } |
| |
| // static |
| const char* PmmChecker::ActionToString(Action action) { |
| switch (action) { |
| case PmmChecker::Action::OOPS: |
| return "oops"; |
| case PmmChecker::Action::PANIC: |
| return "panic"; |
| }; |
| __UNREACHABLE; |
| } |
| |
| // static |
| bool PmmChecker::IsValidFillSize(size_t fill_size) { |
| return fill_size >= 8 && fill_size <= PAGE_SIZE && (fill_size % 8 == 0); |
| } |
| |
| void PmmChecker::SetFillSize(size_t fill_size) { |
| DEBUG_ASSERT(IsValidFillSize(fill_size)); |
| DEBUG_ASSERT(!armed_); |
| fill_size_ = fill_size; |
| } |
| |
| void PmmChecker::Arm() { armed_ = true; } |
| |
| void PmmChecker::Disarm() { armed_ = false; } |
| |
| void PmmChecker::FillPattern(vm_page_t* page) { |
| DEBUG_ASSERT(page->is_free()); |
| void* kvaddr = paddr_to_physmap(page->paddr()); |
| DEBUG_ASSERT(is_kernel_address(reinterpret_cast<vaddr_t>(kvaddr))); |
| __unsanitized_memset(kvaddr, kPatternOneByte, fill_size_); |
| } |
| |
| NO_ASAN bool PmmChecker::ValidatePattern(vm_page_t* page) { |
| if (!armed_) { |
| return true; |
| } |
| |
| // Validate the pattern. There's a decent chance that, on arm64, checking 8 bytes at a time will |
| // be faster than 1 byte at time. |
| auto kvaddr = static_cast<uint64_t*>(paddr_to_physmap(page->paddr())); |
| for (size_t j = 0; j < fill_size_ / 8; ++j) { |
| if (kvaddr[j] != kPattern) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| void PmmChecker::AssertPattern(vm_page_t* page) { |
| if (!ValidatePattern(page)) { |
| kcounter_add(counter_pattern_validation_failed, 1); |
| auto kvaddr = static_cast<void*>(paddr_to_physmap(page->paddr())); |
| switch (action_) { |
| case Action::OOPS: |
| DumpPageAndOops(page, fill_size_, kvaddr); |
| break; |
| case Action::PANIC: |
| DumpPageAndPanic(page, fill_size_, kvaddr); |
| break; |
| } |
| } |
| } |
| |
| void PmmChecker::PrintStatus(FILE* f) const { |
| fprintf(f, "PMM: pmm checker %s, fill size is %lu, action is %s\n", |
| armed_ ? "enabled" : "disabled", fill_size_, ActionToString(action_)); |
| } |
| |
| int64_t PmmChecker::get_validation_failed_count() { |
| return counter_pattern_validation_failed.Value(); |
| } |