kprobes: improve error handling when arming/disarming kprobes
cover kprobes: improve error handling when arming/disarming kprobes
Hi,
This patchset attempts to improve error handling when arming or disarming ftrace-based kprobes. The current behavior is to simply WARN when ftrace (un-)registration fails, without propagating the error code. This can lead to confusing situations where, for example, register_kprobe()/enable_kprobe() would return 0 indicating success even if arming via ftrace had failed. In this scenario we'd end up with a non-functioning kprobe even though kprobe registration (or enablement) returned success. In this patchset, we take errors from ftrace into account and propagate the error when we cannot arm or disarm a kprobe.
Below is an example that illustrates the problem using livepatch and systemtap (which uses kprobes underneath). Both livepatch and kprobes use ftrace ops with the IPMODIFY flag set, so registration at the same function entry is limited to only one ftrace user.
Before
------
# modprobe livepatch-sample # patches cmdline_proc_show, ftrace ops has IPMODIFY set
# stap -e 'probe kernel.function("cmdline_proc_show").call { printf ("cmdline_proc_show\n"); }' .. (nothing prints after reading /proc/cmdline) ..
The systemtap handler doesn't execute due to a kprobe arming failure caused by a ftrace IPMODIFY conflict with livepatch, and there isn't an obvious indication of error from systemtap (because register_kprobe() returned success) unless the user inspects dmesg.
After
-----
# modprobe livepatch-sample
# stap -e 'probe kernel.function("cmdline_proc_show").call { printf ("cmdline_proc_show\n"); }'
WARNING: probe kernel.function("cmdline_proc_show@/home/jeyu/work/linux-next/fs/proc/cmdline.c:6").call (address 0xffffffffa82fe910) registration error (rc -16)Although the systemtap handler doesn't execute (as it shouldn't), the ftrace error is propagated and now systemtap prints a visible error message stating that (kprobe) registration had failed (because register_kprobe() returned an error), along with the propagated error code.
This patchset was 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.
This patchset has been lightly sanity-tested (on linux-next) with kprobes, kretprobes, jprobes, and optimized kprobes. It passes the kprobes smoke test, but more testing is greatly appreciated.
Thanks,
Jessica
---
Jessica Yu (2):
kprobes: propagate error from arm_kprobe_ftrace()
kprobes: propagate error from disarm_kprobe_ftrace()
kernel/kprobes.c | 163 ++++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 112 insertions(+), 51 deletions(-)
Signature · 1 line
2.13.6
PATCH 1/2 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 kprobes 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.
--- kernel/kprobes.c | 87 +++++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 61 insertions(+), 26 deletions(-)
kernel/kprobes.c +61 -26
@@ -979,18 +979,27 @@ static int prepare_kprobe(struct kprobe *p) }/* 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, (unsigned long )p->addr,0 ,0 ); -WARN (ret <0 ,"Failed to arm kprobe-ftrace at %p (%d)\n" , p->addr, ret); - kprobe_ftrace_enabled++; -if (kprobe_ftrace_enabled ==1 ) { +if (WARN (ret <0 ,"Failed to arm kprobe-ftrace at %p (%d)\n" , p->addr, ret)) +return ret; + +if (kprobe_ftrace_enabled ==0 ) { ret =register_ftrace_function (&kprobe_ftrace_ops); -WARN (ret <0 ,"Failed to init kprobe-ftrace (%d)\n" , ret); +if (WARN (ret <0 ,"Failed to init kprobe-ftrace (%d)\n" , ret)) + goto err_ftrace; } + + kprobe_ftrace_enabled++; +return ret; + +err_ftrace: +ftrace_set_filter_ip (&kprobe_ftrace_ops, (unsigned long )p->addr,1 ,0 ); +return ret; }/* Caller must lock kprobe_mutex */
@@ -1009,22 +1018,23 @@ static void disarm_kprobe_ftrace(struct kprobe *p) }#else /* !CONFIG_KPROBES_ON_FTRACE */ #define prepare_kprobe (p) arch_prepare_kprobe(p) -#define arm_kprobe_ftrace (p) do {} while (0) +#define arm_kprobe_ftrace (p) (0)#define disarm_kprobe_ftrace (p) do {} while (0)#endif /* 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))) { -arm_kprobe_ftrace (kp); -return ; - } +if (unlikely (kprobe_ftrace (kp))) +return arm_kprobe_ftrace (kp); +cpus_read_lock ();mutex_lock (&text_mutex);__arm_kprobe (kp);mutex_unlock (&text_mutex);cpus_read_unlock (); + +return 0 ; }/* Disarm a kprobe with text_mutex */
@@ -1363,9 +1373,14 @@ static int register_aggr_kprobe(struct kprobe *orig_p, struct kprobe *p)if (ret ==0 &&kprobe_disabled (ap) && !kprobe_disabled (p)) { ap->flags &= ~KPROBE_FLAG_DISABLED ; -if (!kprobes_all_disarmed) +if (!kprobes_all_disarmed) {/* Arm the breakpoint again. */ -arm_kprobe (ap); + ret =arm_kprobe (ap); +if (ret) { + ap->flags |=KPROBE_FLAG_DISABLED ; +list_del_rcu (&p->list); + } + } }return ret; }
@@ -1570,13 +1585,16 @@ int register_kprobe(struct kprobe *p)if (ret) goto out; +if (!kprobes_all_disarmed && !kprobe_disabled (p)) { + ret =arm_kprobe (p); +if (ret) + goto out; + } +INIT_HLIST_NODE (&p->hlist);hlist_add_head_rcu (&p->hlist, &kprobe_table[hash_ptr (p->addr,KPROBE_HASH_BITS )]); -if (!kprobes_all_disarmed && !kprobe_disabled (p)) -arm_kprobe (p); -/* Try to optimize kprobe */ try_to_optimize_kprobe (p); out:
@@ -2115,7 +2133,9 @@ int enable_kprobe(struct kprobe *kp)if (!kprobes_all_disarmed &&kprobe_disabled (p)) { p->flags &= ~KPROBE_FLAG_DISABLED ; -arm_kprobe (p); + ret =arm_kprobe (p); +if (ret) + p->flags |=KPROBE_FLAG_DISABLED ; } out:mutex_unlock (&kprobe_mutex);
@@ -2406,11 +2426,12 @@ static const struct file_operations debugfs_kprobe_blacklist_ops = { .release = seq_release, }; -static void arm_all_kprobes (void ) +static int arm_all_kprobes (void ) {struct hlist_head *head;struct kprobe *p; -unsigned int i; +unsigned int i, errors =0 ; +int err, ret =0 ;mutex_lock (&kprobe_mutex);
@@ -2427,16 +2448,26 @@ static void arm_all_kprobes(void)/* Arming kprobes doesn't optimize kprobe itself */ for (i =0 ; i <KPROBE_TABLE_SIZE ; i++) { head = &kprobe_table[i]; -hlist_for_each_entry_rcu (p, head, hlist) -if (!kprobe_disabled (p)) -arm_kprobe (p); +/* Arm all kprobes on a best-effort basis */ +hlist_for_each_entry_rcu (p, head, hlist) { +if (!kprobe_disabled (p)) { + err =arm_kprobe (p); +if (err) { + errors++; + ret = err; + } + } + } } -printk (KERN_INFO "Kprobes globally enabled\n" ); +if (errors) +pr_warn ("Kprobes globally enabled, but failed to arm %d kprobes\n" , errors); +else +pr_info ("Kprobes globally enabled\n" ); already_enabled:mutex_unlock (&kprobe_mutex); -return ; +return ret; }static void disarm_all_kprobes (void )
@@ -2493,6 +2524,7 @@ static ssize_t write_enabled_file_bool(struct file *file, {char buf[32 ];size_t buf_size; +int ret =0 ; buf_size =min (count, (sizeof (buf)-1 ));if (copy_from_user (buf, user_buf, buf_size))
@@ -2503,7 +2535,7 @@ static ssize_t write_enabled_file_bool(struct file *file,case 'y' :case 'Y' :case '1' : -arm_all_kprobes (); + ret =arm_all_kprobes ();break ;case 'n' :case 'N' :
@@ -2514,6 +2546,9 @@ static ssize_t write_enabled_file_bool(struct file *file,return -EINVAL ; } +if (ret) +return ret; +return count; }
Signature · 1 line
2.13.6
Masami Hiramatsu
maintainerRe: [PATCH 1/2] kprobes: propagate error from arm_kprobe_ftrace()
Hi Jessica,
Quoted 16 lines · On Wed, 4 Oct 2017 21:14:13 +0200 Jessica Yu <[email protected]> wrote:
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 (sincekprobe_ftrace_opshas this flag set) and returns an error. Such a conflict is possible since livepatches also set the IPMODIFY flag for theirftrace_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 kprobes could be armed.
Quoted 7 lines
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/452However, further work on this had been paused since then and the patches were not upstreamed.
Ok, I have some comment. See below.
Quoted 77 lines
Based-on-patches-by Petr Mladek <[email protected]>Signed-off-by Jessica Yu <[email protected]>--- kernel/kprobes.c | 87 +++++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 61 insertions(+), 26 deletions(-)kernel/kprobes.c +22
-12@@ -979,18 +979,27 @@ static int prepare_kprobe(struct kprobe *p) }/* 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, (unsigned long )p->addr,0 ,0 ); -WARN (ret <0 ,"Failed to arm kprobe-ftrace at %p (%d)\n" , p->addr, ret); - kprobe_ftrace_enabled++; -if (kprobe_ftrace_enabled ==1 ) { +if (WARN (ret <0 ,"Failed to arm kprobe-ftrace at %p (%d)\n" , p->addr, ret)) +return ret; + +if (kprobe_ftrace_enabled ==0 ) { ret =register_ftrace_function (&kprobe_ftrace_ops); -WARN (ret <0 ,"Failed to init kprobe-ftrace (%d)\n" , ret); +if (WARN (ret <0 ,"Failed to init kprobe-ftrace (%d)\n" , ret)) + goto err_ftrace; } + + kprobe_ftrace_enabled++; +return ret; + +err_ftrace: +ftrace_set_filter_ip (&kprobe_ftrace_ops, (unsigned long )p->addr,1 ,0 ); +return ret; }/* Caller must lock kprobe_mutex */ @@ -1009,22 +1018,23 @@ static void disarm_kprobe_ftrace(struct kprobe *p) }#else /* !CONFIG_KPROBES_ON_FTRACE */ #define prepare_kprobe (p) arch_prepare_kprobe(p) -#define arm_kprobe_ftrace (p) do {} while (0) +#define arm_kprobe_ftrace (p) (0)#define disarm_kprobe_ftrace (p) do {} while (0)#endif /* 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))) { -arm_kprobe_ftrace (kp); -return ; - } +if (unlikely (kprobe_ftrace (kp))) +return arm_kprobe_ftrace (kp); +cpus_read_lock ();mutex_lock (&text_mutex);__arm_kprobe (kp);mutex_unlock (&text_mutex);cpus_read_unlock (); + +return 0 ; }/* Disarm a kprobe with text_mutex */ @@ -1363,9 +1373,14 @@ static int register_aggr_kprobe(struct kprobe *orig_p, struct kprobe *p)
kernel/kprobes.c static int register_aggr_kprobe(struct kprobe *orig_p, struct kprobe *p) · line 1381
Quoted 10 lines
kernel/kprobes.c +5
-2if (ret ==0 &&kprobe_disabled (ap) && !kprobe_disabled (p)) { ap->flags &= ~KPROBE_FLAG_DISABLED ; -if (!kprobes_all_disarmed) +if (!kprobes_all_disarmed) {/* Arm the breakpoint again. */ -arm_kprobe (ap); + ret =arm_kprobe (ap); +if (ret) { + ap->flags |=KPROBE_FLAG_DISABLED ; +list_del_rcu (&p->list);
Nice catch :) this list_del_rcu() is important to keep error case behavior sane.
Quoted 6 lines
kernel/kprobes.c +2
-0+ } + } }return ret; }
kernel/kprobes.c int register_kprobe(struct kprobe *p) · line 1593
Quoted 9 lines
kernel/kprobes.c +6
-0@@ -1570,13 +1585,16 @@ int register_kprobe(struct kprobe *p)if (ret) goto out; +if (!kprobes_all_disarmed && !kprobe_disabled (p)) { + ret =arm_kprobe (p); +if (ret) + goto out; + } +
No, this is no good. It is a small chance to hit kprobe on other CPUs before adding it to kprobe_table hashlist. In that case, we will see a stray breakpoint instruction.
kernel/kprobes.c int register_kprobe(struct kprobe *p) · line 1579
Quoted 7 lines
kernel/kprobes.c +0
-3INIT_HLIST_NODE (&p->hlist);hlist_add_head_rcu (&p->hlist, &kprobe_table[hash_ptr (p->addr,KPROBE_HASH_BITS )]); -if (!kprobes_all_disarmed && !kprobe_disabled (p)) -arm_kprobe (p); -
So, you'll have to rollback by hlist_del_rcu() here.
Hmm, by the way, in this case, you also have to add a synchronize_rcu() in the end of error path, so that user can release kprobe right after error return of register_kprobe... (I think that's OK because it is not a hot path)
Thank you,
Signature · 1 line
Masami Hiramatsu <[email protected]>
Re: kprobes: propagate error from arm_kprobe_ftrace()
+++ Masami Hiramatsu [05/10/17 06:23 +0000]:
Quoted 131 lines
Hi Jessica,
Quoted 16 lines · On Wed, 4 Oct 2017 21:14:13 +0200 Jessica Yu <[email protected]> wrote:
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 (sincekprobe_ftrace_opshas this flag set) and returns an error. Such a conflict is possible since livepatches also set the IPMODIFY flag for theirftrace_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 kprobes could be armed.Quoted 7 lines
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/452However, further work on this had been paused since then and the patches were not upstreamed.
Ok, I have some comment. See below.
Quoted 77 lines
Based-on-patches-by Petr Mladek <[email protected]>Signed-off-by Jessica Yu <[email protected]>--- kernel/kprobes.c | 87 +++++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 61 insertions(+), 26 deletions(-)kernel/kprobes.c +22
-12@@ -979,18 +979,27 @@ static int prepare_kprobe(struct kprobe *p) }/* 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, (unsigned long )p->addr,0 ,0 ); -WARN (ret <0 ,"Failed to arm kprobe-ftrace at %p (%d)\n" , p->addr, ret); - kprobe_ftrace_enabled++; -if (kprobe_ftrace_enabled ==1 ) { +if (WARN (ret <0 ,"Failed to arm kprobe-ftrace at %p (%d)\n" , p->addr, ret)) +return ret; + +if (kprobe_ftrace_enabled ==0 ) { ret =register_ftrace_function (&kprobe_ftrace_ops); -WARN (ret <0 ,"Failed to init kprobe-ftrace (%d)\n" , ret); +if (WARN (ret <0 ,"Failed to init kprobe-ftrace (%d)\n" , ret)) + goto err_ftrace; } + + kprobe_ftrace_enabled++; +return ret; + +err_ftrace: +ftrace_set_filter_ip (&kprobe_ftrace_ops, (unsigned long )p->addr,1 ,0 ); +return ret; }/* Caller must lock kprobe_mutex */ @@ -1009,22 +1018,23 @@ static void disarm_kprobe_ftrace(struct kprobe *p) }#else /* !CONFIG_KPROBES_ON_FTRACE */ #define prepare_kprobe (p) arch_prepare_kprobe(p) -#define arm_kprobe_ftrace (p) do {} while (0) +#define arm_kprobe_ftrace (p) (0)#define disarm_kprobe_ftrace (p) do {} while (0)#endif /* 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))) { -arm_kprobe_ftrace (kp); -return ; - } +if (unlikely (kprobe_ftrace (kp))) +return arm_kprobe_ftrace (kp); +cpus_read_lock ();mutex_lock (&text_mutex);__arm_kprobe (kp);mutex_unlock (&text_mutex);cpus_read_unlock (); + +return 0 ; }/* Disarm a kprobe with text_mutex */ @@ -1363,9 +1373,14 @@ static int register_aggr_kprobe(struct kprobe *orig_p, struct kprobe *p)kernel/kprobes.c static int register_aggr_kprobe(struct kprobe *orig_p, struct kprobe *p) · line 1381
Quoted 10 lines
kernel/kprobes.c +5
-2if (ret ==0 &&kprobe_disabled (ap) && !kprobe_disabled (p)) { ap->flags &= ~KPROBE_FLAG_DISABLED ; -if (!kprobes_all_disarmed) +if (!kprobes_all_disarmed) {/* Arm the breakpoint again. */ -arm_kprobe (ap); + ret =arm_kprobe (ap); +if (ret) { + ap->flags |=KPROBE_FLAG_DISABLED ; +list_del_rcu (&p->list);Nice catch :) this
list_del_rcu()is important to keep error case behavior sane.Quoted 9 lines
kernel/kprobes.c +2
-0+ } + } }return ret; }@@ -1570,13 +1585,16 @@ int register_kprobe(struct kprobe *p)if (ret) goto out;
Quoted 10 lines
kernel/kprobes.c int register_kprobe(struct kprobe *p) · line 1593
Quoted 6 lines
kernel/kprobes.c +6
-0+if (!kprobes_all_disarmed && !kprobe_disabled (p)) { + ret =arm_kprobe (p); +if (ret) + goto out; + } +No, this is no good. It is a small chance to hit kprobe on other CPUs before adding it to
kprobe_tablehashlist. In that case, we will see a stray breakpoint instruction.
Ah yes, you are right, this is incorrect. There is a short window of time where we could have a stray breakpoint from an armed kprobe, but the breakpoint handler would not be able to find the associated kprobe in the hashlist. Will fix this in v2.
Quoted 4 lines
Quoted 4 lines
kernel/kprobes.c +0
-0INIT_HLIST_NODE (&p->hlist);hlist_add_head_rcu (&p->hlist, &kprobe_table[hash_ptr (p->addr,KPROBE_HASH_BITS )]);
Quoted 9 lines
kernel/kprobes.c int register_kprobe(struct kprobe *p) · line 1579
Quoted 3 lines
kernel/kprobes.c +0
-3-if (!kprobes_all_disarmed && !kprobe_disabled (p)) -arm_kprobe (p); -So, you'll have to rollback by
hlist_del_rcu()here.
Hmm, by the way, in this case, you also have to add asynchronize_rcu()in the end of error path, so that user can release kprobe right after error return ofregister_kprobe... (I think that's OK because it is not a hot path)
Yes, I'll fix this in the error path as well. Thank you for your comments! Will send a v2.
Jessica
PATCH 2/2 kprobes: propagate error from disarm_kprobe_ftrace()
In reply to Jessica YuImprove error handling when disarming ftrace-based kprobes. Like with arm_kprobe_ftrace(), propagate any errors from disarm_kprobe_ftrace() so that we do not disable/unregister kprobes that are still armed. In other words, unregister_kprobe() and disable_kprobe() should not report success if the kprobe could not be disarmed.
disarm_all_kprobes() keeps its current behavior and attempts to disarm all kprobes. It returns the last encountered error and gives a warning if not all kprobes could be disarmed.
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.
--- kernel/kprobes.c | 76 +++++++++++++++++++++++++++++++++++++------------------- 1 file changed, 51 insertions(+), 25 deletions(-)
kernel/kprobes.c +51 -25
@@ -1003,23 +1003,27 @@ static int arm_kprobe_ftrace(struct kprobe *p) }/* Caller must lock kprobe_mutex */ -static void disarm_kprobe_ftrace (struct kprobe *p) +static int disarm_kprobe_ftrace (struct kprobe *p) { -int ret; +int ret =0 ; - kprobe_ftrace_enabled--; -if (kprobe_ftrace_enabled ==0 ) { +if (kprobe_ftrace_enabled ==1 ) { ret =unregister_ftrace_function (&kprobe_ftrace_ops); -WARN (ret <0 ,"Failed to init kprobe-ftrace (%d)\n" , ret); +if (WARN (ret <0 ,"Failed to unregister kprobe-ftrace (%d)\n" , ret)) +return ret; } + + kprobe_ftrace_enabled--; + ret =ftrace_set_filter_ip (&kprobe_ftrace_ops, (unsigned long )p->addr,1 ,0 );WARN (ret <0 ,"Failed to disarm kprobe-ftrace at %p (%d)\n" , p->addr, ret); +return ret; }#else /* !CONFIG_KPROBES_ON_FTRACE */ #define prepare_kprobe (p) arch_prepare_kprobe(p)#define arm_kprobe_ftrace (p) (0) -#define disarm_kprobe_ftrace (p) do {} while (0) +#define disarm_kprobe_ftrace (p) (0)#endif /* Arm a kprobe with text_mutex */
@@ -1038,18 +1042,18 @@ static int arm_kprobe(struct kprobe *kp) }/* Disarm a kprobe with text_mutex */ -static void disarm_kprobe (struct kprobe *kp,bool reopt) +static int disarm_kprobe (struct kprobe *kp,bool reopt) { -if (unlikely (kprobe_ftrace (kp))) { -disarm_kprobe_ftrace (kp); -return ; - } +if (unlikely (kprobe_ftrace (kp))) +return disarm_kprobe_ftrace (kp);cpus_read_lock ();mutex_lock (&text_mutex);__disarm_kprobe (kp, reopt);mutex_unlock (&text_mutex);cpus_read_unlock (); + +return 0 ; }/*
@@ -1627,11 +1631,12 @@ static int aggr_kprobe_disabled(struct kprobe *ap)static struct kprobe *__disable_kprobe (struct kprobe *p) {struct kprobe *orig_p; +int ret;/* Get an original kprobe for return */ orig_p =__get_valid_kprobe (p);if (unlikely (orig_p ==NULL )) -return NULL ; +return ERR_PTR (-EINVAL );if (!kprobe_disabled (p)) {/* Disable probe if it is a child probe */
@@ -1645,8 +1650,13 @@ static struct kprobe *__disable_kprobe(struct kprobe *p)* should have already been disarmed, so * skip unneed disarming process. */ -if (!kprobes_all_disarmed) -disarm_kprobe (orig_p, true); +if (!kprobes_all_disarmed) { + ret =disarm_kprobe (orig_p, true); +if (ret) { + p->flags &= ~KPROBE_FLAG_DISABLED ; +return ERR_PTR (ret); + } + } orig_p->flags |=KPROBE_FLAG_DISABLED ; } }
@@ -1663,8 +1673,8 @@ static int __unregister_kprobe_top(struct kprobe *p)/* Disable kprobe. This will disarm it if needed. */ ap =__disable_kprobe (p); -if (ap ==NULL ) -return -EINVAL ; +if (IS_ERR (ap)) +return PTR_ERR (ap);if (ap == p)/*
@@ -2095,12 +2105,14 @@ static void kill_kprobe(struct kprobe *p)int disable_kprobe (struct kprobe *kp) {int ret =0 ; +struct kprobe *p;mutex_lock (&kprobe_mutex);/* Disable this kprobe */ -if (__disable_kprobe (kp) ==NULL ) - ret = -EINVAL ; + p =__disable_kprobe (kp); +if (IS_ERR (p)) + ret =PTR_ERR (p);mutex_unlock (&kprobe_mutex);return ret;
@@ -2470,34 +2482,48 @@ static int arm_all_kprobes(void)return ret; } -static void disarm_all_kprobes (void ) +static int disarm_all_kprobes (void ) {struct hlist_head *head;struct kprobe *p; -unsigned int i; +unsigned int i, errors =0 ; +int err, ret =0 ;mutex_lock (&kprobe_mutex);/* If kprobes are already disarmed, just return */ if (kprobes_all_disarmed) {mutex_unlock (&kprobe_mutex); -return ; +return 0 ; } kprobes_all_disarmed = true; -printk (KERN_INFO "Kprobes globally disabled\n" );for (i =0 ; i <KPROBE_TABLE_SIZE ; i++) { head = &kprobe_table[i]; +/* Disarm all kprobes on a best-effort basis */ hlist_for_each_entry_rcu (p, head, hlist) { -if (!arch_trampoline_kprobe (p) && !kprobe_disabled (p)) -disarm_kprobe (p, false); +if (!arch_trampoline_kprobe (p) && !kprobe_disabled (p)) { + err =disarm_kprobe (p, false); +if (err) { + errors++; + ret = err; + } + } } } + +if (errors) +pr_warn ("Kprobes globally disabled, but failed to disarm %d kprobes\n" , errors); +else +pr_info ("Kprobes globally disabled\n" ); +mutex_unlock (&kprobe_mutex);/* Wait for disarming all kprobes by optimizer */ wait_for_kprobe_optimizer (); + +return ret; }/*
@@ -2540,7 +2566,7 @@ static ssize_t write_enabled_file_bool(struct file *file,case 'n' :case 'N' :case '0' : -disarm_all_kprobes (); + ret =disarm_all_kprobes ();break ;default :return -EINVAL ;
Signature · 1 line
2.13.6
Re: [PATCH 2/2] kprobes: propagate error from disarm_kprobe_ftrace()
Quoted 10 lines · On Wed, 4 Oct 2017 21:14:14 +0200 Jessica Yu <[email protected]> wrote:
Improve error handling when disarming ftrace-based kprobes. Like with
arm_kprobe_ftrace(), propagate any errors fromdisarm_kprobe_ftrace()so that we do not disable/unregister kprobes that are still armed. In other words,unregister_kprobe()anddisable_kprobe()should not report success if the kprobe could not be disarmed.
disarm_all_kprobes()keeps its current behavior and attempts to disarm all kprobes. It returns the last encountered error and gives a warning if not all kprobes could be disarmed.
Quoted 8 lines
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/452However, further work on this had been paused since then and the patches were not upstreamed.
OK, this looks good to me :)
Thanks!
Quoted 193 lines
Based-on-patches-by Petr Mladek <[email protected]>Signed-off-by Jessica Yu <[email protected]>--- kernel/kprobes.c | 76 +++++++++++++++++++++++++++++++++++++------------------- 1 file changed, 51 insertions(+), 25 deletions(-)kernel/kprobes.c +51
-25@@ -1003,23 +1003,27 @@ static int arm_kprobe_ftrace(struct kprobe *p) }/* Caller must lock kprobe_mutex */ -static void disarm_kprobe_ftrace (struct kprobe *p) +static int disarm_kprobe_ftrace (struct kprobe *p) { -int ret; +int ret =0 ; - kprobe_ftrace_enabled--; -if (kprobe_ftrace_enabled ==0 ) { +if (kprobe_ftrace_enabled ==1 ) { ret =unregister_ftrace_function (&kprobe_ftrace_ops); -WARN (ret <0 ,"Failed to init kprobe-ftrace (%d)\n" , ret); +if (WARN (ret <0 ,"Failed to unregister kprobe-ftrace (%d)\n" , ret)) +return ret; } + + kprobe_ftrace_enabled--; + ret =ftrace_set_filter_ip (&kprobe_ftrace_ops, (unsigned long )p->addr,1 ,0 );WARN (ret <0 ,"Failed to disarm kprobe-ftrace at %p (%d)\n" , p->addr, ret); +return ret; }#else /* !CONFIG_KPROBES_ON_FTRACE */ #define prepare_kprobe (p) arch_prepare_kprobe(p)#define arm_kprobe_ftrace (p) (0) -#define disarm_kprobe_ftrace (p) do {} while (0) +#define disarm_kprobe_ftrace (p) (0)#endif /* Arm a kprobe with text_mutex */ @@ -1038,18 +1042,18 @@ static int arm_kprobe(struct kprobe *kp) }/* Disarm a kprobe with text_mutex */ -static void disarm_kprobe (struct kprobe *kp,bool reopt) +static int disarm_kprobe (struct kprobe *kp,bool reopt) { -if (unlikely (kprobe_ftrace (kp))) { -disarm_kprobe_ftrace (kp); -return ; - } +if (unlikely (kprobe_ftrace (kp))) +return disarm_kprobe_ftrace (kp);cpus_read_lock ();mutex_lock (&text_mutex);__disarm_kprobe (kp, reopt);mutex_unlock (&text_mutex);cpus_read_unlock (); + +return 0 ; }/* @@ -1627,11 +1631,12 @@ static int aggr_kprobe_disabled(struct kprobe *ap)static struct kprobe *__disable_kprobe (struct kprobe *p) {struct kprobe *orig_p; +int ret;/* Get an original kprobe for return */ orig_p =__get_valid_kprobe (p);if (unlikely (orig_p ==NULL )) -return NULL ; +return ERR_PTR (-EINVAL );if (!kprobe_disabled (p)) {/* Disable probe if it is a child probe */ @@ -1645,8 +1650,13 @@ static struct kprobe *__disable_kprobe(struct kprobe *p)* should have already been disarmed, so * skip unneed disarming process. */ -if (!kprobes_all_disarmed) -disarm_kprobe (orig_p, true); +if (!kprobes_all_disarmed) { + ret =disarm_kprobe (orig_p, true); +if (ret) { + p->flags &= ~KPROBE_FLAG_DISABLED ; +return ERR_PTR (ret); + } + } orig_p->flags |=KPROBE_FLAG_DISABLED ; } }@@ -1663,8 +1673,8 @@ static int __unregister_kprobe_top(struct kprobe *p)/* Disable kprobe. This will disarm it if needed. */ ap =__disable_kprobe (p); -if (ap ==NULL ) -return -EINVAL ; +if (IS_ERR (ap)) +return PTR_ERR (ap);if (ap == p)/* @@ -2095,12 +2105,14 @@ static void kill_kprobe(struct kprobe *p)int disable_kprobe (struct kprobe *kp) {int ret =0 ; +struct kprobe *p;mutex_lock (&kprobe_mutex);/* Disable this kprobe */ -if (__disable_kprobe (kp) ==NULL ) - ret = -EINVAL ; + p =__disable_kprobe (kp); +if (IS_ERR (p)) + ret =PTR_ERR (p);mutex_unlock (&kprobe_mutex);return ret;@@ -2470,34 +2482,48 @@ static int arm_all_kprobes(void)return ret; } -static void disarm_all_kprobes (void ) +static int disarm_all_kprobes (void ) {struct hlist_head *head;struct kprobe *p; -unsigned int i; +unsigned int i, errors =0 ; +int err, ret =0 ;mutex_lock (&kprobe_mutex);/* If kprobes are already disarmed, just return */ if (kprobes_all_disarmed) {mutex_unlock (&kprobe_mutex); -return ; +return 0 ; } kprobes_all_disarmed = true; -printk (KERN_INFO "Kprobes globally disabled\n" );for (i =0 ; i <KPROBE_TABLE_SIZE ; i++) { head = &kprobe_table[i]; +/* Disarm all kprobes on a best-effort basis */ hlist_for_each_entry_rcu (p, head, hlist) { -if (!arch_trampoline_kprobe (p) && !kprobe_disabled (p)) -disarm_kprobe (p, false); +if (!arch_trampoline_kprobe (p) && !kprobe_disabled (p)) { + err =disarm_kprobe (p, false); +if (err) { + errors++; + ret = err; + } + } } } + +if (errors) +pr_warn ("Kprobes globally disabled, but failed to disarm %d kprobes\n" , errors); +else +pr_info ("Kprobes globally disabled\n" ); +mutex_unlock (&kprobe_mutex);/* Wait for disarming all kprobes by optimizer */ wait_for_kprobe_optimizer (); + +return ret; }/* @@ -2540,7 +2566,7 @@ static ssize_t write_enabled_file_bool(struct file *file,case 'n' :case 'N' :case '0' : -disarm_all_kprobes (); + ret =disarm_all_kprobes ();break ;default :return -EINVAL ;Signature · 1 line
2.13.6
Signature · 1 line
Masami Hiramatsu <[email protected]>
Masami,
Can you review these patches?
-- Steve
Quoted 64 lines · On Wed, 4 Oct 2017 21:14:12 +0200 Jessica Yu <[email protected]> wrote:
Hi,
This patchset attempts to improve error handling when arming or disarming ftrace-based kprobes. The current behavior is to simply WARN when ftrace (un-)registration fails, without propagating the error code. This can lead to confusing situations where, for example, register_kprobe()/enable_kprobe() would return 0 indicating success even if arming via ftrace had failed. In this scenario we'd end up with a non-functioning kprobe even though kprobe registration (or enablement) returned success. In this patchset, we take errors from ftrace into account and propagate the error when we cannot arm or disarm a kprobe.
Below is an example that illustrates the problem using livepatch and systemtap (which uses kprobes underneath). Both livepatch and kprobes use ftrace ops with the IPMODIFY flag set, so registration at the same function entry is limited to only one ftrace user.
Before
------# modprobe livepatch-sample # patches cmdline_proc_show, ftrace ops has IPMODIFY set # stap -e 'probe kernel.function("cmdline_proc_show").call { printf ("cmdline_proc_show\n"); }'.. (nothing prints after reading
/proc/cmdline) ..The systemtap handler doesn't execute due to a kprobe arming failure caused by a ftrace IPMODIFY conflict with livepatch, and there isn't an obvious indication of error from systemtap (because
register_kprobe()returned success) unless the user inspects dmesg.After
-----# modprobe livepatch-sample # stap -e 'probe kernel.function("cmdline_proc_show").call { printf ("cmdline_proc_show\n"); }' WARNING: probe kernel.function("cmdline_proc_show@/home/jeyu/work/linux-next/fs/proc/cmdline.c:6").call (address 0xffffffffa82fe910) registration error (rc -16)Although the systemtap handler doesn't execute (as it shouldn't), the ftrace error is propagated and now systemtap prints a visible error message stating that (kprobe) registration had failed (because
register_kprobe()returned an error), along with the propagated error code.This patchset was 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/452However, further work on this had been paused since then and the patches were not upstreamed.
This patchset has been lightly sanity-tested (on linux-next) with kprobes, kretprobes, jprobes, and optimized kprobes. It passes the kprobes smoke test, but more testing is greatly appreciated.
Thanks,
Jessica
---
Jessica Yu (2):
kprobes: propagate error fromarm_kprobe_ftrace()
kprobes: propagate error fromdisarm_kprobe_ftrace()kernel/kprobes.c | 163 ++++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 112 insertions(+), 51 deletions(-)
Masami Hiramatsu
maintainerQuoted 3 lines · On Wed, 4 Oct 2017 15:44:13 -0400 Steven Rostedt <[email protected]> wrote:
Masami,
Can you review these patches?
Yes, of course :)
Quoted 35 lines
-- Steve
Quoted 28 lines · On Wed, 4 Oct 2017 21:14:12 +0200 Jessica Yu <[email protected]> wrote:
Hi,
This patchset attempts to improve error handling when arming or disarming ftrace-based kprobes. The current behavior is to simply WARN when ftrace (un-)registration fails, without propagating the error code. This can lead to confusing situations where, for example, register_kprobe()/enable_kprobe() would return 0 indicating success even if arming via ftrace had failed. In this scenario we'd end up with a non-functioning kprobe even though kprobe registration (or enablement) returned success. In this patchset, we take errors from ftrace into account and propagate the error when we cannot arm or disarm a kprobe.
Below is an example that illustrates the problem using livepatch and systemtap (which uses kprobes underneath). Both livepatch and kprobes use ftrace ops with the IPMODIFY flag set, so registration at the same function entry is limited to only one ftrace user.
Before
------# modprobe livepatch-sample # patches cmdline_proc_show, ftrace ops has IPMODIFY set # stap -e 'probe kernel.function("cmdline_proc_show").call { printf ("cmdline_proc_show\n"); }'.. (nothing prints after reading
/proc/cmdline) ..The systemtap handler doesn't execute due to a kprobe arming failure caused by a ftrace IPMODIFY conflict with livepatch, and there isn't an obvious indication of error from systemtap (because
register_kprobe()returned success) unless the user inspects dmesg.
Ah, right. It should be handled.
Quoted 11 lines
Quoted 11 lines
After
-----# modprobe livepatch-sample # stap -e 'probe kernel.function("cmdline_proc_show").call { printf ("cmdline_proc_show\n"); }' WARNING: probe kernel.function("cmdline_proc_show@/home/jeyu/work/linux-next/fs/proc/cmdline.c:6").call (address 0xffffffffa82fe910) registration error (rc -16)Although the systemtap handler doesn't execute (as it shouldn't), the ftrace error is propagated and now systemtap prints a visible error message stating that (kprobe) registration had failed (because
register_kprobe()returned an error), along with the propagated error code.
Would you have any test-case for this livepatch-kprobes combination?
Thank you,
Quoted 26 lines
Quoted 25 lines
This patchset was 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/452However, further work on this had been paused since then and the patches were not upstreamed.
This patchset has been lightly sanity-tested (on linux-next) with kprobes, kretprobes, jprobes, and optimized kprobes. It passes the kprobes smoke test, but more testing is greatly appreciated.
Thanks,
Jessica
---
Jessica Yu (2):
kprobes: propagate error fromarm_kprobe_ftrace()
kprobes: propagate error fromdisarm_kprobe_ftrace()kernel/kprobes.c | 163 ++++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 112 insertions(+), 51 deletions(-)
Signature · 1 line
Masami Hiramatsu <[email protected]>