Commit 12310e34 authored by Jessica Yu's avatar Jessica Yu Committed by Ingo Molnar

kprobes: Propagate error from arm_kprobe_ftrace()

Improve error handling when arming ftrace-based kprobes. Specifically, if
we fail to arm a ftrace-based kprobe, register_kprobe()/enable_kprobe()
should report an error instead of success. Previously, this has lead to
confusing situations where register_kprobe() would return 0 indicating
success, but the kprobe would not be functional if ftrace registration
during the kprobe arming process had failed. We should therefore take any
errors returned by ftrace into account and propagate this error so that we
do not register/enable kprobes that cannot be armed. This can happen if,
for example, register_ftrace_function() finds an IPMODIFY conflict (since
kprobe_ftrace_ops has this flag set) and returns an error. Such a conflict
is possible since livepatches also set the IPMODIFY flag for their ftrace_ops.

arm_all_kprobes() keeps its current behavior and attempts to arm all
kprobes. It returns the last encountered error and gives a warning if
not all probes could be armed.

This patch is based on Petr Mladek's original patchset (patches 2 and 3)
back in 2015, which improved kprobes error handling, found here:

   https://lkml.org/lkml/2015/2/26/452

However, further work on this had been paused since then and the patches
were not upstreamed.
Based-on-patches-by: default avatarPetr Mladek <pmladek@suse.com>
Signed-off-by: default avatarJessica Yu <jeyu@kernel.org>
Acked-by: default avatarMasami Hiramatsu <mhiramat@kernel.org>
Cc: Ananth N Mavinakayanahalli <ananth@linux.vnet.ibm.com>
Cc: Anil S Keshavamurthy <anil.s.keshavamurthy@intel.com>
Cc: David S . Miller <davem@davemloft.net>
Cc: Jiri Kosina <jikos@kernel.org>
Cc: Joe Lawrence <joe.lawrence@redhat.com>
Cc: Josh Poimboeuf <jpoimboe@redhat.com>
Cc: Linus Torvalds <torvalds@linux-foundation.org>
Cc: Miroslav Benes <mbenes@suse.cz>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Petr Mladek <pmladek@suse.com>
Cc: Steven Rostedt <rostedt@goodmis.org>
Cc: Thomas Gleixner <tglx@linutronix.de>
Cc: live-patching@vger.kernel.org
Link: http://lkml.kernel.org/r/20180109235124.30886-2-jeyu@kernel.orgSigned-off-by: default avatarIngo Molnar <mingo@kernel.org>
parent 3f9e6463
...@@ -978,18 +978,36 @@ static int prepare_kprobe(struct kprobe *p) ...@@ -978,18 +978,36 @@ static int prepare_kprobe(struct kprobe *p)
} }
/* Caller must lock kprobe_mutex */ /* Caller must lock kprobe_mutex */
static void arm_kprobe_ftrace(struct kprobe *p) static int arm_kprobe_ftrace(struct kprobe *p)
{ {
int ret; int ret = 0;
ret = ftrace_set_filter_ip(&kprobe_ftrace_ops, ret = ftrace_set_filter_ip(&kprobe_ftrace_ops,
(unsigned long)p->addr, 0, 0); (unsigned long)p->addr, 0, 0);
WARN(ret < 0, "Failed to arm kprobe-ftrace at %p (%d)\n", p->addr, ret); if (ret) {
kprobe_ftrace_enabled++; pr_debug("Failed to arm kprobe-ftrace at %p (%d)\n", p->addr, ret);
if (kprobe_ftrace_enabled == 1) { return ret;
}
if (kprobe_ftrace_enabled == 0) {
ret = register_ftrace_function(&kprobe_ftrace_ops); ret = register_ftrace_function(&kprobe_ftrace_ops);
WARN(ret < 0, "Failed to init kprobe-ftrace (%d)\n", ret); if (ret) {
pr_debug("Failed to init kprobe-ftrace (%d)\n", ret);
goto err_ftrace;
}
} }
kprobe_ftrace_enabled++;
return ret;
err_ftrace:
/*
* Note: Since kprobe_ftrace_ops has IPMODIFY set, and ftrace requires a
* non-empty filter_hash for IPMODIFY ops, we're safe from an accidental
* empty filter_hash which would undesirably trace all functions.
*/
ftrace_set_filter_ip(&kprobe_ftrace_ops, (unsigned long)p->addr, 1, 0);
return ret;
} }
/* Caller must lock kprobe_mutex */ /* Caller must lock kprobe_mutex */
...@@ -1008,22 +1026,23 @@ static void disarm_kprobe_ftrace(struct kprobe *p) ...@@ -1008,22 +1026,23 @@ static void disarm_kprobe_ftrace(struct kprobe *p)
} }
#else /* !CONFIG_KPROBES_ON_FTRACE */ #else /* !CONFIG_KPROBES_ON_FTRACE */
#define prepare_kprobe(p) arch_prepare_kprobe(p) #define prepare_kprobe(p) arch_prepare_kprobe(p)
#define arm_kprobe_ftrace(p) do {} while (0) #define arm_kprobe_ftrace(p) (-ENODEV)
#define disarm_kprobe_ftrace(p) do {} while (0) #define disarm_kprobe_ftrace(p) do {} while (0)
#endif #endif
/* Arm a kprobe with text_mutex */ /* Arm a kprobe with text_mutex */
static void arm_kprobe(struct kprobe *kp) static int arm_kprobe(struct kprobe *kp)
{ {
if (unlikely(kprobe_ftrace(kp))) { if (unlikely(kprobe_ftrace(kp)))
arm_kprobe_ftrace(kp); return arm_kprobe_ftrace(kp);
return;
}
cpus_read_lock(); cpus_read_lock();
mutex_lock(&text_mutex); mutex_lock(&text_mutex);
__arm_kprobe(kp); __arm_kprobe(kp);
mutex_unlock(&text_mutex); mutex_unlock(&text_mutex);
cpus_read_unlock(); cpus_read_unlock();
return 0;
} }
/* Disarm a kprobe with text_mutex */ /* Disarm a kprobe with text_mutex */
...@@ -1362,9 +1381,15 @@ static int register_aggr_kprobe(struct kprobe *orig_p, struct kprobe *p) ...@@ -1362,9 +1381,15 @@ static int register_aggr_kprobe(struct kprobe *orig_p, struct kprobe *p)
if (ret == 0 && kprobe_disabled(ap) && !kprobe_disabled(p)) { if (ret == 0 && kprobe_disabled(ap) && !kprobe_disabled(p)) {
ap->flags &= ~KPROBE_FLAG_DISABLED; ap->flags &= ~KPROBE_FLAG_DISABLED;
if (!kprobes_all_disarmed) if (!kprobes_all_disarmed) {
/* Arm the breakpoint again. */ /* Arm the breakpoint again. */
arm_kprobe(ap); ret = arm_kprobe(ap);
if (ret) {
ap->flags |= KPROBE_FLAG_DISABLED;
list_del_rcu(&p->list);
synchronize_sched();
}
}
} }
return ret; return ret;
} }
...@@ -1573,8 +1598,14 @@ int register_kprobe(struct kprobe *p) ...@@ -1573,8 +1598,14 @@ int register_kprobe(struct kprobe *p)
hlist_add_head_rcu(&p->hlist, hlist_add_head_rcu(&p->hlist,
&kprobe_table[hash_ptr(p->addr, KPROBE_HASH_BITS)]); &kprobe_table[hash_ptr(p->addr, KPROBE_HASH_BITS)]);
if (!kprobes_all_disarmed && !kprobe_disabled(p)) if (!kprobes_all_disarmed && !kprobe_disabled(p)) {
arm_kprobe(p); ret = arm_kprobe(p);
if (ret) {
hlist_del_rcu(&p->hlist);
synchronize_sched();
goto out;
}
}
/* Try to optimize kprobe */ /* Try to optimize kprobe */
try_to_optimize_kprobe(p); try_to_optimize_kprobe(p);
...@@ -2116,7 +2147,9 @@ int enable_kprobe(struct kprobe *kp) ...@@ -2116,7 +2147,9 @@ int enable_kprobe(struct kprobe *kp)
if (!kprobes_all_disarmed && kprobe_disabled(p)) { if (!kprobes_all_disarmed && kprobe_disabled(p)) {
p->flags &= ~KPROBE_FLAG_DISABLED; p->flags &= ~KPROBE_FLAG_DISABLED;
arm_kprobe(p); ret = arm_kprobe(p);
if (ret)
p->flags |= KPROBE_FLAG_DISABLED;
} }
out: out:
mutex_unlock(&kprobe_mutex); mutex_unlock(&kprobe_mutex);
...@@ -2407,11 +2440,12 @@ static const struct file_operations debugfs_kprobe_blacklist_ops = { ...@@ -2407,11 +2440,12 @@ static const struct file_operations debugfs_kprobe_blacklist_ops = {
.release = seq_release, .release = seq_release,
}; };
static void arm_all_kprobes(void) static int arm_all_kprobes(void)
{ {
struct hlist_head *head; struct hlist_head *head;
struct kprobe *p; struct kprobe *p;
unsigned int i; unsigned int i, total = 0, errors = 0;
int err, ret = 0;
mutex_lock(&kprobe_mutex); mutex_lock(&kprobe_mutex);
...@@ -2428,16 +2462,28 @@ static void arm_all_kprobes(void) ...@@ -2428,16 +2462,28 @@ static void arm_all_kprobes(void)
/* Arming kprobes doesn't optimize kprobe itself */ /* Arming kprobes doesn't optimize kprobe itself */
for (i = 0; i < KPROBE_TABLE_SIZE; i++) { for (i = 0; i < KPROBE_TABLE_SIZE; i++) {
head = &kprobe_table[i]; head = &kprobe_table[i];
hlist_for_each_entry_rcu(p, head, hlist) /* Arm all kprobes on a best-effort basis */
if (!kprobe_disabled(p)) hlist_for_each_entry_rcu(p, head, hlist) {
arm_kprobe(p); if (!kprobe_disabled(p)) {
err = arm_kprobe(p);
if (err) {
errors++;
ret = err;
}
total++;
}
}
} }
printk(KERN_INFO "Kprobes globally enabled\n"); if (errors)
pr_warn("Kprobes globally enabled, but failed to arm %d out of %d probes\n",
errors, total);
else
pr_info("Kprobes globally enabled\n");
already_enabled: already_enabled:
mutex_unlock(&kprobe_mutex); mutex_unlock(&kprobe_mutex);
return; return ret;
} }
static void disarm_all_kprobes(void) static void disarm_all_kprobes(void)
...@@ -2494,6 +2540,7 @@ static ssize_t write_enabled_file_bool(struct file *file, ...@@ -2494,6 +2540,7 @@ static ssize_t write_enabled_file_bool(struct file *file,
{ {
char buf[32]; char buf[32];
size_t buf_size; size_t buf_size;
int ret = 0;
buf_size = min(count, (sizeof(buf)-1)); buf_size = min(count, (sizeof(buf)-1));
if (copy_from_user(buf, user_buf, buf_size)) if (copy_from_user(buf, user_buf, buf_size))
...@@ -2504,7 +2551,7 @@ static ssize_t write_enabled_file_bool(struct file *file, ...@@ -2504,7 +2551,7 @@ static ssize_t write_enabled_file_bool(struct file *file,
case 'y': case 'y':
case 'Y': case 'Y':
case '1': case '1':
arm_all_kprobes(); ret = arm_all_kprobes();
break; break;
case 'n': case 'n':
case 'N': case 'N':
...@@ -2515,6 +2562,9 @@ static ssize_t write_enabled_file_bool(struct file *file, ...@@ -2515,6 +2562,9 @@ static ssize_t write_enabled_file_bool(struct file *file,
return -EINVAL; return -EINVAL;
} }
if (ret)
return ret;
return count; return count;
} }
......
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