Invoke __perf_event_disable() if interrupts are already disabled & Simplify the perf-hwbp code, fix documentation in kernel-perf CVE-2018-1000199

Tracked by Jolla

asked 2018-06-26 07:46:48 +0300

this post is marked as community wiki

This post is a wiki. Anyone with karma >75 is welcome to improve it.

updated 2018-06-26 07:46:48 +0300

lpr gravatar image

The Linux Kernel version 3.18 contains a dangerous feature vulnerability in modify_user_hw_breakpoint() that can result in crash and possibly memory corruption. This attack appear to be exploitable via local code execution and the ability to use ptrace. This vulnerability appears to have been fixed in git commit f67b15037a7a50c57f72e69a6d59941ad90a0f0f.

Kernel-3.2 patches are available: |1| |2|

Files affected: kernel-adaptation-sbj-3.4.108.20171107.1/kernel/events/hw_breakpoint.c kernel-adaptation-sbj-3.4.108.20171107.1/include/linux/perf_event.h kernel-adaptation-sbj-3.4.108.20171107.1/kernel/events/core.c

So the whole patch should look like:

diff --git a/kernel/events/hw_breakpoint.c b/kernel/events/hw_breakpoint.c
index 609e120..eeb84bb 100644
--- a/kernel/events/hw_breakpoint.c
+++ b/kernel/events/hw_breakpoint.c
@@ -443,16 +443,9 @@ EXPORT_SYMBOL_GPL(register_user_hw_breakpoint);
  * modify_user_hw_breakpoint - modify a user-space hardware breakpoint
  * @bp: the breakpoint structure to modify
  * @attr: new breakpoint attributes
- * @triggered: callback to trigger when we hit the breakpoint
- * @tsk: pointer to 'task_struct' of the process to which the address belongs
  */
 int modify_user_hw_breakpoint(struct perf_event *bp, struct perf_event_attr *attr)
 {
-   u64 old_addr = bp->attr.bp_addr;
-   u64 old_len = bp->attr.bp_len;
-   int old_type = bp->attr.bp_type;
-   int err = 0;
-
    /*
     * modify_user_hw_breakpoint can be invoked with IRQs disabled and hence it
     * will not be possible to raise IPIs that invoke __perf_event_disable.

@@ -453,7 +446,16 @@ int modify_user_hw_breakpoint(struct perf_event *bp, struct perf_event_attr *att
    int old_type = bp->attr.bp_type;
    int err = 0;

-   perf_event_disable(bp);
+   /*
+    * modify_user_hw_breakpoint can be invoked with IRQs disabled and hence it
+    * will not be possible to raise IPIs that invoke __perf_event_disable.
+    * So call the function directly after making sure we are targeting the
+    * current task.
+    */
+   if (irqs_disabled() && bp->ctx && bp->ctx->task == current)
+       __perf_event_disable(bp);
+   else
+       perf_event_disable(bp);

    bp->attr.bp_addr = attr->bp_addr;
    bp->attr.bp_type = attr->bp_type;

@@ -458,27 +460,18 @@ int modify_user_hw_breakpoint(struct perf_event *bp, struct perf_event_attr *att
    bp->attr.bp_addr = attr->bp_addr;
    bp->attr.bp_type = attr->bp_type;
    bp->attr.bp_len = attr->bp_len;
+   bp->attr.disabled = 1;

-   if (attr->disabled)
-       goto end;
-
-   err = validate_hw_breakpoint(bp);
-   if (!err)
-       perf_event_enable(bp);
+   if (!attr->disabled) {
+       int err = validate_hw_breakpoint(bp);

-   if (err) {
-       bp->attr.bp_addr = old_addr;
-       bp->attr.bp_type = old_type;
-       bp->attr.bp_len = old_len;
-       if (!bp->attr.disabled)
-           perf_event_enable(bp);
+       if (err)
+           return err;

-       return err;
+       perf_event_enable(bp);
+       bp->attr.disabled = 0;
    }

-end:
-   bp->attr.disabled = attr->disabled;
-
    return 0;
 }
 EXPORT_SYMBOL_GPL(modify_user_hw_breakpoint);

diff --git a/include/linux/perf_event.h b/include/linux/perf_event.h
index 0aa2c4f..bee2357 100644
--- a/include/linux/perf_event.h
+++ b/include/linux/perf_event.h
@@ -1290,6 +1290,7 @@ extern int perf_swevent_get_recursion_context(void);
 extern void perf_swevent_put_recursion_context(int rctx);
 extern void perf_event_enable(struct perf_event *event);
 extern void perf_event_disable(struct perf_event *event);
+extern int __perf_event_disable(void *info);
 extern void perf_event_task_tick(void);
 #else
 static inline void
@@ -1328,6 +1329,7 @@ static inline int  perf_swevent_get_recursion_context(void)       { return -1; }
 static inline void perf_swevent_put_recursion_context(int rctx)        { }
 static inline void perf_event_enable(struct perf_event *event)     { }
 static inline void perf_event_disable(struct perf_event *event)        { }
+static inline int __perf_event_disable(void *info)         { return -1; }
 static inline void perf_event_task_tick(void)              { }
 #endif

diff --git a/kernel/events/core.c b/kernel/events/core.c
index 103b563..beeac8e 100644
--- a/kernel/events/core.c
+++ b/kernel/events/core.c
@@ -1290,7 +1290,7 @@ retry:
 /*
  * Cross CPU call to disable a performance event
  */
-static int __perf_event_disable(void *info)
+int __perf_event_disable(void *info)
 {
    struct perf_event *event = info;
    struct perf_event_context *ctx = event->ctx;
edit retag flag offensive close delete