Skip to content
Snippets Groups Projects
Commit 4675ff05 authored by Levin, Alexander (Sasha Levin)'s avatar Levin, Alexander (Sasha Levin) Committed by Linus Torvalds
Browse files

kmemcheck: rip it out

Fix up makefiles, remove references, and git rm kmemcheck.

Link: http://lkml.kernel.org/r/20171007030159.22241-4-alexander.levin@verizon.com


Signed-off-by: default avatarSasha Levin <alexander.levin@verizon.com>
Cc: Steven Rostedt <rostedt@goodmis.org>
Cc: Vegard Nossum <vegardno@ifi.uio.no>
Cc: Pekka Enberg <penberg@kernel.org>
Cc: Michal Hocko <mhocko@kernel.org>
Cc: Eric W. Biederman <ebiederm@xmission.com>
Cc: Alexander Potapenko <glider@google.com>
Cc: Tim Hansen <devtimhansen@gmail.com>
Signed-off-by: default avatarAndrew Morton <akpm@linux-foundation.org>
Signed-off-by: default avatarLinus Torvalds <torvalds@linux-foundation.org>
parent d8be7566
No related branches found
No related tags found
No related merge requests found
Showing
with 3 additions and 1950 deletions
......@@ -1864,13 +1864,6 @@
Built with CONFIG_DEBUG_KMEMLEAK_DEFAULT_OFF=y,
the default is off.
kmemcheck= [X86] Boot-time kmemcheck enable/disable/one-shot mode
Valid arguments: 0, 1, 2
kmemcheck=0 (disabled)
kmemcheck=1 (enabled)
kmemcheck=2 (one-shot mode)
Default: 2 (one-shot mode)
kvm.ignore_msrs=[KVM] Ignore guest accesses to unhandled MSRs.
Default is 0 (don't ignore, but inject #GP)
......
......@@ -21,7 +21,6 @@ whole; patches welcome!
kasan
ubsan
kmemleak
kmemcheck
gdb-kernel-debugging
kgdb
kselftest
......
This diff is collapsed.
......@@ -7688,16 +7688,6 @@ F: include/linux/kdb.h
F: include/linux/kgdb.h
F: kernel/debug/
KMEMCHECK
M: Vegard Nossum <vegardno@ifi.uio.no>
M: Pekka Enberg <penberg@kernel.org>
S: Maintained
F: Documentation/dev-tools/kmemcheck.rst
F: arch/x86/include/asm/kmemcheck.h
F: arch/x86/mm/kmemcheck/
F: include/linux/kmemcheck.h
F: mm/kmemcheck.c
KMEMLEAK
M: Catalin Marinas <catalin.marinas@arm.com>
S: Maintained
......
......@@ -112,7 +112,6 @@ config X86
select HAVE_ARCH_JUMP_LABEL
select HAVE_ARCH_KASAN if X86_64 && SPARSEMEM_VMEMMAP
select HAVE_ARCH_KGDB
select HAVE_ARCH_KMEMCHECK
select HAVE_ARCH_MMAP_RND_BITS if MMU
select HAVE_ARCH_MMAP_RND_COMPAT_BITS if MMU && COMPAT
select HAVE_ARCH_COMPAT_MMAP_BASES if MMU && COMPAT
......@@ -1430,7 +1429,7 @@ config ARCH_DMA_ADDR_T_64BIT
config X86_DIRECT_GBPAGES
def_bool y
depends on X86_64 && !DEBUG_PAGEALLOC && !KMEMCHECK
depends on X86_64 && !DEBUG_PAGEALLOC
---help---
Certain kernel features effectively disable kernel
linear 1 GB mappings (even if the CPU otherwise
......
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef ASM_X86_KMEMCHECK_H
#define ASM_X86_KMEMCHECK_H
#include <linux/types.h>
#include <asm/ptrace.h>
#ifdef CONFIG_KMEMCHECK
bool kmemcheck_active(struct pt_regs *regs);
void kmemcheck_show(struct pt_regs *regs);
void kmemcheck_hide(struct pt_regs *regs);
bool kmemcheck_fault(struct pt_regs *regs,
unsigned long address, unsigned long error_code);
bool kmemcheck_trap(struct pt_regs *regs);
#else
static inline bool kmemcheck_active(struct pt_regs *regs)
{
return false;
}
static inline void kmemcheck_show(struct pt_regs *regs)
{
}
static inline void kmemcheck_hide(struct pt_regs *regs)
{
}
static inline bool kmemcheck_fault(struct pt_regs *regs,
unsigned long address, unsigned long error_code)
{
return false;
}
static inline bool kmemcheck_trap(struct pt_regs *regs)
{
return false;
}
#endif /* CONFIG_KMEMCHECK */
#endif
......@@ -179,8 +179,6 @@ static inline void *__memcpy3d(void *to, const void *from, size_t len)
* No 3D Now!
*/
#ifndef CONFIG_KMEMCHECK
#if (__GNUC__ >= 4)
#define memcpy(t, f, n) __builtin_memcpy(t, f, n)
#else
......@@ -189,13 +187,6 @@ static inline void *__memcpy3d(void *to, const void *from, size_t len)
? __constant_memcpy((t), (f), (n)) \
: __memcpy((t), (f), (n)))
#endif
#else
/*
* kmemcheck becomes very happy if we use the REP instructions unconditionally,
* because it means that we know both memory operands in advance.
*/
#define memcpy(t, f, n) __memcpy((t), (f), (n))
#endif
#endif
#endif /* !CONFIG_FORTIFY_SOURCE */
......
......@@ -33,7 +33,6 @@ extern void *memcpy(void *to, const void *from, size_t len);
extern void *__memcpy(void *to, const void *from, size_t len);
#ifndef CONFIG_FORTIFY_SOURCE
#ifndef CONFIG_KMEMCHECK
#if (__GNUC__ == 4 && __GNUC_MINOR__ < 3) || __GNUC__ < 4
#define memcpy(dst, src, len) \
({ \
......@@ -46,13 +45,6 @@ extern void *__memcpy(void *to, const void *from, size_t len);
__ret; \
})
#endif
#else
/*
* kmemcheck becomes very happy if we use the REP instructions unconditionally,
* because it means that we know both memory operands in advance.
*/
#define memcpy(dst, src, len) __inline_memcpy((dst), (src), (len))
#endif
#endif /* !CONFIG_FORTIFY_SOURCE */
#define __HAVE_ARCH_MEMSET
......
......@@ -187,21 +187,6 @@ static void early_init_intel(struct cpuinfo_x86 *c)
if (c->x86 == 6 && c->x86_model < 15)
clear_cpu_cap(c, X86_FEATURE_PAT);
#ifdef CONFIG_KMEMCHECK
/*
* P4s have a "fast strings" feature which causes single-
* stepping REP instructions to only generate a #DB on
* cache-line boundaries.
*
* Ingo Molnar reported a Pentium D (model 6) and a Xeon
* (model 2) with the same problem.
*/
if (c->x86 == 15)
if (msr_clear_bit(MSR_IA32_MISC_ENABLE,
MSR_IA32_MISC_ENABLE_FAST_STRING_BIT) > 0)
pr_info("kmemcheck: Disabling fast string operations\n");
#endif
/*
* If fast string is not enabled in IA32_MISC_ENABLE for any reason,
* clear the fast string and enhanced fast string CPU capabilities.
......
......@@ -29,8 +29,6 @@ obj-$(CONFIG_X86_PTDUMP) += debug_pagetables.o
obj-$(CONFIG_HIGHMEM) += highmem_32.o
obj-$(CONFIG_KMEMCHECK) += kmemcheck/
KASAN_SANITIZE_kasan_init_$(BITS).o := n
obj-$(CONFIG_KASAN) += kasan_init_$(BITS).o
......
......@@ -163,12 +163,11 @@ static int page_size_mask;
static void __init probe_page_size_mask(void)
{
/*
* For CONFIG_KMEMCHECK or pagealloc debugging, identity mapping will
* use small pages.
* For pagealloc debugging, identity mapping will use small pages.
* This will simplify cpa(), which otherwise needs to support splitting
* large pages into small in interrupt context, etc.
*/
if (boot_cpu_has(X86_FEATURE_PSE) && !debug_pagealloc_enabled() && !IS_ENABLED(CONFIG_KMEMCHECK))
if (boot_cpu_has(X86_FEATURE_PSE) && !debug_pagealloc_enabled())
page_size_mask |= 1 << PG_LEVEL_2M;
else
direct_gbpages = 0;
......
obj-y := error.o kmemcheck.o opcode.o pte.o selftest.o shadow.o
// SPDX-License-Identifier: GPL-2.0
#include <linux/interrupt.h>
#include <linux/kdebug.h>
#include <linux/kmemcheck.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/ptrace.h>
#include <linux/stacktrace.h>
#include <linux/string.h>
#include "error.h"
#include "shadow.h"
enum kmemcheck_error_type {
KMEMCHECK_ERROR_INVALID_ACCESS,
KMEMCHECK_ERROR_BUG,
};
#define SHADOW_COPY_SIZE (1 << CONFIG_KMEMCHECK_SHADOW_COPY_SHIFT)
struct kmemcheck_error {
enum kmemcheck_error_type type;
union {
/* KMEMCHECK_ERROR_INVALID_ACCESS */
struct {
/* Kind of access that caused the error */
enum kmemcheck_shadow state;
/* Address and size of the erroneous read */
unsigned long address;
unsigned int size;
};
};
struct pt_regs regs;
struct stack_trace trace;
unsigned long trace_entries[32];
/* We compress it to a char. */
unsigned char shadow_copy[SHADOW_COPY_SIZE];
unsigned char memory_copy[SHADOW_COPY_SIZE];
};
/*
* Create a ring queue of errors to output. We can't call printk() directly
* from the kmemcheck traps, since this may call the console drivers and
* result in a recursive fault.
*/
static struct kmemcheck_error error_fifo[CONFIG_KMEMCHECK_QUEUE_SIZE];
static unsigned int error_count;
static unsigned int error_rd;
static unsigned int error_wr;
static unsigned int error_missed_count;
static struct kmemcheck_error *error_next_wr(void)
{
struct kmemcheck_error *e;
if (error_count == ARRAY_SIZE(error_fifo)) {
++error_missed_count;
return NULL;
}
e = &error_fifo[error_wr];
if (++error_wr == ARRAY_SIZE(error_fifo))
error_wr = 0;
++error_count;
return e;
}
static struct kmemcheck_error *error_next_rd(void)
{
struct kmemcheck_error *e;
if (error_count == 0)
return NULL;
e = &error_fifo[error_rd];
if (++error_rd == ARRAY_SIZE(error_fifo))
error_rd = 0;
--error_count;
return e;
}
void kmemcheck_error_recall(void)
{
static const char *desc[] = {
[KMEMCHECK_SHADOW_UNALLOCATED] = "unallocated",
[KMEMCHECK_SHADOW_UNINITIALIZED] = "uninitialized",
[KMEMCHECK_SHADOW_INITIALIZED] = "initialized",
[KMEMCHECK_SHADOW_FREED] = "freed",
};
static const char short_desc[] = {
[KMEMCHECK_SHADOW_UNALLOCATED] = 'a',
[KMEMCHECK_SHADOW_UNINITIALIZED] = 'u',
[KMEMCHECK_SHADOW_INITIALIZED] = 'i',
[KMEMCHECK_SHADOW_FREED] = 'f',
};
struct kmemcheck_error *e;
unsigned int i;
e = error_next_rd();
if (!e)
return;
switch (e->type) {
case KMEMCHECK_ERROR_INVALID_ACCESS:
printk(KERN_WARNING "WARNING: kmemcheck: Caught %d-bit read from %s memory (%p)\n",
8 * e->size, e->state < ARRAY_SIZE(desc) ?
desc[e->state] : "(invalid shadow state)",
(void *) e->address);
printk(KERN_WARNING);
for (i = 0; i < SHADOW_COPY_SIZE; ++i)
printk(KERN_CONT "%02x", e->memory_copy[i]);
printk(KERN_CONT "\n");
printk(KERN_WARNING);
for (i = 0; i < SHADOW_COPY_SIZE; ++i) {
if (e->shadow_copy[i] < ARRAY_SIZE(short_desc))
printk(KERN_CONT " %c", short_desc[e->shadow_copy[i]]);
else
printk(KERN_CONT " ?");
}
printk(KERN_CONT "\n");
printk(KERN_WARNING "%*c\n", 2 + 2
* (int) (e->address & (SHADOW_COPY_SIZE - 1)), '^');
break;
case KMEMCHECK_ERROR_BUG:
printk(KERN_EMERG "ERROR: kmemcheck: Fatal error\n");
break;
}
__show_regs(&e->regs, 1);
print_stack_trace(&e->trace, 0);
}
static void do_wakeup(unsigned long data)
{
while (error_count > 0)
kmemcheck_error_recall();
if (error_missed_count > 0) {
printk(KERN_WARNING "kmemcheck: Lost %d error reports because "
"the queue was too small\n", error_missed_count);
error_missed_count = 0;
}
}
static DECLARE_TASKLET(kmemcheck_tasklet, &do_wakeup, 0);
/*
* Save the context of an error report.
*/
void kmemcheck_error_save(enum kmemcheck_shadow state,
unsigned long address, unsigned int size, struct pt_regs *regs)
{
static unsigned long prev_ip;
struct kmemcheck_error *e;
void *shadow_copy;
void *memory_copy;
/* Don't report several adjacent errors from the same EIP. */
if (regs->ip == prev_ip)
return;
prev_ip = regs->ip;
e = error_next_wr();
if (!e)
return;
e->type = KMEMCHECK_ERROR_INVALID_ACCESS;
e->state = state;
e->address = address;
e->size = size;
/* Save regs */
memcpy(&e->regs, regs, sizeof(*regs));
/* Save stack trace */
e->trace.nr_entries = 0;
e->trace.entries = e->trace_entries;
e->trace.max_entries = ARRAY_SIZE(e->trace_entries);
e->trace.skip = 0;
save_stack_trace_regs(regs, &e->trace);
/* Round address down to nearest 16 bytes */
shadow_copy = kmemcheck_shadow_lookup(address
& ~(SHADOW_COPY_SIZE - 1));
BUG_ON(!shadow_copy);
memcpy(e->shadow_copy, shadow_copy, SHADOW_COPY_SIZE);
kmemcheck_show_addr(address);
memory_copy = (void *) (address & ~(SHADOW_COPY_SIZE - 1));
memcpy(e->memory_copy, memory_copy, SHADOW_COPY_SIZE);
kmemcheck_hide_addr(address);
tasklet_hi_schedule_first(&kmemcheck_tasklet);
}
/*
* Save the context of a kmemcheck bug.
*/
void kmemcheck_error_save_bug(struct pt_regs *regs)
{
struct kmemcheck_error *e;
e = error_next_wr();
if (!e)
return;
e->type = KMEMCHECK_ERROR_BUG;
memcpy(&e->regs, regs, sizeof(*regs));
e->trace.nr_entries = 0;
e->trace.entries = e->trace_entries;
e->trace.max_entries = ARRAY_SIZE(e->trace_entries);
e->trace.skip = 1;
save_stack_trace(&e->trace);
tasklet_hi_schedule_first(&kmemcheck_tasklet);
}
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef ARCH__X86__MM__KMEMCHECK__ERROR_H
#define ARCH__X86__MM__KMEMCHECK__ERROR_H
#include <linux/ptrace.h>
#include "shadow.h"
void kmemcheck_error_save(enum kmemcheck_shadow state,
unsigned long address, unsigned int size, struct pt_regs *regs);
void kmemcheck_error_save_bug(struct pt_regs *regs);
void kmemcheck_error_recall(void);
#endif
/**
* kmemcheck - a heavyweight memory checker for the linux kernel
* Copyright (C) 2007, 2008 Vegard Nossum <vegardno@ifi.uio.no>
* (With a lot of help from Ingo Molnar and Pekka Enberg.)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License (version 2) as
* published by the Free Software Foundation.
*/
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/kallsyms.h>
#include <linux/kernel.h>
#include <linux/kmemcheck.h>
#include <linux/mm.h>
#include <linux/page-flags.h>
#include <linux/percpu.h>
#include <linux/ptrace.h>
#include <linux/string.h>
#include <linux/types.h>
#include <asm/cacheflush.h>
#include <asm/kmemcheck.h>
#include <asm/pgtable.h>
#include <asm/tlbflush.h>
#include "error.h"
#include "opcode.h"
#include "pte.h"
#include "selftest.h"
#include "shadow.h"
#ifdef CONFIG_KMEMCHECK_DISABLED_BY_DEFAULT
# define KMEMCHECK_ENABLED 0
#endif
#ifdef CONFIG_KMEMCHECK_ENABLED_BY_DEFAULT
# define KMEMCHECK_ENABLED 1
#endif
#ifdef CONFIG_KMEMCHECK_ONESHOT_BY_DEFAULT
# define KMEMCHECK_ENABLED 2
#endif
int kmemcheck_enabled = KMEMCHECK_ENABLED;
int __init kmemcheck_init(void)
{
#ifdef CONFIG_SMP
/*
* Limit SMP to use a single CPU. We rely on the fact that this code
* runs before SMP is set up.
*/
if (setup_max_cpus > 1) {
printk(KERN_INFO
"kmemcheck: Limiting number of CPUs to 1.\n");
setup_max_cpus = 1;
}
#endif
if (!kmemcheck_selftest()) {
printk(KERN_INFO "kmemcheck: self-tests failed; disabling\n");
kmemcheck_enabled = 0;
return -EINVAL;
}
printk(KERN_INFO "kmemcheck: Initialized\n");
return 0;
}
early_initcall(kmemcheck_init);
/*
* We need to parse the kmemcheck= option before any memory is allocated.
*/
static int __init param_kmemcheck(char *str)
{
int val;
int ret;
if (!str)
return -EINVAL;
ret = kstrtoint(str, 0, &val);
if (ret)
return ret;
kmemcheck_enabled = val;
return 0;
}
early_param("kmemcheck", param_kmemcheck);
int kmemcheck_show_addr(unsigned long address)
{
pte_t *pte;
pte = kmemcheck_pte_lookup(address);
if (!pte)
return 0;
set_pte(pte, __pte(pte_val(*pte) | _PAGE_PRESENT));
__flush_tlb_one(address);
return 1;
}
int kmemcheck_hide_addr(unsigned long address)
{
pte_t *pte;
pte = kmemcheck_pte_lookup(address);
if (!pte)
return 0;
set_pte(pte, __pte(pte_val(*pte) & ~_PAGE_PRESENT));
__flush_tlb_one(address);
return 1;
}
struct kmemcheck_context {
bool busy;
int balance;
/*
* There can be at most two memory operands to an instruction, but
* each address can cross a page boundary -- so we may need up to
* four addresses that must be hidden/revealed for each fault.
*/
unsigned long addr[4];
unsigned long n_addrs;
unsigned long flags;
/* Data size of the instruction that caused a fault. */
unsigned int size;
};
static DEFINE_PER_CPU(struct kmemcheck_context, kmemcheck_context);
bool kmemcheck_active(struct pt_regs *regs)
{
struct kmemcheck_context *data = this_cpu_ptr(&kmemcheck_context);
return data->balance > 0;
}
/* Save an address that needs to be shown/hidden */
static void kmemcheck_save_addr(unsigned long addr)
{
struct kmemcheck_context *data = this_cpu_ptr(&kmemcheck_context);
BUG_ON(data->n_addrs >= ARRAY_SIZE(data->addr));
data->addr[data->n_addrs++] = addr;
}
static unsigned int kmemcheck_show_all(void)
{
struct kmemcheck_context *data = this_cpu_ptr(&kmemcheck_context);
unsigned int i;
unsigned int n;
n = 0;
for (i = 0; i < data->n_addrs; ++i)
n += kmemcheck_show_addr(data->addr[i]);
return n;
}
static unsigned int kmemcheck_hide_all(void)
{
struct kmemcheck_context *data = this_cpu_ptr(&kmemcheck_context);
unsigned int i;
unsigned int n;
n = 0;
for (i = 0; i < data->n_addrs; ++i)
n += kmemcheck_hide_addr(data->addr[i]);
return n;
}
/*
* Called from the #PF handler.
*/
void kmemcheck_show(struct pt_regs *regs)
{
struct kmemcheck_context *data = this_cpu_ptr(&kmemcheck_context);
BUG_ON(!irqs_disabled());
if (unlikely(data->balance != 0)) {
kmemcheck_show_all();
kmemcheck_error_save_bug(regs);
data->balance = 0;
return;
}
/*
* None of the addresses actually belonged to kmemcheck. Note that
* this is not an error.
*/
if (kmemcheck_show_all() == 0)
return;
++data->balance;
/*
* The IF needs to be cleared as well, so that the faulting
* instruction can run "uninterrupted". Otherwise, we might take
* an interrupt and start executing that before we've had a chance
* to hide the page again.
*
* NOTE: In the rare case of multiple faults, we must not override
* the original flags:
*/
if (!(regs->flags & X86_EFLAGS_TF))
data->flags = regs->flags;
regs->flags |= X86_EFLAGS_TF;
regs->flags &= ~X86_EFLAGS_IF;
}
/*
* Called from the #DB handler.
*/
void kmemcheck_hide(struct pt_regs *regs)
{
struct kmemcheck_context *data = this_cpu_ptr(&kmemcheck_context);
int n;
BUG_ON(!irqs_disabled());
if (unlikely(data->balance != 1)) {
kmemcheck_show_all();
kmemcheck_error_save_bug(regs);
data->n_addrs = 0;
data->balance = 0;
if (!(data->flags & X86_EFLAGS_TF))
regs->flags &= ~X86_EFLAGS_TF;
if (data->flags & X86_EFLAGS_IF)
regs->flags |= X86_EFLAGS_IF;
return;
}
if (kmemcheck_enabled)
n = kmemcheck_hide_all();
else
n = kmemcheck_show_all();
if (n == 0)
return;
--data->balance;
data->n_addrs = 0;
if (!(data->flags & X86_EFLAGS_TF))
regs->flags &= ~X86_EFLAGS_TF;
if (data->flags & X86_EFLAGS_IF)
regs->flags |= X86_EFLAGS_IF;
}
void kmemcheck_show_pages(struct page *p, unsigned int n)
{
unsigned int i;
for (i = 0; i < n; ++i) {
unsigned long address;
pte_t *pte;
unsigned int level;
address = (unsigned long) page_address(&p[i]);
pte = lookup_address(address, &level);
BUG_ON(!pte);
BUG_ON(level != PG_LEVEL_4K);
set_pte(pte, __pte(pte_val(*pte) | _PAGE_PRESENT));
set_pte(pte, __pte(pte_val(*pte) & ~_PAGE_HIDDEN));
__flush_tlb_one(address);
}
}
bool kmemcheck_page_is_tracked(struct page *p)
{
/* This will also check the "hidden" flag of the PTE. */
return kmemcheck_pte_lookup((unsigned long) page_address(p));
}
void kmemcheck_hide_pages(struct page *p, unsigned int n)
{
unsigned int i;
for (i = 0; i < n; ++i) {
unsigned long address;
pte_t *pte;
unsigned int level;
address = (unsigned long) page_address(&p[i]);
pte = lookup_address(address, &level);
BUG_ON(!pte);
BUG_ON(level != PG_LEVEL_4K);
set_pte(pte, __pte(pte_val(*pte) & ~_PAGE_PRESENT));
set_pte(pte, __pte(pte_val(*pte) | _PAGE_HIDDEN));
__flush_tlb_one(address);
}
}
/* Access may NOT cross page boundary */
static void kmemcheck_read_strict(struct pt_regs *regs,
unsigned long addr, unsigned int size)
{
void *shadow;
enum kmemcheck_shadow status;
shadow = kmemcheck_shadow_lookup(addr);
if (!shadow)
return;
kmemcheck_save_addr(addr);
status = kmemcheck_shadow_test(shadow, size);
if (status == KMEMCHECK_SHADOW_INITIALIZED)
return;
if (kmemcheck_enabled)
kmemcheck_error_save(status, addr, size, regs);
if (kmemcheck_enabled == 2)
kmemcheck_enabled = 0;
/* Don't warn about it again. */
kmemcheck_shadow_set(shadow, size);
}
bool kmemcheck_is_obj_initialized(unsigned long addr, size_t size)
{
enum kmemcheck_shadow status;
void *shadow;
shadow = kmemcheck_shadow_lookup(addr);
if (!shadow)
return true;
status = kmemcheck_shadow_test_all(shadow, size);
return status == KMEMCHECK_SHADOW_INITIALIZED;
}
/* Access may cross page boundary */
static void kmemcheck_read(struct pt_regs *regs,
unsigned long addr, unsigned int size)
{
unsigned long page = addr & PAGE_MASK;
unsigned long next_addr = addr + size - 1;
unsigned long next_page = next_addr & PAGE_MASK;
if (likely(page == next_page)) {
kmemcheck_read_strict(regs, addr, size);
return;
}
/*
* What we do is basically to split the access across the
* two pages and handle each part separately. Yes, this means
* that we may now see reads that are 3 + 5 bytes, for
* example (and if both are uninitialized, there will be two
* reports), but it makes the code a lot simpler.
*/
kmemcheck_read_strict(regs, addr, next_page - addr);
kmemcheck_read_strict(regs, next_page, next_addr - next_page);
}
static void kmemcheck_write_strict(struct pt_regs *regs,
unsigned long addr, unsigned int size)
{
void *shadow;
shadow = kmemcheck_shadow_lookup(addr);
if (!shadow)
return;
kmemcheck_save_addr(addr);
kmemcheck_shadow_set(shadow, size);
}
static void kmemcheck_write(struct pt_regs *regs,
unsigned long addr, unsigned int size)
{
unsigned long page = addr & PAGE_MASK;
unsigned long next_addr = addr + size - 1;
unsigned long next_page = next_addr & PAGE_MASK;
if (likely(page == next_page)) {
kmemcheck_write_strict(regs, addr, size);
return;
}
/* See comment in kmemcheck_read(). */
kmemcheck_write_strict(regs, addr, next_page - addr);
kmemcheck_write_strict(regs, next_page, next_addr - next_page);
}
/*
* Copying is hard. We have two addresses, each of which may be split across
* a page (and each page will have different shadow addresses).
*/
static void kmemcheck_copy(struct pt_regs *regs,
unsigned long src_addr, unsigned long dst_addr, unsigned int size)
{
uint8_t shadow[8];
enum kmemcheck_shadow status;
unsigned long page;
unsigned long next_addr;
unsigned long next_page;
uint8_t *x;
unsigned int i;
unsigned int n;
BUG_ON(size > sizeof(shadow));
page = src_addr & PAGE_MASK;
next_addr = src_addr + size - 1;
next_page = next_addr & PAGE_MASK;
if (likely(page == next_page)) {
/* Same page */
x = kmemcheck_shadow_lookup(src_addr);
if (x) {
kmemcheck_save_addr(src_addr);
for (i = 0; i < size; ++i)
shadow[i] = x[i];
} else {
for (i = 0; i < size; ++i)
shadow[i] = KMEMCHECK_SHADOW_INITIALIZED;
}
} else {
n = next_page - src_addr;
BUG_ON(n > sizeof(shadow));
/* First page */
x = kmemcheck_shadow_lookup(src_addr);
if (x) {
kmemcheck_save_addr(src_addr);
for (i = 0; i < n; ++i)
shadow[i] = x[i];
} else {
/* Not tracked */
for (i = 0; i < n; ++i)
shadow[i] = KMEMCHECK_SHADOW_INITIALIZED;
}
/* Second page */
x = kmemcheck_shadow_lookup(next_page);
if (x) {
kmemcheck_save_addr(next_page);
for (i = n; i < size; ++i)
shadow[i] = x[i - n];
} else {
/* Not tracked */
for (i = n; i < size; ++i)
shadow[i] = KMEMCHECK_SHADOW_INITIALIZED;
}
}
page = dst_addr & PAGE_MASK;
next_addr = dst_addr + size - 1;
next_page = next_addr & PAGE_MASK;
if (likely(page == next_page)) {
/* Same page */
x = kmemcheck_shadow_lookup(dst_addr);
if (x) {
kmemcheck_save_addr(dst_addr);
for (i = 0; i < size; ++i) {
x[i] = shadow[i];
shadow[i] = KMEMCHECK_SHADOW_INITIALIZED;
}
}
} else {
n = next_page - dst_addr;
BUG_ON(n > sizeof(shadow));
/* First page */
x = kmemcheck_shadow_lookup(dst_addr);
if (x) {
kmemcheck_save_addr(dst_addr);
for (i = 0; i < n; ++i) {
x[i] = shadow[i];
shadow[i] = KMEMCHECK_SHADOW_INITIALIZED;
}
}
/* Second page */
x = kmemcheck_shadow_lookup(next_page);
if (x) {
kmemcheck_save_addr(next_page);
for (i = n; i < size; ++i) {
x[i - n] = shadow[i];
shadow[i] = KMEMCHECK_SHADOW_INITIALIZED;
}
}
}
status = kmemcheck_shadow_test(shadow, size);
if (status == KMEMCHECK_SHADOW_INITIALIZED)
return;
if (kmemcheck_enabled)
kmemcheck_error_save(status, src_addr, size, regs);
if (kmemcheck_enabled == 2)
kmemcheck_enabled = 0;
}
enum kmemcheck_method {
KMEMCHECK_READ,
KMEMCHECK_WRITE,
};
static void kmemcheck_access(struct pt_regs *regs,
unsigned long fallback_address, enum kmemcheck_method fallback_method)
{
const uint8_t *insn;
const uint8_t *insn_primary;
unsigned int size;
struct kmemcheck_context *data = this_cpu_ptr(&kmemcheck_context);
/* Recursive fault -- ouch. */
if (data->busy) {
kmemcheck_show_addr(fallback_address);
kmemcheck_error_save_bug(regs);
return;
}
data->busy = true;
insn = (const uint8_t *) regs->ip;
insn_primary = kmemcheck_opcode_get_primary(insn);
kmemcheck_opcode_decode(insn, &size);
switch (insn_primary[0]) {
#ifdef CONFIG_KMEMCHECK_BITOPS_OK
/* AND, OR, XOR */
/*
* Unfortunately, these instructions have to be excluded from
* our regular checking since they access only some (and not
* all) bits. This clears out "bogus" bitfield-access warnings.
*/
case 0x80:
case 0x81:
case 0x82:
case 0x83:
switch ((insn_primary[1] >> 3) & 7) {
/* OR */
case 1:
/* AND */
case 4:
/* XOR */
case 6:
kmemcheck_write(regs, fallback_address, size);
goto out;
/* ADD */
case 0:
/* ADC */
case 2:
/* SBB */
case 3:
/* SUB */
case 5:
/* CMP */
case 7:
break;
}
break;
#endif
/* MOVS, MOVSB, MOVSW, MOVSD */
case 0xa4:
case 0xa5:
/*
* These instructions are special because they take two
* addresses, but we only get one page fault.
*/
kmemcheck_copy(regs, regs->si, regs->di, size);
goto out;
/* CMPS, CMPSB, CMPSW, CMPSD */
case 0xa6:
case 0xa7:
kmemcheck_read(regs, regs->si, size);
kmemcheck_read(regs, regs->di, size);
goto out;
}
/*
* If the opcode isn't special in any way, we use the data from the
* page fault handler to determine the address and type of memory
* access.
*/
switch (fallback_method) {
case KMEMCHECK_READ:
kmemcheck_read(regs, fallback_address, size);
goto out;
case KMEMCHECK_WRITE:
kmemcheck_write(regs, fallback_address, size);
goto out;
}
out:
data->busy = false;
}
bool kmemcheck_fault(struct pt_regs *regs, unsigned long address,
unsigned long error_code)
{
pte_t *pte;
/*
* XXX: Is it safe to assume that memory accesses from virtual 86
* mode or non-kernel code segments will _never_ access kernel
* memory (e.g. tracked pages)? For now, we need this to avoid
* invoking kmemcheck for PnP BIOS calls.
*/
if (regs->flags & X86_VM_MASK)
return false;
if (regs->cs != __KERNEL_CS)
return false;
pte = kmemcheck_pte_lookup(address);
if (!pte)
return false;
WARN_ON_ONCE(in_nmi());
if (error_code & 2)
kmemcheck_access(regs, address, KMEMCHECK_WRITE);
else
kmemcheck_access(regs, address, KMEMCHECK_READ);
kmemcheck_show(regs);
return true;
}
bool kmemcheck_trap(struct pt_regs *regs)
{
if (!kmemcheck_active(regs))
return false;
/* We're done. */
kmemcheck_hide(regs);
return true;
}
// SPDX-License-Identifier: GPL-2.0
#include <linux/types.h>
#include "opcode.h"
static bool opcode_is_prefix(uint8_t b)
{
return
/* Group 1 */
b == 0xf0 || b == 0xf2 || b == 0xf3
/* Group 2 */
|| b == 0x2e || b == 0x36 || b == 0x3e || b == 0x26
|| b == 0x64 || b == 0x65
/* Group 3 */
|| b == 0x66
/* Group 4 */
|| b == 0x67;
}
#ifdef CONFIG_X86_64
static bool opcode_is_rex_prefix(uint8_t b)
{
return (b & 0xf0) == 0x40;
}
#else
static bool opcode_is_rex_prefix(uint8_t b)
{
return false;
}
#endif
#define REX_W (1 << 3)
/*
* This is a VERY crude opcode decoder. We only need to find the size of the
* load/store that caused our #PF and this should work for all the opcodes
* that we care about. Moreover, the ones who invented this instruction set
* should be shot.
*/
void kmemcheck_opcode_decode(const uint8_t *op, unsigned int *size)
{
/* Default operand size */
int operand_size_override = 4;
/* prefixes */
for (; opcode_is_prefix(*op); ++op) {
if (*op == 0x66)
operand_size_override = 2;
}
/* REX prefix */
if (opcode_is_rex_prefix(*op)) {
uint8_t rex = *op;
++op;
if (rex & REX_W) {
switch (*op) {
case 0x63:
*size = 4;
return;
case 0x0f:
++op;
switch (*op) {
case 0xb6:
case 0xbe:
*size = 1;
return;
case 0xb7:
case 0xbf:
*size = 2;
return;
}
break;
}
*size = 8;
return;
}
}
/* escape opcode */
if (*op == 0x0f) {
++op;
/*
* This is move with zero-extend and sign-extend, respectively;
* we don't have to think about 0xb6/0xbe, because this is
* already handled in the conditional below.
*/
if (*op == 0xb7 || *op == 0xbf)
operand_size_override = 2;
}
*size = (*op & 1) ? operand_size_override : 1;
}
const uint8_t *kmemcheck_opcode_get_primary(const uint8_t *op)
{
/* skip prefixes */
while (opcode_is_prefix(*op))
++op;
if (opcode_is_rex_prefix(*op))
++op;
return op;
}
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef ARCH__X86__MM__KMEMCHECK__OPCODE_H
#define ARCH__X86__MM__KMEMCHECK__OPCODE_H
#include <linux/types.h>
void kmemcheck_opcode_decode(const uint8_t *op, unsigned int *size);
const uint8_t *kmemcheck_opcode_get_primary(const uint8_t *op);
#endif
// SPDX-License-Identifier: GPL-2.0
#include <linux/mm.h>
#include <asm/pgtable.h>
#include "pte.h"
pte_t *kmemcheck_pte_lookup(unsigned long address)
{
pte_t *pte;
unsigned int level;
pte = lookup_address(address, &level);
if (!pte)
return NULL;
if (level != PG_LEVEL_4K)
return NULL;
if (!pte_hidden(*pte))
return NULL;
return pte;
}
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef ARCH__X86__MM__KMEMCHECK__PTE_H
#define ARCH__X86__MM__KMEMCHECK__PTE_H
#include <linux/mm.h>
#include <asm/pgtable.h>
pte_t *kmemcheck_pte_lookup(unsigned long address);
#endif
// SPDX-License-Identifier: GPL-2.0
#include <linux/bug.h>
#include <linux/kernel.h>
#include "opcode.h"
#include "selftest.h"
struct selftest_opcode {
unsigned int expected_size;
const uint8_t *insn;
const char *desc;
};
static const struct selftest_opcode selftest_opcodes[] = {
/* REP MOVS */
{1, "\xf3\xa4", "rep movsb <mem8>, <mem8>"},
{4, "\xf3\xa5", "rep movsl <mem32>, <mem32>"},
/* MOVZX / MOVZXD */
{1, "\x66\x0f\xb6\x51\xf8", "movzwq <mem8>, <reg16>"},
{1, "\x0f\xb6\x51\xf8", "movzwq <mem8>, <reg32>"},
/* MOVSX / MOVSXD */
{1, "\x66\x0f\xbe\x51\xf8", "movswq <mem8>, <reg16>"},
{1, "\x0f\xbe\x51\xf8", "movswq <mem8>, <reg32>"},
#ifdef CONFIG_X86_64
/* MOVZX / MOVZXD */
{1, "\x49\x0f\xb6\x51\xf8", "movzbq <mem8>, <reg64>"},
{2, "\x49\x0f\xb7\x51\xf8", "movzbq <mem16>, <reg64>"},
/* MOVSX / MOVSXD */
{1, "\x49\x0f\xbe\x51\xf8", "movsbq <mem8>, <reg64>"},
{2, "\x49\x0f\xbf\x51\xf8", "movsbq <mem16>, <reg64>"},
{4, "\x49\x63\x51\xf8", "movslq <mem32>, <reg64>"},
#endif
};
static bool selftest_opcode_one(const struct selftest_opcode *op)
{
unsigned size;
kmemcheck_opcode_decode(op->insn, &size);
if (size == op->expected_size)
return true;
printk(KERN_WARNING "kmemcheck: opcode %s: expected size %d, got %d\n",
op->desc, op->expected_size, size);
return false;
}
static bool selftest_opcodes_all(void)
{
bool pass = true;
unsigned int i;
for (i = 0; i < ARRAY_SIZE(selftest_opcodes); ++i)
pass = pass && selftest_opcode_one(&selftest_opcodes[i]);
return pass;
}
bool kmemcheck_selftest(void)
{
bool pass = true;
pass = pass && selftest_opcodes_all();
return pass;
}
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment