diff mbox series

[bpf] bpf, x86: fix freeing of not-finalized bpf_prog_pack

Message ID 20220706002612.4013790-1-song@kernel.org (mailing list archive)
State Accepted
Commit 1d5f82d9dd477d5c66e0214a68c3e4f308eadd6d
Delegated to: BPF
Headers show
Series [bpf] bpf, x86: fix freeing of not-finalized bpf_prog_pack | expand

Checks

Context Check Description
bpf/vmtest-bpf-PR success PR summary
netdev/tree_selection success Clearly marked for bpf, async
netdev/fixes_present success Fixes tag present in non-next series
netdev/subject_prefix success Link
netdev/cover_letter success Single patches do not need cover letters
netdev/patch_count success Link
netdev/header_inline success No static functions without inline keyword in header files
netdev/build_32bit success Errors and warnings before: 1432 this patch: 1432
netdev/cc_maintainers warning 17 maintainers not CCed: haoluo@google.com tglx@linutronix.de netdev@vger.kernel.org bp@alien8.de x86@kernel.org mingo@redhat.com yoshfuji@linux-ipv6.org sdf@google.com hpa@zytor.com yhs@fb.com davem@davemloft.net john.fastabend@gmail.com martin.lau@linux.dev dsahern@kernel.org jolsa@kernel.org dave.hansen@linux.intel.com kpsingh@kernel.org
netdev/build_clang success Errors and warnings before: 166 this patch: 166
netdev/module_param success Was 0 now: 0
netdev/verify_signedoff success Signed-off-by tag matches author and committer
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: 1439 this patch: 1439
netdev/checkpatch success total: 0 errors, 0 warnings, 0 checks, 108 lines checked
netdev/kdoc success Errors and warnings before: 0 this patch: 0
netdev/source_inline success Was 0 now: 0
bpf/vmtest-bpf-VM_Test-1 success Logs for Kernel LATEST on ubuntu-latest with gcc
bpf/vmtest-bpf-VM_Test-2 success Logs for Kernel LATEST on ubuntu-latest with llvm-15
bpf/vmtest-bpf-VM_Test-3 success Logs for Kernel LATEST on z15 with gcc

Commit Message

Song Liu July 6, 2022, 12:26 a.m. UTC
syzbot reported a few issues with bpf_prog_pack [1], [2]. These are
triggered when the program passed initial JIT in jit_subprogs(), but
failed final pass of JIT. At this point, bpf_jit_binary_pack_free() is
called before bpf_jit_binary_pack_finalize(), and the whole 2MB page is
freed.

Fix this with a custom bpf_jit_free() for x86_64, which calls
bpf_jit_binary_pack_finalize() if necessary. Also, with custom
bpf_jit_free(), bpf_prog_aux->use_bpf_prog_pack is not needed any more,
remove it.

Fixes: 1022a5498f6f ("bpf, x86_64: Use bpf_jit_binary_pack_alloc")
[1] https://syzkaller.appspot.com/bug?extid=2f649ec6d2eea1495a8f
[2] https://syzkaller.appspot.com/bug?extid=87f65c75f4a72db05445
Reported-by: syzbot+2f649ec6d2eea1495a8f@syzkaller.appspotmail.com
Reported-by: syzbot+87f65c75f4a72db05445@syzkaller.appspotmail.com
Signed-off-by: Song Liu <song@kernel.org>
---
 arch/x86/net/bpf_jit_comp.c | 25 +++++++++++++++++++++++++
 include/linux/bpf.h         |  1 -
 include/linux/filter.h      |  8 ++++++++
 kernel/bpf/core.c           | 29 ++++++++++++-----------------
 4 files changed, 45 insertions(+), 18 deletions(-)

Comments

Alexei Starovoitov July 12, 2022, 10:09 p.m. UTC | #1
On Tue, Jul 5, 2022 at 5:26 PM Song Liu <song@kernel.org> wrote:
>
> syzbot reported a few issues with bpf_prog_pack [1], [2]. These are
> triggered when the program passed initial JIT in jit_subprogs(), but
> failed final pass of JIT. At this point, bpf_jit_binary_pack_free() is
> called before bpf_jit_binary_pack_finalize(), and the whole 2MB page is
> freed.
>
> Fix this with a custom bpf_jit_free() for x86_64, which calls
> bpf_jit_binary_pack_finalize() if necessary. Also, with custom
> bpf_jit_free(), bpf_prog_aux->use_bpf_prog_pack is not needed any more,
> remove it.
>
> Fixes: 1022a5498f6f ("bpf, x86_64: Use bpf_jit_binary_pack_alloc")
> [1] https://syzkaller.appspot.com/bug?extid=2f649ec6d2eea1495a8f
> [2] https://syzkaller.appspot.com/bug?extid=87f65c75f4a72db05445
> Reported-by: syzbot+2f649ec6d2eea1495a8f@syzkaller.appspotmail.com
> Reported-by: syzbot+87f65c75f4a72db05445@syzkaller.appspotmail.com
> Signed-off-by: Song Liu <song@kernel.org>
> ---
>  arch/x86/net/bpf_jit_comp.c | 25 +++++++++++++++++++++++++
>  include/linux/bpf.h         |  1 -
>  include/linux/filter.h      |  8 ++++++++
>  kernel/bpf/core.c           | 29 ++++++++++++-----------------
>  4 files changed, 45 insertions(+), 18 deletions(-)
>
> diff --git a/arch/x86/net/bpf_jit_comp.c b/arch/x86/net/bpf_jit_comp.c
> index c98b8c0ed3b8..c3dca4c97e48 100644
> --- a/arch/x86/net/bpf_jit_comp.c
> +++ b/arch/x86/net/bpf_jit_comp.c
> @@ -2492,3 +2492,28 @@ void *bpf_arch_text_copy(void *dst, void *src, size_t len)
>                 return ERR_PTR(-EINVAL);
>         return dst;
>  }
> +
> +void bpf_jit_free(struct bpf_prog *prog)
> +{
> +       if (prog->jited) {
> +               struct x64_jit_data *jit_data = prog->aux->jit_data;
> +               struct bpf_binary_header *hdr;
> +
> +               /*
> +                * If we fail the final pass of JIT (from jit_subprogs),
> +                * the program may not be finalized yet. Call finalize here
> +                * before freeing it.
> +                */
> +               if (jit_data) {
> +                       bpf_jit_binary_pack_finalize(prog, jit_data->header,
> +                                                    jit_data->rw_header);
> +                       kvfree(jit_data->addrs);
> +                       kfree(jit_data);
> +               }

It looks like a workaround for missed cleanup on the JIT side.
When bpf_int_jit_compile() fails it is supposed to free jit_data
immediately.

> passed initial JIT in jit_subprogs(), but
> failed final pass of JIT. At this point, bpf_jit_binary_pack_free() is
> called before bpf_jit_binary_pack_finalize()

It feels that bpf_int_jit_compile() should call
bpf_jit_binary_pack_finalize() instead in the path where
it's failing.
I could be missing details on what exactly
"failed final pass of JIT" means.
Song Liu July 12, 2022, 11:01 p.m. UTC | #2
On Tue, Jul 12, 2022 at 3:09 PM Alexei Starovoitov
<alexei.starovoitov@gmail.com> wrote:
>
> On Tue, Jul 5, 2022 at 5:26 PM Song Liu <song@kernel.org> wrote:
> >
> > syzbot reported a few issues with bpf_prog_pack [1], [2]. These are
> > triggered when the program passed initial JIT in jit_subprogs(), but
> > failed final pass of JIT. At this point, bpf_jit_binary_pack_free() is
> > called before bpf_jit_binary_pack_finalize(), and the whole 2MB page is
> > freed.
> >
> > Fix this with a custom bpf_jit_free() for x86_64, which calls
> > bpf_jit_binary_pack_finalize() if necessary. Also, with custom
> > bpf_jit_free(), bpf_prog_aux->use_bpf_prog_pack is not needed any more,
> > remove it.
> >
> > Fixes: 1022a5498f6f ("bpf, x86_64: Use bpf_jit_binary_pack_alloc")
> > [1] https://syzkaller.appspot.com/bug?extid=2f649ec6d2eea1495a8f
> > [2] https://syzkaller.appspot.com/bug?extid=87f65c75f4a72db05445
> > Reported-by: syzbot+2f649ec6d2eea1495a8f@syzkaller.appspotmail.com
> > Reported-by: syzbot+87f65c75f4a72db05445@syzkaller.appspotmail.com
> > Signed-off-by: Song Liu <song@kernel.org>
> > ---
> >  arch/x86/net/bpf_jit_comp.c | 25 +++++++++++++++++++++++++
> >  include/linux/bpf.h         |  1 -
> >  include/linux/filter.h      |  8 ++++++++
> >  kernel/bpf/core.c           | 29 ++++++++++++-----------------
> >  4 files changed, 45 insertions(+), 18 deletions(-)
> >
> > diff --git a/arch/x86/net/bpf_jit_comp.c b/arch/x86/net/bpf_jit_comp.c
> > index c98b8c0ed3b8..c3dca4c97e48 100644
> > --- a/arch/x86/net/bpf_jit_comp.c
> > +++ b/arch/x86/net/bpf_jit_comp.c
> > @@ -2492,3 +2492,28 @@ void *bpf_arch_text_copy(void *dst, void *src, size_t len)
> >                 return ERR_PTR(-EINVAL);
> >         return dst;
> >  }
> > +
> > +void bpf_jit_free(struct bpf_prog *prog)
> > +{
> > +       if (prog->jited) {
> > +               struct x64_jit_data *jit_data = prog->aux->jit_data;
> > +               struct bpf_binary_header *hdr;
> > +
> > +               /*
> > +                * If we fail the final pass of JIT (from jit_subprogs),
> > +                * the program may not be finalized yet. Call finalize here
> > +                * before freeing it.
> > +                */
> > +               if (jit_data) {
> > +                       bpf_jit_binary_pack_finalize(prog, jit_data->header,
> > +                                                    jit_data->rw_header);
> > +                       kvfree(jit_data->addrs);
> > +                       kfree(jit_data);
> > +               }
>
> It looks like a workaround for missed cleanup on the JIT side.
> When bpf_int_jit_compile() fails it is supposed to free jit_data
> immediately.
>
> > passed initial JIT in jit_subprogs(), but
> > failed final pass of JIT. At this point, bpf_jit_binary_pack_free() is
> > called before bpf_jit_binary_pack_finalize()
>
> It feels that bpf_int_jit_compile() should call
> bpf_jit_binary_pack_finalize() instead in the path where
> it's failing.
> I could be missing details on what exactly
> "failed final pass of JIT" means.

This only happens with multiple subprogs. In jit_subprogs(), we
first call bpf_int_jit_compile() on each sub program. And then,
we call it on each sub program again. jit_data is not freed in the
first call of bpf_int_jit_compile(). Similarly we don't call
bpf_jit_binary_pack_finalize() in the first call of bpf_int_jit_compile().

If bpf_int_jit_compile() failed for one sub program, we will call
bpf_jit_binary_pack_finalize() for this sub program. However,
we don't have a chance to call it for other sub programs. Then
we will hit "goto out_free" in jit_subprogs(), and call bpf_jit_free
on some subprograms that haven't got bpf_jit_binary_pack_finalize()
yet. So, I think bpf_jit_free is the best place we can add the extra
check and call bpf_jit_binary_pack_finalize().

Does this make sense?

Thanks,
Song
Alexei Starovoitov July 13, 2022, 12:37 a.m. UTC | #3
On Tue, Jul 12, 2022 at 4:02 PM Song Liu <song@kernel.org> wrote:
>
> On Tue, Jul 12, 2022 at 3:09 PM Alexei Starovoitov
> <alexei.starovoitov@gmail.com> wrote:
> >
> > On Tue, Jul 5, 2022 at 5:26 PM Song Liu <song@kernel.org> wrote:
> > >
> > > syzbot reported a few issues with bpf_prog_pack [1], [2]. These are
> > > triggered when the program passed initial JIT in jit_subprogs(), but
> > > failed final pass of JIT. At this point, bpf_jit_binary_pack_free() is
> > > called before bpf_jit_binary_pack_finalize(), and the whole 2MB page is
> > > freed.
> > >
> > > Fix this with a custom bpf_jit_free() for x86_64, which calls
> > > bpf_jit_binary_pack_finalize() if necessary. Also, with custom
> > > bpf_jit_free(), bpf_prog_aux->use_bpf_prog_pack is not needed any more,
> > > remove it.
> > >
> > > Fixes: 1022a5498f6f ("bpf, x86_64: Use bpf_jit_binary_pack_alloc")
> > > [1] https://syzkaller.appspot.com/bug?extid=2f649ec6d2eea1495a8f
> > > [2] https://syzkaller.appspot.com/bug?extid=87f65c75f4a72db05445
> > > Reported-by: syzbot+2f649ec6d2eea1495a8f@syzkaller.appspotmail.com
> > > Reported-by: syzbot+87f65c75f4a72db05445@syzkaller.appspotmail.com
> > > Signed-off-by: Song Liu <song@kernel.org>
> > > ---
> > >  arch/x86/net/bpf_jit_comp.c | 25 +++++++++++++++++++++++++
> > >  include/linux/bpf.h         |  1 -
> > >  include/linux/filter.h      |  8 ++++++++
> > >  kernel/bpf/core.c           | 29 ++++++++++++-----------------
> > >  4 files changed, 45 insertions(+), 18 deletions(-)
> > >
> > > diff --git a/arch/x86/net/bpf_jit_comp.c b/arch/x86/net/bpf_jit_comp.c
> > > index c98b8c0ed3b8..c3dca4c97e48 100644
> > > --- a/arch/x86/net/bpf_jit_comp.c
> > > +++ b/arch/x86/net/bpf_jit_comp.c
> > > @@ -2492,3 +2492,28 @@ void *bpf_arch_text_copy(void *dst, void *src, size_t len)
> > >                 return ERR_PTR(-EINVAL);
> > >         return dst;
> > >  }
> > > +
> > > +void bpf_jit_free(struct bpf_prog *prog)
> > > +{
> > > +       if (prog->jited) {
> > > +               struct x64_jit_data *jit_data = prog->aux->jit_data;
> > > +               struct bpf_binary_header *hdr;
> > > +
> > > +               /*
> > > +                * If we fail the final pass of JIT (from jit_subprogs),
> > > +                * the program may not be finalized yet. Call finalize here
> > > +                * before freeing it.
> > > +                */
> > > +               if (jit_data) {
> > > +                       bpf_jit_binary_pack_finalize(prog, jit_data->header,
> > > +                                                    jit_data->rw_header);
> > > +                       kvfree(jit_data->addrs);
> > > +                       kfree(jit_data);
> > > +               }
> >
> > It looks like a workaround for missed cleanup on the JIT side.
> > When bpf_int_jit_compile() fails it is supposed to free jit_data
> > immediately.
> >
> > > passed initial JIT in jit_subprogs(), but
> > > failed final pass of JIT. At this point, bpf_jit_binary_pack_free() is
> > > called before bpf_jit_binary_pack_finalize()
> >
> > It feels that bpf_int_jit_compile() should call
> > bpf_jit_binary_pack_finalize() instead in the path where
> > it's failing.
> > I could be missing details on what exactly
> > "failed final pass of JIT" means.
>
> This only happens with multiple subprogs. In jit_subprogs(), we
> first call bpf_int_jit_compile() on each sub program. And then,
> we call it on each sub program again. jit_data is not freed in the
> first call of bpf_int_jit_compile(). Similarly we don't call
> bpf_jit_binary_pack_finalize() in the first call of bpf_int_jit_compile().
>
> If bpf_int_jit_compile() failed for one sub program, we will call
> bpf_jit_binary_pack_finalize() for this sub program. However,
> we don't have a chance to call it for other sub programs. Then
> we will hit "goto out_free" in jit_subprogs(), and call bpf_jit_free
> on some subprograms that haven't got bpf_jit_binary_pack_finalize()
> yet. So, I think bpf_jit_free is the best place we can add the extra
> check and call bpf_jit_binary_pack_finalize().
>
> Does this make sense?

Got it. I've amended the commit log with the above details.
Considering that we're at rc6 and the amount of conflicts
this patch would cause between bpf and bpf-next trees
I pushed it to bpf-next after applying manually.
Thanks.
patchwork-bot+netdevbpf@kernel.org July 13, 2022, 12:40 a.m. UTC | #4
Hello:

This patch was applied to bpf/bpf-next.git (master)
by Alexei Starovoitov <ast@kernel.org>:

On Tue, 5 Jul 2022 17:26:12 -0700 you wrote:
> syzbot reported a few issues with bpf_prog_pack [1], [2]. These are
> triggered when the program passed initial JIT in jit_subprogs(), but
> failed final pass of JIT. At this point, bpf_jit_binary_pack_free() is
> called before bpf_jit_binary_pack_finalize(), and the whole 2MB page is
> freed.
> 
> Fix this with a custom bpf_jit_free() for x86_64, which calls
> bpf_jit_binary_pack_finalize() if necessary. Also, with custom
> bpf_jit_free(), bpf_prog_aux->use_bpf_prog_pack is not needed any more,
> remove it.
> 
> [...]

Here is the summary with links:
  - [bpf] bpf, x86: fix freeing of not-finalized bpf_prog_pack
    https://git.kernel.org/bpf/bpf-next/c/1d5f82d9dd47

You are awesome, thank you!
diff mbox series

Patch

diff --git a/arch/x86/net/bpf_jit_comp.c b/arch/x86/net/bpf_jit_comp.c
index c98b8c0ed3b8..c3dca4c97e48 100644
--- a/arch/x86/net/bpf_jit_comp.c
+++ b/arch/x86/net/bpf_jit_comp.c
@@ -2492,3 +2492,28 @@  void *bpf_arch_text_copy(void *dst, void *src, size_t len)
 		return ERR_PTR(-EINVAL);
 	return dst;
 }
+
+void bpf_jit_free(struct bpf_prog *prog)
+{
+	if (prog->jited) {
+		struct x64_jit_data *jit_data = prog->aux->jit_data;
+		struct bpf_binary_header *hdr;
+
+		/*
+		 * If we fail the final pass of JIT (from jit_subprogs),
+		 * the program may not be finalized yet. Call finalize here
+		 * before freeing it.
+		 */
+		if (jit_data) {
+			bpf_jit_binary_pack_finalize(prog, jit_data->header,
+						     jit_data->rw_header);
+			kvfree(jit_data->addrs);
+			kfree(jit_data);
+		}
+		hdr = bpf_jit_binary_pack_hdr(prog);
+		bpf_jit_binary_pack_free(hdr, NULL);
+		WARN_ON_ONCE(!bpf_prog_kallsyms_verify_off(prog));
+	}
+
+	bpf_prog_unlock_free(prog);
+}
diff --git a/include/linux/bpf.h b/include/linux/bpf.h
index 2b914a56a2c5..7424cf234ae0 100644
--- a/include/linux/bpf.h
+++ b/include/linux/bpf.h
@@ -1025,7 +1025,6 @@  struct bpf_prog_aux {
 	bool sleepable;
 	bool tail_call_reachable;
 	bool xdp_has_frags;
-	bool use_bpf_prog_pack;
 	/* BTF_KIND_FUNC_PROTO for valid attach_btf_id */
 	const struct btf_type *attach_func_proto;
 	/* function name for valid attach_btf_id */
diff --git a/include/linux/filter.h b/include/linux/filter.h
index ed0c0ff42ad5..5005bf2d30bd 100644
--- a/include/linux/filter.h
+++ b/include/linux/filter.h
@@ -1060,6 +1060,14 @@  u64 bpf_jit_alloc_exec_limit(void);
 void *bpf_jit_alloc_exec(unsigned long size);
 void bpf_jit_free_exec(void *addr);
 void bpf_jit_free(struct bpf_prog *fp);
+struct bpf_binary_header *
+bpf_jit_binary_pack_hdr(const struct bpf_prog *fp);
+
+static inline bool bpf_prog_kallsyms_verify_off(const struct bpf_prog *fp)
+{
+	return list_empty(&fp->aux->ksym.lnode) ||
+	       fp->aux->ksym.lnode.prev == LIST_POISON2;
+}
 
 struct bpf_binary_header *
 bpf_jit_binary_pack_alloc(unsigned int proglen, u8 **ro_image,
diff --git a/kernel/bpf/core.c b/kernel/bpf/core.c
index 5f6f3f829b36..360ceb817639 100644
--- a/kernel/bpf/core.c
+++ b/kernel/bpf/core.c
@@ -647,12 +647,6 @@  static bool bpf_prog_kallsyms_candidate(const struct bpf_prog *fp)
 	return fp->jited && !bpf_prog_was_classic(fp);
 }
 
-static bool bpf_prog_kallsyms_verify_off(const struct bpf_prog *fp)
-{
-	return list_empty(&fp->aux->ksym.lnode) ||
-	       fp->aux->ksym.lnode.prev == LIST_POISON2;
-}
-
 void bpf_prog_kallsyms_add(struct bpf_prog *fp)
 {
 	if (!bpf_prog_kallsyms_candidate(fp) ||
@@ -1150,7 +1144,6 @@  int bpf_jit_binary_pack_finalize(struct bpf_prog *prog,
 		bpf_prog_pack_free(ro_header);
 		return PTR_ERR(ptr);
 	}
-	prog->aux->use_bpf_prog_pack = true;
 	return 0;
 }
 
@@ -1174,17 +1167,23 @@  void bpf_jit_binary_pack_free(struct bpf_binary_header *ro_header,
 	bpf_jit_uncharge_modmem(size);
 }
 
+struct bpf_binary_header *
+bpf_jit_binary_pack_hdr(const struct bpf_prog *fp)
+{
+	unsigned long real_start = (unsigned long)fp->bpf_func;
+	unsigned long addr;
+
+	addr = real_start & BPF_PROG_CHUNK_MASK;
+	return (void *)addr;
+}
+
 static inline struct bpf_binary_header *
 bpf_jit_binary_hdr(const struct bpf_prog *fp)
 {
 	unsigned long real_start = (unsigned long)fp->bpf_func;
 	unsigned long addr;
 
-	if (fp->aux->use_bpf_prog_pack)
-		addr = real_start & BPF_PROG_CHUNK_MASK;
-	else
-		addr = real_start & PAGE_MASK;
-
+	addr = real_start & PAGE_MASK;
 	return (void *)addr;
 }
 
@@ -1197,11 +1196,7 @@  void __weak bpf_jit_free(struct bpf_prog *fp)
 	if (fp->jited) {
 		struct bpf_binary_header *hdr = bpf_jit_binary_hdr(fp);
 
-		if (fp->aux->use_bpf_prog_pack)
-			bpf_jit_binary_pack_free(hdr, NULL /* rw_buffer */);
-		else
-			bpf_jit_binary_free(hdr);
-
+		bpf_jit_binary_free(hdr);
 		WARN_ON_ONCE(!bpf_prog_kallsyms_verify_off(fp));
 	}