Skip to content

Commit

Permalink
tracing/workqueues: Add refcnt to struct cpu_workqueue_stats
Browse files Browse the repository at this point in the history
The stat entries can be freed when the stat file is being read.
The worse is, the ptr can be freed immediately after it's returned
from workqueue_stat_start/next().

Add a refcnt to struct cpu_workqueue_stats to avoid use-after-free.

Signed-off-by: Lai Jiangshan <laijs@cn.fujitsu.com>
Signed-off-by: Li Zefan <lizf@cn.fujitsu.com>
Acked-by: Frederic Weisbecker <fweisbec@gmail.com>
Cc: Steven Rostedt <rostedt@goodmis.org>
LKML-Reference: <4A51B16F.6010608@cn.fujitsu.com>
Signed-off-by: Ingo Molnar <mingo@elte.hu>
  • Loading branch information
Lai Jiangshan authored and Ingo Molnar committed Jul 10, 2009
1 parent d8ea37d commit a357800
Showing 1 changed file with 26 additions and 6 deletions.
32 changes: 26 additions & 6 deletions kernel/trace/trace_workqueue.c
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,15 @@
#include <trace/events/workqueue.h>
#include <linux/list.h>
#include <linux/percpu.h>
#include <linux/kref.h>
#include "trace_stat.h"
#include "trace.h"


/* A cpu workqueue thread */
struct cpu_workqueue_stats {
struct list_head list;
struct kref kref;
int cpu;
pid_t pid;
/* Can be inserted from interrupt or user context, need to be atomic */
Expand All @@ -39,6 +41,11 @@ struct workqueue_global_stats {
static DEFINE_PER_CPU(struct workqueue_global_stats, all_workqueue_stat);
#define workqueue_cpu_stat(cpu) (&per_cpu(all_workqueue_stat, cpu))

static void cpu_workqueue_stat_free(struct kref *kref)
{
kfree(container_of(kref, struct cpu_workqueue_stats, kref));
}

/* Insertion of a work */
static void
probe_workqueue_insertion(struct task_struct *wq_thread,
Expand Down Expand Up @@ -96,8 +103,8 @@ static void probe_workqueue_creation(struct task_struct *wq_thread, int cpu)
return;
}
INIT_LIST_HEAD(&cws->list);
kref_init(&cws->kref);
cws->cpu = cpu;

cws->pid = wq_thread->pid;

spin_lock_irqsave(&workqueue_cpu_stat(cpu)->lock, flags);
Expand All @@ -118,7 +125,7 @@ static void probe_workqueue_destruction(struct task_struct *wq_thread)
list) {
if (node->pid == wq_thread->pid) {
list_del(&node->list);
kfree(node);
kref_put(&node->kref, cpu_workqueue_stat_free);
goto found;
}
}
Expand All @@ -137,9 +144,11 @@ static struct cpu_workqueue_stats *workqueue_stat_start_cpu(int cpu)

spin_lock_irqsave(&workqueue_cpu_stat(cpu)->lock, flags);

if (!list_empty(&workqueue_cpu_stat(cpu)->list))
if (!list_empty(&workqueue_cpu_stat(cpu)->list)) {
ret = list_entry(workqueue_cpu_stat(cpu)->list.next,
struct cpu_workqueue_stats, list);
kref_get(&ret->kref);
}

spin_unlock_irqrestore(&workqueue_cpu_stat(cpu)->lock, flags);

Expand All @@ -162,9 +171,9 @@ static void *workqueue_stat_start(struct tracer_stat *trace)
static void *workqueue_stat_next(void *prev, int idx)
{
struct cpu_workqueue_stats *prev_cws = prev;
struct cpu_workqueue_stats *ret;
int cpu = prev_cws->cpu;
unsigned long flags;
void *ret = NULL;

spin_lock_irqsave(&workqueue_cpu_stat(cpu)->lock, flags);
if (list_is_last(&prev_cws->list, &workqueue_cpu_stat(cpu)->list)) {
Expand All @@ -175,11 +184,14 @@ static void *workqueue_stat_next(void *prev, int idx)
return NULL;
} while (!(ret = workqueue_stat_start_cpu(cpu)));
return ret;
} else {
ret = list_entry(prev_cws->list.next,
struct cpu_workqueue_stats, list);
kref_get(&ret->kref);
}
spin_unlock_irqrestore(&workqueue_cpu_stat(cpu)->lock, flags);

return list_entry(prev_cws->list.next, struct cpu_workqueue_stats,
list);
return ret;
}

static int workqueue_stat_show(struct seq_file *s, void *p)
Expand All @@ -203,6 +215,13 @@ static int workqueue_stat_show(struct seq_file *s, void *p)
return 0;
}

static void workqueue_stat_release(void *stat)
{
struct cpu_workqueue_stats *node = stat;

kref_put(&node->kref, cpu_workqueue_stat_free);
}

static int workqueue_stat_headers(struct seq_file *s)
{
seq_printf(s, "# CPU INSERTED EXECUTED NAME\n");
Expand All @@ -215,6 +234,7 @@ struct tracer_stat workqueue_stats __read_mostly = {
.stat_start = workqueue_stat_start,
.stat_next = workqueue_stat_next,
.stat_show = workqueue_stat_show,
.stat_release = workqueue_stat_release,
.stat_headers = workqueue_stat_headers
};

Expand Down

0 comments on commit a357800

Please sign in to comment.