Commit 376e2424 authored by Masami Hiramatsu's avatar Masami Hiramatsu Committed by Ingo Molnar

kprobes: Introduce NOKPROBE_SYMBOL() macro to maintain kprobes blacklist

Introduce NOKPROBE_SYMBOL() macro which builds a kprobes
blacklist at kernel build time.

The usage of this macro is similar to EXPORT_SYMBOL(),
placed after the function definition:

  NOKPROBE_SYMBOL(function);

Since this macro will inhibit inlining of static/inline
functions, this patch also introduces a nokprobe_inline macro
for static/inline functions. In this case, we must use
NOKPROBE_SYMBOL() for the inline function caller.

When CONFIG_KPROBES=y, the macro stores the given function
address in the "_kprobe_blacklist" section.

Since the data structures are not fully initialized by the
macro (because there is no "size" information),  those
are re-initialized at boot time by using kallsyms.
Signed-off-by: default avatarMasami Hiramatsu <masami.hiramatsu.pt@hitachi.com>
Link: http://lkml.kernel.org/r/20140417081705.26341.96719.stgit@ltc230.yrl.intra.hitachi.co.jp
Cc: Alok Kataria <akataria@vmware.com>
Cc: Ananth N Mavinakayanahalli <ananth@in.ibm.com>
Cc: Andrew Morton <akpm@linux-foundation.org>
Cc: Anil S Keshavamurthy <anil.s.keshavamurthy@intel.com>
Cc: Arnd Bergmann <arnd@arndb.de>
Cc: Christopher Li <sparse@chrisli.org>
Cc: Chris Wright <chrisw@sous-sol.org>
Cc: David S. Miller <davem@davemloft.net>
Cc: Jan-Simon Möller <dl9pf@gmx.de>
Cc: Jeremy Fitzhardinge <jeremy@goop.org>
Cc: Linus Torvalds <torvalds@linux-foundation.org>
Cc: Randy Dunlap <rdunlap@infradead.org>
Cc: Rusty Russell <rusty@rustcorp.com.au>
Cc: linux-arch@vger.kernel.org
Cc: linux-doc@vger.kernel.org
Cc: linux-sparse@vger.kernel.org
Cc: virtualization@lists.linux-foundation.org
Signed-off-by: default avatarIngo Molnar <mingo@kernel.org>
parent be8f2743
...@@ -22,8 +22,9 @@ Appendix B: The kprobes sysctl interface ...@@ -22,8 +22,9 @@ Appendix B: The kprobes sysctl interface
Kprobes enables you to dynamically break into any kernel routine and Kprobes enables you to dynamically break into any kernel routine and
collect debugging and performance information non-disruptively. You collect debugging and performance information non-disruptively. You
can trap at almost any kernel code address, specifying a handler can trap at almost any kernel code address(*), specifying a handler
routine to be invoked when the breakpoint is hit. routine to be invoked when the breakpoint is hit.
(*: some parts of the kernel code can not be trapped, see 1.5 Blacklist)
There are currently three types of probes: kprobes, jprobes, and There are currently three types of probes: kprobes, jprobes, and
kretprobes (also called return probes). A kprobe can be inserted kretprobes (also called return probes). A kprobe can be inserted
...@@ -273,6 +274,19 @@ using one of the following techniques: ...@@ -273,6 +274,19 @@ using one of the following techniques:
or or
- Execute 'sysctl -w debug.kprobes_optimization=n' - Execute 'sysctl -w debug.kprobes_optimization=n'
1.5 Blacklist
Kprobes can probe most of the kernel except itself. This means
that there are some functions where kprobes cannot probe. Probing
(trapping) such functions can cause a recursive trap (e.g. double
fault) or the nested probe handler may never be called.
Kprobes manages such functions as a blacklist.
If you want to add a function into the blacklist, you just need
to (1) include linux/kprobes.h and (2) use NOKPROBE_SYMBOL() macro
to specify a blacklisted function.
Kprobes checks the given probe address against the blacklist and
rejects registering it, if the given address is in the blacklist.
2. Architectures Supported 2. Architectures Supported
Kprobes, jprobes, and return probes are implemented on the following Kprobes, jprobes, and return probes are implemented on the following
......
...@@ -57,6 +57,12 @@ ...@@ -57,6 +57,12 @@
.long (from) - . ; \ .long (from) - . ; \
.long (to) - . + 0x7ffffff0 ; \ .long (to) - . + 0x7ffffff0 ; \
.popsection .popsection
# define _ASM_NOKPROBE(entry) \
.pushsection "_kprobe_blacklist","aw" ; \
_ASM_ALIGN ; \
_ASM_PTR (entry); \
.popsection
#else #else
# define _ASM_EXTABLE(from,to) \ # define _ASM_EXTABLE(from,to) \
" .pushsection \"__ex_table\",\"a\"\n" \ " .pushsection \"__ex_table\",\"a\"\n" \
...@@ -71,6 +77,7 @@ ...@@ -71,6 +77,7 @@
" .long (" #from ") - .\n" \ " .long (" #from ") - .\n" \
" .long (" #to ") - . + 0x7ffffff0\n" \ " .long (" #to ") - . + 0x7ffffff0\n" \
" .popsection\n" " .popsection\n"
/* For C file, we already have NOKPROBE_SYMBOL macro */
#endif #endif
#endif /* _ASM_X86_ASM_H */ #endif /* _ASM_X86_ASM_H */
...@@ -23,6 +23,7 @@ ...@@ -23,6 +23,7 @@
#include <linux/efi.h> #include <linux/efi.h>
#include <linux/bcd.h> #include <linux/bcd.h>
#include <linux/highmem.h> #include <linux/highmem.h>
#include <linux/kprobes.h>
#include <asm/bug.h> #include <asm/bug.h>
#include <asm/paravirt.h> #include <asm/paravirt.h>
...@@ -389,6 +390,9 @@ __visible struct pv_cpu_ops pv_cpu_ops = { ...@@ -389,6 +390,9 @@ __visible struct pv_cpu_ops pv_cpu_ops = {
.end_context_switch = paravirt_nop, .end_context_switch = paravirt_nop,
}; };
/* At this point, native_get_debugreg has a real function entry */
NOKPROBE_SYMBOL(native_get_debugreg);
struct pv_apic_ops pv_apic_ops = { struct pv_apic_ops pv_apic_ops = {
#ifdef CONFIG_X86_LOCAL_APIC #ifdef CONFIG_X86_LOCAL_APIC
.startup_ipi_hook = paravirt_nop, .startup_ipi_hook = paravirt_nop,
......
...@@ -109,6 +109,14 @@ ...@@ -109,6 +109,14 @@
#define BRANCH_PROFILE() #define BRANCH_PROFILE()
#endif #endif
#ifdef CONFIG_KPROBES
#define KPROBE_BLACKLIST() VMLINUX_SYMBOL(__start_kprobe_blacklist) = .; \
*(_kprobe_blacklist) \
VMLINUX_SYMBOL(__stop_kprobe_blacklist) = .;
#else
#define KPROBE_BLACKLIST()
#endif
#ifdef CONFIG_EVENT_TRACING #ifdef CONFIG_EVENT_TRACING
#define FTRACE_EVENTS() . = ALIGN(8); \ #define FTRACE_EVENTS() . = ALIGN(8); \
VMLINUX_SYMBOL(__start_ftrace_events) = .; \ VMLINUX_SYMBOL(__start_ftrace_events) = .; \
...@@ -507,6 +515,7 @@ ...@@ -507,6 +515,7 @@
*(.init.rodata) \ *(.init.rodata) \
FTRACE_EVENTS() \ FTRACE_EVENTS() \
TRACE_SYSCALLS() \ TRACE_SYSCALLS() \
KPROBE_BLACKLIST() \
MEM_DISCARD(init.rodata) \ MEM_DISCARD(init.rodata) \
CLK_OF_TABLES() \ CLK_OF_TABLES() \
RESERVEDMEM_OF_TABLES() \ RESERVEDMEM_OF_TABLES() \
......
...@@ -374,7 +374,9 @@ void ftrace_likely_update(struct ftrace_branch_data *f, int val, int expect); ...@@ -374,7 +374,9 @@ void ftrace_likely_update(struct ftrace_branch_data *f, int val, int expect);
/* Ignore/forbid kprobes attach on very low level functions marked by this attribute: */ /* Ignore/forbid kprobes attach on very low level functions marked by this attribute: */
#ifdef CONFIG_KPROBES #ifdef CONFIG_KPROBES
# define __kprobes __attribute__((__section__(".kprobes.text"))) # define __kprobes __attribute__((__section__(".kprobes.text")))
# define nokprobe_inline __always_inline
#else #else
# define __kprobes # define __kprobes
# define nokprobe_inline inline
#endif #endif
#endif /* __LINUX_COMPILER_H */ #endif /* __LINUX_COMPILER_H */
...@@ -205,10 +205,10 @@ struct kretprobe_blackpoint { ...@@ -205,10 +205,10 @@ struct kretprobe_blackpoint {
void *addr; void *addr;
}; };
struct kprobe_blackpoint { struct kprobe_blacklist_entry {
const char *name; struct list_head list;
unsigned long start_addr; unsigned long start_addr;
unsigned long range; unsigned long end_addr;
}; };
#ifdef CONFIG_KPROBES #ifdef CONFIG_KPROBES
...@@ -477,4 +477,18 @@ static inline int enable_jprobe(struct jprobe *jp) ...@@ -477,4 +477,18 @@ static inline int enable_jprobe(struct jprobe *jp)
return enable_kprobe(&jp->kp); return enable_kprobe(&jp->kp);
} }
#ifdef CONFIG_KPROBES
/*
* Blacklist ganerating macro. Specify functions which is not probed
* by using this macro.
*/
#define __NOKPROBE_SYMBOL(fname) \
static unsigned long __used \
__attribute__((section("_kprobe_blacklist"))) \
_kbl_addr_##fname = (unsigned long)fname;
#define NOKPROBE_SYMBOL(fname) __NOKPROBE_SYMBOL(fname)
#else
#define NOKPROBE_SYMBOL(fname)
#endif
#endif /* _LINUX_KPROBES_H */ #endif /* _LINUX_KPROBES_H */
...@@ -86,18 +86,8 @@ static raw_spinlock_t *kretprobe_table_lock_ptr(unsigned long hash) ...@@ -86,18 +86,8 @@ static raw_spinlock_t *kretprobe_table_lock_ptr(unsigned long hash)
return &(kretprobe_table_locks[hash].lock); return &(kretprobe_table_locks[hash].lock);
} }
/* /* Blacklist -- list of struct kprobe_blacklist_entry */
* Normally, functions that we'd want to prohibit kprobes in, are marked static LIST_HEAD(kprobe_blacklist);
* __kprobes. But, there are cases where such functions already belong to
* a different section (__sched for preempt_schedule)
*
* For such cases, we now have a blacklist
*/
static struct kprobe_blackpoint kprobe_blacklist[] = {
{"preempt_schedule",},
{"native_get_debugreg",},
{NULL} /* Terminator */
};
#ifdef __ARCH_WANT_KPROBES_INSN_SLOT #ifdef __ARCH_WANT_KPROBES_INSN_SLOT
/* /*
...@@ -1328,24 +1318,22 @@ bool __weak arch_within_kprobe_blacklist(unsigned long addr) ...@@ -1328,24 +1318,22 @@ bool __weak arch_within_kprobe_blacklist(unsigned long addr)
addr < (unsigned long)__kprobes_text_end; addr < (unsigned long)__kprobes_text_end;
} }
static int __kprobes in_kprobes_functions(unsigned long addr) static bool __kprobes within_kprobe_blacklist(unsigned long addr)
{ {
struct kprobe_blackpoint *kb; struct kprobe_blacklist_entry *ent;
if (arch_within_kprobe_blacklist(addr)) if (arch_within_kprobe_blacklist(addr))
return -EINVAL; return true;
/* /*
* If there exists a kprobe_blacklist, verify and * If there exists a kprobe_blacklist, verify and
* fail any probe registration in the prohibited area * fail any probe registration in the prohibited area
*/ */
for (kb = kprobe_blacklist; kb->name != NULL; kb++) { list_for_each_entry(ent, &kprobe_blacklist, list) {
if (kb->start_addr) { if (addr >= ent->start_addr && addr < ent->end_addr)
if (addr >= kb->start_addr && return true;
addr < (kb->start_addr + kb->range))
return -EINVAL;
}
} }
return 0;
return false;
} }
/* /*
...@@ -1436,7 +1424,7 @@ static __kprobes int check_kprobe_address_safe(struct kprobe *p, ...@@ -1436,7 +1424,7 @@ static __kprobes int check_kprobe_address_safe(struct kprobe *p,
/* Ensure it is not in reserved area nor out of text */ /* Ensure it is not in reserved area nor out of text */
if (!kernel_text_address((unsigned long) p->addr) || if (!kernel_text_address((unsigned long) p->addr) ||
in_kprobes_functions((unsigned long) p->addr) || within_kprobe_blacklist((unsigned long) p->addr) ||
jump_label_text_reserved(p->addr, p->addr)) { jump_label_text_reserved(p->addr, p->addr)) {
ret = -EINVAL; ret = -EINVAL;
goto out; goto out;
...@@ -2022,6 +2010,38 @@ void __kprobes dump_kprobe(struct kprobe *kp) ...@@ -2022,6 +2010,38 @@ void __kprobes dump_kprobe(struct kprobe *kp)
kp->symbol_name, kp->addr, kp->offset); kp->symbol_name, kp->addr, kp->offset);
} }
/*
* Lookup and populate the kprobe_blacklist.
*
* Unlike the kretprobe blacklist, we'll need to determine
* the range of addresses that belong to the said functions,
* since a kprobe need not necessarily be at the beginning
* of a function.
*/
static int __init populate_kprobe_blacklist(unsigned long *start,
unsigned long *end)
{
unsigned long *iter;
struct kprobe_blacklist_entry *ent;
unsigned long offset = 0, size = 0;
for (iter = start; iter < end; iter++) {
if (!kallsyms_lookup_size_offset(*iter, &size, &offset)) {
pr_err("Failed to find blacklist %p\n", (void *)*iter);
continue;
}
ent = kmalloc(sizeof(*ent), GFP_KERNEL);
if (!ent)
return -ENOMEM;
ent->start_addr = *iter;
ent->end_addr = *iter + size;
INIT_LIST_HEAD(&ent->list);
list_add_tail(&ent->list, &kprobe_blacklist);
}
return 0;
}
/* Module notifier call back, checking kprobes on the module */ /* Module notifier call back, checking kprobes on the module */
static int __kprobes kprobes_module_callback(struct notifier_block *nb, static int __kprobes kprobes_module_callback(struct notifier_block *nb,
unsigned long val, void *data) unsigned long val, void *data)
...@@ -2065,14 +2085,13 @@ static struct notifier_block kprobe_module_nb = { ...@@ -2065,14 +2085,13 @@ static struct notifier_block kprobe_module_nb = {
.priority = 0 .priority = 0
}; };
/* Markers of _kprobe_blacklist section */
extern unsigned long __start_kprobe_blacklist[];
extern unsigned long __stop_kprobe_blacklist[];
static int __init init_kprobes(void) static int __init init_kprobes(void)
{ {
int i, err = 0; int i, err = 0;
unsigned long offset = 0, size = 0;
char *modname, namebuf[KSYM_NAME_LEN];
const char *symbol_name;
void *addr;
struct kprobe_blackpoint *kb;
/* FIXME allocate the probe table, currently defined statically */ /* FIXME allocate the probe table, currently defined statically */
/* initialize all list heads */ /* initialize all list heads */
...@@ -2082,26 +2101,11 @@ static int __init init_kprobes(void) ...@@ -2082,26 +2101,11 @@ static int __init init_kprobes(void)
raw_spin_lock_init(&(kretprobe_table_locks[i].lock)); raw_spin_lock_init(&(kretprobe_table_locks[i].lock));
} }
/* err = populate_kprobe_blacklist(__start_kprobe_blacklist,
* Lookup and populate the kprobe_blacklist. __stop_kprobe_blacklist);
* if (err) {
* Unlike the kretprobe blacklist, we'll need to determine pr_err("kprobes: failed to populate blacklist: %d\n", err);
* the range of addresses that belong to the said functions, pr_err("Please take care of using kprobes.\n");
* since a kprobe need not necessarily be at the beginning
* of a function.
*/
for (kb = kprobe_blacklist; kb->name != NULL; kb++) {
kprobe_lookup_name(kb->name, addr);
if (!addr)
continue;
kb->start_addr = (unsigned long)addr;
symbol_name = kallsyms_lookup(kb->start_addr,
&size, &offset, &modname, namebuf);
if (!symbol_name)
kb->range = 0;
else
kb->range = size;
} }
if (kretprobe_blacklist_size) { if (kretprobe_blacklist_size) {
......
...@@ -2804,6 +2804,7 @@ asmlinkage void __sched notrace preempt_schedule(void) ...@@ -2804,6 +2804,7 @@ asmlinkage void __sched notrace preempt_schedule(void)
barrier(); barrier();
} while (need_resched()); } while (need_resched());
} }
NOKPROBE_SYMBOL(preempt_schedule);
EXPORT_SYMBOL(preempt_schedule); EXPORT_SYMBOL(preempt_schedule);
#endif /* CONFIG_PREEMPT */ #endif /* CONFIG_PREEMPT */
......
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