diff mbox series

nouveau: fix instmem race condition around ptr stores

Message ID 20240409003401.2224446-1-airlied@gmail.com (mailing list archive)
State New, archived
Headers show
Series nouveau: fix instmem race condition around ptr stores | expand

Commit Message

Dave Airlie April 9, 2024, 12:34 a.m. UTC
From: Dave Airlie <airlied@redhat.com>

Running a lot of VK CTS in parallel against nouveau, once every
few hours you might see something like this crash.

BUG: kernel NULL pointer dereference, address: 0000000000000008
PGD 8000000114e6e067 P4D 8000000114e6e067 PUD 109046067 PMD 0
Oops: 0000 [#1] PREEMPT SMP PTI
CPU: 7 PID: 53891 Comm: deqp-vk Not tainted 6.8.0-rc6+ #27
Hardware name: Gigabyte Technology Co., Ltd. Z390 I AORUS PRO WIFI/Z390 I AORUS PRO WIFI-CF, BIOS F8 11/05/2021
RIP: 0010:gp100_vmm_pgt_mem+0xe3/0x180 [nouveau]
Code: c7 48 01 c8 49 89 45 58 85 d2 0f 84 95 00 00 00 41 0f b7 46 12 49 8b 7e 08 89 da 42 8d 2c f8 48 8b 47 08 41 83 c7 01 48 89 ee <48> 8b 40 08 ff d0 0f 1f 00 49 8b 7e 08 48 89 d9 48 8d 75 04 48 c1
RSP: 0000:ffffac20c5857838 EFLAGS: 00010202
RAX: 0000000000000000 RBX: 00000000004d8001 RCX: 0000000000000001
RDX: 00000000004d8001 RSI: 00000000000006d8 RDI: ffffa07afe332180
RBP: 00000000000006d8 R08: ffffac20c5857ad0 R09: 0000000000ffff10
R10: 0000000000000001 R11: ffffa07af27e2de0 R12: 000000000000001c
R13: ffffac20c5857ad0 R14: ffffa07a96fe9040 R15: 000000000000001c
FS:  00007fe395eed7c0(0000) GS:ffffa07e2c980000(0000) knlGS:0000000000000000
CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: 0000000000000008 CR3: 000000011febe001 CR4: 00000000003706f0
DR0: 0000000000000000 DR1: 0000000000000000 DR2: 0000000000000000
DR3: 0000000000000000 DR6: 00000000fffe0ff0 DR7: 0000000000000400
Call Trace:

...

 ? gp100_vmm_pgt_mem+0xe3/0x180 [nouveau]
 ? gp100_vmm_pgt_mem+0x37/0x180 [nouveau]
 nvkm_vmm_iter+0x351/0xa20 [nouveau]
 ? __pfx_nvkm_vmm_ref_ptes+0x10/0x10 [nouveau]
 ? __pfx_gp100_vmm_pgt_mem+0x10/0x10 [nouveau]
 ? __pfx_gp100_vmm_pgt_mem+0x10/0x10 [nouveau]
 ? __lock_acquire+0x3ed/0x2170
 ? __pfx_gp100_vmm_pgt_mem+0x10/0x10 [nouveau]
 nvkm_vmm_ptes_get_map+0xc2/0x100 [nouveau]
 ? __pfx_nvkm_vmm_ref_ptes+0x10/0x10 [nouveau]
 ? __pfx_gp100_vmm_pgt_mem+0x10/0x10 [nouveau]
 nvkm_vmm_map_locked+0x224/0x3a0 [nouveau]

Adding any sort of useful debug usually makes it go away, so I hand
wrote the function in a line, and debugged the asm.

Every so often pt->memory->ptrs is NULL. This ptrs ptr is set in
the nv50_instobj_acquire called from nvkm_kmap.

If Thread A and Thread B both get to nv50_instobj_acquire around
the same time, and Thread A hits the refcount_set line, and in
lockstep thread B succeeds at refcount_inc_not_zero, there is a
chance the ptrs value won't have been stored since refcount_set
is unordered. Force a memory barrier here, I picked smp_mb, since
we want it on all CPUs and it's write followed by a read.

Cc: linux-stable
Signed-off-by: Dave Airlie <airlied@redhat.com>
---
 drivers/gpu/drm/nouveau/nvkm/subdev/instmem/nv50.c | 3 +++
 1 file changed, 3 insertions(+)

Comments

Lucas Stach April 9, 2024, 8:27 a.m. UTC | #1
Am Dienstag, dem 09.04.2024 um 10:34 +1000 schrieb Dave Airlie:
> From: Dave Airlie <airlied@redhat.com>
> 
> Running a lot of VK CTS in parallel against nouveau, once every
> few hours you might see something like this crash.
> 
> BUG: kernel NULL pointer dereference, address: 0000000000000008
> PGD 8000000114e6e067 P4D 8000000114e6e067 PUD 109046067 PMD 0
> Oops: 0000 [#1] PREEMPT SMP PTI
> CPU: 7 PID: 53891 Comm: deqp-vk Not tainted 6.8.0-rc6+ #27
> Hardware name: Gigabyte Technology Co., Ltd. Z390 I AORUS PRO WIFI/Z390 I AORUS PRO WIFI-CF, BIOS F8 11/05/2021
> RIP: 0010:gp100_vmm_pgt_mem+0xe3/0x180 [nouveau]
> Code: c7 48 01 c8 49 89 45 58 85 d2 0f 84 95 00 00 00 41 0f b7 46 12 49 8b 7e 08 89 da 42 8d 2c f8 48 8b 47 08 41 83 c7 01 48 89 ee <48> 8b 40 08 ff d0 0f 1f 00 49 8b 7e 08 48 89 d9 48 8d 75 04 48 c1
> RSP: 0000:ffffac20c5857838 EFLAGS: 00010202
> RAX: 0000000000000000 RBX: 00000000004d8001 RCX: 0000000000000001
> RDX: 00000000004d8001 RSI: 00000000000006d8 RDI: ffffa07afe332180
> RBP: 00000000000006d8 R08: ffffac20c5857ad0 R09: 0000000000ffff10
> R10: 0000000000000001 R11: ffffa07af27e2de0 R12: 000000000000001c
> R13: ffffac20c5857ad0 R14: ffffa07a96fe9040 R15: 000000000000001c
> FS:  00007fe395eed7c0(0000) GS:ffffa07e2c980000(0000) knlGS:0000000000000000
> CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
> CR2: 0000000000000008 CR3: 000000011febe001 CR4: 00000000003706f0
> DR0: 0000000000000000 DR1: 0000000000000000 DR2: 0000000000000000
> DR3: 0000000000000000 DR6: 00000000fffe0ff0 DR7: 0000000000000400
> Call Trace:
> 
> ...
> 
>  ? gp100_vmm_pgt_mem+0xe3/0x180 [nouveau]
>  ? gp100_vmm_pgt_mem+0x37/0x180 [nouveau]
>  nvkm_vmm_iter+0x351/0xa20 [nouveau]
>  ? __pfx_nvkm_vmm_ref_ptes+0x10/0x10 [nouveau]
>  ? __pfx_gp100_vmm_pgt_mem+0x10/0x10 [nouveau]
>  ? __pfx_gp100_vmm_pgt_mem+0x10/0x10 [nouveau]
>  ? __lock_acquire+0x3ed/0x2170
>  ? __pfx_gp100_vmm_pgt_mem+0x10/0x10 [nouveau]
>  nvkm_vmm_ptes_get_map+0xc2/0x100 [nouveau]
>  ? __pfx_nvkm_vmm_ref_ptes+0x10/0x10 [nouveau]
>  ? __pfx_gp100_vmm_pgt_mem+0x10/0x10 [nouveau]
>  nvkm_vmm_map_locked+0x224/0x3a0 [nouveau]
> 
> Adding any sort of useful debug usually makes it go away, so I hand
> wrote the function in a line, and debugged the asm.
> 
> Every so often pt->memory->ptrs is NULL. This ptrs ptr is set in
> the nv50_instobj_acquire called from nvkm_kmap.
> 
> If Thread A and Thread B both get to nv50_instobj_acquire around
> the same time, and Thread A hits the refcount_set line, and in
> lockstep thread B succeeds at refcount_inc_not_zero, there is a
> chance the ptrs value won't have been stored since refcount_set
> is unordered. Force a memory barrier here, I picked smp_mb, since
> we want it on all CPUs and it's write followed by a read.
> 
> Cc: linux-stable
> Signed-off-by: Dave Airlie <airlied@redhat.com>
> ---
>  drivers/gpu/drm/nouveau/nvkm/subdev/instmem/nv50.c | 3 +++
>  1 file changed, 3 insertions(+)
> 
> diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/instmem/nv50.c b/drivers/gpu/drm/nouveau/nvkm/subdev/instmem/nv50.c
> index a7f3fc342d87..cbacc7b11f8c 100644
> --- a/drivers/gpu/drm/nouveau/nvkm/subdev/instmem/nv50.c
> +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/instmem/nv50.c
> @@ -250,6 +250,9 @@ nv50_instobj_acquire(struct nvkm_memory *memory)
>  			iobj->base.memory.ptrs = &nv50_instobj_fast;
>  		else
>  			iobj->base.memory.ptrs = &nv50_instobj_slow;
> +		/* barrier to ensure ptrs is written before another thread
> +		   does refcount_inc_not_zero successfully. */
> +		smp_mb();

Doesn't this miss the corresponding smp_rmb after
refcount_inc_not_zero? Without it a sufficiently speculating CPU might
still hoist the NULL ptr load across the refcount increase.

Regards,
Lucas
Danilo Krummrich April 9, 2024, 11:33 a.m. UTC | #2
On 4/9/24 10:27, Lucas Stach wrote:
> Am Dienstag, dem 09.04.2024 um 10:34 +1000 schrieb Dave Airlie:
>> From: Dave Airlie <airlied@redhat.com>
>>
>> Running a lot of VK CTS in parallel against nouveau, once every
>> few hours you might see something like this crash.
>>
>> BUG: kernel NULL pointer dereference, address: 0000000000000008
>> PGD 8000000114e6e067 P4D 8000000114e6e067 PUD 109046067 PMD 0
>> Oops: 0000 [#1] PREEMPT SMP PTI
>> CPU: 7 PID: 53891 Comm: deqp-vk Not tainted 6.8.0-rc6+ #27
>> Hardware name: Gigabyte Technology Co., Ltd. Z390 I AORUS PRO WIFI/Z390 I AORUS PRO WIFI-CF, BIOS F8 11/05/2021
>> RIP: 0010:gp100_vmm_pgt_mem+0xe3/0x180 [nouveau]
>> Code: c7 48 01 c8 49 89 45 58 85 d2 0f 84 95 00 00 00 41 0f b7 46 12 49 8b 7e 08 89 da 42 8d 2c f8 48 8b 47 08 41 83 c7 01 48 89 ee <48> 8b 40 08 ff d0 0f 1f 00 49 8b 7e 08 48 89 d9 48 8d 75 04 48 c1
>> RSP: 0000:ffffac20c5857838 EFLAGS: 00010202
>> RAX: 0000000000000000 RBX: 00000000004d8001 RCX: 0000000000000001
>> RDX: 00000000004d8001 RSI: 00000000000006d8 RDI: ffffa07afe332180
>> RBP: 00000000000006d8 R08: ffffac20c5857ad0 R09: 0000000000ffff10
>> R10: 0000000000000001 R11: ffffa07af27e2de0 R12: 000000000000001c
>> R13: ffffac20c5857ad0 R14: ffffa07a96fe9040 R15: 000000000000001c
>> FS:  00007fe395eed7c0(0000) GS:ffffa07e2c980000(0000) knlGS:0000000000000000
>> CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
>> CR2: 0000000000000008 CR3: 000000011febe001 CR4: 00000000003706f0
>> DR0: 0000000000000000 DR1: 0000000000000000 DR2: 0000000000000000
>> DR3: 0000000000000000 DR6: 00000000fffe0ff0 DR7: 0000000000000400
>> Call Trace:
>>
>> ...
>>
>>   ? gp100_vmm_pgt_mem+0xe3/0x180 [nouveau]
>>   ? gp100_vmm_pgt_mem+0x37/0x180 [nouveau]
>>   nvkm_vmm_iter+0x351/0xa20 [nouveau]
>>   ? __pfx_nvkm_vmm_ref_ptes+0x10/0x10 [nouveau]
>>   ? __pfx_gp100_vmm_pgt_mem+0x10/0x10 [nouveau]
>>   ? __pfx_gp100_vmm_pgt_mem+0x10/0x10 [nouveau]
>>   ? __lock_acquire+0x3ed/0x2170
>>   ? __pfx_gp100_vmm_pgt_mem+0x10/0x10 [nouveau]
>>   nvkm_vmm_ptes_get_map+0xc2/0x100 [nouveau]
>>   ? __pfx_nvkm_vmm_ref_ptes+0x10/0x10 [nouveau]
>>   ? __pfx_gp100_vmm_pgt_mem+0x10/0x10 [nouveau]
>>   nvkm_vmm_map_locked+0x224/0x3a0 [nouveau]
>>
>> Adding any sort of useful debug usually makes it go away, so I hand
>> wrote the function in a line, and debugged the asm.
>>
>> Every so often pt->memory->ptrs is NULL. This ptrs ptr is set in
>> the nv50_instobj_acquire called from nvkm_kmap.
>>
>> If Thread A and Thread B both get to nv50_instobj_acquire around
>> the same time, and Thread A hits the refcount_set line, and in
>> lockstep thread B succeeds at refcount_inc_not_zero, there is a
>> chance the ptrs value won't have been stored since refcount_set
>> is unordered. Force a memory barrier here, I picked smp_mb, since
>> we want it on all CPUs and it's write followed by a read.

Good catch!

>>
>> Cc: linux-stable
>> Signed-off-by: Dave Airlie <airlied@redhat.com>
>> ---
>>   drivers/gpu/drm/nouveau/nvkm/subdev/instmem/nv50.c | 3 +++
>>   1 file changed, 3 insertions(+)
>>
>> diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/instmem/nv50.c b/drivers/gpu/drm/nouveau/nvkm/subdev/instmem/nv50.c
>> index a7f3fc342d87..cbacc7b11f8c 100644
>> --- a/drivers/gpu/drm/nouveau/nvkm/subdev/instmem/nv50.c
>> +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/instmem/nv50.c
>> @@ -250,6 +250,9 @@ nv50_instobj_acquire(struct nvkm_memory *memory)
>>   			iobj->base.memory.ptrs = &nv50_instobj_fast;
>>   		else
>>   			iobj->base.memory.ptrs = &nv50_instobj_slow;
>> +		/* barrier to ensure ptrs is written before another thread
>> +		   does refcount_inc_not_zero successfully. */
>> +		smp_mb();
> 
> Doesn't this miss the corresponding smp_rmb after
> refcount_inc_not_zero? Without it a sufficiently speculating CPU might
> still hoist the NULL ptr load across the refcount increase.

Agree, also think this one could be smp_wmb() only.

I also think it's reasonable to keep "the fast path refcount_inc_not_zero
that doesn't take the lock", since the scope for this being potentially racy
is limited to this function only.

> 
> Regards,
> Lucas
Dave Airlie April 9, 2024, 7:37 p.m. UTC | #3
On Tue, 9 Apr 2024 at 21:33, Danilo Krummrich <me@dakr.org> wrote:
>
> On 4/9/24 10:27, Lucas Stach wrote:
> > Am Dienstag, dem 09.04.2024 um 10:34 +1000 schrieb Dave Airlie:
> >> From: Dave Airlie <airlied@redhat.com>
> >>
> >> Running a lot of VK CTS in parallel against nouveau, once every
> >> few hours you might see something like this crash.
> >>
> >> BUG: kernel NULL pointer dereference, address: 0000000000000008
> >> PGD 8000000114e6e067 P4D 8000000114e6e067 PUD 109046067 PMD 0
> >> Oops: 0000 [#1] PREEMPT SMP PTI
> >> CPU: 7 PID: 53891 Comm: deqp-vk Not tainted 6.8.0-rc6+ #27
> >> Hardware name: Gigabyte Technology Co., Ltd. Z390 I AORUS PRO WIFI/Z390 I AORUS PRO WIFI-CF, BIOS F8 11/05/2021
> >> RIP: 0010:gp100_vmm_pgt_mem+0xe3/0x180 [nouveau]
> >> Code: c7 48 01 c8 49 89 45 58 85 d2 0f 84 95 00 00 00 41 0f b7 46 12 49 8b 7e 08 89 da 42 8d 2c f8 48 8b 47 08 41 83 c7 01 48 89 ee <48> 8b 40 08 ff d0 0f 1f 00 49 8b 7e 08 48 89 d9 48 8d 75 04 48 c1
> >> RSP: 0000:ffffac20c5857838 EFLAGS: 00010202
> >> RAX: 0000000000000000 RBX: 00000000004d8001 RCX: 0000000000000001
> >> RDX: 00000000004d8001 RSI: 00000000000006d8 RDI: ffffa07afe332180
> >> RBP: 00000000000006d8 R08: ffffac20c5857ad0 R09: 0000000000ffff10
> >> R10: 0000000000000001 R11: ffffa07af27e2de0 R12: 000000000000001c
> >> R13: ffffac20c5857ad0 R14: ffffa07a96fe9040 R15: 000000000000001c
> >> FS:  00007fe395eed7c0(0000) GS:ffffa07e2c980000(0000) knlGS:0000000000000000
> >> CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
> >> CR2: 0000000000000008 CR3: 000000011febe001 CR4: 00000000003706f0
> >> DR0: 0000000000000000 DR1: 0000000000000000 DR2: 0000000000000000
> >> DR3: 0000000000000000 DR6: 00000000fffe0ff0 DR7: 0000000000000400
> >> Call Trace:
> >>
> >> ...
> >>
> >>   ? gp100_vmm_pgt_mem+0xe3/0x180 [nouveau]
> >>   ? gp100_vmm_pgt_mem+0x37/0x180 [nouveau]
> >>   nvkm_vmm_iter+0x351/0xa20 [nouveau]
> >>   ? __pfx_nvkm_vmm_ref_ptes+0x10/0x10 [nouveau]
> >>   ? __pfx_gp100_vmm_pgt_mem+0x10/0x10 [nouveau]
> >>   ? __pfx_gp100_vmm_pgt_mem+0x10/0x10 [nouveau]
> >>   ? __lock_acquire+0x3ed/0x2170
> >>   ? __pfx_gp100_vmm_pgt_mem+0x10/0x10 [nouveau]
> >>   nvkm_vmm_ptes_get_map+0xc2/0x100 [nouveau]
> >>   ? __pfx_nvkm_vmm_ref_ptes+0x10/0x10 [nouveau]
> >>   ? __pfx_gp100_vmm_pgt_mem+0x10/0x10 [nouveau]
> >>   nvkm_vmm_map_locked+0x224/0x3a0 [nouveau]
> >>
> >> Adding any sort of useful debug usually makes it go away, so I hand
> >> wrote the function in a line, and debugged the asm.
> >>
> >> Every so often pt->memory->ptrs is NULL. This ptrs ptr is set in
> >> the nv50_instobj_acquire called from nvkm_kmap.
> >>
> >> If Thread A and Thread B both get to nv50_instobj_acquire around
> >> the same time, and Thread A hits the refcount_set line, and in
> >> lockstep thread B succeeds at refcount_inc_not_zero, there is a
> >> chance the ptrs value won't have been stored since refcount_set
> >> is unordered. Force a memory barrier here, I picked smp_mb, since
> >> we want it on all CPUs and it's write followed by a read.
>
> Good catch!
>
> >>
> >> Cc: linux-stable
> >> Signed-off-by: Dave Airlie <airlied@redhat.com>
> >> ---
> >>   drivers/gpu/drm/nouveau/nvkm/subdev/instmem/nv50.c | 3 +++
> >>   1 file changed, 3 insertions(+)
> >>
> >> diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/instmem/nv50.c b/drivers/gpu/drm/nouveau/nvkm/subdev/instmem/nv50.c
> >> index a7f3fc342d87..cbacc7b11f8c 100644
> >> --- a/drivers/gpu/drm/nouveau/nvkm/subdev/instmem/nv50.c
> >> +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/instmem/nv50.c
> >> @@ -250,6 +250,9 @@ nv50_instobj_acquire(struct nvkm_memory *memory)
> >>                      iobj->base.memory.ptrs = &nv50_instobj_fast;
> >>              else
> >>                      iobj->base.memory.ptrs = &nv50_instobj_slow;
> >> +            /* barrier to ensure ptrs is written before another thread
> >> +               does refcount_inc_not_zero successfully. */
> >> +            smp_mb();
> >
> > Doesn't this miss the corresponding smp_rmb after
> > refcount_inc_not_zero? Without it a sufficiently speculating CPU might
> > still hoist the NULL ptr load across the refcount increase.
>
> Agree, also think this one could be smp_wmb() only.
>
> I also think it's reasonable to keep "the fast path refcount_inc_not_zero
> that doesn't take the lock", since the scope for this being potentially racy
> is limited to this function only.

I've been retesting with just barrier() here, since this seems at
least to be compiler related, but probably the smp_rmb/smp_wmb combo
is the safest answer across arches.

Dave.
diff mbox series

Patch

diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/instmem/nv50.c b/drivers/gpu/drm/nouveau/nvkm/subdev/instmem/nv50.c
index a7f3fc342d87..cbacc7b11f8c 100644
--- a/drivers/gpu/drm/nouveau/nvkm/subdev/instmem/nv50.c
+++ b/drivers/gpu/drm/nouveau/nvkm/subdev/instmem/nv50.c
@@ -250,6 +250,9 @@  nv50_instobj_acquire(struct nvkm_memory *memory)
 			iobj->base.memory.ptrs = &nv50_instobj_fast;
 		else
 			iobj->base.memory.ptrs = &nv50_instobj_slow;
+		/* barrier to ensure ptrs is written before another thread
+		   does refcount_inc_not_zero successfully. */
+		smp_mb();
 		refcount_set(&iobj->maps, 1);
 	}