Skip to contentSkip to conversation

patches to cure race in lock_set_class()

PATCH In review Yong Zhang 6 participants 31 messages Nov 4, 2011 – Dec 6, 2011 Locking primitives

lore
Share & tools
lore

Yong Zhang

author
#0 lore raw

cover patches to cure race in lock_set_class()

This a new version of my previous post[1].

patch#1 is the one which fix the reported bug.
So Sergey, Borislav, would you please give it a test?

patch#2~4 is an enhancement which fix a potential race in
lock_set_class(). But the fact is the only current user in kernel is lock_set_subclass(), we still have no user which change its 'key' in flying. So I mark it with RFC.

More detail in each patch :)

Thanks,
Yong

[1]:http://marc.info/?l=linux-kernel&m=131919035525533

Yong Zhang (4):
lockdep: lock_set_subclass() fix
lockdep: Let register_lock_class() can be called with/without
graph_lock
lockdep: split lockdep_init_map()
lockdep: fix race condition in __lock_set_class()

 include/linux/lockdep.h |    2 +-
 kernel/lockdep.c        |   50 ++++++++++++++++++++++++++++++++--------------
 2 files changed, 36 insertions(+), 16 deletions(-)
Signature · 1 line
1.7.5.4
author
#1 lore raw

PATCH 1/4 lockdep: lock_set_subclass() fix

Since commit f59de89 [lockdep: Clear whole lockdep_map on initialization], lockdep_init_map() will clear all the struct. But it will break lock_set_class()/lock_set_subclass(). A typical race condition is like below:

CPU A CPU B
lock_set_subclass(lockA);

  lock_set_class(lockA);
    lockdep_init_map(lockA);
      /* lockA->name is cleared */
      memset(lockA);
                                      __lock_acquire(lockA);
                                        /* lockA->class_cache[] is cleared */
                                        register_lock_class(lockA);
                                          look_up_lock_class(lockA);
                                            WARN_ON_ONCE(class->name !=
                                                      lock->name);

      lock->name = name;
---
 include/linux/lockdep.h |    2 +-
 kernel/lockdep.c        |    9 +++++----
 2 files changed, 6 insertions(+), 5 deletions(-)
include/linux/lockdep.h +1 -1
index b6a56e3..182fb2d 100644
@@ -330,7 +330,7 @@ extern void lock_set_class(struct lockdep_map *lock, const char *name,
 static inline void lock_set_subclass(struct lockdep_map *lock,
 		unsigned int subclass, unsigned long ip)
 {
-	lock_set_class(lock, lock->name, lock->key, subclass, ip);
+	lock_set_class(lock, NULL, NULL, subclass, ip);
 }
 
 extern void lockdep_set_current_reclaim_state(gfp_t gfp_mask);
kernel/lockdep.c +5 -4
index e69434b..4a16b0a 100644
@@ -3247,7 +3247,6 @@ __lock_set_class(struct lockdep_map *lock, const char *name,
 {
 	struct task_struct *curr = current;
 	struct held_lock *hlock, *prev_hlock;
-	struct lock_class *class;
 	unsigned int depth;
 	int i;
 
@@ -3274,9 +3273,11 @@ __lock_set_class(struct lockdep_map *lock, const char *name,
 	return print_unlock_inbalance_bug(curr, lock, ip);
 
 found_it:
-	lockdep_init_map(lock, name, key, 0);
-	class = register_lock_class(lock, subclass, 0);
-	hlock->class_idx = class - lock_classes + 1;
+	/* optimizing for lock_set_subclass() */
+	if (key) {
+		lockdep_init_map(lock, name, key, 0);
+		register_lock_class(lock, subclass, 0);
+	}
 
 	curr->lockdep_depth = i;
 	curr->curr_chain_key = hlock->prev_chain_key;
Signature · 1 line
1.7.5.4
#2 lore raw

Re: [PATCH 1/4] lockdep: lock_set_subclass() fix

Quoted 4 lines · Yong Zhang

Since commit f59de89 [lockdep: Clear whole lockdep_map on initialization], lockdep_init_map() will clear all the struct. But it will break lock_set_class()/lock_set_subclass(). A typical race condition is like below:

This is a horridly ugly patch, why not simply revert that memset commit? I really can't see the point of that, and keeping the name/key pointers around (which can only be over-written with the same values, right?) would also cure the problem.

Sadly the changelog is completely devoid of useful information (which is my own damn fault, I should never have accepted the patch in that form), so I can't actually comment on what it was supposed to fix.

Arguably kmemcheck is on crack or so since both name and key pointers should be in .data so there cannot be a leak by copying the thing over.

author
#3 lore raw

Re: [PATCH 1/4] lockdep: lock_set_subclass() fix

Quoted 7 lines · Peter Zijlstra
Quoted 4 lines · Yong Zhang

Since commit f59de89 [lockdep: Clear whole lockdep_map on initialization], lockdep_init_map() will clear all the struct. But it will break lock_set_class()/lock_set_subclass(). A typical race condition is like below:

This is a horridly ugly patch, why not simply revert that memset commit?

I prefer reverting that commit, but bugzilla is down and I don't know what the real problem behind that commit.

Quoted 7 lines

I really can't see the point of that, and keeping the name/key pointers around (which can only be over-written with the same values, right?) would also cure the problem.

Sadly the changelog is completely devoid of useful information (which is my own damn fault, I should never have accepted the patch in that form), so I can't actually comment on what it was supposed to fix.

me too :(
And going through lkml history, I find nothing about it.

Quoted 3 lines

Arguably kmemcheck is on crack or so since both name and key pointers should be in .data so there cannot be a leak by copying the thing over.

Maybe Tejun can give more detail on it.

Thanks,
Yong

#4 lore raw

Re: [PATCH 1/4] lockdep: lock_set_subclass() fix

(cc'ing the bug reporter and kmemcheck ppl)

Hello,

Quoted 2 lines · Yong Zhang

I prefer reverting that commit, but bugzilla is down and I don't know what the real problem behind that commit.

So, this was the kmemcheck which triggers.

PCI: Using host bridge windows from ACPI; if necessary, use "pci=nocrs" and report a bug
WARNING: kmemcheck: Caught 64-bit read from uninitialized memory (ffff8801c6783c58) 0000000000000000ee0caa81ffffffff00000000000000000000000000000000
i i i i i i i i i i i i i i i i i i i i u u u u u u u u u u u u

						  ^

Pid: 1, comm: swapper Not tainted 3.0.0-rc2 #8 Acer Aspire 7750G/JE70_HR

  RIP: 0010:[<ffffffff8108f3b3>]  [<ffffffff8108f3b3>]
  process_one_work+0x83/0x450
  RSP: 0018:ffff8801c6507d80  EFLAGS: 00010002
  RAX: 0000000000000000 RBX: ffff8801c7418300 RCX: 0000000000000000
  RDX: ffff8801c79d5800 RSI: ffff8801c6783c10 RDI: ffff8801c7418300
  RBP: ffff8801c6507e10 R08: ffff8801c64f8058 R09: 0000000000000001
  R10: 0000000000000000 R11: 0000000000000000 R12: ffff8801c6783c10
  R13: ffff8801c79d5800 R14: ffff8801c780ca00 R15: 000000000000003b
  FS:  0000000000000000(0000) GS:ffff8801c7800000(0000) knlGS:0000000000000000
  CS:  0010 DS: 0000 ES: 0000 CR0: 000000008005003b
  CR2: ffff8801c7430364 CR3: 0000000001c03000 CR4: 00000000000406f0
  DR0: 0000000000000000 DR1: 0000000000000000 DR2: 0000000000000000
  DR3: 0000000000000000 DR6: 00000000ffff4ff0 DR7: 0000000000000400
   [<ffffffff810927cc>] worker_thread+0x15c/0x330
   [<ffffffff81097396>] kthread+0xa6/0xb0
   [<ffffffff817b3c54>] kernel_thread_helper+0x4/0x10
   [<ffffffffffffffff>] 0xffffffffffffffff
  \_SB_.PCI0:_OSC invalid UUID
  _OSC request data:1 8 1f

kmemcheck is complaining that memory area which hasn't been initialized before is being read. The offending code line is "struct lockdep_map = work->lockdep_map" in process_one_work(), which caches lockdep_map because work item can be destroyed once it starts execution.

I don't have problem with reverting the patch but it would be much better if it can play nice with kmemcheck. It seems lockdep_map init requirements are rather weird. Maybe just avoiding clearing ->name is better?

Thanks.

Signature · 1 line
tejun
#5 lore raw

Re: [PATCH 1/4] lockdep: lock_set_subclass() fix

In reply to Peter Zijlstra
Quoted 10 lines · Peter Zijlstra
Quoted 4 lines · Yong Zhang

Since commit f59de89 [lockdep: Clear whole lockdep_map on initialization], lockdep_init_map() will clear all the struct. But it will break lock_set_class()/lock_set_subclass(). A typical race condition is like below:

This is a horridly ugly patch, why not simply revert that memset commit? I really can't see the point of that, and keeping the name/key pointers around (which can only be over-written with the same values, right?) would also cure the problem.

FWIW,

I ran the box with the memset commit reverted for a couple of days before applying Young's patches and it didn't show any signs of problems.

Signature · 2 lines
Regards/Gruss,
Boris.
#6 lore raw

Re: [PATCH 1/4] lockdep: lock_set_subclass() fix

In reply to Peter Zijlstra
Quoted 2 lines · Peter Zijlstra

Arguably kmemcheck is on crack or so since both name and key pointers should be in .data so there cannot be a leak by copying the thing over.

There is no leak, it's only about uninitialised memory.

The problem is that kmemcheck cannot track "initialisedness" of stack data and we're doing a heap-to-stack copy in process_one_work(), as Tejun pointed out:

        struct lockdep_map lockdep_map = work->lockdep_map;

This is of course fine in itself. But how can we tell kmemcheck that it's fine? There are basically two options:

1. Initialise the thing completely before doing the copy, or
2. Ignore the warning.

The memset() patch (f59de8992aa6dc85e81aadc26b0f69e17809721d) attempts to do the first, i.e. to clear the whole struct in lockdep_init_map().

I think nr. 1 is the best way to go in principle, but I don't know what it takes for this to work properly. The blanket-clear memset() presumably doesn't work because it clears out something that was already initialised by the caller (right?).

Yong Zhang, can you think of a way to avoid the race you described, perhaps by memset()ing only the right/relevant parts of struct lockdep_map in lockdep_init_map()?

Peter Zijlstra, if you prefer, we can also just tell kmemcheck that this particular copy is fine, but it means that kmemcheck will not be able to detect any real bugs in this code. It can be done with something like:

kernel/lockdep.c +0 -0
index e69434b..08a2b1b 100644
@@ -2948,7 +2948,7 @@ static int mark_lock(struct task_struct *curr,

struct held_lock *this,
void lockdep_init_map(struct lockdep_map *lock, const char *name,
struct lock_class_key *key, int subclass)
{
- memset(lock, 0, sizeof(*lock));
+ kmemcheck_mark_initialized(lock, sizeof(*lock));

 #ifdef CONFIG_LOCK_STAT
        lock->cpu = raw_smp_processor_id();

Christian Casteyde, do you mind testing this patch as well?

(Yong Zhang, do you think this would still be vulnerable to the race you described?)

Thanks,

And sorry for the inconvenience.

I've been thinking about chucking kmemcheck and reimplementing it using LibVEX (the dynamic translation framework used by Valgrind itself). Whoever is interested, feel free to have a look:

https://github.com/vegard/linux-2.6/tree/kmemcheck2

Vegard

#7 lore raw

Re: [PATCH 1/4] lockdep: lock_set_subclass() fix

Quoted 21 lines · Vegard Nossum
Quoted 2 lines · Peter Zijlstra

Arguably kmemcheck is on crack or so since both name and key pointers should be in .data so there cannot be a leak by copying the thing over.

There is no leak, it's only about uninitialised memory.

The problem is that kmemcheck cannot track "initialisedness" of stack data and we're doing a heap-to-stack copy in process_one_work(), as Tejun pointed out:

        struct lockdep_map lockdep_map = work->lockdep_map;

This is of course fine in itself. But how can we tell kmemcheck that it's fine? There are basically two options:

1. Initialise the thing completely before doing the copy, or
2. Ignore the warning.

The memset() patch (f59de8992aa6dc85e81aadc26b0f69e17809721d) attempts to do the first, i.e. to clear the whole struct in lockdep_init_map().

Quoted 8 lines

I think nr. 1 is the best way to go in principle, but I don't know what it takes for this to work properly. The blanket-clear memset() presumably doesn't work because it clears out something that was already initialised by the caller (right?).

Yong Zhang, can you think of a way to avoid the race you described, perhaps by memset()ing only the right/relevant parts of struct lockdep_map in lockdep_init_map()?

We could move the key and name pointer to the start of the structure and memset everything after that, however wouldn't that leave kmemcheck with the same problem? It wouldn't know those two pointers would be initialized properly.

Quoted 4 lines

Peter Zijlstra, if you prefer, we can also just tell kmemcheck that this particular copy is fine, but it means that kmemcheck will not be able to detect any real bugs in this code. It can be done with something like:

Something like this, although it would be best to come up with a nicer way to write it..

---
 include/linux/lockdep.h |    2 +-
 kernel/lockdep.c        |    3 ++-
 2 files changed, 3 insertions(+), 2 deletions(-)
include/linux/lockdep.h +1 -1
index b6a56e3..7d66268 100644
@@ -148,9 +148,9 @@ void clear_lock_stats(struct lock_class *class);
  * This is embedded into specific lock instances:
  */
 struct lockdep_map {
+	const char			*name;
 	struct lock_class_key		*key;
 	struct lock_class		*class_cache[NR_LOCKDEP_CACHING_CLASSES];
-	const char			*name;
 #ifdef CONFIG_LOCK_STAT
 	int				cpu;
 	unsigned long			ip;
kernel/lockdep.c +2 -1
index e69434b..81855cf 100644
@@ -2948,7 +2948,8 @@ static int mark_lock(struct task_struct *curr, struct held_lock *this,
 void lockdep_init_map(struct lockdep_map *lock, const char *name,
 		      struct lock_class_key *key, int subclass)
 {
-	memset(lock, 0, sizeof(*lock));
+	kmemcheck_mark_initialized(lock, 2*sizeof(void *));
+	memset(&lock->class_cache[0], 0, sizeof(*lock)-2*sizeof(void *));
 
 #ifdef CONFIG_LOCK_STAT
 	lock->cpu = raw_smp_processor_id();
#8 lore raw

Re: [PATCH 1/4] lockdep: lock_set_subclass() fix

Hello,

Quoted 4 lines · Peter Zijlstra

We could move the key and name pointer to the start of the structure and memset everything after that, however wouldn't that leave kmemcheck with the same problem? It wouldn't know those two pointers would be initialized properly.

At that point, lockdep_map is guaranteed to have passed through lockdep_init_map(), so I think it should be fine.

include/linux/lockdep.h void clear_lock_stats(struct lock_class *class); · line 156

Quoted 11 lines
include/linux/lockdep.h +1 -1
@@ -148,9 +148,9 @@ void clear_lock_stats(struct lock_class *class);
  * This is embedded into specific lock instances:
  */
 struct lockdep_map {
+	const char			*name;
 	struct lock_class_key		*key;
 	struct lock_class		*class_cache[NR_LOCKDEP_CACHING_CLASSES];
-	const char			*name;
 #ifdef CONFIG_LOCK_STAT
 	int				cpu;
 	unsigned long			ip;

Probably fat comment explaining the ordering requirement here w/

#define LOCKDEP_MAP_INIT_OFFSET offsetof(struct lockdep_map, class_cache)

kernel/lockdep.c static int mark_lock(struct task_struct *curr, struct held_lock *this, · line 2952

Quoted 11 lines
kernel/lockdep.c +2 -1
index e69434b..81855cf 100644
@@ -2948,7 +2948,8 @@ static int mark_lock(struct task_struct *curr, struct held_lock *this,
 void lockdep_init_map(struct lockdep_map *lock, const char *name,
 		      struct lock_class_key *key, int subclass)
 {
-	memset(lock, 0, sizeof(*lock));
+	kmemcheck_mark_initialized(lock, 2*sizeof(void *));
+	memset(&lock->class_cache[0], 0, sizeof(*lock)-2*sizeof(void *));

And something like the following?

	memset((void *)lock + LOCKDEP_MAP_INIT_OFFSET, 0,
		sizeof(*lock) - LOCKDEP_MAP_INIT_OFFSET);

Thanks.

Signature · 1 line
tejun
#9 lore raw

Re: [PATCH 1/4] lockdep: lock_set_subclass() fix

Quoted 5 lines · Tejun Heo
Quoted 5 lines
include/linux/lockdep.h +1 -0
@@ -148,9 +148,9 @@ void clear_lock_stats(struct lock_class *class);
  * This is embedded into specific lock instances:
  */
 struct lockdep_map {
+	const char			*name;
Quoted 8 lines

include/linux/lockdep.h void clear_lock_stats(struct lock_class *class); · line 156

Quoted 6 lines
include/linux/lockdep.h +0 -1
 	struct lock_class_key		*key;
 	struct lock_class		*class_cache[NR_LOCKDEP_CACHING_CLASSES];
-	const char			*name;
 #ifdef CONFIG_LOCK_STAT
 	int				cpu;
 	unsigned long			ip;

Probably fat comment explaining the ordering requirement here w/

yes.

Quoted 16 lines

#define LOCKDEP_MAP_INIT_OFFSET offsetof(struct lockdep_map, class_cache)

kernel/lockdep.c static int mark_lock(struct task_struct *curr, struct held_lock *this, · line 2952

Quoted 11 lines
kernel/lockdep.c +2 -1
index e69434b..81855cf 100644
@@ -2948,7 +2948,8 @@ static int mark_lock(struct task_struct *curr, struct held_lock *this,
 void lockdep_init_map(struct lockdep_map *lock, const char *name,
 		      struct lock_class_key *key, int subclass)
 {
-	memset(lock, 0, sizeof(*lock));
+	kmemcheck_mark_initialized(lock, 2*sizeof(void *));
+	memset(&lock->class_cache[0], 0, sizeof(*lock)-2*sizeof(void *));

And something like the following?

Quoted 2 lines
	memset((void *)lock + LOCKDEP_MAP_INIT_OFFSET, 0,
		sizeof(*lock) - LOCKDEP_MAP_INIT_OFFSET);

Much better indeed!

author
#10 lore raw

Re: [PATCH 1/4] lockdep: lock_set_subclass() fix

In reply to Peter Zijlstra
Quoted 12 lines · Peter Zijlstra
Quoted 11 lines · Vegard Nossum

1. Initialise the thing completely before doing the copy, or
2. Ignore the warning.

The memset() patch (f59de8992aa6dc85e81aadc26b0f69e17809721d) attempts to do the first, i.e. to clear the whole struct in lockdep_init_map().

I think nr. 1 is the best way to go in principle, but I don't know what it takes for this to work properly. The blanket-clear memset() presumably doesn't work because it clears out something that was already initialised by the caller (right?).

Quoted 13 lines
Quoted 3 lines

Yong Zhang, can you think of a way to avoid the race you described, perhaps by memset()ing only the right/relevant parts of struct lockdep_map in lockdep_init_map()?

We could move the key and name pointer to the start of the structure and memset everything after that, however wouldn't that leave kmemcheck with the same problem? It wouldn't know those two pointers would be initialized properly.

Quoted 4 lines

Peter Zijlstra, if you prefer, we can also just tell kmemcheck that this particular copy is fine, but it means that kmemcheck will not be able to detect any real bugs in this code. It can be done with something like:

We should take ->calss_cache more carefully, because if we memset() it unconditionnally we will have no chance to set it anymore. Thus the performace brought by ->class_cache will be gone.

1) for lock_set_subclass(): we can't initialize ->class_cache because it's still valid and we need it.
2) for lock_set_class(): we have to initialize ->class_cache because it's invalid anymore.

Maybe we could unconditionally set it we look_up_lock_class() find the class?

Quoted 29 lines

Something like this, although it would be best to come up with a nicer way to write it..

---
 include/linux/lockdep.h |    2 +-
 kernel/lockdep.c        |    3 ++-
 2 files changed, 3 insertions(+), 2 deletions(-)
include/linux/lockdep.h +1 -1
index b6a56e3..7d66268 100644
@@ -148,9 +148,9 @@ void clear_lock_stats(struct lock_class *class);
  * This is embedded into specific lock instances:
  */
 struct lockdep_map {
+	const char			*name;
 	struct lock_class_key		*key;
 	struct lock_class		*class_cache[NR_LOCKDEP_CACHING_CLASSES];
-	const char			*name;
 #ifdef CONFIG_LOCK_STAT
 	int				cpu;
 	unsigned long			ip;
kernel/lockdep.c +0 -0
index e69434b..81855cf 100644

kernel/lockdep.c static int mark_lock(struct task_struct *curr, struct held_lock *this, · line 2952

Quoted 6 lines
kernel/lockdep.c +2 -1
@@ -2948,7 +2948,8 @@ static int mark_lock(struct task_struct *curr, struct held_lock *this,
 void lockdep_init_map(struct lockdep_map *lock, const char *name,
 		      struct lock_class_key *key, int subclass)
 {
-	memset(lock, 0, sizeof(*lock));
+	kmemcheck_mark_initialized(lock, 2*sizeof(void *));
+	memset(&lock->class_cache[0], 0, sizeof(*lock)-2*sizeof(void *));

That means ->key have chance to be 0 at some time, right? Then I think it'll lead to another false positive warning like what Borislav has reported:
http://marc.info/?l=linux-kernel&m=132039877026653

The reason is some rq->lock could carry a wrong key at certain time.

	CPU A				CPU B
  lock_set_subclass(lockA)
    __lock_set_class(lockA)
      lockdep_init_map(lockA)
        memset() /* ->key = NULL */
      				__lock_acquire(lockA)
				  register_lock_class(lockA)
				    look_up_lock_class(lockA)
				      if (unlikely(!lock->key))
				              lock->key = (void *)lock;
	->key = key;
				/* lockA maybe carry wrong class in later running
				 * due to ->class_cache
				 */

Then when another lock_set_subclass() comes:
CPU A CPU B

  lock_set_subclass(lockA);
    lock_set_class(lockA);
      				__lock_acquire(lockA)
                                  /* lockA->class_cache[] is not set,
				   * different subclass */
                                  register_lock_class(lockA);
                                     look_up_lock_class(lockA); /* retrun NULL */
      lockdep_init_map(lockA);
        memset(lockA); /* ->key = NULL */
                                  if (!static_obj(lock->key))
                                  /* we get warning here */

So maybe the simplest way is just annotating ->lock like this:

	kmemcheck_mark_initialized(lock, sizeof(*lock));

Thanks,
Yong

author
#11 lore raw

Re: [PATCH 1/4] lockdep: lock_set_subclass() fix

Quoted 81 lines · Yong Zhang
Quoted 12 lines · Peter Zijlstra
Quoted 11 lines · Vegard Nossum

1. Initialise the thing completely before doing the copy, or
2. Ignore the warning.

The memset() patch (f59de8992aa6dc85e81aadc26b0f69e17809721d) attempts to do the first, i.e. to clear the whole struct in lockdep_init_map().

I think nr. 1 is the best way to go in principle, but I don't know what it takes for this to work properly. The blanket-clear memset() presumably doesn't work because it clears out something that was already initialised by the caller (right?).

Quoted 13 lines
Quoted 3 lines

Yong Zhang, can you think of a way to avoid the race you described, perhaps by memset()ing only the right/relevant parts of struct lockdep_map in lockdep_init_map()?

We could move the key and name pointer to the start of the structure and memset everything after that, however wouldn't that leave kmemcheck with the same problem? It wouldn't know those two pointers would be initialized properly.

Quoted 4 lines

Peter Zijlstra, if you prefer, we can also just tell kmemcheck that this particular copy is fine, but it means that kmemcheck will not be able to detect any real bugs in this code. It can be done with something like:

We should take ->calss_cache more carefully, because if we memset() it unconditionnally we will have no chance to set it anymore. Thus the performace brought by ->class_cache will be gone.

1) for lock_set_subclass(): we can't initialize ->class_cache because it's still valid and we need it.
2) for lock_set_class(): we have to initialize ->class_cache because it's invalid anymore.

Maybe we could unconditionally set it we look_up_lock_class() find the class?

Quoted 29 lines

Something like this, although it would be best to come up with a nicer way to write it..

---
 include/linux/lockdep.h |    2 +-
 kernel/lockdep.c        |    3 ++-
 2 files changed, 3 insertions(+), 2 deletions(-)
include/linux/lockdep.h +1 -1
index b6a56e3..7d66268 100644
@@ -148,9 +148,9 @@ void clear_lock_stats(struct lock_class *class);
  * This is embedded into specific lock instances:
  */
 struct lockdep_map {
+	const char			*name;
 	struct lock_class_key		*key;
 	struct lock_class		*class_cache[NR_LOCKDEP_CACHING_CLASSES];
-	const char			*name;
 #ifdef CONFIG_LOCK_STAT
 	int				cpu;
 	unsigned long			ip;
kernel/lockdep.c +0 -0
index e69434b..81855cf 100644

kernel/lockdep.c static int mark_lock(struct task_struct *curr, struct held_lock *this, · line 2952

Quoted 6 lines
kernel/lockdep.c +2 -1
@@ -2948,7 +2948,8 @@ static int mark_lock(struct task_struct *curr, struct held_lock *this,
 void lockdep_init_map(struct lockdep_map *lock, const char *name,
 		      struct lock_class_key *key, int subclass)
 {
-	memset(lock, 0, sizeof(*lock));
+	kmemcheck_mark_initialized(lock, 2*sizeof(void *));
+	memset(&lock->class_cache[0], 0, sizeof(*lock)-2*sizeof(void *));

That means ->key have chance to be 0 at some time, right? Then I think it'll lead to another false positive warning like what Borislav has reported:
http://marc.info/?l=linux-kernel&m=132039877026653

The reason is some rq->lock could carry a wrong key at certain time.

Quoted 14 lines
	CPU A				CPU B
  lock_set_subclass(lockA)
    __lock_set_class(lockA)
      lockdep_init_map(lockA)
        memset() /* ->key = NULL */
      				__lock_acquire(lockA)
				  register_lock_class(lockA)
				    look_up_lock_class(lockA)
				      if (unlikely(!lock->key))
				              lock->key = (void *)lock;
	->key = key;
				/* lockA maybe carry wrong class in later running
				 * due to ->class_cache
				 */

And lockA could also carry different key:

	CPU A				CPU B
  lock_set_subclass(lockA)
    __lock_set_class(lockA)
      lockdep_init_map(lockA)
        memset() /* ->key = NULL */
      				__lock_acquire(lockA)
				  register_lock_class(lockA)
				    look_up_lock_class(lockA)
				      if (unlikely(!lock->key))
	->key = key;
				              lock->key = (void *)lock;
				/* lockA maybe carry wrong key in later running
				 * due to ->class_cache
				 */

Thanks,
Yong

Quoted 22 lines

Then when another lock_set_subclass() comes:
CPU A CPU B

  lock_set_subclass(lockA);
    lock_set_class(lockA);
      				__lock_acquire(lockA)
                                  /* lockA->class_cache[] is not set,
				   * different subclass */
                                  register_lock_class(lockA);
                                     look_up_lock_class(lockA); /* retrun NULL */
      lockdep_init_map(lockA);
        memset(lockA); /* ->key = NULL */
                                  if (!static_obj(lock->key))
                                  /* we get warning here */

So maybe the simplest way is just annotating ->lock like this:

	kmemcheck_mark_initialized(lock, sizeof(*lock));

Thanks,
Yong

Signature · 1 line
Only stand for myself
#12 lore raw

Re: [PATCH 1/4] lockdep: lock_set_subclass() fix

In reply to Yong Zhang
Quoted 13 lines · Yong Zhang
Quoted 13 lines

struct lockdep_map {

include/linux/lockdep.h +1 -1
+     const char                      *name;
      struct lock_class_key           *key;
      struct lock_class               *class_cache[NR_LOCKDEP_CACHING_CLASSES];
-     const char                      *name;
 #ifdef CONFIG_LOCK_STAT
      int                             cpu;
      unsigned long                   ip;
kernel/lockdep.c +0 -0
index e69434b..81855cf 100644
@@ -2948,7 +2948,8 @@ static int mark_lock(struct task_struct *curr, struct held_lock *this,
Quoted 8 lines

kernel/lockdep.c static int mark_lock(struct task_struct *curr, struct held_lock *this, · line 2952

Quoted 6 lines
kernel/lockdep.c +2 -1
 void lockdep_init_map(struct lockdep_map *lock, const char *name,
                    struct lock_class_key *key, int subclass)
 {
-     memset(lock, 0, sizeof(*lock));
+     kmemcheck_mark_initialized(lock, 2*sizeof(void *));
+     memset(&lock->class_cache[0], 0, sizeof(*lock)-2*sizeof(void *));

That means ->key have chance to be 0 at some time, right?

How? We only memset from class_cache onwards, leaving name and key untouched.

author
#13 lore raw

Re: [PATCH 1/4] lockdep: lock_set_subclass() fix

Quoted 23 lines · Peter Zijlstra
Quoted 13 lines · Yong Zhang
Quoted 13 lines

struct lockdep_map {

include/linux/lockdep.h +1 -1
+     const char                      *name;
      struct lock_class_key           *key;
      struct lock_class               *class_cache[NR_LOCKDEP_CACHING_CLASSES];
-     const char                      *name;
 #ifdef CONFIG_LOCK_STAT
      int                             cpu;
      unsigned long                   ip;
kernel/lockdep.c +0 -0
index e69434b..81855cf 100644
@@ -2948,7 +2948,8 @@ static int mark_lock(struct task_struct *curr, struct held_lock *this,
Quoted 8 lines

kernel/lockdep.c static int mark_lock(struct task_struct *curr, struct held_lock *this, · line 2952

Quoted 6 lines
kernel/lockdep.c +2 -1
 void lockdep_init_map(struct lockdep_map *lock, const char *name,
                    struct lock_class_key *key, int subclass)
 {
-     memset(lock, 0, sizeof(*lock));
+     kmemcheck_mark_initialized(lock, 2*sizeof(void *));
+     memset(&lock->class_cache[0], 0, sizeof(*lock)-2*sizeof(void *));

That means ->key have chance to be 0 at some time, right?

Quoted 2 lines

How? We only memset from class_cache onwards, leaving name and key untouched.

Hmm, we don't touch key in your patch. My eyes was keeping on ->name. Sorry.

But how do we deal with ->class_cache? Always set it in loop_up_lock_class()?

Thanks,
Yong

#14 lore raw

Re: [PATCH 1/4] lockdep: lock_set_subclass() fix

Quoted 3 lines · Yong Zhang

But how do we deal with ->class_cache? Always set it in loop_up_lock_class()?

Hrm.. good point, aside from that there's another problem as well, I think we can deal with the cache being NULL, but is memset() an atomic write? If not a read could observe an intermediate state and go funny.

I'm tempted to go with the pure kmemcheck_mark_initialized() thing for now.

author
#15 lore raw

Re: [PATCH 1/4] lockdep: lock_set_subclass() fix

Quoted 8 lines · Peter Zijlstra
Quoted 3 lines · Yong Zhang

But how do we deal with ->class_cache? Always set it in loop_up_lock_class()?

Hrm.. good point, aside from that there's another problem as well, I think we can deal with the cache being NULL, but is memset() an atomic write? If not a read could observe an intermediate state and go funny.

Yup.

Quoted 3 lines

I'm tempted to go with the pure kmemcheck_mark_initialized() thing for now.

me too :)

So something like below?

Thanks,
Yong
---
From: Yong Zhang <[email protected]>
Subject: [PATCH] lockdep: kmemcheck: annotate ->lock in lockdep_init_map()

Since commit f59de89 [lockdep: Clear whole lockdep_map on initialization], lockdep_init_map() will clear all the struct. But it will break lock_set_class()/lock_set_subclass(). A typical race condition is like below:

CPU A CPU B
lock_set_subclass(lockA);
lock_set_class(lockA);

   lockdep_init_map(lockA);
     /* lockA->name is cleared */
     memset(lockA);
                                     __lock_acquire(lockA);
                                       /* lockA->class_cache[] is cleared */
                                       register_lock_class(lockA);
                                         look_up_lock_class(lockA);
                                           WARN_ON_ONCE(class->name !=
                                                     lock->name);

     lock->name = name;

So annotate ->lock with kmemcheck_mark_initialized() to cure this problem and the one reported in commit f59de89.

---
 kernel/lockdep.c |    2 +-
 1 files changed, 1 insertions(+), 1 deletions(-)
kernel/lockdep.c +1 -1
index e69434b..08a2b1b 100644
@@ -2948,7 +2948,7 @@ static int mark_lock(struct task_struct *curr, struct held_lock *this,
 void lockdep_init_map(struct lockdep_map *lock, const char *name,
 		      struct lock_class_key *key, int subclass)
 {
-	memset(lock, 0, sizeof(*lock));
+	kmemcheck_mark_initialized(lock, sizeof(*lock));
 
 #ifdef CONFIG_LOCK_STAT
 	lock->cpu = raw_smp_processor_id();
Signature · 1 line
1.7.5.4
author
#16 lore raw

Re: [PATCH 1/4] lockdep: lock_set_subclass() fix

Quoted 66 lines · Yong Zhang
Quoted 8 lines · Peter Zijlstra
Quoted 3 lines · Yong Zhang

But how do we deal with ->class_cache? Always set it in loop_up_lock_class()?

Hrm.. good point, aside from that there's another problem as well, I think we can deal with the cache being NULL, but is memset() an atomic write? If not a read could observe an intermediate state and go funny.

Yup.

Quoted 3 lines

I'm tempted to go with the pure kmemcheck_mark_initialized() thing for now.

me too :)

So something like below?

Thanks,
Yong
---
From: Yong Zhang <[email protected]>
Subject: [PATCH] lockdep: kmemcheck: annotate ->lock in lockdep_init_map()

Since commit f59de89 [lockdep: Clear whole lockdep_map on initialization], lockdep_init_map() will clear all the struct. But it will break lock_set_class()/lock_set_subclass(). A typical race condition is like below:

CPU A CPU B
lock_set_subclass(lockA);

 lock_set_class(lockA);
   lockdep_init_map(lockA);
     /* lockA->name is cleared */
     memset(lockA);
                                     __lock_acquire(lockA);
                                       /* lockA->class_cache[] is cleared */
                                       register_lock_class(lockA);
                                         look_up_lock_class(lockA);
                                           WARN_ON_ONCE(class->name !=
                                                     lock->name);

     lock->name = name;

So annotate ->lock with kmemcheck_mark_initialized() to cure this problem and the one reported in commit f59de89.

---
 kernel/lockdep.c |    2 +-
 1 files changed, 1 insertions(+), 1 deletions(-)
kernel/lockdep.c +0 -0
index e69434b..08a2b1b 100644

kernel/lockdep.c static int mark_lock(struct task_struct *curr, struct held_lock *this, · line 2951

Quoted 5 lines
kernel/lockdep.c +1 -1
@@ -2948,7 +2948,7 @@ static int mark_lock(struct task_struct *curr, struct held_lock *this,
 void lockdep_init_map(struct lockdep_map *lock, const char *name,
 		      struct lock_class_key *key, int subclass)
 {
-	memset(lock, 0, sizeof(*lock));
+	kmemcheck_mark_initialized(lock, sizeof(*lock));

But for a lock in initializing stage (like spin_lock_init()), we still need to guarantee that lock->class_cache[] doesn't contain garbage.

Need more thought here.

Thanks,
Yong

Quoted 6 lines
kernel/lockdep.c +0 -0
 #ifdef CONFIG_LOCK_STAT
 	lock->cpu = raw_smp_processor_id();
Signature · 1 line
1.7.5.4
Signature · 1 line
Only stand for myself
#17 lore raw

Re: [PATCH 1/4] lockdep: lock_set_subclass() fix

In reply to Yong Zhang
Quoted 1 line · Yong Zhang

So something like below?

that fails to clear/init the class_cache, leading to all sorts of problems.

Wiping the class_cache just reduces performance somewhat, not wiping them is disastrous since it can results in wild pointer derefs.

Now we could fix up register_lock_class() to reset the class_cache, although that's a little tricky and I'm not sure its worth it.

Anyway, I see you just realized the same.. ;-)

author
#18 lore raw

PATCH 1/2 lockdep: kmemcheck: annotate ->lock in lockdep_init_map()

Quoted 11 lines · Peter Zijlstra
Quoted 1 line · Yong Zhang

So something like below?

that fails to clear/init the class_cache, leading to all sorts of problems.

Wiping the class_cache just reduces performance somewhat, not wiping them is disastrous since it can results in wild pointer derefs.

Now we could fix up register_lock_class() to reset the class_cache, although that's a little tricky and I'm not sure its worth it.

Yeah, I have done some benchmark which show it's worthful, please check patch#2.

And below is the one which cure the current problem.

Thanks,
Yong

---
From: Yong Zhang <[email protected]>
Subject: [PATCH 1/2] lockdep: kmemcheck: annotate ->lock in lockdep_init_map()

Since commit f59de89 [lockdep: Clear whole lockdep_map on initialization], lockdep_init_map() will clear all the struct. But it will break lock_set_class()/lock_set_subclass(). A typical race condition is like below:

CPU A CPU B
lock_set_subclass(lockA);
lock_set_class(lockA);

   lockdep_init_map(lockA);
     /* lockA->name is cleared */
     memset(lockA);
                                     __lock_acquire(lockA);
                                       /* lockA->class_cache[] is cleared */
                                       register_lock_class(lockA);
                                         look_up_lock_class(lockA);
                                           WARN_ON_ONCE(class->name !=
                                                     lock->name);

     lock->name = name;

So restore to what we have done before commit f59de89 but annotate ->lock with kmemcheck_mark_initialized() to suppress the kmemcheck warning reported in commit f59de89.

---
 kernel/lockdep.c |    7 ++++++-
 1 files changed, 6 insertions(+), 1 deletions(-)
kernel/lockdep.c +6 -1
index e69434b..21ea1dc 100644
@@ -2948,7 +2948,12 @@ static int mark_lock(struct task_struct *curr, struct held_lock *this,
 void lockdep_init_map(struct lockdep_map *lock, const char *name,
 		      struct lock_class_key *key, int subclass)
 {
-	memset(lock, 0, sizeof(*lock));
+	int i;
+
+	kmemcheck_mark_initialized(lock, sizeof(*lock));
+
+	for (i = 0; i < NR_LOCKDEP_CACHING_CLASSES; i++)
+		lock->class_cache[i] = NULL;
 
 #ifdef CONFIG_LOCK_STAT
 	lock->cpu = raw_smp_processor_id();
Signature · 1 line
1.7.5.4
author
#19 lore raw

PATCH 2/2 lockdep: always try to set ->class_cache in register_lock_class() lockdep_init_map()

From: Yong Zhang <[email protected]>

commit [62016250 lockdep: Add improved subclass caching] try to improve performance (expecially to reduce the cost of rq->lock) when using lockdep, but it fails due to lockdep_init_map() in which ->class_cache is cleared. The typical caller is lock_set_subclass(), after that class will not be cached anymore.

This patch try to achive the goal of commit 62016250 by always setting ->class_cache in register_lock_class().

=== Score comparison of benchmarks ===

for i in seq 1 10; do ./perf bench -f simple sched messaging; done

before:  min: 0.604, max: 0.660, avg: 0.622
after:   min: 0.414, max: 0.473, avg: 0.427

for i in seq 1 10; do ./perf bench -f simple sched messaging -g 40; done

before:  min: 2.347, max: 2.421, avg: 2.391
after:   min: 1.652, max: 1.699, avg: 1.671
Cc Hitoshi Mitake <[email protected]>
Cc Peter Zijlstra <[email protected]>
Cc Ingo Molnar <[email protected]>
---
 kernel/lockdep.c |    3 ++-
 1 files changed, 2 insertions(+), 1 deletions(-)
kernel/lockdep.c +2 -1
index 21ea1dc..e9cbb99 100644
@@ -722,7 +722,7 @@ register_lock_class(struct lockdep_map *lock, unsigned int subclass, int force)
 
 	class = look_up_lock_class(lock, subclass);
 	if (likely(class))
-		return class;
+		goto out_set_class_cache;
 
 	/*
 	 * Debug-check: all keys must be persistent!
@@ -807,6 +807,7 @@ out_unlock_set:
 	graph_unlock();
 	raw_local_irq_restore(flags);
 
+out_set_class_cache:
 	if (!subclass || force)
 		lock->class_cache[0] = class;
 	else if (subclass < NR_LOCKDEP_CACHING_CLASSES)
Signature · 1 line
1.7.5.4
#20 lore raw

[tip:core/locking] lockdep: Always try to set ->class_cache in register_lock_class() lockdep_init_map()

Commit-ID:  87cdee71166fa107c5dc8e43060eeefa533c6a3b
Gitweb:     http://git.kernel.org/tip/87cdee71166fa107c5dc8e43060eeefa533c6a3b
Author:     Yong Zhang <[email protected]>
AuthorDate: Wed, 9 Nov 2011 16:07:14 +0800
Committer:  Ingo Molnar <[email protected]>
CommitDate: Mon, 14 Nov 2011 13:54:11 +0100

lockdep: Always try to set ->class_cache in register_lock_class() lockdep_init_map()

Commit ["62016250 lockdep: Add improved subclass caching"] tries to improve performance (expecially to reduce the cost of rq->lock) when using lockdep, but it fails due to lockdep_init_map() in which ->class_cache is cleared. The typical caller is lock_set_subclass(), after that class will not be cached anymore.

This patch tries to achive the goal of commit 62016250 by always setting ->class_cache in register_lock_class().

=== Score comparison of benchmarks ===

for i in seq 1 10; do ./perf bench -f simple sched messaging; done

before:  min: 0.604, max: 0.660, avg: 0.622
after:   min: 0.414, max: 0.473, avg: 0.427

for i in seq 1 10; do ./perf bench -f simple sched messaging -g 40; done

before:  min: 2.347, max: 2.421, avg: 2.391
after:   min: 1.652, max: 1.699, avg: 1.671
---
 kernel/lockdep.c |    3 ++-
 1 files changed, 2 insertions(+), 1 deletions(-)
kernel/lockdep.c +2 -1
index e69434b..103bed8 100644
@@ -722,7 +722,7 @@ register_lock_class(struct lockdep_map *lock, unsigned int subclass, int force)
 
 	class = look_up_lock_class(lock, subclass);
 	if (likely(class))
-		return class;
+		goto out_set_class_cache;
 
 	/*
 	 * Debug-check: all keys must be persistent!
@@ -807,6 +807,7 @@ out_unlock_set:
 	graph_unlock();
 	raw_local_irq_restore(flags);
 
+out_set_class_cache:
 	if (!subclass || force)
 		lock->class_cache[0] = class;
 	else if (subclass < NR_LOCKDEP_CACHING_CLASSES)
#21 lore raw

[tip:core/locking] lockdep, kmemcheck: Annotate ->lock in lockdep_init_map()

In reply to Yong Zhang
Commit-ID:  d3d03d4fc5b1bec3a579112de170a9676e9d97cb
Gitweb:     http://git.kernel.org/tip/d3d03d4fc5b1bec3a579112de170a9676e9d97cb
Author:     Yong Zhang <[email protected]>
AuthorDate: Wed, 9 Nov 2011 16:04:51 +0800
Committer:  Ingo Molnar <[email protected]>
CommitDate: Tue, 6 Dec 2011 08:16:51 +0100

lockdep, kmemcheck: Annotate ->lock in lockdep_init_map()

Since commit f59de89 ("lockdep: Clear whole lockdep_map on initialization"), lockdep_init_map() will clear all the struct. But it will break lock_set_class()/lock_set_subclass(). A typical race condition is like below:

CPU A CPU B
lock_set_subclass(lockA);
lock_set_class(lockA);

   lockdep_init_map(lockA);
     /* lockA->name is cleared */
     memset(lockA);
                                     __lock_acquire(lockA);
                                       /* lockA->class_cache[] is cleared */
                                       register_lock_class(lockA);
                                         look_up_lock_class(lockA);
                                           WARN_ON_ONCE(class->name !=
                                                     lock->name);

     lock->name = name;

So restore to what we have done before commit f59de89 but annotate ->lock with kmemcheck_mark_initialized() to suppress the kmemcheck warning reported in commit f59de89.

---
 kernel/lockdep.c |    8 +++++++-
 1 files changed, 7 insertions(+), 1 deletions(-)
kernel/lockdep.c +7 -1
index a2ab30c..54cc0dc 100644
@@ -44,6 +44,7 @@
 #include <linux/stringify.h>
 #include <linux/bitops.h>
 #include <linux/gfp.h>
+#include <linux/kmemcheck.h>
 
 #include <asm/sections.h>
 
@@ -2950,7 +2951,12 @@ static int mark_lock(struct task_struct *curr, struct held_lock *this,
 void lockdep_init_map(struct lockdep_map *lock, const char *name,
 		      struct lock_class_key *key, int subclass)
 {
-	memset(lock, 0, sizeof(*lock));
+	int i;
+
+	kmemcheck_mark_initialized(lock, sizeof(*lock));
+
+	for (i = 0; i < NR_LOCKDEP_CACHING_CLASSES; i++)
+		lock->class_cache[i] = NULL;
 
 #ifdef CONFIG_LOCK_STAT
 	lock->cpu = raw_smp_processor_id();
#22 lore raw

Re: [tip:core/locking] lockdep, kmemcheck: Annotate ->lock in lockdep_init_map()

Quoted 33 lines · tip-bot for Yong Zhang
Commit-ID:  d3d03d4fc5b1bec3a579112de170a9676e9d97cb
Gitweb:     http://git.kernel.org/tip/d3d03d4fc5b1bec3a579112de170a9676e9d97cb
Author:     Yong Zhang <[email protected]>
AuthorDate: Wed, 9 Nov 2011 16:04:51 +0800
Committer:  Ingo Molnar <[email protected]>
CommitDate: Tue, 6 Dec 2011 08:16:51 +0100

lockdep, kmemcheck: Annotate ->lock in lockdep_init_map()

Since commit f59de89 ("lockdep: Clear whole lockdep_map on initialization"), lockdep_init_map() will clear all the struct. But it will break lock_set_class()/lock_set_subclass(). A typical race condition is like below:

CPU A CPU B
lock_set_subclass(lockA);

 lock_set_class(lockA);
   lockdep_init_map(lockA);
     /* lockA->name is cleared */
     memset(lockA);
                                     __lock_acquire(lockA);
                                       /* lockA->class_cache[] is cleared */
                                       register_lock_class(lockA);
                                         look_up_lock_class(lockA);
                                           WARN_ON_ONCE(class->name !=
                                                     lock->name);

     lock->name = name;

So restore to what we have done before commit f59de89 but annotate ->lock with kmemcheck_mark_initialized() to suppress the kmemcheck warning reported in commit f59de89.

Quoted 6 lines
Quoted 3 lines
#23 lore raw

[tip:perf/urgent] lockdep, kmemcheck: Annotate ->lock in lockdep_init_map()

In reply to Yong Zhang
Commit-ID:  a33caeb118198286309859f014c0662f3ed54ed4
Gitweb:     http://git.kernel.org/tip/a33caeb118198286309859f014c0662f3ed54ed4
Author:     Yong Zhang <[email protected]>
AuthorDate: Wed, 9 Nov 2011 16:04:51 +0800
Committer:  Ingo Molnar <[email protected]>
CommitDate: Tue, 6 Dec 2011 18:18:13 +0100

lockdep, kmemcheck: Annotate ->lock in lockdep_init_map()

Since commit f59de89 ("lockdep: Clear whole lockdep_map on initialization"), lockdep_init_map() will clear all the struct. But it will break lock_set_class()/lock_set_subclass(). A typical race condition is like below:

CPU A CPU B
lock_set_subclass(lockA);
lock_set_class(lockA);

   lockdep_init_map(lockA);
     /* lockA->name is cleared */
     memset(lockA);
                                     __lock_acquire(lockA);
                                       /* lockA->class_cache[] is cleared */
                                       register_lock_class(lockA);
                                         look_up_lock_class(lockA);
                                           WARN_ON_ONCE(class->name !=
                                                     lock->name);

     lock->name = name;

So restore to what we have done before commit f59de89 but annotate ->lock with kmemcheck_mark_initialized() to suppress the kmemcheck warning reported in commit f59de89.

---
 kernel/lockdep.c |    8 +++++++-
 1 files changed, 7 insertions(+), 1 deletions(-)
kernel/lockdep.c +7 -1
index e69434b..b2e08c9 100644
@@ -44,6 +44,7 @@
 #include <linux/stringify.h>
 #include <linux/bitops.h>
 #include <linux/gfp.h>
+#include <linux/kmemcheck.h>
 
 #include <asm/sections.h>
 
@@ -2948,7 +2949,12 @@ static int mark_lock(struct task_struct *curr, struct held_lock *this,
 void lockdep_init_map(struct lockdep_map *lock, const char *name,
 		      struct lock_class_key *key, int subclass)
 {
-	memset(lock, 0, sizeof(*lock));
+	int i;
+
+	kmemcheck_mark_initialized(lock, sizeof(*lock));
+
+	for (i = 0; i < NR_LOCKDEP_CACHING_CLASSES; i++)
+		lock->class_cache[i] = NULL;
 
 #ifdef CONFIG_LOCK_STAT
 	lock->cpu = raw_smp_processor_id();
author
#24 lore raw

Re: [PATCH 1/4] lockdep: lock_set_subclass() fix

In reply to Vegard Nossum
Quoted 7 lines · Vegard Nossum

1. Initialise the thing completely before doing the copy, or
2. Ignore the warning.

The memset() patch (f59de8992aa6dc85e81aadc26b0f69e17809721d) attempts to do the first, i.e. to clear the whole struct in lockdep_init_map().

Quoted 8 lines

I think nr. 1 is the best way to go in principle, but I don't know what it takes for this to work properly. The blanket-clear memset() presumably doesn't work because it clears out something that was already initialised by the caller (right?).

Yong Zhang, can you think of a way to avoid the race you described, perhaps by memset()ing only the right/relevant parts of struct lockdep_map in lockdep_init_map()?

That could work, but we should take more care on the member 'class_cache', because under some condition (lock_set_subclass()) we don't need to initialise it for performance issue, but under other condtion ( set a new valid key to a class) we need to initialise it since it's invalid anymore.

Another option is always seting ->class_cache if lookup_lock_class() find the class. Will talk about it with Peter in another thread.

Quoted 21 lines

Peter Zijlstra, if you prefer, we can also just tell kmemcheck that this particular copy is fine, but it means that kmemcheck will not be able to detect any real bugs in this code. It can be done with something like:

kernel/lockdep.c +1 -1
index e69434b..08a2b1b 100644
@@ -2948,7 +2948,7 @@ static int mark_lock(struct task_struct *curr,
 struct held_lock *this,
 void lockdep_init_map(struct lockdep_map *lock, const char *name,
                      struct lock_class_key *key, int subclass)
 {
-       memset(lock, 0, sizeof(*lock));
+       kmemcheck_mark_initialized(lock, sizeof(*lock));
 
 #ifdef CONFIG_LOCK_STAT
        lock->cpu = raw_smp_processor_id();
Quoted 4 lines

Christian Casteyde, do you mind testing this patch as well?

(Yong Zhang, do you think this would still be vulnerable to the race you described?)

No, this will work because we just retore the previous behavior except kmemcheck annotation, right?

Thanks,
Yong

author
#25 lore raw

RFC PATCH 2/4 lockdep: Let register_lock_class() can be called with/without graph_lock

In reply to Yong Zhang

Will be used in later patch which fix some race condition in lock_set_class().

Cc Peter Zijlstra <[email protected]>
Cc Ingo Molnar <[email protected]>
---
 kernel/lockdep.c |   14 +++++++++-----
 1 files changed, 9 insertions(+), 5 deletions(-)
kernel/lockdep.c +9 -5
index 4a16b0a..e7d38fa 100644
@@ -711,9 +711,13 @@ look_up_lock_class(struct lockdep_map *lock, unsigned int subclass)
  * Register a lock's class in the hash-table, if the class is not present
  * yet. Otherwise we look it up. We cache the result in the lock object
  * itself, so actual lookup of the hash should be once per lock object.
+ *
+ * NOTE: register_lock_class() can be called with/without graph_lock hold
+ *	 depending on @locked. But it will release graph_lock before return.
  */
 static inline struct lock_class *
-register_lock_class(struct lockdep_map *lock, unsigned int subclass, int force)
+register_lock_class(struct lockdep_map *lock, unsigned int subclass,
+		    int force, int locked)
 {
 	struct lockdep_subclass_key *key;
 	struct list_head *hash_head;
@@ -741,7 +745,7 @@ register_lock_class(struct lockdep_map *lock, unsigned int subclass, int force)
 	hash_head = classhashentry(key);
 
 	raw_local_irq_save(flags);
-	if (!graph_lock()) {
+	if (!locked && !graph_lock()) {
 		raw_local_irq_restore(flags);
 		return NULL;
 	}
@@ -2986,7 +2990,7 @@ void lockdep_init_map(struct lockdep_map *lock, const char *name,
 		return;
 
 	if (subclass)
-		register_lock_class(lock, subclass, 1);
+		register_lock_class(lock, subclass, 1, 0);
 }
 EXPORT_SYMBOL_GPL(lockdep_init_map);
 
@@ -3032,7 +3036,7 @@ static int __lock_acquire(struct lockdep_map *lock, unsigned int subclass,
 	 * Not cached?
 	 */
 	if (unlikely(!class)) {
-		class = register_lock_class(lock, subclass, 0);
+		class = register_lock_class(lock, subclass, 0, 0);
 		if (!class)
 			return 0;
 	}
@@ -3276,7 +3280,7 @@ found_it:
 	/* optimizing for lock_set_subclass() */
 	if (key) {
 		lockdep_init_map(lock, name, key, 0);
-		register_lock_class(lock, subclass, 0);
+		register_lock_class(lock, subclass, 0, 0);
 	}
 
 	curr->lockdep_depth = i;
Signature · 1 line
1.7.5.4
author
#26 lore raw

RFC PATCH 3/4 lockdep: split lockdep_init_map()

In reply to Yong Zhang

A new one __lockdep_init_map() is introduced which only initialize the struct. It will be call in later patch in __lock_set_class() to reduce recursive call to register_lock_class().

Cc Peter Zijlstra <[email protected]>
Cc Ingo Molnar <[email protected]>
---
 kernel/lockdep.c |   25 ++++++++++++++++++-------
 1 files changed, 18 insertions(+), 7 deletions(-)
kernel/lockdep.c +18 -7
index e7d38fa..3af87ad 100644
@@ -2949,8 +2949,9 @@ static int mark_lock(struct task_struct *curr, struct held_lock *this,
 /*
  * Initialize a lock instance's lock-class mapping info:
  */
-void lockdep_init_map(struct lockdep_map *lock, const char *name,
-		      struct lock_class_key *key, int subclass)
+static inline int
+__lockdep_init_map(struct lockdep_map *lock, const char *name,
+		 struct lock_class_key *key, int subclass)
 {
 	memset(lock, 0, sizeof(*lock));
 
@@ -2963,7 +2964,7 @@ void lockdep_init_map(struct lockdep_map *lock, const char *name,
 	 */
 	if (DEBUG_LOCKS_WARN_ON(!name)) {
 		lock->name = "NULL";
-		return;
+		return 0;
 	}
 
 	lock->name = name;
@@ -2972,7 +2973,7 @@ void lockdep_init_map(struct lockdep_map *lock, const char *name,
 	 * No key, no joy, we need to hash something.
 	 */
 	if (DEBUG_LOCKS_WARN_ON(!key))
-		return;
+		return 0;
 	/*
 	 * Sanity check, the lock-class key must be persistent:
 	 */
@@ -2982,15 +2983,25 @@ void lockdep_init_map(struct lockdep_map *lock, const char *name,
 		 * What it says above ^^^^^, I suggest you read it.
 		 */
 		DEBUG_LOCKS_WARN_ON(1);
-		return;
+		return 0;
 	}
 	lock->key = key;
 
 	if (unlikely(!debug_locks))
-		return;
+		return 0;
+
+	return 1;
+}
+
+void lockdep_init_map(struct lockdep_map *lock, const char *name,
+		      struct lock_class_key *key, int subclass)
+{
+	int ret;
 
-	if (subclass)
+	ret = __lockdep_init_map(lock, name, key, subclass);
+	if (ret && subclass)
 		register_lock_class(lock, subclass, 1, 0);
+
 }
 EXPORT_SYMBOL_GPL(lockdep_init_map);
 
Signature · 1 line
1.7.5.4
author
#27 lore raw

RFC PATCH 4/4 lockdep: fix race condition in __lock_set_class()

In reply to Yong Zhang

When someone call lock_set_class() with valid key, nothing protect the initializing of 'lockdep_map'; thus could lead to flase positive warning from lockdep (such as "key not in .data!"). This patch cure that potential issue.

Cc Peter Zijlstra <[email protected]>
Cc Ingo Molnar <[email protected]>
---
 kernel/lockdep.c |    8 ++++++--
 1 files changed, 6 insertions(+), 2 deletions(-)
kernel/lockdep.c +6 -2
index 3af87ad..fd4d816 100644
@@ -3290,8 +3290,12 @@ __lock_set_class(struct lockdep_map *lock, const char *name,
 found_it:
 	/* optimizing for lock_set_subclass() */
 	if (key) {
-		lockdep_init_map(lock, name, key, 0);
-		register_lock_class(lock, subclass, 0, 0);
+		if (!graph_lock())
+			return 0;
+
+		__lockdep_init_map(lock, name, key, 0);
+		/* will release graph_lock() there */
+		register_lock_class(lock, subclass, 0, 1);
 	}
 
 	curr->lockdep_depth = i;
Signature · 1 line
1.7.5.4
#28 lore raw

Re: [RFC PATCH 4/4] lockdep: fix race condition in __lock_set_class()

Quoted 4 lines · Yong Zhang

When someone call lock_set_class() with valid key, nothing protect the initializing of 'lockdep_map'; thus could lead to flase positive warning from lockdep (such as "key not in .data!"). This patch cure that potential issue.

How? that warning is triggered of the lock_class_key * passed into lockdep_init_map and is unrelated to the actual content of the lockdep_map.

author
#29 lore raw

Re: [RFC PATCH 4/4] lockdep: fix race condition in __lock_set_class()

Quoted 9 lines · Peter Zijlstra
Quoted 4 lines · Yong Zhang

When someone call lock_set_class() with valid key, nothing protect the initializing of 'lockdep_map'; thus could lead to flase positive warning from lockdep (such as "key not in .data!"). This patch cure that potential issue.

How? that warning is triggered of the lock_class_key * passed into lockdep_init_map and is unrelated to the actual content of the lockdep_map.

Oh, my fault, the false positive warning could be "non-static key".

But seems we should find the right direction for this bug :) Some comments in replies to patch#1.

Thanks,
Yong

#30 lore raw
In reply to Yong Zhang
Quoted 4 lines · Yong Zhang

This a new version of my previous post[1].

patch#1 is the one which fix the reported bug.
So Sergey, Borislav, would you please give it a test?

Yep, applied and running now, will let you know what happens.

Thanks.

Signature · 2 lines
Regards/Gruss,
   Boris