diff mbox series

[v3,1/2] wait_bit: do read barrier after testing a bit

Message ID alpine.LRH.2.02.2207311639360.21350@file01.intranet.prod.int.rdu2.redhat.com (mailing list archive)
State New, archived
Headers show
Series [v3,1/2] wait_bit: do read barrier after testing a bit | expand

Commit Message

Mikulas Patocka July 31, 2022, 8:40 p.m. UTC
wait_on_bit tests the bit without any memory barriers, consequently the
code that follows wait_on_bit may be moved before testing the bit on
architectures with weak memory ordering. When the code tests for some
event using wait_on_bit and then performs a load operation, the load may
be unexpectedly moved before wait_on_bit and it may return data that
existed before the event occurred.

Such bugs exist in fs/buffer.c:__wait_on_buffer,
drivers/md/dm-bufio.c:new_read,
drivers/media/usb/dvb-usb-v2/dvb_usb_core.c:dvb_usb_start_feed,
drivers/bluetooth/btusb.c:btusb_mtk_hci_wmt_sync
and perhaps in other places.

We fix this class of bugs by adding a read barrier after test_bit().

Signed-off-by: Mikulas Patocka <mpatocka@redhat.com>
Cc: stable@vger.kernel.org

Comments

Linus Torvalds July 31, 2022, 8:57 p.m. UTC | #1
On Sun, Jul 31, 2022 at 1:41 PM Mikulas Patocka <mpatocka@redhat.com> wrote:
>
> -       if (!test_bit(bit, word))
> +       if (!test_bit(bit, word)) {
> +               smp_rmb();

Logically, I don't think that makes sense.

Maybe you're checking the buffer being up-to-date before you *write* to it?

So smp_rmb() seems entirely wrong.

I think it should consistently aim for just doing

        unsigned long state = smp_read_acquire(word);
        if (!(state & (1 << bit)))
                return 0;

or whatever.

We should strive to *not* add new uses of the legacy memory barriers.
They are garbage from last century when people didn't know better.

Then people learnt to use acquire and release, and things improved.
Let's live in that improved world.

                 Linus
Alan Stern Aug. 1, 2022, 12:27 a.m. UTC | #2
On Sun, Jul 31, 2022 at 04:40:59PM -0400, Mikulas Patocka wrote:
> wait_on_bit tests the bit without any memory barriers, consequently the
> code that follows wait_on_bit may be moved before testing the bit on
> architectures with weak memory ordering. When the code tests for some
> event using wait_on_bit and then performs a load operation, the load may
> be unexpectedly moved before wait_on_bit and it may return data that
> existed before the event occurred.
> 
> Such bugs exist in fs/buffer.c:__wait_on_buffer,
> drivers/md/dm-bufio.c:new_read,
> drivers/media/usb/dvb-usb-v2/dvb_usb_core.c:dvb_usb_start_feed,
> drivers/bluetooth/btusb.c:btusb_mtk_hci_wmt_sync
> and perhaps in other places.
> 
> We fix this class of bugs by adding a read barrier after test_bit().
> 
> Signed-off-by: Mikulas Patocka <mpatocka@redhat.com>
> Cc: stable@vger.kernel.org
> 
> Index: linux-2.6/include/linux/wait_bit.h
> ===================================================================
> --- linux-2.6.orig/include/linux/wait_bit.h
> +++ linux-2.6/include/linux/wait_bit.h
> @@ -71,8 +71,10 @@ static inline int
>  wait_on_bit(unsigned long *word, int bit, unsigned mode)
>  {
>  	might_sleep();
> -	if (!test_bit(bit, word))
> +	if (!test_bit(bit, word)) {
> +		smp_rmb();

Any new code using smp_rmb or an acquire access should always include a 
comment that explains where the matching smp_wmb or release access is.

Alan Stern

>  		return 0;
> +	}
>  	return out_of_line_wait_on_bit(word, bit,
>  				       bit_wait,
>  				       mode);
> @@ -96,8 +98,10 @@ static inline int
>  wait_on_bit_io(unsigned long *word, int bit, unsigned mode)
>  {
>  	might_sleep();
> -	if (!test_bit(bit, word))
> +	if (!test_bit(bit, word)) {
> +		smp_rmb();
>  		return 0;
> +	}
>  	return out_of_line_wait_on_bit(word, bit,
>  				       bit_wait_io,
>  				       mode);
> @@ -123,8 +127,10 @@ wait_on_bit_timeout(unsigned long *word,
>  		    unsigned long timeout)
>  {
>  	might_sleep();
> -	if (!test_bit(bit, word))
> +	if (!test_bit(bit, word)) {
> +		smp_rmb();
>  		return 0;
> +	}
>  	return out_of_line_wait_on_bit_timeout(word, bit,
>  					       bit_wait_timeout,
>  					       mode, timeout);
> @@ -151,8 +157,10 @@ wait_on_bit_action(unsigned long *word,
>  		   unsigned mode)
>  {
>  	might_sleep();
> -	if (!test_bit(bit, word))
> +	if (!test_bit(bit, word)) {
> +		smp_rmb();
>  		return 0;
> +	}
>  	return out_of_line_wait_on_bit(word, bit, action, mode);
>  }
>  
> Index: linux-2.6/kernel/sched/wait_bit.c
> ===================================================================
> --- linux-2.6.orig/kernel/sched/wait_bit.c
> +++ linux-2.6/kernel/sched/wait_bit.c
> @@ -51,6 +51,8 @@ __wait_on_bit(struct wait_queue_head *wq
>  
>  	finish_wait(wq_head, &wbq_entry->wq_entry);
>  
> +	smp_rmb();
> +
>  	return ret;
>  }
>  EXPORT_SYMBOL(__wait_on_bit);
>
Mikulas Patocka Aug. 1, 2022, 10:40 a.m. UTC | #3
On Sun, 31 Jul 2022, Linus Torvalds wrote:

> On Sun, Jul 31, 2022 at 1:41 PM Mikulas Patocka <mpatocka@redhat.com> wrote:
> >
> > -       if (!test_bit(bit, word))
> > +       if (!test_bit(bit, word)) {
> > +               smp_rmb();
> 
> Logically, I don't think that makes sense.
> 
> Maybe you're checking the buffer being up-to-date before you *write* to it?

None of the CPUs have speculative writes - so the write can't be moved 
before the "test_bit" function. So, we are only concerned about reads.

> So smp_rmb() seems entirely wrong.
> 
> I think it should consistently aim for just doing
> 
>         unsigned long state = smp_read_acquire(word);
>         if (!(state & (1 << bit)))
>                 return 0;
> 
> or whatever.
> 
> We should strive to *not* add new uses of the legacy memory barriers.
> They are garbage from last century when people didn't know better.
> 
> Then people learnt to use acquire and release, and things improved.
> Let's live in that improved world.
> 
>                  Linus

OK - I'm sending new patches that introduce the function test_bit_acquire.

Mikulas
diff mbox series

Patch

Index: linux-2.6/include/linux/wait_bit.h
===================================================================
--- linux-2.6.orig/include/linux/wait_bit.h
+++ linux-2.6/include/linux/wait_bit.h
@@ -71,8 +71,10 @@  static inline int
 wait_on_bit(unsigned long *word, int bit, unsigned mode)
 {
 	might_sleep();
-	if (!test_bit(bit, word))
+	if (!test_bit(bit, word)) {
+		smp_rmb();
 		return 0;
+	}
 	return out_of_line_wait_on_bit(word, bit,
 				       bit_wait,
 				       mode);
@@ -96,8 +98,10 @@  static inline int
 wait_on_bit_io(unsigned long *word, int bit, unsigned mode)
 {
 	might_sleep();
-	if (!test_bit(bit, word))
+	if (!test_bit(bit, word)) {
+		smp_rmb();
 		return 0;
+	}
 	return out_of_line_wait_on_bit(word, bit,
 				       bit_wait_io,
 				       mode);
@@ -123,8 +127,10 @@  wait_on_bit_timeout(unsigned long *word,
 		    unsigned long timeout)
 {
 	might_sleep();
-	if (!test_bit(bit, word))
+	if (!test_bit(bit, word)) {
+		smp_rmb();
 		return 0;
+	}
 	return out_of_line_wait_on_bit_timeout(word, bit,
 					       bit_wait_timeout,
 					       mode, timeout);
@@ -151,8 +157,10 @@  wait_on_bit_action(unsigned long *word,
 		   unsigned mode)
 {
 	might_sleep();
-	if (!test_bit(bit, word))
+	if (!test_bit(bit, word)) {
+		smp_rmb();
 		return 0;
+	}
 	return out_of_line_wait_on_bit(word, bit, action, mode);
 }
 
Index: linux-2.6/kernel/sched/wait_bit.c
===================================================================
--- linux-2.6.orig/kernel/sched/wait_bit.c
+++ linux-2.6/kernel/sched/wait_bit.c
@@ -51,6 +51,8 @@  __wait_on_bit(struct wait_queue_head *wq
 
 	finish_wait(wq_head, &wbq_entry->wq_entry);
 
+	smp_rmb();
+
 	return ret;
 }
 EXPORT_SYMBOL(__wait_on_bit);