diff mbox series

[bpf-next,2/2] bpf: bring back removal of dev-bound id from idr

Message ID 20231114045453.1816995-3-sdf@google.com (mailing list archive)
State Changes Requested
Delegated to: BPF
Headers show
Series bpf: fix couple of netdevsim issues | expand

Checks

Context Check Description
netdev/series_format success Posting correctly formatted
netdev/tree_selection success Clearly marked for bpf-next, async
netdev/fixes_present success Fixes tag not required for -next series
netdev/header_inline success No static functions without inline keyword in header files
netdev/build_32bit success Errors and warnings before: 2678 this patch: 2678
netdev/cc_maintainers fail 1 blamed authors not CCed: paul@paul-moore.com; 2 maintainers not CCed: yonghong.song@linux.dev paul@paul-moore.com
netdev/build_clang success Errors and warnings before: 1296 this patch: 1296
netdev/verify_signedoff success Signed-off-by tag matches author and committer
netdev/deprecated_api success None detected
netdev/check_selftest success No net selftest shell script
netdev/verify_fixes success Fixes tag looks correct
netdev/build_allmodconfig_warn success Errors and warnings before: 2757 this patch: 2757
netdev/checkpatch warning WARNING: Unknown link reference '0:', use 'Link:' or 'Closes:' instead
netdev/build_clang_rust success No Rust files in patch. Skipping build
netdev/kdoc success Errors and warnings before: 0 this patch: 0
netdev/source_inline success Was 0 now: 0
bpf/vmtest-bpf-next-VM_Test-12 success Logs for s390x-gcc / test (test_progs_no_alu32, false, 360) / test_progs_no_alu32 on s390x with gcc
bpf/vmtest-bpf-next-VM_Test-13 success Logs for s390x-gcc / test (test_verifier, false, 360) / test_verifier on s390x with gcc
bpf/vmtest-bpf-next-PR success PR summary
bpf/vmtest-bpf-next-VM_Test-1 success Logs for ShellCheck
bpf/vmtest-bpf-next-VM_Test-2 success Logs for Validate matrix.py
bpf/vmtest-bpf-next-VM_Test-3 success Logs for aarch64-gcc / build / build for aarch64 with gcc
bpf/vmtest-bpf-next-VM_Test-10 success Logs for set-matrix
bpf/vmtest-bpf-next-VM_Test-0 success Logs for Lint
bpf/vmtest-bpf-next-VM_Test-8 success Logs for aarch64-gcc / veristat
bpf/vmtest-bpf-next-VM_Test-7 success Logs for aarch64-gcc / test (test_verifier, false, 360) / test_verifier on aarch64 with gcc
bpf/vmtest-bpf-next-VM_Test-11 success Logs for x86_64-gcc / build / build for x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-4 success Logs for aarch64-gcc / test (test_maps, false, 360) / test_maps on aarch64 with gcc
bpf/vmtest-bpf-next-VM_Test-6 success Logs for aarch64-gcc / test (test_progs_no_alu32, false, 360) / test_progs_no_alu32 on aarch64 with gcc
bpf/vmtest-bpf-next-VM_Test-5 success Logs for aarch64-gcc / test (test_progs, false, 360) / test_progs on aarch64 with gcc
bpf/vmtest-bpf-next-VM_Test-20 success Logs for x86_64-gcc / test (test_progs_no_alu32_parallel, true, 30) / test_progs_no_alu32_parallel on x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-16 success Logs for x86_64-gcc / build / build for x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-28 success Logs for x86_64-llvm-16 / test (test_verifier, false, 360) / test_verifier on x86_64 with llvm-16
bpf/vmtest-bpf-next-VM_Test-15 success Logs for set-matrix
bpf/vmtest-bpf-next-VM_Test-24 success Logs for x86_64-llvm-16 / build / build for x86_64 with llvm-16
bpf/vmtest-bpf-next-VM_Test-18 success Logs for x86_64-gcc / test (test_progs, false, 360) / test_progs on x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-25 success Logs for x86_64-llvm-16 / test (test_maps, false, 360) / test_maps on x86_64 with llvm-16
bpf/vmtest-bpf-next-VM_Test-21 success Logs for x86_64-gcc / test (test_progs_parallel, true, 30) / test_progs_parallel on x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-23 success Logs for x86_64-gcc / veristat / veristat on x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-14 success Logs for s390x-gcc / veristat
bpf/vmtest-bpf-next-VM_Test-19 success Logs for x86_64-gcc / test (test_progs_no_alu32, false, 360) / test_progs_no_alu32 on x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-22 success Logs for x86_64-gcc / test (test_verifier, false, 360) / test_verifier on x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-17 success Logs for x86_64-gcc / test (test_maps, false, 360) / test_maps on x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-9 success Logs for s390x-gcc / build / build for s390x with gcc
bpf/vmtest-bpf-next-VM_Test-29 success Logs for x86_64-llvm-16 / veristat
bpf/vmtest-bpf-next-VM_Test-26 success Logs for x86_64-llvm-16 / test (test_progs, false, 360) / test_progs on x86_64 with llvm-16
bpf/vmtest-bpf-next-VM_Test-27 success Logs for x86_64-llvm-16 / test (test_progs_no_alu32, false, 360) / test_progs_no_alu32 on x86_64 with llvm-16

Commit Message

Stanislav Fomichev Nov. 14, 2023, 4:54 a.m. UTC
Commit ef01f4e25c17 ("bpf: restore the ebpf program ID for BPF_AUDIT_UNLOAD
and PERF_BPF_EVENT_PROG_UNLOAD") stopped removing program's id from
idr when the offloaded/bound netdev goes away. I was supposed to
take a look and check in [0], but apparently I did not.

The purpose of idr removal is to avoid BPF_PROG_GET_NEXT_ID returning
stale ids for the programs that have a dead netdev. This functionality
is verified by test_offload.py, but we don't run this test in the CI.

Introduce new bpf_prog_remove_from_idr which takes care of correctly
dealing with potential double idr_remove() via separate skip_idr_remove
flag in the aux.

Verified by running the test manually:
test_offload.py: OK

0: https://lore.kernel.org/all/CAKH8qBtyR20ZWAc11z1-6pGb3Hd47AQUTbE_cfoktG59TqaJ7Q@mail.gmail.com/

Fixes: ef01f4e25c17 ("bpf: restore the ebpf program ID for BPF_AUDIT_UNLOAD and PERF_BPF_EVENT_PROG_UNLOAD")
Signed-off-by: Stanislav Fomichev <sdf@google.com>
---
 include/linux/bpf.h  |  2 ++
 kernel/bpf/offload.c |  3 +++
 kernel/bpf/syscall.c | 15 +++++++++++----
 3 files changed, 16 insertions(+), 4 deletions(-)

Comments

Stanislav Fomichev Nov. 20, 2023, 9:39 p.m. UTC | #1
On 11/13, Stanislav Fomichev wrote:
> Commit ef01f4e25c17 ("bpf: restore the ebpf program ID for BPF_AUDIT_UNLOAD
> and PERF_BPF_EVENT_PROG_UNLOAD") stopped removing program's id from
> idr when the offloaded/bound netdev goes away. I was supposed to
> take a look and check in [0], but apparently I did not.
> 
> The purpose of idr removal is to avoid BPF_PROG_GET_NEXT_ID returning
> stale ids for the programs that have a dead netdev. This functionality
> is verified by test_offload.py, but we don't run this test in the CI.
> 
> Introduce new bpf_prog_remove_from_idr which takes care of correctly
> dealing with potential double idr_remove() via separate skip_idr_remove
> flag in the aux.
> 
> Verified by running the test manually:
> test_offload.py: OK
> 
> 0: https://lore.kernel.org/all/CAKH8qBtyR20ZWAc11z1-6pGb3Hd47AQUTbE_cfoktG59TqaJ7Q@mail.gmail.com/
> 
> Fixes: ef01f4e25c17 ("bpf: restore the ebpf program ID for BPF_AUDIT_UNLOAD and PERF_BPF_EVENT_PROG_UNLOAD")
> Signed-off-by: Stanislav Fomichev <sdf@google.com>
> ---
>  include/linux/bpf.h  |  2 ++
>  kernel/bpf/offload.c |  3 +++
>  kernel/bpf/syscall.c | 15 +++++++++++----
>  3 files changed, 16 insertions(+), 4 deletions(-)
> 
> diff --git a/include/linux/bpf.h b/include/linux/bpf.h
> index 4001d11be151..d2aa4b59bf1e 100644
> --- a/include/linux/bpf.h
> +++ b/include/linux/bpf.h
> @@ -1414,6 +1414,7 @@ struct bpf_prog_aux {
>  	bool xdp_has_frags;
>  	bool exception_cb;
>  	bool exception_boundary;
> +	bool skip_idr_remove;
>  	/* BTF_KIND_FUNC_PROTO for valid attach_btf_id */
>  	const struct btf_type *attach_func_proto;
>  	/* function name for valid attach_btf_id */
> @@ -2049,6 +2050,7 @@ void bpf_prog_inc(struct bpf_prog *prog);
>  struct bpf_prog * __must_check bpf_prog_inc_not_zero(struct bpf_prog *prog);
>  void bpf_prog_put(struct bpf_prog *prog);
>  
> +void bpf_prog_remove_from_idr(struct bpf_prog *prog);
>  void bpf_prog_free_id(struct bpf_prog *prog);
>  void bpf_map_free_id(struct bpf_map *map);
>  
> diff --git a/kernel/bpf/offload.c b/kernel/bpf/offload.c
> index 1a4fec330eaa..6f4fe492ee2a 100644
> --- a/kernel/bpf/offload.c
> +++ b/kernel/bpf/offload.c
> @@ -112,6 +112,9 @@ static void __bpf_prog_offload_destroy(struct bpf_prog *prog)
>  	if (offload->dev_state)
>  		offload->offdev->ops->destroy(prog);
>  
> +	/* Make sure BPF_PROG_GET_NEXT_ID can't find this dead program */
> +	bpf_prog_remove_from_idr(prog);
> +
>  	list_del_init(&offload->offloads);
>  	kfree(offload);
>  	prog->aux->offload = NULL;
> diff --git a/kernel/bpf/syscall.c b/kernel/bpf/syscall.c
> index 0ed286b8a0f0..bc813e03e2cf 100644
> --- a/kernel/bpf/syscall.c
> +++ b/kernel/bpf/syscall.c
> @@ -2083,10 +2083,19 @@ static int bpf_prog_alloc_id(struct bpf_prog *prog)
>  	return id > 0 ? 0 : id;
>  }
>  
> -void bpf_prog_free_id(struct bpf_prog *prog)
> +void bpf_prog_remove_from_idr(struct bpf_prog *prog)
>  {
>  	unsigned long flags;
>  
> +	spin_lock_irqsave(&prog_idr_lock, flags);
> +	if (!prog->aux->skip_idr_remove)
> +		idr_remove(&prog_idr, prog->aux->id);
> +	prog->aux->skip_idr_remove = 1;
> +	spin_unlock_irqrestore(&prog_idr_lock, flags);
> +}
> +
> +void bpf_prog_free_id(struct bpf_prog *prog)
> +{
>  	/* cBPF to eBPF migrations are currently not in the idr store.
>  	 * Offloaded programs are removed from the store when their device
>  	 * disappears - even if someone grabs an fd to them they are unusable,
> @@ -2095,10 +2104,8 @@ void bpf_prog_free_id(struct bpf_prog *prog)
>  	if (!prog->aux->id)
>  		return;
>  
> -	spin_lock_irqsave(&prog_idr_lock, flags);
> -	idr_remove(&prog_idr, prog->aux->id);
> +	bpf_prog_remove_from_idr(prog);
>  	prog->aux->id = 0;
> -	spin_unlock_irqrestore(&prog_idr_lock, flags);
>  }
>  
>  static void __bpf_prog_put_rcu(struct rcu_head *rcu)
> -- 
> 2.42.0.869.gea05f2083d-goog
> 

Same here, should have been CC'ed to netdev.
Martin KaFai Lau Nov. 21, 2023, 9:03 p.m. UTC | #2
On 11/13/23 8:54 PM, Stanislav Fomichev wrote:
> Commit ef01f4e25c17 ("bpf: restore the ebpf program ID for BPF_AUDIT_UNLOAD
> and PERF_BPF_EVENT_PROG_UNLOAD") stopped removing program's id from
> idr when the offloaded/bound netdev goes away. I was supposed to
> take a look and check in [0], but apparently I did not.
> 
> The purpose of idr removal is to avoid BPF_PROG_GET_NEXT_ID returning
> stale ids for the programs that have a dead netdev. This functionality

What may be wrong if BPF_PROG_GET_NEXT_ID returns the id?
e.g. If the prog is pinned somewhere, it may be useful to know a prog is still 
loaded in the system.

Does the fixes mean to be for the bpf tree instead?

> is verified by test_offload.py, but we don't run this test in the CI.
> 
> Introduce new bpf_prog_remove_from_idr which takes care of correctly
> dealing with potential double idr_remove() via separate skip_idr_remove
> flag in the aux.
> 
> Verified by running the test manually:
> test_offload.py: OK
> 
> 0: https://lore.kernel.org/all/CAKH8qBtyR20ZWAc11z1-6pGb3Hd47AQUTbE_cfoktG59TqaJ7Q@mail.gmail.com/
> 
> Fixes: ef01f4e25c17 ("bpf: restore the ebpf program ID for BPF_AUDIT_UNLOAD and PERF_BPF_EVENT_PROG_UNLOAD")
> Signed-off-by: Stanislav Fomichev <sdf@google.com>
> ---
>   include/linux/bpf.h  |  2 ++
>   kernel/bpf/offload.c |  3 +++
>   kernel/bpf/syscall.c | 15 +++++++++++----
>   3 files changed, 16 insertions(+), 4 deletions(-)
> 
> diff --git a/include/linux/bpf.h b/include/linux/bpf.h
> index 4001d11be151..d2aa4b59bf1e 100644
> --- a/include/linux/bpf.h
> +++ b/include/linux/bpf.h
> @@ -1414,6 +1414,7 @@ struct bpf_prog_aux {
>   	bool xdp_has_frags;
>   	bool exception_cb;
>   	bool exception_boundary;
> +	bool skip_idr_remove;
>   	/* BTF_KIND_FUNC_PROTO for valid attach_btf_id */
>   	const struct btf_type *attach_func_proto;
>   	/* function name for valid attach_btf_id */
> @@ -2049,6 +2050,7 @@ void bpf_prog_inc(struct bpf_prog *prog);
>   struct bpf_prog * __must_check bpf_prog_inc_not_zero(struct bpf_prog *prog);
>   void bpf_prog_put(struct bpf_prog *prog);
>   
> +void bpf_prog_remove_from_idr(struct bpf_prog *prog);
>   void bpf_prog_free_id(struct bpf_prog *prog);
>   void bpf_map_free_id(struct bpf_map *map);
>
Daniel Borkmann Nov. 22, 2023, 3:07 p.m. UTC | #3
On 11/21/23 10:03 PM, Martin KaFai Lau wrote:
> On 11/13/23 8:54 PM, Stanislav Fomichev wrote:
>> Commit ef01f4e25c17 ("bpf: restore the ebpf program ID for BPF_AUDIT_UNLOAD
>> and PERF_BPF_EVENT_PROG_UNLOAD") stopped removing program's id from
>> idr when the offloaded/bound netdev goes away. I was supposed to
>> take a look and check in [0], but apparently I did not.
>>
>> The purpose of idr removal is to avoid BPF_PROG_GET_NEXT_ID returning
>> stale ids for the programs that have a dead netdev. This functionality
> 
> What may be wrong if BPF_PROG_GET_NEXT_ID returns the id?
> e.g. If the prog is pinned somewhere, it may be useful to know a prog is still loaded in the system.

Wouldn't this strictly speaking provide an invalid id (== 0) upon unload
back to audit - see the bpf_audit_prog(prog, BPF_AUDIT_UNLOAD) call location?

> Does the fixes mean to be for the bpf tree instead?

+1 given syzbot report, I'll take the first one in for now.

>> is verified by test_offload.py, but we don't run this test in the CI.
>>
>> Introduce new bpf_prog_remove_from_idr which takes care of correctly
>> dealing with potential double idr_remove() via separate skip_idr_remove
>> flag in the aux.
>>
>> Verified by running the test manually:
>> test_offload.py: OK
>>
>> 0: https://lore.kernel.org/all/CAKH8qBtyR20ZWAc11z1-6pGb3Hd47AQUTbE_cfoktG59TqaJ7Q@mail.gmail.com/
>>
>> Fixes: ef01f4e25c17 ("bpf: restore the ebpf program ID for BPF_AUDIT_UNLOAD and PERF_BPF_EVENT_PROG_UNLOAD")
>> Signed-off-by: Stanislav Fomichev <sdf@google.com>
>> ---
>>   include/linux/bpf.h  |  2 ++
>>   kernel/bpf/offload.c |  3 +++
>>   kernel/bpf/syscall.c | 15 +++++++++++----
>>   3 files changed, 16 insertions(+), 4 deletions(-)
>>
>> diff --git a/include/linux/bpf.h b/include/linux/bpf.h
>> index 4001d11be151..d2aa4b59bf1e 100644
>> --- a/include/linux/bpf.h
>> +++ b/include/linux/bpf.h
>> @@ -1414,6 +1414,7 @@ struct bpf_prog_aux {
>>       bool xdp_has_frags;
>>       bool exception_cb;
>>       bool exception_boundary;
>> +    bool skip_idr_remove;
>>       /* BTF_KIND_FUNC_PROTO for valid attach_btf_id */
>>       const struct btf_type *attach_func_proto;
>>       /* function name for valid attach_btf_id */
>> @@ -2049,6 +2050,7 @@ void bpf_prog_inc(struct bpf_prog *prog);
>>   struct bpf_prog * __must_check bpf_prog_inc_not_zero(struct bpf_prog *prog);
>>   void bpf_prog_put(struct bpf_prog *prog);
>> +void bpf_prog_remove_from_idr(struct bpf_prog *prog);
>>   void bpf_prog_free_id(struct bpf_prog *prog);
>>   void bpf_map_free_id(struct bpf_map *map);
> 
>
Stanislav Fomichev Nov. 22, 2023, 6:05 p.m. UTC | #4
On 11/22, Daniel Borkmann wrote:
> On 11/21/23 10:03 PM, Martin KaFai Lau wrote:
> > On 11/13/23 8:54 PM, Stanislav Fomichev wrote:
> > > Commit ef01f4e25c17 ("bpf: restore the ebpf program ID for BPF_AUDIT_UNLOAD
> > > and PERF_BPF_EVENT_PROG_UNLOAD") stopped removing program's id from
> > > idr when the offloaded/bound netdev goes away. I was supposed to
> > > take a look and check in [0], but apparently I did not.
> > > 
> > > The purpose of idr removal is to avoid BPF_PROG_GET_NEXT_ID returning
> > > stale ids for the programs that have a dead netdev. This functionality
> > 
> > What may be wrong if BPF_PROG_GET_NEXT_ID returns the id?
> > e.g. If the prog is pinned somewhere, it may be useful to know a prog is still loaded in the system.

bpftool is a bit spooked by those prog ids currently: calling GET_INFO_BY_ID
on those programs returns ENODEV. So we can keep those ids around, but
need some tweaks on the bpftool in this case. LMK if any of you prefer
this option.

> Wouldn't this strictly speaking provide an invalid id (== 0) upon unload
> back to audit - see the bpf_audit_prog(prog, BPF_AUDIT_UNLOAD) call location?

Removing from idr shouldn't affect bpf_audit_prog, right? bpf_audit_prog
is using prog->aux->id for its purposes, so as long as we are not resetting
this value - we're good.
Martin KaFai Lau Nov. 22, 2023, 6:40 p.m. UTC | #5
On 11/22/23 10:05 AM, Stanislav Fomichev wrote:
>>>> Commit ef01f4e25c17 ("bpf: restore the ebpf program ID for BPF_AUDIT_UNLOAD
>>>> and PERF_BPF_EVENT_PROG_UNLOAD") stopped removing program's id from
>>>> idr when the offloaded/bound netdev goes away. I was supposed to
>>>> take a look and check in [0], but apparently I did not.
>>>>
>>>> The purpose of idr removal is to avoid BPF_PROG_GET_NEXT_ID returning
>>>> stale ids for the programs that have a dead netdev. This functionality
>>>
>>> What may be wrong if BPF_PROG_GET_NEXT_ID returns the id?
>>> e.g. If the prog is pinned somewhere, it may be useful to know a prog is still loaded in the system.
> 
> bpftool is a bit spooked by those prog ids currently: calling GET_INFO_BY_ID
> on those programs returns ENODEV. So we can keep those ids around, but
> need some tweaks on the bpftool in this case. LMK if any of you prefer
> this option.

I think it is in general useful to improve 'bpftool prog show' to keep going for 
the next prog id if possible. May be print an error message after the prog id 
and then keep going for the next prog id?
Stanislav Fomichev Nov. 22, 2023, 10:41 p.m. UTC | #6
On Wed, Nov 22, 2023 at 10:40 AM Martin KaFai Lau <martin.lau@linux.dev> wrote:
>
> On 11/22/23 10:05 AM, Stanislav Fomichev wrote:
> >>>> Commit ef01f4e25c17 ("bpf: restore the ebpf program ID for BPF_AUDIT_UNLOAD
> >>>> and PERF_BPF_EVENT_PROG_UNLOAD") stopped removing program's id from
> >>>> idr when the offloaded/bound netdev goes away. I was supposed to
> >>>> take a look and check in [0], but apparently I did not.
> >>>>
> >>>> The purpose of idr removal is to avoid BPF_PROG_GET_NEXT_ID returning
> >>>> stale ids for the programs that have a dead netdev. This functionality
> >>>
> >>> What may be wrong if BPF_PROG_GET_NEXT_ID returns the id?
> >>> e.g. If the prog is pinned somewhere, it may be useful to know a prog is still loaded in the system.
> >
> > bpftool is a bit spooked by those prog ids currently: calling GET_INFO_BY_ID
> > on those programs returns ENODEV. So we can keep those ids around, but
> > need some tweaks on the bpftool in this case. LMK if any of you prefer
> > this option.
>
> I think it is in general useful to improve 'bpftool prog show' to keep going for
> the next prog id if possible. May be print an error message after the prog id
> and then keep going for the next prog id?

Replied with a v2 where I mark those progs as 'orphaned'!
Daniel Borkmann Nov. 22, 2023, 10:54 p.m. UTC | #7
On 11/22/23 11:41 PM, Stanislav Fomichev wrote:
> On Wed, Nov 22, 2023 at 10:40 AM Martin KaFai Lau <martin.lau@linux.dev> wrote:
>> On 11/22/23 10:05 AM, Stanislav Fomichev wrote:
>>>>>> Commit ef01f4e25c17 ("bpf: restore the ebpf program ID for BPF_AUDIT_UNLOAD
>>>>>> and PERF_BPF_EVENT_PROG_UNLOAD") stopped removing program's id from
>>>>>> idr when the offloaded/bound netdev goes away. I was supposed to
>>>>>> take a look and check in [0], but apparently I did not.
>>>>>>
>>>>>> The purpose of idr removal is to avoid BPF_PROG_GET_NEXT_ID returning
>>>>>> stale ids for the programs that have a dead netdev. This functionality
>>>>>
>>>>> What may be wrong if BPF_PROG_GET_NEXT_ID returns the id?
>>>>> e.g. If the prog is pinned somewhere, it may be useful to know a prog is still loaded in the system.
>>>
>>> bpftool is a bit spooked by those prog ids currently: calling GET_INFO_BY_ID
>>> on those programs returns ENODEV. So we can keep those ids around, but
>>> need some tweaks on the bpftool in this case. LMK if any of you prefer
>>> this option.
>>
>> I think it is in general useful to improve 'bpftool prog show' to keep going for
>> the next prog id if possible. May be print an error message after the prog id
>> and then keep going for the next prog id?
> 
> Replied with a v2 where I mark those progs as 'orphaned'!

Sg, we could perhaps do something similar for netdev detached links.
diff mbox series

Patch

diff --git a/include/linux/bpf.h b/include/linux/bpf.h
index 4001d11be151..d2aa4b59bf1e 100644
--- a/include/linux/bpf.h
+++ b/include/linux/bpf.h
@@ -1414,6 +1414,7 @@  struct bpf_prog_aux {
 	bool xdp_has_frags;
 	bool exception_cb;
 	bool exception_boundary;
+	bool skip_idr_remove;
 	/* BTF_KIND_FUNC_PROTO for valid attach_btf_id */
 	const struct btf_type *attach_func_proto;
 	/* function name for valid attach_btf_id */
@@ -2049,6 +2050,7 @@  void bpf_prog_inc(struct bpf_prog *prog);
 struct bpf_prog * __must_check bpf_prog_inc_not_zero(struct bpf_prog *prog);
 void bpf_prog_put(struct bpf_prog *prog);
 
+void bpf_prog_remove_from_idr(struct bpf_prog *prog);
 void bpf_prog_free_id(struct bpf_prog *prog);
 void bpf_map_free_id(struct bpf_map *map);
 
diff --git a/kernel/bpf/offload.c b/kernel/bpf/offload.c
index 1a4fec330eaa..6f4fe492ee2a 100644
--- a/kernel/bpf/offload.c
+++ b/kernel/bpf/offload.c
@@ -112,6 +112,9 @@  static void __bpf_prog_offload_destroy(struct bpf_prog *prog)
 	if (offload->dev_state)
 		offload->offdev->ops->destroy(prog);
 
+	/* Make sure BPF_PROG_GET_NEXT_ID can't find this dead program */
+	bpf_prog_remove_from_idr(prog);
+
 	list_del_init(&offload->offloads);
 	kfree(offload);
 	prog->aux->offload = NULL;
diff --git a/kernel/bpf/syscall.c b/kernel/bpf/syscall.c
index 0ed286b8a0f0..bc813e03e2cf 100644
--- a/kernel/bpf/syscall.c
+++ b/kernel/bpf/syscall.c
@@ -2083,10 +2083,19 @@  static int bpf_prog_alloc_id(struct bpf_prog *prog)
 	return id > 0 ? 0 : id;
 }
 
-void bpf_prog_free_id(struct bpf_prog *prog)
+void bpf_prog_remove_from_idr(struct bpf_prog *prog)
 {
 	unsigned long flags;
 
+	spin_lock_irqsave(&prog_idr_lock, flags);
+	if (!prog->aux->skip_idr_remove)
+		idr_remove(&prog_idr, prog->aux->id);
+	prog->aux->skip_idr_remove = 1;
+	spin_unlock_irqrestore(&prog_idr_lock, flags);
+}
+
+void bpf_prog_free_id(struct bpf_prog *prog)
+{
 	/* cBPF to eBPF migrations are currently not in the idr store.
 	 * Offloaded programs are removed from the store when their device
 	 * disappears - even if someone grabs an fd to them they are unusable,
@@ -2095,10 +2104,8 @@  void bpf_prog_free_id(struct bpf_prog *prog)
 	if (!prog->aux->id)
 		return;
 
-	spin_lock_irqsave(&prog_idr_lock, flags);
-	idr_remove(&prog_idr, prog->aux->id);
+	bpf_prog_remove_from_idr(prog);
 	prog->aux->id = 0;
-	spin_unlock_irqrestore(&prog_idr_lock, flags);
 }
 
 static void __bpf_prog_put_rcu(struct rcu_head *rcu)