perf: Fix list corruption in perf_cgroup_switch()
[linux-2.6-microblaze.git] / kernel / events / core.c
index 479c9e6..6859229 100644 (file)
@@ -839,7 +839,7 @@ static DEFINE_PER_CPU(struct list_head, cgrp_cpuctx_list);
  */
 static void perf_cgroup_switch(struct task_struct *task, int mode)
 {
-       struct perf_cpu_context *cpuctx;
+       struct perf_cpu_context *cpuctx, *tmp;
        struct list_head *list;
        unsigned long flags;
 
@@ -850,7 +850,7 @@ static void perf_cgroup_switch(struct task_struct *task, int mode)
        local_irq_save(flags);
 
        list = this_cpu_ptr(&cgrp_cpuctx_list);
-       list_for_each_entry(cpuctx, list, cgrp_cpuctx_entry) {
+       list_for_each_entry_safe(cpuctx, tmp, list, cgrp_cpuctx_entry) {
                WARN_ON_ONCE(cpuctx->ctx.nr_cgroups == 0);
 
                perf_ctx_lock(cpuctx, cpuctx->task_ctx);
@@ -2462,7 +2462,11 @@ static void perf_remove_from_context(struct perf_event *event, unsigned long fla
         * event_function_call() user.
         */
        raw_spin_lock_irq(&ctx->lock);
-       if (!ctx->is_active) {
+       /*
+        * Cgroup events are per-cpu events, and must IPI because of
+        * cgrp_cpuctx_list.
+        */
+       if (!ctx->is_active && !is_cgroup_event(event)) {
                __perf_remove_from_context(event, __get_cpu_context(ctx),
                                           ctx, (void *)flags);
                raw_spin_unlock_irq(&ctx->lock);
@@ -2895,11 +2899,14 @@ perf_install_in_context(struct perf_event_context *ctx,
         * perf_event_attr::disabled events will not run and can be initialized
         * without IPI. Except when this is the first event for the context, in
         * that case we need the magic of the IPI to set ctx->is_active.
+        * Similarly, cgroup events for the context also needs the IPI to
+        * manipulate the cgrp_cpuctx_list.
         *
         * The IOC_ENABLE that is sure to follow the creation of a disabled
         * event will issue the IPI and reprogram the hardware.
         */
-       if (__perf_effective_state(event) == PERF_EVENT_STATE_OFF && ctx->nr_events) {
+       if (__perf_effective_state(event) == PERF_EVENT_STATE_OFF &&
+           ctx->nr_events && !is_cgroup_event(event)) {
                raw_spin_lock_irq(&ctx->lock);
                if (ctx->task == TASK_TOMBSTONE) {
                        raw_spin_unlock_irq(&ctx->lock);
@@ -3231,6 +3238,15 @@ static int perf_event_modify_breakpoint(struct perf_event *bp,
        return err;
 }
 
+/*
+ * Copy event-type-independent attributes that may be modified.
+ */
+static void perf_event_modify_copy_attr(struct perf_event_attr *to,
+                                       const struct perf_event_attr *from)
+{
+       to->sig_data = from->sig_data;
+}
+
 static int perf_event_modify_attr(struct perf_event *event,
                                  struct perf_event_attr *attr)
 {
@@ -3253,10 +3269,17 @@ static int perf_event_modify_attr(struct perf_event *event,
        WARN_ON_ONCE(event->ctx->parent_ctx);
 
        mutex_lock(&event->child_mutex);
+       /*
+        * Event-type-independent attributes must be copied before event-type
+        * modification, which will validate that final attributes match the
+        * source attributes after all relevant attributes have been copied.
+        */
+       perf_event_modify_copy_attr(&event->attr, attr);
        err = func(event, attr);
        if (err)
                goto out;
        list_for_each_entry(child, &event->child_list, child_list) {
+               perf_event_modify_copy_attr(&child->attr, attr);
                err = func(child, attr);
                if (err)
                        goto out;
@@ -5985,6 +6008,8 @@ static void ring_buffer_attach(struct perf_event *event,
        struct perf_buffer *old_rb = NULL;
        unsigned long flags;
 
+       WARN_ON_ONCE(event->parent);
+
        if (event->rb) {
                /*
                 * Should be impossible, we set this when removing
@@ -6042,6 +6067,9 @@ static void ring_buffer_wakeup(struct perf_event *event)
 {
        struct perf_buffer *rb;
 
+       if (event->parent)
+               event = event->parent;
+
        rcu_read_lock();
        rb = rcu_dereference(event->rb);
        if (rb) {
@@ -6055,6 +6083,9 @@ struct perf_buffer *ring_buffer_get(struct perf_event *event)
 {
        struct perf_buffer *rb;
 
+       if (event->parent)
+               event = event->parent;
+
        rcu_read_lock();
        rb = rcu_dereference(event->rb);
        if (rb) {
@@ -6763,7 +6794,7 @@ static unsigned long perf_prepare_sample_aux(struct perf_event *event,
        if (WARN_ON_ONCE(READ_ONCE(sampler->oncpu) != smp_processor_id()))
                goto out;
 
-       rb = ring_buffer_get(sampler->parent ? sampler->parent : sampler);
+       rb = ring_buffer_get(sampler);
        if (!rb)
                goto out;
 
@@ -6829,7 +6860,7 @@ static void perf_aux_sample_output(struct perf_event *event,
        if (WARN_ON_ONCE(!sampler || !data->aux_size))
                return;
 
-       rb = ring_buffer_get(sampler->parent ? sampler->parent : sampler);
+       rb = ring_buffer_get(sampler);
        if (!rb)
                return;