Commit 8b7d89d0 authored by Pekka Paalanen's avatar Pekka Paalanen Committed by Thomas Gleixner

x86: mmiotrace - trace memory mapped IO

Mmiotrace is a tool for trapping memory mapped IO (MMIO) accesses within
the kernel. It is used for debugging and especially for reverse
engineering evil binary drivers.

Mmiotrace works by wrapping the ioremap family of kernel functions and
marking the returned pages as not present. Access to the IO memory
triggers a page fault, which will be handled by mmiotrace's custom page
fault handler. This will single-step the faulted instruction with the
MMIO page marked as present. Access logs are directed to user space via
relay and debug_fs.

This page fault approach is necessary, because binary drivers have
readl/writel etc. calls inlined and therefore extremely difficult to
trap with with e.g. kprobes.

This patch depends on the custom page fault handlers patch.
Signed-off-by: default avatarPekka Paalanen <pq@iki.fi>
Signed-off-by: default avatarIngo Molnar <mingo@elte.hu>
Signed-off-by: default avatarThomas Gleixner <tglx@linutronix.de>
parent 677aa9f7
......@@ -176,6 +176,33 @@ config PAGE_FAULT_HANDLERS
register a function that is called on every page fault. Custom
handlers are used by some debugging and reverse engineering tools.
config MMIOTRACE
tristate "Memory mapped IO tracing"
depends on DEBUG_KERNEL && PAGE_FAULT_HANDLERS && RELAY && DEBUG_FS
default n
help
This will build a kernel module called mmiotrace.
Mmiotrace traces Memory Mapped I/O access and is meant for debugging
and reverse engineering. The kernel module offers wrapped
versions of the ioremap family of functions. The driver to be traced
must be modified to call these wrappers. A user space program is
required to collect the MMIO data.
See http://nouveau.freedesktop.org/wiki/MmioTrace
If you are not helping to develop drivers, say N.
config MMIOTRACE_TEST
tristate "Test module for mmiotrace"
depends on MMIOTRACE && m
default n
help
This is a dumb module for testing mmiotrace. It is very dangerous
as it will write garbage to IO memory starting at a given address.
However, it should be safe to use on e.g. unused portion of VRAM.
Say N, unless you absolutely know what you are doing.
#
# IO delay types:
#
......
......@@ -79,6 +79,8 @@ obj-$(CONFIG_KGDB) += kgdb.o
obj-$(CONFIG_VM86) += vm86_32.o
obj-$(CONFIG_EARLY_PRINTK) += early_printk.o
obj-$(CONFIG_MMIOTRACE) += mmiotrace/
obj-$(CONFIG_HPET_TIMER) += hpet.o
obj-$(CONFIG_K8_NB) += k8.o
......
......@@ -15,6 +15,7 @@ static struct signal_struct init_signals = INIT_SIGNALS(init_signals);
static struct sighand_struct init_sighand = INIT_SIGHAND(init_sighand);
struct mm_struct init_mm = INIT_MM(init_mm);
EXPORT_UNUSED_SYMBOL(init_mm); /* will be removed in 2.6.26 */
EXPORT_SYMBOL_GPL(init_mm);
/*
* Initial thread structure.
......
obj-$(CONFIG_MMIOTRACE) += mmiotrace.o
mmiotrace-objs := pf_in.o kmmio.o mmio-mod.o
obj-$(CONFIG_MMIOTRACE_TEST) += testmmiotrace.o
/* Support for MMIO probes.
* Benfit many code from kprobes
* (C) 2002 Louis Zhuang <louis.zhuang@intel.com>.
* 2007 Alexander Eichner
* 2008 Pekka Paalanen <pq@iki.fi>
*/
#include <linux/version.h>
#include <linux/spinlock.h>
#include <linux/hash.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/kernel.h>
#include <linux/mm.h>
#include <linux/uaccess.h>
#include <linux/ptrace.h>
#include <linux/preempt.h>
#include <asm/io.h>
#include <asm/cacheflush.h>
#include <asm/errno.h>
#include <asm/tlbflush.h>
#include "kmmio.h"
#define KMMIO_HASH_BITS 6
#define KMMIO_TABLE_SIZE (1 << KMMIO_HASH_BITS)
#define KMMIO_PAGE_HASH_BITS 4
#define KMMIO_PAGE_TABLE_SIZE (1 << KMMIO_PAGE_HASH_BITS)
struct kmmio_context {
struct kmmio_fault_page *fpage;
struct kmmio_probe *probe;
unsigned long saved_flags;
int active;
};
static int kmmio_page_fault(struct pt_regs *regs, unsigned long error_code,
unsigned long address);
static int kmmio_die_notifier(struct notifier_block *nb, unsigned long val,
void *args);
static DEFINE_SPINLOCK(kmmio_lock);
/* These are protected by kmmio_lock */
unsigned int kmmio_count;
static unsigned int handler_registered;
static struct list_head kmmio_page_table[KMMIO_PAGE_TABLE_SIZE];
static LIST_HEAD(kmmio_probes);
static struct kmmio_context kmmio_ctx[NR_CPUS];
static struct pf_handler kmmio_pf_hook = {
.handler = kmmio_page_fault
};
static struct notifier_block nb_die = {
.notifier_call = kmmio_die_notifier
};
int init_kmmio(void)
{
int i;
for (i = 0; i < KMMIO_PAGE_TABLE_SIZE; i++)
INIT_LIST_HEAD(&kmmio_page_table[i]);
register_die_notifier(&nb_die);
return 0;
}
void cleanup_kmmio(void)
{
/*
* Assume the following have been already cleaned by calling
* unregister_kmmio_probe() appropriately:
* kmmio_page_table, kmmio_probes
*/
if (handler_registered) {
unregister_page_fault_handler(&kmmio_pf_hook);
synchronize_rcu();
}
unregister_die_notifier(&nb_die);
}
/*
* this is basically a dynamic stabbing problem:
* Could use the existing prio tree code or
* Possible better implementations:
* The Interval Skip List: A Data Structure for Finding All Intervals That
* Overlap a Point (might be simple)
* Space Efficient Dynamic Stabbing with Fast Queries - Mikkel Thorup
*/
/* Get the kmmio at this addr (if any). You must be holding kmmio_lock. */
static struct kmmio_probe *get_kmmio_probe(unsigned long addr)
{
struct kmmio_probe *p;
list_for_each_entry(p, &kmmio_probes, list) {
if (addr >= p->addr && addr <= (p->addr + p->len))
return p;
}
return NULL;
}
static struct kmmio_fault_page *get_kmmio_fault_page(unsigned long page)
{
struct list_head *head, *tmp;
page &= PAGE_MASK;
head = &kmmio_page_table[hash_long(page, KMMIO_PAGE_HASH_BITS)];
list_for_each(tmp, head) {
struct kmmio_fault_page *p
= list_entry(tmp, struct kmmio_fault_page, list);
if (p->page == page)
return p;
}
return NULL;
}
static void arm_kmmio_fault_page(unsigned long page, int *large)
{
unsigned long address = page & PAGE_MASK;
pgd_t *pgd = pgd_offset_k(address);
pud_t *pud = pud_offset(pgd, address);
pmd_t *pmd = pmd_offset(pud, address);
pte_t *pte = pte_offset_kernel(pmd, address);
if (pmd_large(*pmd)) {
set_pmd(pmd, __pmd(pmd_val(*pmd) & ~_PAGE_PRESENT));
if (large)
*large = 1;
} else {
set_pte(pte, __pte(pte_val(*pte) & ~_PAGE_PRESENT));
}
__flush_tlb_one(page);
}
static void disarm_kmmio_fault_page(unsigned long page, int *large)
{
unsigned long address = page & PAGE_MASK;
pgd_t *pgd = pgd_offset_k(address);
pud_t *pud = pud_offset(pgd, address);
pmd_t *pmd = pmd_offset(pud, address);
pte_t *pte = pte_offset_kernel(pmd, address);
if (large && *large) {
set_pmd(pmd, __pmd(pmd_val(*pmd) | _PAGE_PRESENT));
*large = 0;
} else {
set_pte(pte, __pte(pte_val(*pte) | _PAGE_PRESENT));
}
__flush_tlb_one(page);
}
/*
* Interrupts are disabled on entry as trap3 is an interrupt gate
* and they remain disabled thorough out this function.
*/
static int kmmio_handler(struct pt_regs *regs, unsigned long addr)
{
struct kmmio_context *ctx;
int cpu;
/*
* Preemption is now disabled to prevent process switch during
* single stepping. We can only handle one active kmmio trace
* per cpu, so ensure that we finish it before something else
* gets to run.
*
* XXX what if an interrupt occurs between returning from
* do_page_fault() and entering the single-step exception handler?
* And that interrupt triggers a kmmio trap?
*/
preempt_disable();
cpu = smp_processor_id();
ctx = &kmmio_ctx[cpu];
/* interrupts disabled and CPU-local data => atomicity guaranteed. */
if (ctx->active) {
/*
* This avoids a deadlock with kmmio_lock.
* If this page fault really was due to kmmio trap,
* all hell breaks loose.
*/
printk(KERN_EMERG "mmiotrace: recursive probe hit on CPU %d, "
"for address %lu. Ignoring.\n",
cpu, addr);
goto no_kmmio;
}
ctx->active++;
/*
* Acquire the kmmio lock to prevent changes affecting
* get_kmmio_fault_page() and get_kmmio_probe(), since we save their
* returned pointers.
* The lock is released in post_kmmio_handler().
* XXX: could/should get_kmmio_*() be using RCU instead of spinlock?
*/
spin_lock(&kmmio_lock);
ctx->fpage = get_kmmio_fault_page(addr);
if (!ctx->fpage) {
/* this page fault is not caused by kmmio */
goto no_kmmio_locked;
}
ctx->probe = get_kmmio_probe(addr);
ctx->saved_flags = (regs->flags & (TF_MASK|IF_MASK));
if (ctx->probe && ctx->probe->pre_handler)
ctx->probe->pre_handler(ctx->probe, regs, addr);
regs->flags |= TF_MASK;
regs->flags &= ~IF_MASK;
/* We hold lock, now we set present bit in PTE and single step. */
disarm_kmmio_fault_page(ctx->fpage->page, NULL);
return 1;
no_kmmio_locked:
spin_unlock(&kmmio_lock);
ctx->active--;
no_kmmio:
preempt_enable_no_resched();
/* page fault not handled by kmmio */
return 0;
}
/*
* Interrupts are disabled on entry as trap1 is an interrupt gate
* and they remain disabled thorough out this function.
* And we hold kmmio lock.
*/
static int post_kmmio_handler(unsigned long condition, struct pt_regs *regs)
{
int cpu = smp_processor_id();
struct kmmio_context *ctx = &kmmio_ctx[cpu];
if (!ctx->active)
return 0;
if (ctx->probe && ctx->probe->post_handler)
ctx->probe->post_handler(ctx->probe, condition, regs);
arm_kmmio_fault_page(ctx->fpage->page, NULL);
regs->flags &= ~TF_MASK;
regs->flags |= ctx->saved_flags;
/* These were acquired in kmmio_handler(). */
ctx->active--;
spin_unlock(&kmmio_lock);
preempt_enable_no_resched();
/*
* if somebody else is singlestepping across a probe point, flags
* will have TF set, in which case, continue the remaining processing
* of do_debug, as if this is not a probe hit.
*/
if (regs->flags & TF_MASK)
return 0;
return 1;
}
static int add_kmmio_fault_page(unsigned long page)
{
struct kmmio_fault_page *f;
page &= PAGE_MASK;
f = get_kmmio_fault_page(page);
if (f) {
f->count++;
return 0;
}
f = kmalloc(sizeof(*f), GFP_ATOMIC);
if (!f)
return -1;
f->count = 1;
f->page = page;
list_add(&f->list,
&kmmio_page_table[hash_long(f->page, KMMIO_PAGE_HASH_BITS)]);
arm_kmmio_fault_page(f->page, NULL);
return 0;
}
static void release_kmmio_fault_page(unsigned long page)
{
struct kmmio_fault_page *f;
page &= PAGE_MASK;
f = get_kmmio_fault_page(page);
if (!f)
return;
f->count--;
if (!f->count) {
disarm_kmmio_fault_page(f->page, NULL);
list_del(&f->list);
}
}
int register_kmmio_probe(struct kmmio_probe *p)
{
int ret = 0;
unsigned long size = 0;
spin_lock_irq(&kmmio_lock);
kmmio_count++;
if (get_kmmio_probe(p->addr)) {
ret = -EEXIST;
goto out;
}
list_add(&p->list, &kmmio_probes);
/*printk("adding fault pages...\n");*/
while (size < p->len) {
if (add_kmmio_fault_page(p->addr + size))
printk(KERN_ERR "mmio: Unable to set page fault.\n");
size += PAGE_SIZE;
}
if (!handler_registered) {
register_page_fault_handler(&kmmio_pf_hook);
handler_registered++;
}
out:
spin_unlock_irq(&kmmio_lock);
/*
* XXX: What should I do here?
* Here was a call to global_flush_tlb(), but it does not exist
* anymore.
*/
return ret;
}
void unregister_kmmio_probe(struct kmmio_probe *p)
{
unsigned long size = 0;
spin_lock_irq(&kmmio_lock);
while (size < p->len) {
release_kmmio_fault_page(p->addr + size);
size += PAGE_SIZE;
}
list_del(&p->list);
kmmio_count--;
spin_unlock_irq(&kmmio_lock);
}
/*
* According to 2.6.20, mainly x86_64 arch:
* This is being called from do_page_fault(), via the page fault notifier
* chain. The chain is called for both user space faults and kernel space
* faults (address >= TASK_SIZE64), except not on faults serviced by
* vmalloc_fault().
*
* We may be in an interrupt or a critical section. Also prefecthing may
* trigger a page fault. We may be in the middle of process switch.
* The page fault hook functionality has put us inside RCU read lock.
*
* Local interrupts are disabled, so preemption cannot happen.
* Do not enable interrupts, do not sleep, and watch out for other CPUs.
*/
static int kmmio_page_fault(struct pt_regs *regs, unsigned long error_code,
unsigned long address)
{
if (is_kmmio_active())
if (kmmio_handler(regs, address) == 1)
return -1;
return 0;
}
static int kmmio_die_notifier(struct notifier_block *nb, unsigned long val,
void *args)
{
struct die_args *arg = args;
if (val == DIE_DEBUG)
if (post_kmmio_handler(arg->err, arg->regs) == 1)
return NOTIFY_STOP;
return NOTIFY_DONE;
}
#ifndef _LINUX_KMMIO_H
#define _LINUX_KMMIO_H
#include <linux/list.h>
#include <linux/notifier.h>
#include <linux/smp.h>
#include <linux/types.h>
#include <linux/ptrace.h>
#include <linux/version.h>
#include <linux/kdebug.h>
struct kmmio_probe;
struct kmmio_fault_page;
struct pt_regs;
typedef void (*kmmio_pre_handler_t)(struct kmmio_probe *,
struct pt_regs *, unsigned long addr);
typedef void (*kmmio_post_handler_t)(struct kmmio_probe *,
unsigned long condition, struct pt_regs *);
struct kmmio_probe {
struct list_head list;
/* start location of the probe point */
unsigned long addr;
/* length of the probe region */
unsigned long len;
/* Called before addr is executed. */
kmmio_pre_handler_t pre_handler;
/* Called after addr is executed, unless... */
kmmio_post_handler_t post_handler;
};
struct kmmio_fault_page {
struct list_head list;
/* location of the fault page */
unsigned long page;
int count;
};
/* kmmio is active by some kmmio_probes? */
static inline int is_kmmio_active(void)
{
extern unsigned int kmmio_count;
return kmmio_count;
}
int init_kmmio(void);
void cleanup_kmmio(void);
int register_kmmio_probe(struct kmmio_probe *p);
void unregister_kmmio_probe(struct kmmio_probe *p);
#endif /* _LINUX_KMMIO_H */
This diff is collapsed.
This diff is collapsed.
/*
* Fault Injection Test harness (FI)
* Copyright (C) Intel Crop.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
* USA.
*
*/
#ifndef __PF_H_
#define __PF_H_
enum reason_type {
NOT_ME, /* page fault is not in regions */
NOTHING, /* access others point in regions */
REG_READ, /* read from addr to reg */
REG_WRITE, /* write from reg to addr */
IMM_WRITE, /* write from imm to addr */
OTHERS /* Other instructions can not intercept */
};
enum reason_type get_ins_type(unsigned long ins_addr);
unsigned int get_ins_mem_width(unsigned long ins_addr);
unsigned long get_ins_reg_val(unsigned long ins_addr, struct pt_regs *regs);
unsigned long get_ins_imm_val(unsigned long ins_addr);
#endif /* __PF_H_ */
/*
* Written by Pekka Paalanen, 2008 <pq@iki.fi>
*/
#include <linux/module.h>
#include <asm/io.h>
extern void __iomem *ioremap_nocache_trace(unsigned long offset,
unsigned long size);
extern void iounmap_trace(volatile void __iomem *addr);
#define MODULE_NAME "testmmiotrace"
static unsigned long mmio_address;
module_param(mmio_address, ulong, 0);
MODULE_PARM_DESC(mmio_address, "Start address of the mapping of 16 kB.");
static void do_write_test(void __iomem *p)
{
unsigned int i;
for (i = 0; i < 256; i++)
iowrite8(i, p + i);
for (i = 1024; i < (5 * 1024); i += 2)
iowrite16(i * 12 + 7, p + i);
for (i = (5 * 1024); i < (16 * 1024); i += 4)
iowrite32(i * 212371 + 13, p + i);
}
static void do_read_test(void __iomem *p)
{
unsigned int i;
volatile unsigned int v;
for (i = 0; i < 256; i++)
v = ioread8(p + i);
for (i = 1024; i < (5 * 1024); i += 2)
v = ioread16(p + i);
for (i = (5 * 1024); i < (16 * 1024); i += 4)
v = ioread32(p + i);
}
static void do_test(void)
{
void __iomem *p = ioremap_nocache_trace(mmio_address, 0x4000);
if (!p) {
printk(KERN_ERR MODULE_NAME ": could not ioremap IO memory, "
"aborting.\n");
return;
}
do_write_test(p);
do_read_test(p);
iounmap_trace(p);
}
static int __init init(void)
{
if (mmio_address == 0) {
printk(KERN_ERR MODULE_NAME ": you have to use the module "
"argument mmio_address.\n");
printk(KERN_ERR MODULE_NAME ": DO NOT LOAD THIS MODULE UNLESS"
" YOU REALLY KNOW WHAT YOU ARE DOING!\n");
return -ENXIO;
}
printk(KERN_WARNING MODULE_NAME ": WARNING: mapping 16 kB @ 0x%08lx "
"in PCI address space, and writing "
"rubbish in there.\n", mmio_address);
do_test();
return 0;
}
static void __exit cleanup(void)
{
printk(KERN_DEBUG MODULE_NAME ": unloaded.\n");
}
module_init(init);
module_exit(cleanup);
MODULE_LICENSE("GPL");
#ifndef MMIOTRACE_H
#define MMIOTRACE_H
#include <asm/types.h>
#define MMIO_VERSION 0x04
/* mm_io_header.type */
#define MMIO_OPCODE_MASK 0xff
#define MMIO_OPCODE_SHIFT 0
#define MMIO_WIDTH_MASK 0xff00
#define MMIO_WIDTH_SHIFT 8
#define MMIO_MAGIC (0x6f000000 | (MMIO_VERSION<<16))
#define MMIO_MAGIC_MASK 0xffff0000
enum mm_io_opcode { /* payload type: */
MMIO_READ = 0x1, /* struct mm_io_rw */
MMIO_WRITE = 0x2, /* struct mm_io_rw */
MMIO_PROBE = 0x3, /* struct mm_io_map */
MMIO_UNPROBE = 0x4, /* struct mm_io_map */
MMIO_MARKER = 0x5, /* raw char data */
MMIO_UNKNOWN_OP = 0x6, /* struct mm_io_rw */
};
struct mm_io_header {
__u32 type;
__u32 sec; /* timestamp */
__u32 nsec;
__u32 pid; /* PID of the process, or 0 for kernel core */
__u16 data_len; /* length of the following payload */
};
struct mm_io_rw {
__u64 address; /* virtual address of register */
__u64 value;
__u64 pc; /* optional program counter */
};
struct mm_io_map {
__u64 phys; /* base address in PCI space */
__u64 addr; /* base virtual address */
__u64 len; /* mapping size */
__u64 pc; /* optional program counter */
};
/*
* These structures are used to allow a single relay_write()
* call to write a full packet.
*/
struct mm_io_header_rw {
struct mm_io_header header;
struct mm_io_rw rw;
} __attribute__((packed));
struct mm_io_header_map {
struct mm_io_header header;
struct mm_io_map map;
} __attribute__((packed));
#endif /* MMIOTRACE_H */
Markdown is supported
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