blob: 3a84afc2ec8ba21b36463c28f184b5a18e630403 [file] [log] [blame]
// 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/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.SumAcrossAllCpus();
}