From patchwork Sat Apr 16 06:34:29 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Joanne Koong X-Patchwork-Id: 12815695 X-Patchwork-Delegate: bpf@iogearbox.net Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id A5E76C433FE for ; Sat, 16 Apr 2022 06:38:34 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S229659AbiDPGlD (ORCPT ); Sat, 16 Apr 2022 02:41:03 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:53090 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S229775AbiDPGlC (ORCPT ); Sat, 16 Apr 2022 02:41:02 -0400 Received: from 66-220-155-178.mail-mxout.facebook.com (66-220-155-178.mail-mxout.facebook.com [66.220.155.178]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 75646100E0F for ; Fri, 15 Apr 2022 23:38:28 -0700 (PDT) Received: by devbig010.atn6.facebook.com (Postfix, from userid 115148) id 48B05B1DE622; Fri, 15 Apr 2022 23:35:40 -0700 (PDT) From: Joanne Koong To: bpf@vger.kernel.org Cc: andrii@kernel.org, memxor@gmail.com, ast@kernel.org, daniel@iogearbox.net, toke@redhat.com, Joanne Koong Subject: [PATCH bpf-next v2 7/7] bpf: Dynptr tests Date: Fri, 15 Apr 2022 23:34:29 -0700 Message-Id: <20220416063429.3314021-8-joannelkoong@gmail.com> X-Mailer: git-send-email 2.30.2 In-Reply-To: <20220416063429.3314021-1-joannelkoong@gmail.com> References: <20220416063429.3314021-1-joannelkoong@gmail.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: bpf@vger.kernel.org X-Patchwork-Delegate: bpf@iogearbox.net This patch adds tests for dynptrs. These include scenarios that the verifier needs to reject, as well as some successful use cases of dynptrs that should pass. Some of the failure scenarios include checking against invalid bpf_dynptr_puts, invalid writes, invalid reads, and invalid ringbuf API usages. Signed-off-by: Joanne Koong --- .../testing/selftests/bpf/prog_tests/dynptr.c | 138 ++++ .../testing/selftests/bpf/progs/dynptr_fail.c | 643 ++++++++++++++++++ .../selftests/bpf/progs/dynptr_success.c | 217 ++++++ 3 files changed, 998 insertions(+) create mode 100644 tools/testing/selftests/bpf/prog_tests/dynptr.c create mode 100644 tools/testing/selftests/bpf/progs/dynptr_fail.c create mode 100644 tools/testing/selftests/bpf/progs/dynptr_success.c diff --git a/tools/testing/selftests/bpf/prog_tests/dynptr.c b/tools/testing/selftests/bpf/prog_tests/dynptr.c new file mode 100644 index 000000000000..5bf161e1838c --- /dev/null +++ b/tools/testing/selftests/bpf/prog_tests/dynptr.c @@ -0,0 +1,138 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (c) 2022 Facebook */ + +#include +#include "dynptr_fail.skel.h" +#include "dynptr_success.skel.h" + +size_t log_buf_sz = 1048576; /* 1 MB */ +static char obj_log_buf[1048576]; + +struct { + const char *prog_name; + const char *expected_err_msg; +} dynptr_tests[] = { + /* failure cases */ + {"missing_put", "spi=0 is an unreleased dynptr"}, + {"missing_put_callback", "spi=0 is an unreleased dynptr"}, + {"put_nonalloc", "arg 1 is an unacquired reference"}, + {"put_data_slice", "type=alloc_mem expected=fp"}, + {"put_uninit_dynptr", "arg 1 is an unacquired reference"}, + {"use_after_put", "Expected an initialized dynptr as arg #3"}, + {"alloc_twice", "Arg #3 dynptr has to be an uninitialized dynptr"}, + {"add_dynptr_to_map1", "invalid indirect read from stack"}, + {"add_dynptr_to_map2", "invalid indirect read from stack"}, + {"ringbuf_invalid_access", "invalid mem access 'scalar'"}, + {"ringbuf_invalid_api", + "func bpf_ringbuf_submit#132 reference has not been acquired before"}, + {"ringbuf_out_of_bounds", "value is outside of the allowed memory range"}, + {"data_slice_out_of_bounds", "value is outside of the allowed memory range"}, + {"data_slice_use_after_put", "invalid mem access 'scalar'"}, + {"invalid_helper1", "invalid indirect read from stack"}, + {"invalid_helper2", "Expected an initialized dynptr as arg #3"}, + {"invalid_write1", "direct write into dynptr is not permitted"}, + {"invalid_write2", "direct write into dynptr is not permitted"}, + {"invalid_write3", "direct write into dynptr is not permitted"}, + {"invalid_write4", "direct write into dynptr is not permitted"}, + {"invalid_read1", "invalid read from stack"}, + {"invalid_read2", "cannot pass in non-zero dynptr offset"}, + {"invalid_read3", "invalid read from stack"}, + {"invalid_offset", "invalid write to stack"}, + {"global", "R3 type=map_value expected=fp"}, + {"put_twice", "arg 1 is an unacquired reference"}, + {"put_twice_callback", "arg 1 is an unacquired reference"}, + {"invalid_nested_dynptrs1", "direct write into dynptr is not permitted"}, + {"invalid_nested_dynptrs2", "Arg #3 cannot be a memory reference for another dynptr"}, + {"invalid_ref_mem1", "Arg #1 cannot be a referenced object"}, + {"invalid_ref_mem2", "Arg #1 cannot be a referenced object"}, + {"zero_slice_access", "invalid access to memory, mem_size=0 off=0 size=1"}, + /* success cases */ + {"test_basic", NULL}, + {"test_data_slice", NULL}, + {"test_ringbuf", NULL}, + {"test_alloc_zero_bytes", NULL}, +}; + +static void verify_fail(const char *prog_name, const char *expected_err_msg) +{ + LIBBPF_OPTS(bpf_object_open_opts, opts); + struct bpf_program *prog; + struct dynptr_fail *skel; + int err; + + opts.kernel_log_buf = obj_log_buf; + opts.kernel_log_size = log_buf_sz; + opts.kernel_log_level = 1; + + skel = dynptr_fail__open_opts(&opts); + if (!ASSERT_OK_PTR(skel, "dynptr_fail__open_opts")) + return; + + bpf_object__for_each_program(prog, skel->obj) + bpf_program__set_autoload(prog, false); + + prog = bpf_object__find_program_by_name(skel->obj, prog_name); + if (!ASSERT_OK_PTR(prog, "bpf_object__find_program_by_name")) + return; + + bpf_program__set_autoload(prog, true); + + err = dynptr_fail__load(skel); + + ASSERT_ERR(err, "dynptr_fail__load"); + + if (!ASSERT_OK_PTR(strstr(obj_log_buf, expected_err_msg), "expected_err_msg")) { + fprintf(stderr, "Expected err_msg: %s\n", expected_err_msg); + fprintf(stderr, "Verifier output: %s\n", obj_log_buf); + } + + dynptr_fail__destroy(skel); +} + +static void verify_success(const char *prog_name) +{ + struct dynptr_success *skel; + struct bpf_program *prog; + struct bpf_link *link; + + skel = dynptr_success__open(); + if (!ASSERT_OK_PTR(skel, "dynptr_success__open")) + return; + + skel->bss->pid = getpid(); + + dynptr_success__load(skel); + if (!ASSERT_OK_PTR(skel, "dynptr_success__load")) + return; + + prog = bpf_object__find_program_by_name(skel->obj, prog_name); + if (!ASSERT_OK_PTR(prog, "bpf_object__find_program_by_name")) + return; + + link = bpf_program__attach(prog); + if (!ASSERT_OK_PTR(link, "bpf_program__attach")) + return; + + usleep(1); + + ASSERT_EQ(skel->bss->err, 0, "err"); + + bpf_link__destroy(link); + + dynptr_success__destroy(skel); +} + +void test_dynptr(void) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(dynptr_tests); i++) { + if (!test__start_subtest(dynptr_tests[i].prog_name)) + continue; + + if (dynptr_tests[i].expected_err_msg) + verify_fail(dynptr_tests[i].prog_name, dynptr_tests[i].expected_err_msg); + else + verify_success(dynptr_tests[i].prog_name); + } +} diff --git a/tools/testing/selftests/bpf/progs/dynptr_fail.c b/tools/testing/selftests/bpf/progs/dynptr_fail.c new file mode 100644 index 000000000000..215069cb7e0d --- /dev/null +++ b/tools/testing/selftests/bpf/progs/dynptr_fail.c @@ -0,0 +1,643 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (c) 2022 Facebook */ + +#include +#include +#include +#include "bpf_misc.h" + +char _license[] SEC("license") = "GPL"; + +struct { + __uint(type, BPF_MAP_TYPE_ARRAY); + __uint(max_entries, 1); + __type(key, __u32); + __type(value, struct bpf_dynptr); +} array_map SEC(".maps"); + +struct sample { + int pid; + long value; + char comm[16]; +}; + +struct { + __uint(type, BPF_MAP_TYPE_RINGBUF); + __uint(max_entries, 1 << 12); +} ringbuf SEC(".maps"); + +int err = 0; +int val; + +/* Every bpf_dynptr_alloc call must have a corresponding bpf_dynptr_put call */ +SEC("raw_tp/sys_nanosleep") +int missing_put(void *ctx) +{ + struct bpf_dynptr mem; + + bpf_dynptr_alloc(8, 0, &mem); + + /* missing a call to bpf_dynptr_put(&mem) */ + + return 0; +} + +/* A non-alloc-ed dynptr can't be used by bpf_dynptr_put */ +SEC("raw_tp/sys_nanosleep") +int put_nonalloc(void *ctx) +{ + struct bpf_dynptr ptr; + __u32 x = 0; + + bpf_dynptr_from_mem(&x, sizeof(x), 0, &ptr); + + /* this should fail */ + bpf_dynptr_put(&ptr); + + return 0; +} + +/* A data slice from a dynptr can't be used by bpf_dynptr_put */ +SEC("raw_tp/sys_nanosleep") +int put_data_slice(void *ctx) +{ + struct bpf_dynptr ptr; + void *data; + + bpf_dynptr_alloc(8, 0, &ptr); + + data = bpf_dynptr_data(&ptr, 0, 8); + if (!data) + goto done; + + /* this should fail */ + bpf_dynptr_put(data); + +done: + bpf_dynptr_put(&ptr); + return 0; +} + +/* Can't call bpf_dynptr_put on a non-initialized dynptr */ +SEC("raw_tp/sys_nanosleep") +int put_uninit_dynptr(void *ctx) +{ + struct bpf_dynptr ptr; + + /* this should fail */ + bpf_dynptr_put(&ptr); + + return 0; +} + +/* A dynptr can't be used after bpf_dynptr_put has been called on it */ +SEC("raw_tp/sys_nanosleep") +int use_after_put(void *ctx) +{ + struct bpf_dynptr ptr = {}; + char read_data[64] = {}; + + bpf_dynptr_alloc(8, 0, &ptr); + + bpf_dynptr_read(read_data, sizeof(read_data), &ptr, 0); + + bpf_dynptr_put(&ptr); + + /* this should fail */ + bpf_dynptr_read(read_data, sizeof(read_data), &ptr, 0); + + return 0; +} + +/* + * Can't bpf_dynptr_alloc an existing allocated bpf_dynptr that bpf_dynptr_put + * hasn't been called on yet + */ +SEC("raw_tp/sys_nanosleep") +int alloc_twice(void *ctx) +{ + struct bpf_dynptr ptr; + + bpf_dynptr_alloc(8, 0, &ptr); + + /* this should fail */ + bpf_dynptr_alloc(2, 0, &ptr); + + bpf_dynptr_put(&ptr); + + return 0; +} + +/* + * Can't access a ring buffer record after submit or discard has been called + * on the dynptr + */ +SEC("raw_tp/sys_nanosleep") +int ringbuf_invalid_access(void *ctx) +{ + struct bpf_dynptr ptr; + struct sample *sample; + + err = bpf_ringbuf_reserve_dynptr(&ringbuf, sizeof(*sample), 0, &ptr); + sample = bpf_dynptr_data(&ptr, 0, sizeof(*sample)); + if (!sample) + goto done; + + sample->pid = 123; + + bpf_ringbuf_submit_dynptr(&ptr, 0); + + /* this should fail */ + err = sample->pid; + + return 0; + +done: + bpf_ringbuf_discard_dynptr(&ptr, 0); + return 0; +} + +/* Can't call non-dynptr ringbuf APIs on a dynptr ringbuf sample */ +SEC("raw_tp/sys_nanosleep") +int ringbuf_invalid_api(void *ctx) +{ + struct bpf_dynptr ptr; + struct sample *sample; + + err = bpf_ringbuf_reserve_dynptr(&ringbuf, sizeof(*sample), 0, &ptr); + sample = bpf_dynptr_data(&ptr, 0, sizeof(*sample)); + if (!sample) + goto done; + + sample->pid = 123; + + /* invalid API use. need to use dynptr API to submit/discard */ + bpf_ringbuf_submit(sample, 0); + +done: + bpf_ringbuf_discard_dynptr(&ptr, 0); + return 0; +} + +/* Can't access memory outside a ringbuf record range */ +SEC("raw_tp/sys_nanosleep") +int ringbuf_out_of_bounds(void *ctx) +{ + struct bpf_dynptr ptr; + struct sample *sample; + + err = bpf_ringbuf_reserve_dynptr(&ringbuf, sizeof(*sample), 0, &ptr); + sample = bpf_dynptr_data(&ptr, 0, sizeof(*sample)); + if (!sample) + goto done; + + /* Can't access beyond sample range */ + *(__u8 *)((void *)sample + sizeof(*sample)) = 123; + + bpf_ringbuf_submit_dynptr(&ptr, 0); + + return 0; + +done: + bpf_ringbuf_discard_dynptr(&ptr, 0); + return 0; +} + +/* Can't add a dynptr to a map */ +SEC("raw_tp/sys_nanosleep") +int add_dynptr_to_map1(void *ctx) +{ + struct bpf_dynptr ptr = {}; + char buf[64] = {}; + int key = 0; + + err = bpf_dynptr_from_mem(buf, sizeof(buf), 0, &ptr); + + /* this should fail */ + bpf_map_update_elem(&array_map, &key, &ptr, 0); + + return 0; +} + +/* Can't add a struct with an embedded dynptr to a map */ +SEC("raw_tp/sys_nanosleep") +int add_dynptr_to_map2(void *ctx) +{ + struct info { + int x; + struct bpf_dynptr ptr; + }; + struct info x; + int key = 0; + + bpf_dynptr_alloc(8, 0, &x.ptr); + + /* this should fail */ + bpf_map_update_elem(&array_map, &key, &x, 0); + + return 0; +} + +/* Can't pass in a dynptr as an arg to a helper function that doesn't take in a + * dynptr argument + */ +SEC("raw_tp/sys_nanosleep") +int invalid_helper1(void *ctx) +{ + struct bpf_dynptr ptr = {}; + + bpf_dynptr_alloc(8, 0, &ptr); + + /* this should fail */ + bpf_strncmp((const char *)&ptr, sizeof(ptr), "hello!"); + + bpf_dynptr_put(&ptr); + + return 0; +} + +/* A dynptr can't be passed into a helper function at a non-zero offset */ +SEC("raw_tp/sys_nanosleep") +int invalid_helper2(void *ctx) +{ + struct bpf_dynptr ptr = {}; + char read_data[64] = {}; + __u64 x = 0; + + bpf_dynptr_from_mem(&x, sizeof(x), 0, &ptr); + + /* this should fail */ + bpf_dynptr_read(read_data, sizeof(read_data), (void *)&ptr + 8, 0); + + return 0; +} + +/* A data slice can't be accessed out of bounds */ +SEC("raw_tp/sys_nanosleep") +int data_slice_out_of_bounds(void *ctx) +{ + struct bpf_dynptr ptr = {}; + void *data; + + bpf_dynptr_alloc(8, 0, &ptr); + + data = bpf_dynptr_data(&ptr, 0, 8); + if (!data) + goto done; + + /* can't index out of bounds of the data slice */ + val = *((char *)data + 8); + +done: + bpf_dynptr_put(&ptr); + return 0; +} + +/* A data slice can't be used after bpf_dynptr_put is called */ +SEC("raw_tp/sys_nanosleep") +int data_slice_use_after_put(void *ctx) +{ + struct bpf_dynptr ptr = {}; + void *data; + + bpf_dynptr_alloc(8, 0, &ptr); + + data = bpf_dynptr_data(&ptr, 0, 8); + if (!data) + goto done; + + bpf_dynptr_put(&ptr); + + /* this should fail */ + val = *(__u8 *)data; + +done: + bpf_dynptr_put(&ptr); + return 0; +} + +/* + * A bpf_dynptr can't be written directly to by the bpf program, + * only through dynptr helper functions + */ +SEC("raw_tp/sys_nanosleep") +int invalid_write1(void *ctx) +{ + struct bpf_dynptr ptr = {}; + __u8 x = 0; + + bpf_dynptr_alloc(8, 0, &ptr); + + /* this should fail */ + memcpy(&ptr, &x, sizeof(x)); + + bpf_dynptr_put(&ptr); + + return 0; +} + +/* + * A bpf_dynptr at a non-zero offset can't be written directly to + * by the bpf program, only through dynptr helper functions + */ +SEC("raw_tp/sys_nanosleep") +int invalid_write2(void *ctx) +{ + struct bpf_dynptr ptr = {}; + char read_data[64] = {}; + __u8 x = 0, y = 0; + + bpf_dynptr_from_mem(&x, sizeof(x), 0, &ptr); + + /* this should fail */ + memcpy((void *)&ptr, &y, sizeof(y)); + + bpf_dynptr_read(read_data, sizeof(read_data), &ptr, 0); + + return 0; +} + +/* A non-const write into a dynptr is not permitted */ +SEC("raw_tp/sys_nanosleep") +int invalid_write3(void *ctx) +{ + struct bpf_dynptr ptr = {}; + char stack_buf[16]; + unsigned long len; + __u8 x = 0; + + bpf_dynptr_alloc(8, 0, &ptr); + + memcpy(stack_buf, &val, sizeof(val)); + len = stack_buf[0] & 0xf; + + /* this should fail */ + memcpy((void *)&ptr + len, &x, sizeof(x)); + + bpf_dynptr_put(&ptr); + + return 0; +} + +static int invalid_write4_callback(__u32 index, void *data) +{ + /* this should fail */ + *(__u32 *)data = 123; + + bpf_dynptr_put(data); + + return 0; +} + +/* An invalid write can't occur in a callback function */ +SEC("raw_tp/sys_nanosleep") +int invalid_write4(void *ctx) +{ + struct bpf_dynptr ptr; + __u64 x = 0; + + bpf_dynptr_from_mem(&x, sizeof(x), 0, &ptr); + + bpf_loop(10, invalid_write4_callback, &ptr, 0); + + return 0; +} + +/* A globally-defined bpf_dynptr can't be used (it must reside as a stack frame) */ +struct bpf_dynptr global_dynptr; +SEC("raw_tp/sys_nanosleep") +int global(void *ctx) +{ + /* this should fail */ + bpf_dynptr_alloc(4, 0, &global_dynptr); + + bpf_dynptr_put(&global_dynptr); + + return 0; +} + +/* A direct read should fail */ +SEC("raw_tp/sys_nanosleep") +int invalid_read1(void *ctx) +{ + struct bpf_dynptr ptr = {}; + __u32 x = 2; + + bpf_dynptr_from_mem(&x, sizeof(x), 0, &ptr); + + /* this should fail */ + val = *(int *)&ptr; + + return 0; +} + +/* A direct read at an offset should fail */ +SEC("raw_tp/sys_nanosleep") +int invalid_read2(void *ctx) +{ + struct bpf_dynptr ptr = {}; + char read_data[64] = {}; + __u64 x = 0; + + bpf_dynptr_from_mem(&x, sizeof(x), 0, &ptr); + + /* this should fail */ + bpf_dynptr_read(read_data, sizeof(read_data), (void *)&ptr + 1, 0); + + return 0; +} + +/* A direct read at an offset into the lower stack slot should fail */ +SEC("raw_tp/sys_nanosleep") +int invalid_read3(void *ctx) +{ + struct bpf_dynptr ptr = {}; + struct bpf_dynptr ptr2 = {}; + __u32 x = 2; + + bpf_dynptr_from_mem(&x, sizeof(x), 0, &ptr); + bpf_dynptr_from_mem(&x, sizeof(x), 0, &ptr2); + + /* this should fail */ + memcpy(&val, (void *)&ptr + 8, sizeof(val)); + + return 0; +} + +/* Calling bpf_dynptr_from_mem on an offset should fail */ +SEC("raw_tp/sys_nanosleep") +int invalid_offset(void *ctx) +{ + struct bpf_dynptr ptr = {}; + __u64 x = 0; + + /* this should fail */ + bpf_dynptr_from_mem(&x, sizeof(x), 0, &ptr + 1); + + return 0; +} + +/* Can't call bpf_dynptr_put twice */ +SEC("raw_tp/sys_nanosleep") +int put_twice(void *ctx) +{ + struct bpf_dynptr ptr; + + bpf_dynptr_alloc(8, 0, &ptr); + + bpf_dynptr_put(&ptr); + + /* this second put should fail */ + bpf_dynptr_put(&ptr); + + return 0; +} + +static int put_twice_callback_fn(__u32 index, void *data) +{ + /* this should fail */ + bpf_dynptr_put(data); + val = index; + return 0; +} + +/* Test that calling bpf_dynptr_put twice, where the 2nd put happens within a + * calback function, fails + */ +SEC("raw_tp/sys_nanosleep") +int put_twice_callback(void *ctx) +{ + struct bpf_dynptr ptr; + + bpf_dynptr_alloc(8, 0, &ptr); + + bpf_dynptr_put(&ptr); + + bpf_loop(10, put_twice_callback_fn, &ptr, 0); + + return 0; +} + +static int missing_put_callback_fn(__u32 index, void *data) +{ + struct bpf_dynptr ptr; + + bpf_dynptr_alloc(8, 0, &ptr); + + val = index; + + /* missing bpf_dynptr_put(&ptr) */ + + return 0; +} + +/* Any dynptr initialized within a callback must have bpf_dynptr_put called */ +SEC("raw_tp/sys_nanosleep") +int missing_put_callback(void *ctx) +{ + bpf_loop(10, missing_put_callback_fn, NULL, 0); + return 0; +} + +/* We can't have nested dynptrs or else the dynptr stack data can be written into */ +SEC("raw_tp/sys_nanosleep") +int invalid_nested_dynptrs1(void *ctx) +{ + struct bpf_dynptr local; + struct bpf_dynptr ptr; + char write_data[64] = {}; + + bpf_dynptr_alloc(16, 0, &ptr); + + /* this should fail */ + bpf_dynptr_from_mem(&ptr, sizeof(ptr), 0, &local); + + bpf_dynptr_write(&local, 0, write_data, sizeof(ptr)); + + bpf_dynptr_put(&ptr); + + return 0; +} + +SEC("raw_tp/sys_nanosleep") +int invalid_nested_dynptrs2(void *ctx) +{ + struct bpf_dynptr local; + struct bpf_dynptr ptr; + char write_data[64] = {}; + + bpf_dynptr_from_mem(&ptr, sizeof(ptr), 0, &local); + + /* this should fail */ + bpf_dynptr_alloc(16, 0, &ptr); + + bpf_dynptr_write(&local, 0, write_data, sizeof(ptr)); + + bpf_dynptr_put(&ptr); + + return 0; +} + +/* Can't have local dynptr to referenced memory */ +SEC("raw_tp/sys_nanosleep") +int invalid_ref_mem1(void *ctx) +{ + struct bpf_dynptr ptr; + struct bpf_dynptr local; + void *data; + + bpf_dynptr_alloc(16, 0, &ptr); + data = bpf_dynptr_data(&ptr, 0, 8); + if (!data) + goto done; + + /* this should fail */ + bpf_dynptr_from_mem(data, 1, 0, &local); + +done: + bpf_dynptr_put(&ptr); + return 0; +} + +SEC("raw_tp/sys_nanosleep") +int invalid_ref_mem2(void *ctx) +{ + struct bpf_dynptr ptr; + struct bpf_dynptr local; + struct sample *sample; + + err = bpf_ringbuf_reserve_dynptr(&ringbuf, sizeof(*sample), 0, &ptr); + sample = bpf_dynptr_data(&ptr, 0, sizeof(*sample)); + if (!sample) + goto done; + + /* this should fail */ + bpf_dynptr_from_mem(sample, sizeof(*sample), 0, &local); + +done: + bpf_ringbuf_discard_dynptr(&ptr, 0); + return 0; +} + +/* Can't access memory in a zero-slice */ +SEC("raw_tp/sys_nanosleep") +int zero_slice_access(void *ctx) +{ + struct bpf_dynptr ptr; + void *data; + + bpf_dynptr_alloc(0, 0, &ptr); + + data = bpf_dynptr_data(&ptr, 0, 0); + if (!data) + goto done; + + /* this should fail */ + *(__u8 *)data = 23; + + val = *(__u8 *)data; + +done: + bpf_dynptr_put(&ptr); + return 0; +} diff --git a/tools/testing/selftests/bpf/progs/dynptr_success.c b/tools/testing/selftests/bpf/progs/dynptr_success.c new file mode 100644 index 000000000000..e23ece559f56 --- /dev/null +++ b/tools/testing/selftests/bpf/progs/dynptr_success.c @@ -0,0 +1,217 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (c) 2022 Facebook */ + +#include +#include +#include +#include "bpf_misc.h" +#include "errno.h" + +char _license[] SEC("license") = "GPL"; + +int pid = 0; +int err = 0; +int val; + +struct sample { + int pid; + int seq; + long value; + char comm[16]; +}; + +struct { + __uint(type, BPF_MAP_TYPE_RINGBUF); + __uint(max_entries, 1 << 12); +} ringbuf SEC(".maps"); + +SEC("tp/syscalls/sys_enter_nanosleep") +int test_basic(void *ctx) +{ + char write_data[64] = "hello there, world!!"; + char read_data[64] = {}, buf[64] = {}; + struct bpf_dynptr ptr = {}; + int i; + + if (bpf_get_current_pid_tgid() >> 32 != pid) + return 0; + + err = bpf_dynptr_from_mem(buf, sizeof(buf), 0, &ptr); + if (err) + return 0; + + /* Write data into the dynptr */ + err = bpf_dynptr_write(&ptr, 0, write_data, sizeof(write_data)); + if (err) + return 0; + + /* Read the data that was written into the dynptr */ + err = bpf_dynptr_read(read_data, sizeof(read_data), &ptr, 0); + if (err) + return 0; + + /* Ensure the data we read matches the data we wrote */ + for (i = 0; i < sizeof(read_data); i++) { + if (read_data[i] != write_data[i]) { + err = 1; + return 0; + } + } + + return 0; +} + +SEC("tp/syscalls/sys_enter_nanosleep") +int test_data_slice(void *ctx) +{ + struct bpf_dynptr ptr; + __u32 alloc_size = 16; + void *data; + + if (bpf_get_current_pid_tgid() >> 32 != pid) + return 0; + + /* test passing in an invalid flag */ + err = bpf_dynptr_alloc(alloc_size, 1, &ptr); + if (err != -EINVAL) { + err = 1; + goto done; + } + bpf_dynptr_put(&ptr); + + err = bpf_dynptr_alloc(alloc_size, 0, &ptr); + if (err) + goto done; + + /* Try getting a data slice that is out of range */ + data = bpf_dynptr_data(&ptr, alloc_size + 1, 1); + if (data) { + err = 2; + goto done; + } + + /* Try getting more bytes than available */ + data = bpf_dynptr_data(&ptr, 0, alloc_size + 1); + if (data) { + err = 3; + goto done; + } + + data = bpf_dynptr_data(&ptr, 0, sizeof(int)); + if (!data) { + err = 4; + goto done; + } + + *(__u32 *)data = 999; + + err = bpf_probe_read_kernel(&val, sizeof(val), data); + if (err) + goto done; + + if (val != *(int *)data) + err = 5; + +done: + bpf_dynptr_put(&ptr); + return 0; +} + +static int ringbuf_callback(__u32 index, void *data) +{ + struct sample *sample; + + struct bpf_dynptr *ptr = (struct bpf_dynptr *)data; + + sample = bpf_dynptr_data(ptr, 0, sizeof(*sample)); + if (!sample) { + err = 2; + return 0; + } + + sample->pid += val; + + return 0; +} + +SEC("tp/syscalls/sys_enter_nanosleep") +int test_ringbuf(void *ctx) +{ + struct bpf_dynptr ptr; + struct sample *sample; + + if (bpf_get_current_pid_tgid() >> 32 != pid) + return 0; + + val = 100; + + /* check that you can reserve a dynamic size reservation */ + err = bpf_ringbuf_reserve_dynptr(&ringbuf, val, 0, &ptr); + if (err) + goto done; + + sample = bpf_dynptr_data(&ptr, 0, sizeof(*sample)); + if (!sample) { + err = 1; + goto done; + } + + sample->pid = 123; + + /* Can pass dynptr to callback functions */ + bpf_loop(10, ringbuf_callback, &ptr, 0); + + bpf_ringbuf_submit_dynptr(&ptr, 0); + + return 0; + +done: + bpf_ringbuf_discard_dynptr(&ptr, 0); + return 0; +} + +SEC("tp/syscalls/sys_enter_nanosleep") +int test_alloc_zero_bytes(void *ctx) +{ + struct bpf_dynptr ptr; + void *data; + __u8 x = 0; + + if (bpf_get_current_pid_tgid() >> 32 != pid) + return 0; + + err = bpf_dynptr_alloc(0, 0, &ptr); + if (err) + goto done; + + err = bpf_dynptr_write(&ptr, 0, &x, sizeof(x)); + if (err != -EINVAL) { + err = 1; + goto done; + } + + err = bpf_dynptr_read(&x, sizeof(x), &ptr, 0); + if (err != -EINVAL) { + err = 2; + goto done; + } + + /* try to access memory we don't have access to */ + data = bpf_dynptr_data(&ptr, 0, 1); + if (data) { + err = 3; + goto done; + } + + data = bpf_dynptr_data(&ptr, 0, 0); + if (!data) { + err = 4; + goto done; + } + + err = 0; + +done: + bpf_dynptr_put(&ptr); + return 0; +}