diff options
author | Peter Zijlstra <peterz@infradead.org> | 2015-06-02 06:50:13 -0400 |
---|---|---|
committer | Ingo Molnar <mingo@kernel.org> | 2015-06-07 09:46:30 -0400 |
commit | cee34d88cabd1ba5fc93e09b5b12232bc9338c7c (patch) | |
tree | 278d17b06e20285f8c2822d724ecc41a69b27e9d /kernel | |
parent | 37ef1647b7f73d4ff4c7993984599b6c4f26443a (diff) |
lockdep: Fix a race between /proc/lock_stat and module unload
The lock_class iteration of /proc/lock_stat is not serialized against
the lockdep_free_key_range() call from module unload.
Therefore it can happen that we find a class of which ->name/->key are
no longer valid.
There is a further bug in zap_class() that left ->name dangling. Cure
this. Use RCU_INIT_POINTER() because NULL.
Since lockdep_free_key_range() is rcu_sched serialized, we can read
both ->name and ->key under rcu_read_lock_sched() (preempt-disable)
and be assured that if we observe a !NULL value it stays safe to use
for as long as we hold that lock.
If we observe both NULL, skip the entry.
Reported-by: Jerome Marchand <jmarchan@redhat.com>
Tested-by: Jerome Marchand <jmarchan@redhat.com>
Signed-off-by: Peter Zijlstra (Intel) <peterz@infradead.org>
Cc: Andrew Morton <akpm@linux-foundation.org>
Cc: H. Peter Anvin <hpa@zytor.com>
Cc: Linus Torvalds <torvalds@linux-foundation.org>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Thomas Gleixner <tglx@linutronix.de>
Link: http://lkml.kernel.org/r/20150602105013.GS3644@twins.programming.kicks-ass.net
Signed-off-by: Ingo Molnar <mingo@kernel.org>
Diffstat (limited to 'kernel')
-rw-r--r-- | kernel/locking/lockdep.c | 3 | ||||
-rw-r--r-- | kernel/locking/lockdep_proc.c | 22 |
2 files changed, 19 insertions, 6 deletions
diff --git a/kernel/locking/lockdep.c b/kernel/locking/lockdep.c index a0831e1b99f4..aaeae885d9af 100644 --- a/kernel/locking/lockdep.c +++ b/kernel/locking/lockdep.c | |||
@@ -3900,7 +3900,8 @@ static void zap_class(struct lock_class *class) | |||
3900 | list_del_rcu(&class->hash_entry); | 3900 | list_del_rcu(&class->hash_entry); |
3901 | list_del_rcu(&class->lock_entry); | 3901 | list_del_rcu(&class->lock_entry); |
3902 | 3902 | ||
3903 | class->key = NULL; | 3903 | RCU_INIT_POINTER(class->key, NULL); |
3904 | RCU_INIT_POINTER(class->name, NULL); | ||
3904 | } | 3905 | } |
3905 | 3906 | ||
3906 | static inline int within(const void *addr, void *start, unsigned long size) | 3907 | static inline int within(const void *addr, void *start, unsigned long size) |
diff --git a/kernel/locking/lockdep_proc.c b/kernel/locking/lockdep_proc.c index ef43ac4bafb5..d83d798bef95 100644 --- a/kernel/locking/lockdep_proc.c +++ b/kernel/locking/lockdep_proc.c | |||
@@ -426,10 +426,12 @@ static void seq_lock_time(struct seq_file *m, struct lock_time *lt) | |||
426 | 426 | ||
427 | static void seq_stats(struct seq_file *m, struct lock_stat_data *data) | 427 | static void seq_stats(struct seq_file *m, struct lock_stat_data *data) |
428 | { | 428 | { |
429 | char name[39]; | 429 | struct lockdep_subclass_key *ckey; |
430 | struct lock_class *class; | ||
431 | struct lock_class_stats *stats; | 430 | struct lock_class_stats *stats; |
431 | struct lock_class *class; | ||
432 | const char *cname; | ||
432 | int i, namelen; | 433 | int i, namelen; |
434 | char name[39]; | ||
433 | 435 | ||
434 | class = data->class; | 436 | class = data->class; |
435 | stats = &data->stats; | 437 | stats = &data->stats; |
@@ -440,15 +442,25 @@ static void seq_stats(struct seq_file *m, struct lock_stat_data *data) | |||
440 | if (class->subclass) | 442 | if (class->subclass) |
441 | namelen -= 2; | 443 | namelen -= 2; |
442 | 444 | ||
443 | if (!class->name) { | 445 | rcu_read_lock_sched(); |
446 | cname = rcu_dereference_sched(class->name); | ||
447 | ckey = rcu_dereference_sched(class->key); | ||
448 | |||
449 | if (!cname && !ckey) { | ||
450 | rcu_read_unlock_sched(); | ||
451 | return; | ||
452 | |||
453 | } else if (!cname) { | ||
444 | char str[KSYM_NAME_LEN]; | 454 | char str[KSYM_NAME_LEN]; |
445 | const char *key_name; | 455 | const char *key_name; |
446 | 456 | ||
447 | key_name = __get_key_name(class->key, str); | 457 | key_name = __get_key_name(ckey, str); |
448 | snprintf(name, namelen, "%s", key_name); | 458 | snprintf(name, namelen, "%s", key_name); |
449 | } else { | 459 | } else { |
450 | snprintf(name, namelen, "%s", class->name); | 460 | snprintf(name, namelen, "%s", cname); |
451 | } | 461 | } |
462 | rcu_read_unlock_sched(); | ||
463 | |||
452 | namelen = strlen(name); | 464 | namelen = strlen(name); |
453 | if (class->name_version > 1) { | 465 | if (class->name_version > 1) { |
454 | snprintf(name+namelen, 3, "#%d", class->name_version); | 466 | snprintf(name+namelen, 3, "#%d", class->name_version); |