kprobes: Allow kprobes coexist with livepatch
authorMasami Hiramatsu <mhiramat@kernel.org>
Thu, 25 Jul 2019 06:24:37 +0000 (15:24 +0900)
committerSteven Rostedt (VMware) <rostedt@goodmis.org>
Sat, 31 Aug 2019 16:19:10 +0000 (12:19 -0400)
Allow kprobes which do not modify regs->ip, coexist with livepatch
by dropping FTRACE_OPS_FL_IPMODIFY from ftrace_ops.

User who wants to modify regs->ip (e.g. function fault injection)
must set a dummy post_handler to its kprobes when registering.
However, if such regs->ip modifying kprobes is set on a function,
that function can not be livepatched.

Link: http://lkml.kernel.org/r/156403587671.30117.5233558741694155985.stgit@devnote2
Acked-by: Joe Lawrence <joe.lawrence@redhat.com>
Signed-off-by: Masami Hiramatsu <mhiramat@kernel.org>
Signed-off-by: Steven Rostedt (VMware) <rostedt@goodmis.org>
kernel/kprobes.c

index d9770a5393c89925d2fa3f391dfb341a34a70409..f57deec96ba16e9094b49880b7863d5d30e53a60 100644 (file)
@@ -961,9 +961,16 @@ static struct kprobe *alloc_aggr_kprobe(struct kprobe *p)
 
 #ifdef CONFIG_KPROBES_ON_FTRACE
 static struct ftrace_ops kprobe_ftrace_ops __read_mostly = {
+       .func = kprobe_ftrace_handler,
+       .flags = FTRACE_OPS_FL_SAVE_REGS,
+};
+
+static struct ftrace_ops kprobe_ipmodify_ops __read_mostly = {
        .func = kprobe_ftrace_handler,
        .flags = FTRACE_OPS_FL_SAVE_REGS | FTRACE_OPS_FL_IPMODIFY,
 };
+
+static int kprobe_ipmodify_enabled;
 static int kprobe_ftrace_enabled;
 
 /* Must ensure p->addr is really on ftrace */
@@ -976,58 +983,75 @@ static int prepare_kprobe(struct kprobe *p)
 }
 
 /* Caller must lock kprobe_mutex */
-static int arm_kprobe_ftrace(struct kprobe *p)
+static int __arm_kprobe_ftrace(struct kprobe *p, struct ftrace_ops *ops,
+                              int *cnt)
 {
        int ret = 0;
 
-       ret = ftrace_set_filter_ip(&kprobe_ftrace_ops,
-                                  (unsigned long)p->addr, 0, 0);
+       ret = ftrace_set_filter_ip(ops, (unsigned long)p->addr, 0, 0);
        if (ret) {
                pr_debug("Failed to arm kprobe-ftrace at %pS (%d)\n",
                         p->addr, ret);
                return ret;
        }
 
-       if (kprobe_ftrace_enabled == 0) {
-               ret = register_ftrace_function(&kprobe_ftrace_ops);
+       if (*cnt == 0) {
+               ret = register_ftrace_function(ops);
                if (ret) {
                        pr_debug("Failed to init kprobe-ftrace (%d)\n", ret);
                        goto err_ftrace;
                }
        }
 
-       kprobe_ftrace_enabled++;
+       (*cnt)++;
        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.
+        * At this point, sinec ops is not registered, we should be sefe from
+        * registering empty filter.
         */
-       ftrace_set_filter_ip(&kprobe_ftrace_ops, (unsigned long)p->addr, 1, 0);
+       ftrace_set_filter_ip(ops, (unsigned long)p->addr, 1, 0);
        return ret;
 }
 
+static int arm_kprobe_ftrace(struct kprobe *p)
+{
+       bool ipmodify = (p->post_handler != NULL);
+
+       return __arm_kprobe_ftrace(p,
+               ipmodify ? &kprobe_ipmodify_ops : &kprobe_ftrace_ops,
+               ipmodify ? &kprobe_ipmodify_enabled : &kprobe_ftrace_enabled);
+}
+
 /* Caller must lock kprobe_mutex */
-static int disarm_kprobe_ftrace(struct kprobe *p)
+static int __disarm_kprobe_ftrace(struct kprobe *p, struct ftrace_ops *ops,
+                                 int *cnt)
 {
        int ret = 0;
 
-       if (kprobe_ftrace_enabled == 1) {
-               ret = unregister_ftrace_function(&kprobe_ftrace_ops);
+       if (*cnt == 1) {
+               ret = unregister_ftrace_function(ops);
                if (WARN(ret < 0, "Failed to unregister kprobe-ftrace (%d)\n", ret))
                        return ret;
        }
 
-       kprobe_ftrace_enabled--;
+       (*cnt)--;
 
-       ret = ftrace_set_filter_ip(&kprobe_ftrace_ops,
-                          (unsigned long)p->addr, 1, 0);
+       ret = ftrace_set_filter_ip(ops, (unsigned long)p->addr, 1, 0);
        WARN_ONCE(ret < 0, "Failed to disarm kprobe-ftrace at %pS (%d)\n",
                  p->addr, ret);
        return ret;
 }
+
+static int disarm_kprobe_ftrace(struct kprobe *p)
+{
+       bool ipmodify = (p->post_handler != NULL);
+
+       return __disarm_kprobe_ftrace(p,
+               ipmodify ? &kprobe_ipmodify_ops : &kprobe_ftrace_ops,
+               ipmodify ? &kprobe_ipmodify_enabled : &kprobe_ftrace_enabled);
+}
 #else  /* !CONFIG_KPROBES_ON_FTRACE */
 #define prepare_kprobe(p)      arch_prepare_kprobe(p)
 #define arm_kprobe_ftrace(p)   (-ENODEV)