diff mbox series

[bpf-next,v2,2/2] selftests/bpf: test for malformed BPF_CORE_TYPE_ID_LOCAL relocation

Message ID 20240822001837.2715909-3-eddyz87@gmail.com (mailing list archive)
State Superseded
Delegated to: BPF
Headers show
Series bpf: fix null pointer access for malformed BPF_CORE_TYPE_ID_LOCAL relos | expand

Checks

Context Check Description
bpf/vmtest-bpf-next-PR success PR summary
bpf/vmtest-bpf-next-VM_Test-5 success Logs for aarch64-gcc / build-release
bpf/vmtest-bpf-next-VM_Test-3 success Logs for Validate matrix.py
bpf/vmtest-bpf-next-VM_Test-1 success Logs for ShellCheck
bpf/vmtest-bpf-next-VM_Test-2 success Logs for Unittests
bpf/vmtest-bpf-next-VM_Test-0 success Logs for Lint
bpf/vmtest-bpf-next-VM_Test-11 success Logs for s390x-gcc / build / build for s390x with gcc
bpf/vmtest-bpf-next-VM_Test-15 success Logs for s390x-gcc / test (test_verifier, false, 360) / test_verifier on s390x with gcc
bpf/vmtest-bpf-next-VM_Test-14 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-18 success Logs for x86_64-gcc / build / build for x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-21 success Logs for x86_64-gcc / test (test_progs, false, 360) / test_progs on x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-17 success Logs for set-matrix
bpf/vmtest-bpf-next-VM_Test-16 success Logs for s390x-gcc / veristat
bpf/vmtest-bpf-next-VM_Test-27 success Logs for x86_64-llvm-17 / build / build for x86_64 with llvm-17
bpf/vmtest-bpf-next-VM_Test-23 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-25 success Logs for x86_64-gcc / test (test_verifier, false, 360) / test_verifier on x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-10 success Logs for aarch64-gcc / veristat
bpf/vmtest-bpf-next-VM_Test-26 success Logs for x86_64-gcc / veristat / veristat on x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-33 success Logs for x86_64-llvm-17 / veristat
bpf/vmtest-bpf-next-VM_Test-30 success Logs for x86_64-llvm-17 / test (test_progs, false, 360) / test_progs on x86_64 with llvm-17
bpf/vmtest-bpf-next-VM_Test-28 success Logs for x86_64-llvm-17 / build-release / build for x86_64 with llvm-17-O2
bpf/vmtest-bpf-next-VM_Test-32 success Logs for x86_64-llvm-17 / test (test_verifier, false, 360) / test_verifier on x86_64 with llvm-17
bpf/vmtest-bpf-next-VM_Test-31 success Logs for x86_64-llvm-17 / test (test_progs_no_alu32, false, 360) / test_progs_no_alu32 on x86_64 with llvm-17
bpf/vmtest-bpf-next-VM_Test-19 success Logs for x86_64-gcc / build-release
bpf/vmtest-bpf-next-VM_Test-36 success Logs for x86_64-llvm-18 / test (test_maps, false, 360) / test_maps on x86_64 with llvm-18
bpf/vmtest-bpf-next-VM_Test-20 success Logs for x86_64-gcc / test (test_maps, false, 360) / test_maps on x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-29 success Logs for x86_64-llvm-17 / test (test_maps, false, 360) / test_maps on x86_64 with llvm-17
bpf/vmtest-bpf-next-VM_Test-12 success Logs for s390x-gcc / build-release
bpf/vmtest-bpf-next-VM_Test-35 success Logs for x86_64-llvm-18 / build-release / build for x86_64 with llvm-18-O2
bpf/vmtest-bpf-next-VM_Test-34 success Logs for x86_64-llvm-18 / build / build for x86_64 with llvm-18
bpf/vmtest-bpf-next-VM_Test-22 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-38 success Logs for x86_64-llvm-18 / test (test_progs_cpuv4, false, 360) / test_progs_cpuv4 on x86_64 with llvm-18
bpf/vmtest-bpf-next-VM_Test-40 success Logs for x86_64-llvm-18 / test (test_verifier, false, 360) / test_verifier on x86_64 with llvm-18
bpf/vmtest-bpf-next-VM_Test-24 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-41 success Logs for x86_64-llvm-18 / veristat
bpf/vmtest-bpf-next-VM_Test-37 success Logs for x86_64-llvm-18 / test (test_progs, false, 360) / test_progs on x86_64 with llvm-18
bpf/vmtest-bpf-next-VM_Test-4 success Logs for aarch64-gcc / build / build for aarch64 with gcc
bpf/vmtest-bpf-next-VM_Test-39 success Logs for x86_64-llvm-18 / test (test_progs_no_alu32, false, 360) / test_progs_no_alu32 on x86_64 with llvm-18
bpf/vmtest-bpf-next-VM_Test-9 success Logs for aarch64-gcc / test (test_verifier, false, 360) / test_verifier on aarch64 with gcc
bpf/vmtest-bpf-next-VM_Test-6 success Logs for aarch64-gcc / test (test_maps, false, 360) / test_maps on aarch64 with gcc
bpf/vmtest-bpf-next-VM_Test-8 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-13 success Logs for s390x-gcc / test (test_progs, false, 360) / test_progs on s390x with gcc
bpf/vmtest-bpf-next-VM_Test-7 success Logs for aarch64-gcc / test (test_progs, false, 360) / test_progs on aarch64 with gcc
netdev/series_format success Posting correctly formatted
netdev/tree_selection success Clearly marked for bpf-next
netdev/ynl success Generated files up to date; no warnings/errors; no diff in generated;
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: 42 this patch: 42
netdev/build_tools success Errors and warnings before: 0 this patch: 0
netdev/cc_maintainers warning 9 maintainers not CCed: sdf@fomichev.me mykolal@fb.com shuah@kernel.org jolsa@kernel.org haoluo@google.com linux-kselftest@vger.kernel.org song@kernel.org kpsingh@kernel.org john.fastabend@gmail.com
netdev/build_clang success Errors and warnings before: 44 this patch: 44
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 No Fixes tag
netdev/build_allmodconfig_warn success Errors and warnings before: 49 this patch: 49
netdev/checkpatch warning WARNING: Missing a blank line after declarations WARNING: added, moved or deleted file(s), does MAINTAINERS need updating? WARNING: line length of 82 exceeds 80 columns WARNING: line length of 85 exceeds 80 columns WARNING: line length of 86 exceeds 80 columns WARNING: line length of 88 exceeds 80 columns WARNING: line length of 96 exceeds 80 columns
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

Commit Message

Eduard Zingerman Aug. 22, 2024, 12:18 a.m. UTC
Check that verifier rejects BPF program containing relocation
pointing to non-existent BTF type.

To force relocation resolution on kernel side test case uses
bpf_attr->core_relos field. This field is not exposed by libbpf,
so directly do BPF system call in the test.

Signed-off-by: Eduard Zingerman <eddyz87@gmail.com>
---
 .../selftests/bpf/prog_tests/core_reloc_raw.c | 124 ++++++++++++++++++
 1 file changed, 124 insertions(+)
 create mode 100644 tools/testing/selftests/bpf/prog_tests/core_reloc_raw.c

Comments

Andrii Nakryiko Aug. 22, 2024, 4:29 a.m. UTC | #1
On Wed, Aug 21, 2024 at 5:18 PM Eduard Zingerman <eddyz87@gmail.com> wrote:
>
> Check that verifier rejects BPF program containing relocation
> pointing to non-existent BTF type.
>
> To force relocation resolution on kernel side test case uses
> bpf_attr->core_relos field. This field is not exposed by libbpf,
> so directly do BPF system call in the test.
>
> Signed-off-by: Eduard Zingerman <eddyz87@gmail.com>
> ---
>  .../selftests/bpf/prog_tests/core_reloc_raw.c | 124 ++++++++++++++++++
>  1 file changed, 124 insertions(+)
>  create mode 100644 tools/testing/selftests/bpf/prog_tests/core_reloc_raw.c
>
> diff --git a/tools/testing/selftests/bpf/prog_tests/core_reloc_raw.c b/tools/testing/selftests/bpf/prog_tests/core_reloc_raw.c
> new file mode 100644
> index 000000000000..1ab3ab305d3b
> --- /dev/null
> +++ b/tools/testing/selftests/bpf/prog_tests/core_reloc_raw.c
> @@ -0,0 +1,124 @@
> +// SPDX-License-Identifier: GPL-2.0
> +
> +/* Test cases that can't load programs using libbpf and need direct
> + * BPF syscall access
> + */
> +
> +#include <sys/syscall.h>
> +#include <bpf/libbpf.h>
> +#include <bpf/btf.h>
> +
> +#include "test_progs.h"
> +#include "test_btf.h"
> +#include "bpf/libbpf_internal.h"
> +
> +static char log[16 * 1024];
> +
> +/* Check that verifier rejects BPF program containing relocation
> + * pointing to non-existent BTF type.
> + */
> +static void test_bad_local_id(void)
> +{
> +       struct test_btf {
> +               struct btf_header hdr;
> +               __u32 types[15];
> +               char strings[128];
> +       } raw_btf = {
> +               .hdr = {
> +                       .magic = BTF_MAGIC,
> +                       .version = BTF_VERSION,
> +                       .hdr_len = sizeof(struct btf_header),
> +                       .type_off = 0,
> +                       .type_len = sizeof(raw_btf.types),
> +                       .str_off = offsetof(struct test_btf, strings) -
> +                                  offsetof(struct test_btf, types),
> +                       .str_len = sizeof(raw_btf.strings),
> +               },
> +               .types = {
> +                       BTF_PTR_ENC(0),                                 /* [1] void*  */
> +                       BTF_TYPE_INT_ENC(1, BTF_INT_SIGNED, 0, 32, 4),  /* [2] int    */
> +                       BTF_FUNC_PROTO_ENC(2, 1),                       /* [3] int (*)(void*) */
> +                       BTF_FUNC_PROTO_ARG_ENC(8, 1),
> +                       BTF_FUNC_ENC(8, 3)                      /* [4] FUNC 'foo' type_id=2   */
> +               },
> +               .strings = "\0int\0 0\0foo\0"
> +       };
> +       __u32 log_level = 1 | 2 | 4;
> +       LIBBPF_OPTS(bpf_btf_load_opts, opts,
> +                   .log_buf = log,
> +                   .log_size = sizeof(log),
> +                   .log_level = log_level,
> +       );
> +       struct bpf_insn insns[] = {
> +               BPF_ALU64_IMM(BPF_MOV, BPF_REG_0, 0),
> +               BPF_EXIT_INSN(),
> +       };
> +       struct bpf_func_info funcs[] = {
> +               {
> +                       .insn_off = 0,
> +                       .type_id = 4,
> +               }
> +       };
> +       struct bpf_core_relo relos[] = {
> +               {
> +                       .insn_off = 0,          /* patch first instruction (r0 = 0) */
> +                       .type_id = 100500,      /* !!! this type id does not exist */
> +                       .access_str_off = 6,    /* offset of "0" */
> +                       .kind = BPF_CORE_TYPE_ID_LOCAL,
> +               }
> +       };
> +       union bpf_attr attr = {};
> +       int saved_errno;
> +       int prog_fd = -1;
> +       int btf_fd = -1;
> +
> +       btf_fd = bpf_btf_load(&raw_btf, sizeof(raw_btf), &opts);
> +       saved_errno = errno;
> +       if (btf_fd < 0 || env.verbosity > VERBOSE_NORMAL) {
> +               printf("-------- BTF load log start --------\n");
> +               printf("%s", log);
> +               printf("-------- BTF load log end ----------\n");
> +       }
> +       if (btf_fd < 0) {
> +               PRINT_FAIL("bpf_btf_load() failed, errno=%d\n", saved_errno);
> +               return;
> +       }
> +
> +       memset(log, 0, sizeof(log));

generally speaking there is no need to memset log buffer (maybe just a
first byte, to be safe)

on the other hand, just `union bpf_attr attr = {};` is breakage
waiting to happen, I'd do memset(0) on that, we did run into problems
with that before (I believe it was systemd)

> +       attr.prog_btf_fd = btf_fd;
> +       attr.prog_type = BPF_TRACE_RAW_TP;
> +       attr.license = (__u64)"GPL";
> +       attr.insns = (__u64)&insns;
> +       attr.insn_cnt = sizeof(insns) / sizeof(*insns);
> +       attr.log_buf = (__u64)log;
> +       attr.log_size = sizeof(log);
> +       attr.log_level = log_level;
> +       attr.func_info = (__u64)funcs;
> +       attr.func_info_cnt = sizeof(funcs) / sizeof(*funcs);
> +       attr.func_info_rec_size = sizeof(*funcs);
> +       attr.core_relos = (__u64)relos;
> +       attr.core_relo_cnt = sizeof(relos) / sizeof(*relos);
> +       attr.core_relo_rec_size = sizeof(*relos);

I was wondering for a bit why you didn't just use bpf_prog_load(), and
it seems like it's due to core_relos fields? I don't see why we can't
extend the bpf_prog_load() API to allow to specify those. (would allow
to avoid open-coding this whole bpf_attr business, but it's fine as is
as well)

> +       prog_fd = sys_bpf_prog_load(&attr, sizeof(attr), 1);
> +       saved_errno = errno;
> +       if (prog_fd < 0 || env.verbosity > VERBOSE_NORMAL) {
> +               printf("-------- program load log start --------\n");
> +               printf("%s", log);
> +               printf("-------- program load log end ----------\n");
> +       }
> +       if (prog_fd >= 0) {
> +               PRINT_FAIL("sys_bpf_prog_load() expected to fail\n");
> +               goto out;
> +       }
> +       ASSERT_HAS_SUBSTR(log, "relo #0: bad type id 100500", "program load log");
> +
> +out:
> +       close(prog_fd);
> +       close(btf_fd);
> +}
> +
> +void test_core_reloc_raw(void)
> +{
> +       if (test__start_subtest("bad_local_id"))
> +               test_bad_local_id();
> +}
> --
> 2.45.2
>
Eduard Zingerman Aug. 22, 2024, 4:39 a.m. UTC | #2
On Wed, 2024-08-21 at 21:29 -0700, Andrii Nakryiko wrote:

[...]

> > +       btf_fd = bpf_btf_load(&raw_btf, sizeof(raw_btf), &opts);
> > +       saved_errno = errno;
> > +       if (btf_fd < 0 || env.verbosity > VERBOSE_NORMAL) {
> > +               printf("-------- BTF load log start --------\n");
> > +               printf("%s", log);
> > +               printf("-------- BTF load log end ----------\n");
> > +       }
> > +       if (btf_fd < 0) {
> > +               PRINT_FAIL("bpf_btf_load() failed, errno=%d\n", saved_errno);
> > +               return;
> > +       }
> > +
> > +       memset(log, 0, sizeof(log));
> 
> generally speaking there is no need to memset log buffer (maybe just a
> first byte, to be safe)

Will change.

> on the other hand, just `union bpf_attr attr = {};` is breakage
> waiting to happen, I'd do memset(0) on that, we did run into problems
> with that before (I believe it was systemd)

Compilers optimize out 'smth = {}' where 'smth' escapes?
I mean, I will change it to memset(0), but the fact that you observed
such behaviour is disturbing beyond limit...

I already run into gcc vs clang behaviour differences for the first
iteration of this test where I had:

    union bpf_attr {
	.prog_type = ...
    };

clang did not zero out all members of the union, while gcc did.

> > +       attr.prog_btf_fd = btf_fd;
> > +       attr.prog_type = BPF_TRACE_RAW_TP;
> > +       attr.license = (__u64)"GPL";
> > +       attr.insns = (__u64)&insns;
> > +       attr.insn_cnt = sizeof(insns) / sizeof(*insns);
> > +       attr.log_buf = (__u64)log;
> > +       attr.log_size = sizeof(log);
> > +       attr.log_level = log_level;
> > +       attr.func_info = (__u64)funcs;
> > +       attr.func_info_cnt = sizeof(funcs) / sizeof(*funcs);
> > +       attr.func_info_rec_size = sizeof(*funcs);
> > +       attr.core_relos = (__u64)relos;
> > +       attr.core_relo_cnt = sizeof(relos) / sizeof(*relos);
> > +       attr.core_relo_rec_size = sizeof(*relos);
> 
> I was wondering for a bit why you didn't just use bpf_prog_load(), and
> it seems like it's due to core_relos fields?

Yes, it is in commit message :)

> I don't see why we can't extend the bpf_prog_load() API to allow to
> specify those. (would allow to avoid open-coding this whole bpf_attr
> business, but it's fine as is as well)

Maybe extend API as a followup?
The test won't change much, just options instead of bpf_attr.

[...]
Andrii Nakryiko Aug. 22, 2024, 4:51 p.m. UTC | #3
On Wed, Aug 21, 2024 at 9:39 PM Eduard Zingerman <eddyz87@gmail.com> wrote:
>
> On Wed, 2024-08-21 at 21:29 -0700, Andrii Nakryiko wrote:
>
> [...]
>
> > > +       btf_fd = bpf_btf_load(&raw_btf, sizeof(raw_btf), &opts);
> > > +       saved_errno = errno;
> > > +       if (btf_fd < 0 || env.verbosity > VERBOSE_NORMAL) {
> > > +               printf("-------- BTF load log start --------\n");
> > > +               printf("%s", log);
> > > +               printf("-------- BTF load log end ----------\n");
> > > +       }
> > > +       if (btf_fd < 0) {
> > > +               PRINT_FAIL("bpf_btf_load() failed, errno=%d\n", saved_errno);
> > > +               return;
> > > +       }
> > > +
> > > +       memset(log, 0, sizeof(log));
> >
> > generally speaking there is no need to memset log buffer (maybe just a
> > first byte, to be safe)
>
> Will change.
>
> > on the other hand, just `union bpf_attr attr = {};` is breakage
> > waiting to happen, I'd do memset(0) on that, we did run into problems
> > with that before (I believe it was systemd)
>
> Compilers optimize out 'smth = {}' where 'smth' escapes?
> I mean, I will change it to memset(0), but the fact that you observed
> such behaviour is disturbing beyond limit...

compiler is not obligated to zero out padding in the struct/union, and
kernel is pretty strict about that, that's the issue. memset(0)
guarantees all the bytes are set to zero, not just those that belong
to fields

>
> I already run into gcc vs clang behaviour differences for the first
> iteration of this test where I had:
>
>     union bpf_attr {
>         .prog_type = ...
>     };
>
> clang did not zero out all members of the union, while gcc did.
>
> > > +       attr.prog_btf_fd = btf_fd;
> > > +       attr.prog_type = BPF_TRACE_RAW_TP;
> > > +       attr.license = (__u64)"GPL";
> > > +       attr.insns = (__u64)&insns;
> > > +       attr.insn_cnt = sizeof(insns) / sizeof(*insns);
> > > +       attr.log_buf = (__u64)log;
> > > +       attr.log_size = sizeof(log);
> > > +       attr.log_level = log_level;
> > > +       attr.func_info = (__u64)funcs;
> > > +       attr.func_info_cnt = sizeof(funcs) / sizeof(*funcs);
> > > +       attr.func_info_rec_size = sizeof(*funcs);
> > > +       attr.core_relos = (__u64)relos;
> > > +       attr.core_relo_cnt = sizeof(relos) / sizeof(*relos);
> > > +       attr.core_relo_rec_size = sizeof(*relos);
> >
> > I was wondering for a bit why you didn't just use bpf_prog_load(), and
> > it seems like it's due to core_relos fields?
>
> Yes, it is in commit message :)
>

ain't nobody got time for reading commit messages ;)

> > I don't see why we can't extend the bpf_prog_load() API to allow to
> > specify those. (would allow to avoid open-coding this whole bpf_attr
> > business, but it's fine as is as well)
>
> Maybe extend API as a followup?
> The test won't change much, just options instead of bpf_attr.

yep, follow up is good, thanks

>
> [...]
>
Alexei Starovoitov Aug. 22, 2024, 4:55 p.m. UTC | #4
On Thu, Aug 22, 2024 at 9:51 AM Andrii Nakryiko
<andrii.nakryiko@gmail.com> wrote:
>
> > > I don't see why we can't extend the bpf_prog_load() API to allow to
> > > specify those. (would allow to avoid open-coding this whole bpf_attr
> > > business, but it's fine as is as well)
> >
> > Maybe extend API as a followup?
> > The test won't change much, just options instead of bpf_attr.
>
> yep, follow up is good, thanks

I don't think we want this extension to bpf_prog_load() libbpf api.
This is internal gen_loader use.
Andrii Nakryiko Aug. 22, 2024, 5:27 p.m. UTC | #5
On Thu, Aug 22, 2024 at 9:55 AM Alexei Starovoitov
<alexei.starovoitov@gmail.com> wrote:
>
> On Thu, Aug 22, 2024 at 9:51 AM Andrii Nakryiko
> <andrii.nakryiko@gmail.com> wrote:
> >
> > > > I don't see why we can't extend the bpf_prog_load() API to allow to
> > > > specify those. (would allow to avoid open-coding this whole bpf_attr
> > > > business, but it's fine as is as well)
> > >
> > > Maybe extend API as a followup?
> > > The test won't change much, just options instead of bpf_attr.
> >
> > yep, follow up is good, thanks
>
> I don't think we want this extension to bpf_prog_load() libbpf api.
> This is internal gen_loader use.

bpf_prog_load() is just a wrapper around BPF_PROG_LOAD command of
bpf() syscall, so it feels appropriate to expose all the available
kernel functionality, even if libbpf itself doesn't use some parts of
it. Those core_relos fields are there in bpf_attr and are part of
UAPI, what's wrong with making them available in low-level API?
Alexei Starovoitov Aug. 22, 2024, 5:53 p.m. UTC | #6
On Thu, Aug 22, 2024 at 10:27 AM Andrii Nakryiko
<andrii.nakryiko@gmail.com> wrote:
>
> On Thu, Aug 22, 2024 at 9:55 AM Alexei Starovoitov
> <alexei.starovoitov@gmail.com> wrote:
> >
> > On Thu, Aug 22, 2024 at 9:51 AM Andrii Nakryiko
> > <andrii.nakryiko@gmail.com> wrote:
> > >
> > > > > I don't see why we can't extend the bpf_prog_load() API to allow to
> > > > > specify those. (would allow to avoid open-coding this whole bpf_attr
> > > > > business, but it's fine as is as well)
> > > >
> > > > Maybe extend API as a followup?
> > > > The test won't change much, just options instead of bpf_attr.
> > >
> > > yep, follow up is good, thanks
> >
> > I don't think we want this extension to bpf_prog_load() libbpf api.
> > This is internal gen_loader use.
>
> bpf_prog_load() is just a wrapper around BPF_PROG_LOAD command of
> bpf() syscall, so it feels appropriate to expose all the available
> kernel functionality, even if libbpf itself doesn't use some parts of
> it. Those core_relos fields are there in bpf_attr and are part of
> UAPI, what's wrong with making them available in low-level API?

because it's a maintenance cost for something where
the single user is a selftest.
Hence I wouldn't bother, but I don't insist.
diff mbox series

Patch

diff --git a/tools/testing/selftests/bpf/prog_tests/core_reloc_raw.c b/tools/testing/selftests/bpf/prog_tests/core_reloc_raw.c
new file mode 100644
index 000000000000..1ab3ab305d3b
--- /dev/null
+++ b/tools/testing/selftests/bpf/prog_tests/core_reloc_raw.c
@@ -0,0 +1,124 @@ 
+// SPDX-License-Identifier: GPL-2.0
+
+/* Test cases that can't load programs using libbpf and need direct
+ * BPF syscall access
+ */
+
+#include <sys/syscall.h>
+#include <bpf/libbpf.h>
+#include <bpf/btf.h>
+
+#include "test_progs.h"
+#include "test_btf.h"
+#include "bpf/libbpf_internal.h"
+
+static char log[16 * 1024];
+
+/* Check that verifier rejects BPF program containing relocation
+ * pointing to non-existent BTF type.
+ */
+static void test_bad_local_id(void)
+{
+	struct test_btf {
+		struct btf_header hdr;
+		__u32 types[15];
+		char strings[128];
+	} raw_btf = {
+		.hdr = {
+			.magic = BTF_MAGIC,
+			.version = BTF_VERSION,
+			.hdr_len = sizeof(struct btf_header),
+			.type_off = 0,
+			.type_len = sizeof(raw_btf.types),
+			.str_off = offsetof(struct test_btf, strings) -
+				   offsetof(struct test_btf, types),
+			.str_len = sizeof(raw_btf.strings),
+		},
+		.types = {
+			BTF_PTR_ENC(0),					/* [1] void*  */
+			BTF_TYPE_INT_ENC(1, BTF_INT_SIGNED, 0, 32, 4),	/* [2] int    */
+			BTF_FUNC_PROTO_ENC(2, 1),			/* [3] int (*)(void*) */
+			BTF_FUNC_PROTO_ARG_ENC(8, 1),
+			BTF_FUNC_ENC(8, 3)			/* [4] FUNC 'foo' type_id=2   */
+		},
+		.strings = "\0int\0 0\0foo\0"
+	};
+	__u32 log_level = 1 | 2 | 4;
+	LIBBPF_OPTS(bpf_btf_load_opts, opts,
+		    .log_buf = log,
+		    .log_size = sizeof(log),
+		    .log_level = log_level,
+	);
+	struct bpf_insn insns[] = {
+		BPF_ALU64_IMM(BPF_MOV, BPF_REG_0, 0),
+		BPF_EXIT_INSN(),
+	};
+	struct bpf_func_info funcs[] = {
+		{
+			.insn_off = 0,
+			.type_id = 4,
+		}
+	};
+	struct bpf_core_relo relos[] = {
+		{
+			.insn_off = 0,		/* patch first instruction (r0 = 0) */
+			.type_id = 100500,	/* !!! this type id does not exist */
+			.access_str_off = 6,	/* offset of "0" */
+			.kind = BPF_CORE_TYPE_ID_LOCAL,
+		}
+	};
+	union bpf_attr attr = {};
+	int saved_errno;
+	int prog_fd = -1;
+	int btf_fd = -1;
+
+	btf_fd = bpf_btf_load(&raw_btf, sizeof(raw_btf), &opts);
+	saved_errno = errno;
+	if (btf_fd < 0 || env.verbosity > VERBOSE_NORMAL) {
+		printf("-------- BTF load log start --------\n");
+		printf("%s", log);
+		printf("-------- BTF load log end ----------\n");
+	}
+	if (btf_fd < 0) {
+		PRINT_FAIL("bpf_btf_load() failed, errno=%d\n", saved_errno);
+		return;
+	}
+
+	memset(log, 0, sizeof(log));
+	attr.prog_btf_fd = btf_fd;
+	attr.prog_type = BPF_TRACE_RAW_TP;
+	attr.license = (__u64)"GPL";
+	attr.insns = (__u64)&insns;
+	attr.insn_cnt = sizeof(insns) / sizeof(*insns);
+	attr.log_buf = (__u64)log;
+	attr.log_size = sizeof(log);
+	attr.log_level = log_level;
+	attr.func_info = (__u64)funcs;
+	attr.func_info_cnt = sizeof(funcs) / sizeof(*funcs);
+	attr.func_info_rec_size = sizeof(*funcs);
+	attr.core_relos = (__u64)relos;
+	attr.core_relo_cnt = sizeof(relos) / sizeof(*relos);
+	attr.core_relo_rec_size = sizeof(*relos);
+	prog_fd = sys_bpf_prog_load(&attr, sizeof(attr), 1);
+	saved_errno = errno;
+	if (prog_fd < 0 || env.verbosity > VERBOSE_NORMAL) {
+		printf("-------- program load log start --------\n");
+		printf("%s", log);
+		printf("-------- program load log end ----------\n");
+	}
+	if (prog_fd >= 0) {
+		PRINT_FAIL("sys_bpf_prog_load() expected to fail\n");
+		goto out;
+	}
+	ASSERT_HAS_SUBSTR(log, "relo #0: bad type id 100500", "program load log");
+
+out:
+	close(prog_fd);
+	close(btf_fd);
+}
+
+void test_core_reloc_raw(void)
+{
+	if (test__start_subtest("bad_local_id"))
+		test_bad_local_id();
+}