diff mbox series

[bpf] bpf: Fix UAF in task local storage

Message ID 20230601122622.513140-1-kpsingh@kernel.org (mailing list archive)
State Superseded
Delegated to: BPF
Headers show
Series [bpf] bpf: Fix UAF in task local storage | expand

Checks

Context Check Description
bpf/vmtest-bpf-PR success PR summary
netdev/series_format success Single patches do not need cover letters
netdev/tree_selection success Clearly marked for bpf
netdev/fixes_present success Fixes tag present in non-next series
netdev/header_inline success No static functions without inline keyword in header files
netdev/build_32bit success Errors and warnings before: 51 this patch: 51
netdev/cc_maintainers warning 3 maintainers not CCed: akpm@linux-foundation.org Liam.Howlett@oracle.com brauner@kernel.org
netdev/build_clang success Errors and warnings before: 21 this patch: 21
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: 51 this patch: 51
netdev/checkpatch warning WARNING: Possible repeated word: 'the'
netdev/kdoc success Errors and warnings before: 13 this patch: 13
netdev/source_inline success Was 0 now: 0
bpf/vmtest-bpf-VM_Test-1 success Logs for ShellCheck
bpf/vmtest-bpf-VM_Test-6 success Logs for set-matrix
bpf/vmtest-bpf-VM_Test-3 success Logs for build for s390x with gcc
bpf/vmtest-bpf-VM_Test-4 success Logs for build for x86_64 with gcc
bpf/vmtest-bpf-VM_Test-5 success Logs for build for x86_64 with llvm-16
bpf/vmtest-bpf-VM_Test-2 success Logs for build for aarch64 with gcc
bpf/vmtest-bpf-VM_Test-7 success Logs for test_maps on aarch64 with gcc
bpf/vmtest-bpf-VM_Test-15 fail Logs for test_progs_no_alu32 on aarch64 with gcc
bpf/vmtest-bpf-VM_Test-19 success Logs for test_progs_no_alu32_parallel on aarch64 with gcc
bpf/vmtest-bpf-VM_Test-22 success Logs for test_progs_parallel on aarch64 with gcc
bpf/vmtest-bpf-VM_Test-25 success Logs for test_verifier on aarch64 with gcc
bpf/vmtest-bpf-VM_Test-27 success Logs for test_verifier on x86_64 with gcc
bpf/vmtest-bpf-VM_Test-29 success Logs for veristat
bpf/vmtest-bpf-VM_Test-9 success Logs for test_maps on x86_64 with gcc
bpf/vmtest-bpf-VM_Test-10 success Logs for test_maps on x86_64 with llvm-16
bpf/vmtest-bpf-VM_Test-11 fail Logs for test_progs on aarch64 with gcc
bpf/vmtest-bpf-VM_Test-13 fail Logs for test_progs on x86_64 with gcc
bpf/vmtest-bpf-VM_Test-17 fail Logs for test_progs_no_alu32 on x86_64 with gcc
bpf/vmtest-bpf-VM_Test-20 success Logs for test_progs_no_alu32_parallel on x86_64 with gcc
bpf/vmtest-bpf-VM_Test-21 success Logs for test_progs_no_alu32_parallel on x86_64 with llvm-16
bpf/vmtest-bpf-VM_Test-23 success Logs for test_progs_parallel on x86_64 with gcc
bpf/vmtest-bpf-VM_Test-24 success Logs for test_progs_parallel on x86_64 with llvm-16
bpf/vmtest-bpf-VM_Test-28 success Logs for test_verifier on x86_64 with llvm-16
bpf/vmtest-bpf-VM_Test-14 fail Logs for test_progs on x86_64 with llvm-16
bpf/vmtest-bpf-VM_Test-18 fail Logs for test_progs_no_alu32 on x86_64 with llvm-16
bpf/vmtest-bpf-VM_Test-26 success Logs for test_verifier on s390x with gcc
bpf/vmtest-bpf-VM_Test-12 fail Logs for test_progs on s390x with gcc
bpf/vmtest-bpf-VM_Test-16 fail Logs for test_progs_no_alu32 on s390x with gcc
bpf/vmtest-bpf-VM_Test-8 success Logs for test_maps on s390x with gcc

Commit Message

KP Singh June 1, 2023, 12:26 p.m. UTC
When the the task local storage was generalized for tracing programs, the
bpf_task_local_storage callback was moved from a BPF LSM hook callback
for security_task_free LSM hook to it's own callback. But a failure case
in bad_fork_cleanup_security was missed which, when triggered, led to a dangling
task owner pointer and a subsequent use-after-free.

This issue was noticed when a BPF LSM program was attached to the
task_alloc hook on a kernel with KASAN enabled. The program used
bpf_task_storage_get to copy the task local storage from the current
task to the new task being created.

Fixes: a10787e6d58c ("bpf: Enable task local storage for tracing programs")
Reported-by: Kuba Piecuch <jpiecuch@google.com>
Signed-off-by: KP Singh <kpsingh@kernel.org>
---

This fixes the regression from the LSM blob based implementation, we can
still have UAFs, if bpf_task_storage_get is invoked after bpf_task_storage_free
in the cleanup path.

 kernel/fork.c | 1 +
 1 file changed, 1 insertion(+)

Comments

Song Liu June 1, 2023, 4:18 p.m. UTC | #1
On Thu, Jun 1, 2023 at 5:26 AM KP Singh <kpsingh@kernel.org> wrote:
>
> When the the task local storage was generalized for tracing programs, the
> bpf_task_local_storage callback was moved from a BPF LSM hook callback
> for security_task_free LSM hook to it's own callback. But a failure case
> in bad_fork_cleanup_security was missed which, when triggered, led to a dangling
> task owner pointer and a subsequent use-after-free.
>
> This issue was noticed when a BPF LSM program was attached to the
> task_alloc hook on a kernel with KASAN enabled. The program used
> bpf_task_storage_get to copy the task local storage from the current
> task to the new task being created.

This is pretty tricky. Let's add a selftest for this.

>
> Fixes: a10787e6d58c ("bpf: Enable task local storage for tracing programs")
> Reported-by: Kuba Piecuch <jpiecuch@google.com>
> Signed-off-by: KP Singh <kpsingh@kernel.org>
> ---
>
> This fixes the regression from the LSM blob based implementation, we can
> still have UAFs, if bpf_task_storage_get is invoked after bpf_task_storage_free
> in the cleanup path.

Can we fix this by calling bpf_task_storage_free() from free_task()?

Thanks,
Song

>
>  kernel/fork.c | 1 +
>  1 file changed, 1 insertion(+)
>
> diff --git a/kernel/fork.c b/kernel/fork.c
> index ed4e01daccaa..112d85091ae6 100644
> --- a/kernel/fork.c
> +++ b/kernel/fork.c
> @@ -2781,6 +2781,7 @@ __latent_entropy struct task_struct *copy_process(
>         exit_sem(p);
>  bad_fork_cleanup_security:
>         security_task_free(p);
> +       bpf_task_storage_free(p);
>  bad_fork_cleanup_audit:
>         audit_free(p);
>  bad_fork_cleanup_perf:
> --
> 2.41.0.rc0.172.g3f132b7071-goog
>
>
KP Singh June 1, 2023, 4:27 p.m. UTC | #2
On Thu, Jun 1, 2023 at 6:18 PM Song Liu <song@kernel.org> wrote:
>
> On Thu, Jun 1, 2023 at 5:26 AM KP Singh <kpsingh@kernel.org> wrote:
> >
> > When the the task local storage was generalized for tracing programs, the
> > bpf_task_local_storage callback was moved from a BPF LSM hook callback
> > for security_task_free LSM hook to it's own callback. But a failure case
> > in bad_fork_cleanup_security was missed which, when triggered, led to a dangling
> > task owner pointer and a subsequent use-after-free.
> >
> > This issue was noticed when a BPF LSM program was attached to the
> > task_alloc hook on a kernel with KASAN enabled. The program used
> > bpf_task_storage_get to copy the task local storage from the current
> > task to the new task being created.
>
> This is pretty tricky. Let's add a selftest for this.

I don't have an easy repro for this (the UAF does not trigger
immediately), Also I am not sure how one would test a UAF in a
selftest. What actually happens is:

* We have a dangling task pointer in local storage.
* This is used sometime later which then leads to weird memory
corruption errors.

>
> >
> > Fixes: a10787e6d58c ("bpf: Enable task local storage for tracing programs")
> > Reported-by: Kuba Piecuch <jpiecuch@google.com>
> > Signed-off-by: KP Singh <kpsingh@kernel.org>
> > ---
> >
> > This fixes the regression from the LSM blob based implementation, we can
> > still have UAFs, if bpf_task_storage_get is invoked after bpf_task_storage_free
> > in the cleanup path.
>
> Can we fix this by calling bpf_task_storage_free() from free_task()?

I think we can yeah. But, this is yet another deviation from how the
security blob is managed (security_task_free) frees the blob that we
were previously using.

>
> Thanks,
> Song
>
> >
> >  kernel/fork.c | 1 +
> >  1 file changed, 1 insertion(+)
> >
> > diff --git a/kernel/fork.c b/kernel/fork.c
> > index ed4e01daccaa..112d85091ae6 100644
> > --- a/kernel/fork.c
> > +++ b/kernel/fork.c
> > @@ -2781,6 +2781,7 @@ __latent_entropy struct task_struct *copy_process(
> >         exit_sem(p);
> >  bad_fork_cleanup_security:
> >         security_task_free(p);
> > +       bpf_task_storage_free(p);
> >  bad_fork_cleanup_audit:
> >         audit_free(p);
> >  bad_fork_cleanup_perf:
> > --
> > 2.41.0.rc0.172.g3f132b7071-goog
> >
> >
Song Liu June 1, 2023, 4:54 p.m. UTC | #3
> On Jun 1, 2023, at 9:27 AM, KP Singh <kpsingh@kernel.org> wrote:
> 
> On Thu, Jun 1, 2023 at 6:18 PM Song Liu <song@kernel.org> wrote:
>> 
>> On Thu, Jun 1, 2023 at 5:26 AM KP Singh <kpsingh@kernel.org> wrote:
>>> 
>>> When the the task local storage was generalized for tracing programs, the
>>> bpf_task_local_storage callback was moved from a BPF LSM hook callback
>>> for security_task_free LSM hook to it's own callback. But a failure case
>>> in bad_fork_cleanup_security was missed which, when triggered, led to a dangling
>>> task owner pointer and a subsequent use-after-free.
>>> 
>>> This issue was noticed when a BPF LSM program was attached to the
>>> task_alloc hook on a kernel with KASAN enabled. The program used
>>> bpf_task_storage_get to copy the task local storage from the current
>>> task to the new task being created.
>> 
>> This is pretty tricky. Let's add a selftest for this.
> 
> I don't have an easy repro for this (the UAF does not trigger
> immediately), Also I am not sure how one would test a UAF in a
> selftest. What actually happens is:
> 
> * We have a dangling task pointer in local storage.
> * This is used sometime later which then leads to weird memory
> corruption errors.

I think we will see it easily with KASAN, no? 

> 
>> 
>>> 
>>> Fixes: a10787e6d58c ("bpf: Enable task local storage for tracing programs")
>>> Reported-by: Kuba Piecuch <jpiecuch@google.com>
>>> Signed-off-by: KP Singh <kpsingh@kernel.org>
>>> ---
>>> 
>>> This fixes the regression from the LSM blob based implementation, we can
>>> still have UAFs, if bpf_task_storage_get is invoked after bpf_task_storage_free
>>> in the cleanup path.
>> 
>> Can we fix this by calling bpf_task_storage_free() from free_task()?
> 
> I think we can yeah. But, this is yet another deviation from how the
> security blob is managed (security_task_free) frees the blob that we
> were previously using.

Yeah, this will make the code even more tricky. 

Another idea I had is to filter on task->__state in the helper. IOW, 
task local storage does not work on starting or died tasks. But I am
not sure whether this will make BPF_LSM less effective (not covering
certain tasks). 

Thanks,
Song
Hao Luo June 1, 2023, 5:31 p.m. UTC | #4
On Thu, Jun 1, 2023 at 9:54 AM Song Liu <songliubraving@meta.com> wrote:
>
>
>
> > On Jun 1, 2023, at 9:27 AM, KP Singh <kpsingh@kernel.org> wrote:
> >
> > On Thu, Jun 1, 2023 at 6:18 PM Song Liu <song@kernel.org> wrote:
> >>
> >> On Thu, Jun 1, 2023 at 5:26 AM KP Singh <kpsingh@kernel.org> wrote:
> >>>
<...>
> >>> Fixes: a10787e6d58c ("bpf: Enable task local storage for tracing programs")
> >>> Reported-by: Kuba Piecuch <jpiecuch@google.com>
> >>> Signed-off-by: KP Singh <kpsingh@kernel.org>
> >>> ---
> >>>
> >>> This fixes the regression from the LSM blob based implementation, we can
> >>> still have UAFs, if bpf_task_storage_get is invoked after bpf_task_storage_free
> >>> in the cleanup path.
> >>
> >> Can we fix this by calling bpf_task_storage_free() from free_task()?
> >
> > I think we can yeah. But, this is yet another deviation from how the
> > security blob is managed (security_task_free) frees the blob that we
> > were previously using.
>
> Yeah, this will make the code even more tricky.
>
> Another idea I had is to filter on task->__state in the helper. IOW,
> task local storage does not work on starting or died tasks. But I am
> not sure whether this will make BPF_LSM less effective (not covering
> certain tasks).
>

I thought about this as well. But I think some use cases would want to
use task local storage at task creation. At least attaching at the
sched_newtask tracepoints should be fine in those cases. But at the
time of sched_newtask, task->__state is still TASK_NEW.

Another idea, is it possible to allow LSM + local storage on
starting/dying tasks, but restrict tracing + local storage to created
tasks?

Hao
Martin KaFai Lau June 1, 2023, 5:47 p.m. UTC | #5
On 6/1/23 9:54 AM, Song Liu wrote:
> 
> 
>> On Jun 1, 2023, at 9:27 AM, KP Singh <kpsingh@kernel.org> wrote:
>>
>> On Thu, Jun 1, 2023 at 6:18 PM Song Liu <song@kernel.org> wrote:
>>>
>>> On Thu, Jun 1, 2023 at 5:26 AM KP Singh <kpsingh@kernel.org> wrote:
>>>>
>>>> When the the task local storage was generalized for tracing programs, the
>>>> bpf_task_local_storage callback was moved from a BPF LSM hook callback
>>>> for security_task_free LSM hook to it's own callback. But a failure case
>>>> in bad_fork_cleanup_security was missed which, when triggered, led to a dangling
>>>> task owner pointer and a subsequent use-after-free.
>>>>
>>>> This issue was noticed when a BPF LSM program was attached to the
>>>> task_alloc hook on a kernel with KASAN enabled. The program used
>>>> bpf_task_storage_get to copy the task local storage from the current
>>>> task to the new task being created.
>>>
>>> This is pretty tricky. Let's add a selftest for this.
>>
>> I don't have an easy repro for this (the UAF does not trigger
>> immediately), Also I am not sure how one would test a UAF in a
>> selftest. What actually happens is:
>>
>> * We have a dangling task pointer in local storage.
>> * This is used sometime later which then leads to weird memory
>> corruption errors.
> 
> I think we will see it easily with KASAN, no?
> 
>>
>>>
>>>>
>>>> Fixes: a10787e6d58c ("bpf: Enable task local storage for tracing programs")
>>>> Reported-by: Kuba Piecuch <jpiecuch@google.com>
>>>> Signed-off-by: KP Singh <kpsingh@kernel.org>
>>>> ---
>>>>
>>>> This fixes the regression from the LSM blob based implementation, we can
>>>> still have UAFs, if bpf_task_storage_get is invoked after bpf_task_storage_free
>>>> in the cleanup path.
>>>
>>> Can we fix this by calling bpf_task_storage_free() from free_task()?
>>
>> I think we can yeah. But, this is yet another deviation from how the
>> security blob is managed (security_task_free) frees the blob that we
>> were previously using.

Does it mean doing bpf_task_storage_free() in free_task() will break some use 
cases? Could you explain?
Doing bpf_task_storage_free() in free_task() seems to be more straight forward.

> 
> Yeah, this will make the code even more tricky.
> 
> Another idea I had is to filter on task->__state in the helper. IOW,
> task local storage does not work on starting or died tasks. But I am
> not sure whether this will make BPF_LSM less effective (not covering
> certain tasks).
> 
> Thanks,
> Song
> 
>
KP Singh June 1, 2023, 6:24 p.m. UTC | #6
On Thu, Jun 1, 2023 at 7:47 PM Martin KaFai Lau <martin.lau@linux.dev> wrote:
>
> On 6/1/23 9:54 AM, Song Liu wrote:
> >
> >
> >> On Jun 1, 2023, at 9:27 AM, KP Singh <kpsingh@kernel.org> wrote:
> >>
> >> On Thu, Jun 1, 2023 at 6:18 PM Song Liu <song@kernel.org> wrote:
> >>>
> >>> On Thu, Jun 1, 2023 at 5:26 AM KP Singh <kpsingh@kernel.org> wrote:
> >>>>
> >>>> When the the task local storage was generalized for tracing programs, the
> >>>> bpf_task_local_storage callback was moved from a BPF LSM hook callback
> >>>> for security_task_free LSM hook to it's own callback. But a failure case
> >>>> in bad_fork_cleanup_security was missed which, when triggered, led to a dangling
> >>>> task owner pointer and a subsequent use-after-free.
> >>>>
> >>>> This issue was noticed when a BPF LSM program was attached to the
> >>>> task_alloc hook on a kernel with KASAN enabled. The program used
> >>>> bpf_task_storage_get to copy the task local storage from the current
> >>>> task to the new task being created.
> >>>
> >>> This is pretty tricky. Let's add a selftest for this.
> >>
> >> I don't have an easy repro for this (the UAF does not trigger
> >> immediately), Also I am not sure how one would test a UAF in a
> >> selftest. What actually happens is:
> >>
> >> * We have a dangling task pointer in local storage.
> >> * This is used sometime later which then leads to weird memory
> >> corruption errors.
> >
> > I think we will see it easily with KASAN, no?

No, the issue only happens when copy_process fails for some reason
(which one can possibly trigger with error injection / fexit) and then
somehow triggers the UAF.

Even if one does manage to trigger the KASAN warning, we won't fail
the selftest, so I don't see this in the selftest territory TBH. What
do you have in mind?

> >
> >>
> >>>
> >>>>
> >>>> Fixes: a10787e6d58c ("bpf: Enable task local storage for tracing programs")
> >>>> Reported-by: Kuba Piecuch <jpiecuch@google.com>
> >>>> Signed-off-by: KP Singh <kpsingh@kernel.org>
> >>>> ---
> >>>>
> >>>> This fixes the regression from the LSM blob based implementation, we can
> >>>> still have UAFs, if bpf_task_storage_get is invoked after bpf_task_storage_free
> >>>> in the cleanup path.
> >>>
> >>> Can we fix this by calling bpf_task_storage_free() from free_task()?
> >>
> >> I think we can yeah. But, this is yet another deviation from how the
> >> security blob is managed (security_task_free) frees the blob that we
> >> were previously using.
>
> Does it mean doing bpf_task_storage_free() in free_task() will break some use
> cases? Could you explain?
> Doing bpf_task_storage_free() in free_task() seems to be more straight forward.

Superficially, I don't see any issues . All I am saying is that,
before we generalized task local storage, it was allocated and freed
as a security blob and now it's deviating further. Should we just
consider moving security_task_free into task_free then?

>
> >
> > Yeah, this will make the code even more tricky.
> >
> > Another idea I had is to filter on task->__state in the helper. IOW,

bailing out on __state == TASK_DEAD should be reasonable.

> > task local storage does not work on starting or died tasks. But I am
> > not sure whether this will make BPF_LSM less effective (not covering
> > certain tasks).

As long as the task local storage is usable in LSM hooks like
security_task_alloc it's okay

> >
> > Thanks,
> > Song
> >
> >
>
KP Singh June 1, 2023, 6:32 p.m. UTC | #7
On Thu, Jun 1, 2023 at 8:24 PM KP Singh <kpsingh@kernel.org> wrote:
>
> On Thu, Jun 1, 2023 at 7:47 PM Martin KaFai Lau <martin.lau@linux.dev> wrote:
> >
> > On 6/1/23 9:54 AM, Song Liu wrote:
> > >
> > >
> > >> On Jun 1, 2023, at 9:27 AM, KP Singh <kpsingh@kernel.org> wrote:
> > >>
> > >> On Thu, Jun 1, 2023 at 6:18 PM Song Liu <song@kernel.org> wrote:
> > >>>
> > >>> On Thu, Jun 1, 2023 at 5:26 AM KP Singh <kpsingh@kernel.org> wrote:
> > >>>>
> > >>>> When the the task local storage was generalized for tracing programs, the
> > >>>> bpf_task_local_storage callback was moved from a BPF LSM hook callback
> > >>>> for security_task_free LSM hook to it's own callback. But a failure case
> > >>>> in bad_fork_cleanup_security was missed which, when triggered, led to a dangling
> > >>>> task owner pointer and a subsequent use-after-free.
> > >>>>
> > >>>> This issue was noticed when a BPF LSM program was attached to the
> > >>>> task_alloc hook on a kernel with KASAN enabled. The program used
> > >>>> bpf_task_storage_get to copy the task local storage from the current
> > >>>> task to the new task being created.
> > >>>
> > >>> This is pretty tricky. Let's add a selftest for this.
> > >>
> > >> I don't have an easy repro for this (the UAF does not trigger
> > >> immediately), Also I am not sure how one would test a UAF in a
> > >> selftest. What actually happens is:
> > >>
> > >> * We have a dangling task pointer in local storage.
> > >> * This is used sometime later which then leads to weird memory
> > >> corruption errors.
> > >
> > > I think we will see it easily with KASAN, no?
>
> No, the issue only happens when copy_process fails for some reason
> (which one can possibly trigger with error injection / fexit) and then
> somehow triggers the UAF.
>
> Even if one does manage to trigger the KASAN warning, we won't fail
> the selftest, so I don't see this in the selftest territory TBH. What
> do you have in mind?
>
> > >
> > >>
> > >>>
> > >>>>
> > >>>> Fixes: a10787e6d58c ("bpf: Enable task local storage for tracing programs")
> > >>>> Reported-by: Kuba Piecuch <jpiecuch@google.com>
> > >>>> Signed-off-by: KP Singh <kpsingh@kernel.org>
> > >>>> ---
> > >>>>
> > >>>> This fixes the regression from the LSM blob based implementation, we can
> > >>>> still have UAFs, if bpf_task_storage_get is invoked after bpf_task_storage_free
> > >>>> in the cleanup path.
> > >>>
> > >>> Can we fix this by calling bpf_task_storage_free() from free_task()?
> > >>
> > >> I think we can yeah. But, this is yet another deviation from how the
> > >> security blob is managed (security_task_free) frees the blob that we
> > >> were previously using.
> >
> > Does it mean doing bpf_task_storage_free() in free_task() will break some use
> > cases? Could you explain?
> > Doing bpf_task_storage_free() in free_task() seems to be more straight forward.
>
> Superficially, I don't see any issues . All I am saying is that,
> before we generalized task local storage, it was allocated and freed
> as a security blob and now it's deviating further. Should we just
> consider moving security_task_free into task_free then?

For now, I am going to move bpf_task_storage_free into free_task as
it's the immediate fix and respin.

>
> >
> > >
> > > Yeah, this will make the code even more tricky.
> > >
> > > Another idea I had is to filter on task->__state in the helper. IOW,
>
> bailing out on __state == TASK_DEAD should be reasonable.
>
> > > task local storage does not work on starting or died tasks. But I am
> > > not sure whether this will make BPF_LSM less effective (not covering
> > > certain tasks).
>
> As long as the task local storage is usable in LSM hooks like
> security_task_alloc it's okay
>
> > >
> > > Thanks,
> > > Song
> > >
> > >
> >
diff mbox series

Patch

diff --git a/kernel/fork.c b/kernel/fork.c
index ed4e01daccaa..112d85091ae6 100644
--- a/kernel/fork.c
+++ b/kernel/fork.c
@@ -2781,6 +2781,7 @@  __latent_entropy struct task_struct *copy_process(
 	exit_sem(p);
 bad_fork_cleanup_security:
 	security_task_free(p);
+	bpf_task_storage_free(p);
 bad_fork_cleanup_audit:
 	audit_free(p);
 bad_fork_cleanup_perf: