diff mbox series

[bpf-next,v1,7/7] bpf: Dynptr tests

Message ID 20220402015826.3941317-8-joannekoong@fb.com (mailing list archive)
State Changes Requested
Delegated to: BPF
Headers show
Series Dynamic pointers | expand

Checks

Context Check Description
bpf/vmtest-bpf-next-PR fail PR summary
netdev/tree_selection success Clearly marked for bpf-next, async
netdev/fixes_present success Fixes tag not required for -next series
netdev/subject_prefix success Link
netdev/cover_letter success Series has a cover letter
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: 0 this patch: 0
netdev/cc_maintainers warning 8 maintainers not CCed: songliubraving@fb.com shuah@kernel.org netdev@vger.kernel.org kafai@fb.com linux-kselftest@vger.kernel.org yhs@fb.com john.fastabend@gmail.com kpsingh@kernel.org
netdev/build_clang success Errors and warnings before: 0 this patch: 0
netdev/module_param success Was 0 now: 0
netdev/verify_signedoff success Signed-off-by tag matches author and committer
netdev/verify_fixes success No Fixes tag
netdev/build_allmodconfig_warn success Errors and warnings before: 0 this patch: 0
netdev/checkpatch warning WARNING: added, moved or deleted file(s), does MAINTAINERS need updating? WARNING: line length of 83 exceeds 80 columns WARNING: line length of 85 exceeds 80 columns WARNING: line length of 94 exceeds 80 columns
netdev/kdoc success Errors and warnings before: 0 this patch: 0
netdev/source_inline success Was 0 now: 0

Commit Message

Joanne Koong April 2, 2022, 1:58 a.m. UTC
From: Joanne Koong <joannelkoong@gmail.com>

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_frees,
invalid writes, invalid reads, and invalid ringbuf API usages.

Signed-off-by: Joanne Koong <joannelkoong@gmail.com>
---
 .../testing/selftests/bpf/prog_tests/dynptr.c | 303 ++++++++++
 .../testing/selftests/bpf/progs/dynptr_fail.c | 527 ++++++++++++++++++
 .../selftests/bpf/progs/dynptr_success.c      | 147 +++++
 3 files changed, 977 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

Comments

Andrii Nakryiko April 6, 2022, 11:11 p.m. UTC | #1
On Fri, Apr 1, 2022 at 7:00 PM Joanne Koong <joannekoong@fb.com> wrote:
>
> From: Joanne Koong <joannelkoong@gmail.com>
>
> 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_frees,
> invalid writes, invalid reads, and invalid ringbuf API usages.
>
> Signed-off-by: Joanne Koong <joannelkoong@gmail.com>
> ---

Great set of tests! Hard to keep reading 500+ lines of failing use
cases, but seems like a lot of interesting corner cases are handled!
Great job!

>  .../testing/selftests/bpf/prog_tests/dynptr.c | 303 ++++++++++
>  .../testing/selftests/bpf/progs/dynptr_fail.c | 527 ++++++++++++++++++
>  .../selftests/bpf/progs/dynptr_success.c      | 147 +++++
>  3 files changed, 977 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..7107ebee3427
> --- /dev/null
> +++ b/tools/testing/selftests/bpf/prog_tests/dynptr.c
> @@ -0,0 +1,303 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/* Copyright (c) 2022 Facebook */
> +
> +#include <test_progs.h>
> +#include "dynptr_fail.skel.h"
> +#include "dynptr_success.skel.h"
> +
> +size_t log_buf_sz = 1024 * 1024;
> +
> +enum fail_case {
> +       MISSING_FREE,
> +       MISSING_FREE_CALLBACK,
> +       INVALID_FREE1,
> +       INVALID_FREE2,
> +       USE_AFTER_FREE,
> +       MALLOC_TWICE,
> +       INVALID_MAP_CALL1,
> +       INVALID_MAP_CALL2,
> +       RINGBUF_INVALID_ACCESS,
> +       RINGBUF_INVALID_API,
> +       RINGBUF_OUT_OF_BOUNDS,
> +       DATA_SLICE_OUT_OF_BOUNDS,
> +       DATA_SLICE_USE_AFTER_FREE,
> +       INVALID_HELPER1,
> +       INVALID_HELPER2,
> +       INVALID_WRITE1,
> +       INVALID_WRITE2,
> +       INVALID_WRITE3,
> +       INVALID_WRITE4,
> +       INVALID_READ1,
> +       INVALID_READ2,
> +       INVALID_READ3,
> +       INVALID_OFFSET,
> +       GLOBAL,
> +       FREE_TWICE,
> +       FREE_TWICE_CALLBACK,
> +};

it might make sense to just pass the program name as a string instead,
just like expected error message. This will allow more table-like
subtest specification (I'll expand below)

> +
> +static void verify_fail(enum fail_case fail, char *obj_log_buf,  char *err_msg)

nit: extra space

> +{
> +       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;

see below, this could easily be just a static array variable, no need
to pass it in

> +       opts.kernel_log_level = 1;
> +
> +       skel = dynptr_fail__open_opts(&opts);
> +       if (!ASSERT_OK_PTR(skel, "skel_open"))
> +               return;
> +
> +       bpf_object__for_each_program(prog, skel->obj)
> +               bpf_program__set_autoload(prog, false);
> +
> +       /* these programs should all be rejected by the verifier */
> +       switch (fail) {
> +       case MISSING_FREE:
> +               prog = skel->progs.missing_free;
> +               break;
> +       case MISSING_FREE_CALLBACK:
> +               prog = skel->progs.missing_free_callback;
> +               break;

[...]

> +               break;
> +       case GLOBAL:
> +               prog = skel->progs.global;
> +               break;
> +       case FREE_TWICE:
> +               prog = skel->progs.free_twice;
> +               break;
> +       case FREE_TWICE_CALLBACK:
> +               prog = skel->progs.free_twice_callback;
> +               break;
> +       default:
> +               fprintf(stderr, "unknown fail_case\n");
> +               return;
> +       }

so instead of maintaining this enum definition and corresponding
mapping to prog, you can just specify program name as a string and use
bpf_object__find_program_by_name(). The only downside is that if you
make a typo in program name or it is renamed, you'll catch it at
runtime not at compilation time. But I think it's acceptable tradeoff.

> +
> +       bpf_program__set_autoload(prog, true);
> +
> +       err = dynptr_fail__load(skel);
> +
> +       ASSERT_OK_PTR(strstr(obj_log_buf, err_msg), "err_msg not found");

let's also print out the full log if something goes wrong. It will be
hard to debug when something (even the message itself) changes on
verifier side.

> +
> +       ASSERT_ERR(err, "unexpected load success");

nit: move this before log_buf check? seems like it's logically first
check you need to do

> +
> +       dynptr_fail__destroy(skel);
> +}
> +
> +static void run_prog(struct dynptr_success *skel, struct bpf_program *prog)
> +{
> +       struct bpf_link *link;
> +
> +       link = bpf_program__attach(prog);
> +       if (!ASSERT_OK_PTR(link, "bpf program attach"))

or ASSERT_xxx() macros this second string argument is something like
entity/variable name, it's not a message. See how ASSERT_xxx() uses it
internally. So keeping it short and identifier-like makes it easier to
follow actual failure messages.

> +               return;
> +
> +       usleep(1);
> +
> +       ASSERT_EQ(skel->bss->err, 0, "err");
> +
> +       bpf_link__destroy(link);
> +}
> +
> +static void verify_success(void)
> +{
> +       struct dynptr_success *skel;
> +
> +       skel = dynptr_success__open();
> +
> +       skel->bss->pid = getpid();
> +
> +       dynptr_success__load(skel);
> +       if (!ASSERT_OK_PTR(skel, "dynptr__open_and_load"))
> +               return;
> +
> +       run_prog(skel, skel->progs.prog_success);
> +       run_prog(skel, skel->progs.prog_success_data_slice);
> +       run_prog(skel, skel->progs.prog_success_ringbuf);

let's keep it generic as well as for negative tests and pass program
name? it will be easier to extend such framework

> +
> +       dynptr_success__destroy(skel);
> +}
> +
> +void test_dynptr(void)
> +{
> +       char *obj_log_buf;
> +
> +       obj_log_buf = malloc(3 * log_buf_sz);
> +       if (!ASSERT_OK_PTR(obj_log_buf, "obj_log_buf"))
> +               return;
> +       obj_log_buf[0] = '\0';

I'd keep it simple and just have a global static log buf of necessary
size. Less parameters to pass a well

> +
> +       if (test__start_subtest("missing_free"))
> +               verify_fail(MISSING_FREE, obj_log_buf,
> +                           "spi=0 is an unreleased dynptr");
> +

[...]

> +       if (test__start_subtest("free_twice_callback"))
> +               verify_fail(FREE_TWICE_CALLBACK, obj_log_buf,
> +                           "arg #1 is an unacquired reference and hence cannot be released");
> +
> +       if (test__start_subtest("success"))
> +               verify_success();

so instead of manually coded set of tests, it's more "scalable" to go
with table-driven approach. Something like

struct {
    const char *prog_name;
    const char *exp_msg;
} tests = {
  {"invalid_read2", "Expected an initialized dynptr as arg #3"},
  {"prog_success_ringbuf", NULL /* success case */},
  ...
};

then you can just succinctly:

for (i = 0; i < ARRAY_SIZE(tests); i++) {
  if (!test__start_subtest(tests[i].prog_name))
    continue;

  if (tests[i].exp_msg)
    verify_fail(tests[i].prog_name, tests[i].exp_msg);
  else
    verify_success(tests[i].prog_name);
}

Then adding new cases would be only adding BPF code and adding a
single line in the tests table.

> +
> +       free(obj_log_buf);
> +}

[...]

> +/* 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);

this will be rejected also due to missing discard_dynptr() in this
code path, right? But if you remove return 0 below and fall through
into done this will go away.

> +
> +       return 0;
> +
> +done:
> +       bpf_ringbuf_discard_dynptr(&ptr, 0);
> +       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), &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("fentry/" SYS_PREFIX "sys_nanosleep")

why switching to fentry here with this ugly SYS_PREFIX thingy?

> +int data_slice_out_of_bounds(void *ctx)
> +{
> +       struct bpf_dynptr ptr = {};
> +       void *data;
> +
> +       bpf_malloc(8, &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_free(&ptr);
> +       return 0;
> +}
> +

[...]
Joanne Koong April 8, 2022, 11:16 p.m. UTC | #2
On Wed, Apr 6, 2022 at 4:11 PM Andrii Nakryiko
<andrii.nakryiko@gmail.com> wrote:
>
> On Fri, Apr 1, 2022 at 7:00 PM Joanne Koong <joannekoong@fb.com> wrote:
> >
> > From: Joanne Koong <joannelkoong@gmail.com>
> >
> > 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_frees,
> > invalid writes, invalid reads, and invalid ringbuf API usages.
> >
> > Signed-off-by: Joanne Koong <joannelkoong@gmail.com>
> > ---
>
> Great set of tests! Hard to keep reading 500+ lines of failing use
> cases, but seems like a lot of interesting corner cases are handled!
> Great job!
>
> >  .../testing/selftests/bpf/prog_tests/dynptr.c | 303 ++++++++++
> >  .../testing/selftests/bpf/progs/dynptr_fail.c | 527 ++++++++++++++++++
> >  .../selftests/bpf/progs/dynptr_success.c      | 147 +++++
> >  3 files changed, 977 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..7107ebee3427
> > --- /dev/null
> > +++ b/tools/testing/selftests/bpf/prog_tests/dynptr.c
> > @@ -0,0 +1,303 @@
> > +// SPDX-License-Identifier: GPL-2.0
> > +/* Copyright (c) 2022 Facebook */
> > +
> > +#include <test_progs.h>
> > +#include "dynptr_fail.skel.h"
> > +#include "dynptr_success.skel.h"
> > +
> > +size_t log_buf_sz = 1024 * 1024;
> > +
> > +enum fail_case {
> > +       MISSING_FREE,
> > +       MISSING_FREE_CALLBACK,
> > +       INVALID_FREE1,
> > +       INVALID_FREE2,
> > +       USE_AFTER_FREE,
> > +       MALLOC_TWICE,
> > +       INVALID_MAP_CALL1,
> > +       INVALID_MAP_CALL2,
> > +       RINGBUF_INVALID_ACCESS,
> > +       RINGBUF_INVALID_API,
> > +       RINGBUF_OUT_OF_BOUNDS,
> > +       DATA_SLICE_OUT_OF_BOUNDS,
> > +       DATA_SLICE_USE_AFTER_FREE,
> > +       INVALID_HELPER1,
> > +       INVALID_HELPER2,
> > +       INVALID_WRITE1,
> > +       INVALID_WRITE2,
> > +       INVALID_WRITE3,
> > +       INVALID_WRITE4,
> > +       INVALID_READ1,
> > +       INVALID_READ2,
> > +       INVALID_READ3,
> > +       INVALID_OFFSET,
> > +       GLOBAL,
> > +       FREE_TWICE,
> > +       FREE_TWICE_CALLBACK,
> > +};
>
> it might make sense to just pass the program name as a string instead,
> just like expected error message. This will allow more table-like
> subtest specification (I'll expand below)
>
[...]
> > +
> > +       if (test__start_subtest("missing_free"))
> > +               verify_fail(MISSING_FREE, obj_log_buf,
> > +                           "spi=0 is an unreleased dynptr");
> > +
>
> [...]
>
> > +       if (test__start_subtest("free_twice_callback"))
> > +               verify_fail(FREE_TWICE_CALLBACK, obj_log_buf,
> > +                           "arg #1 is an unacquired reference and hence cannot be released");
> > +
> > +       if (test__start_subtest("success"))
> > +               verify_success();
>
> so instead of manually coded set of tests, it's more "scalable" to go
> with table-driven approach. Something like
>
> struct {
>     const char *prog_name;
>     const char *exp_msg;
> } tests = {
>   {"invalid_read2", "Expected an initialized dynptr as arg #3"},
>   {"prog_success_ringbuf", NULL /* success case */},
>   ...
> };
>
> then you can just succinctly:
>
> for (i = 0; i < ARRAY_SIZE(tests); i++) {
>   if (!test__start_subtest(tests[i].prog_name))
>     continue;
>
>   if (tests[i].exp_msg)
>     verify_fail(tests[i].prog_name, tests[i].exp_msg);
>   else
>     verify_success(tests[i].prog_name);
> }
>
> Then adding new cases would be only adding BPF code and adding a
> single line in the tests table.
>
Awesome!! I love this. This will make it a lot easier to read!
> > +
> > +       free(obj_log_buf);
> > +}
[...]
>
> > +/* 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), &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("fentry/" SYS_PREFIX "sys_nanosleep")
>
> why switching to fentry here with this ugly SYS_PREFIX thingy?
Ooh thanks for spotting this, I forgot to switch this over. Will
definitely change this for v2!
>
[...]
diff mbox series

Patch

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..7107ebee3427
--- /dev/null
+++ b/tools/testing/selftests/bpf/prog_tests/dynptr.c
@@ -0,0 +1,303 @@ 
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2022 Facebook */
+
+#include <test_progs.h>
+#include "dynptr_fail.skel.h"
+#include "dynptr_success.skel.h"
+
+size_t log_buf_sz = 1024 * 1024;
+
+enum fail_case {
+	MISSING_FREE,
+	MISSING_FREE_CALLBACK,
+	INVALID_FREE1,
+	INVALID_FREE2,
+	USE_AFTER_FREE,
+	MALLOC_TWICE,
+	INVALID_MAP_CALL1,
+	INVALID_MAP_CALL2,
+	RINGBUF_INVALID_ACCESS,
+	RINGBUF_INVALID_API,
+	RINGBUF_OUT_OF_BOUNDS,
+	DATA_SLICE_OUT_OF_BOUNDS,
+	DATA_SLICE_USE_AFTER_FREE,
+	INVALID_HELPER1,
+	INVALID_HELPER2,
+	INVALID_WRITE1,
+	INVALID_WRITE2,
+	INVALID_WRITE3,
+	INVALID_WRITE4,
+	INVALID_READ1,
+	INVALID_READ2,
+	INVALID_READ3,
+	INVALID_OFFSET,
+	GLOBAL,
+	FREE_TWICE,
+	FREE_TWICE_CALLBACK,
+};
+
+static void verify_fail(enum fail_case fail, char *obj_log_buf,  char *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, "skel_open"))
+		return;
+
+	bpf_object__for_each_program(prog, skel->obj)
+		bpf_program__set_autoload(prog, false);
+
+	/* these programs should all be rejected by the verifier */
+	switch (fail) {
+	case MISSING_FREE:
+		prog = skel->progs.missing_free;
+		break;
+	case MISSING_FREE_CALLBACK:
+		prog = skel->progs.missing_free_callback;
+		break;
+	case INVALID_FREE1:
+		prog = skel->progs.invalid_free1;
+		break;
+	case INVALID_FREE2:
+		prog = skel->progs.invalid_free2;
+		break;
+	case USE_AFTER_FREE:
+		prog = skel->progs.use_after_free;
+		break;
+	case MALLOC_TWICE:
+		prog = skel->progs.malloc_twice;
+		break;
+	case INVALID_MAP_CALL1:
+		prog = skel->progs.invalid_map_call1;
+		break;
+	case INVALID_MAP_CALL2:
+		prog = skel->progs.invalid_map_call2;
+		break;
+	case RINGBUF_INVALID_ACCESS:
+		prog = skel->progs.ringbuf_invalid_access;
+		break;
+	case RINGBUF_INVALID_API:
+		prog = skel->progs.ringbuf_invalid_api;
+		break;
+	case RINGBUF_OUT_OF_BOUNDS:
+		prog = skel->progs.ringbuf_out_of_bounds;
+		break;
+	case DATA_SLICE_OUT_OF_BOUNDS:
+		prog = skel->progs.data_slice_out_of_bounds;
+		break;
+	case DATA_SLICE_USE_AFTER_FREE:
+		prog = skel->progs.data_slice_use_after_free;
+		break;
+	case INVALID_HELPER1:
+		prog = skel->progs.invalid_helper1;
+		break;
+	case INVALID_HELPER2:
+		prog = skel->progs.invalid_helper2;
+		break;
+	case INVALID_WRITE1:
+		prog = skel->progs.invalid_write1;
+		break;
+	case INVALID_WRITE2:
+		prog = skel->progs.invalid_write2;
+		break;
+	case INVALID_WRITE3:
+		prog = skel->progs.invalid_write3;
+		break;
+	case INVALID_WRITE4:
+		prog = skel->progs.invalid_write4;
+		break;
+	case INVALID_READ1:
+		prog = skel->progs.invalid_read1;
+		break;
+	case INVALID_READ2:
+		prog = skel->progs.invalid_read2;
+		break;
+	case INVALID_READ3:
+		prog = skel->progs.invalid_read3;
+		break;
+	case INVALID_OFFSET:
+		prog = skel->progs.invalid_offset;
+		break;
+	case GLOBAL:
+		prog = skel->progs.global;
+		break;
+	case FREE_TWICE:
+		prog = skel->progs.free_twice;
+		break;
+	case FREE_TWICE_CALLBACK:
+		prog = skel->progs.free_twice_callback;
+		break;
+	default:
+		fprintf(stderr, "unknown fail_case\n");
+		return;
+	}
+
+	bpf_program__set_autoload(prog, true);
+
+	err = dynptr_fail__load(skel);
+
+	ASSERT_OK_PTR(strstr(obj_log_buf, err_msg), "err_msg not found");
+
+	ASSERT_ERR(err, "unexpected load success");
+
+	dynptr_fail__destroy(skel);
+}
+
+static void run_prog(struct dynptr_success *skel, struct bpf_program *prog)
+{
+	struct bpf_link *link;
+
+	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);
+}
+
+static void verify_success(void)
+{
+	struct dynptr_success *skel;
+
+	skel = dynptr_success__open();
+
+	skel->bss->pid = getpid();
+
+	dynptr_success__load(skel);
+	if (!ASSERT_OK_PTR(skel, "dynptr__open_and_load"))
+		return;
+
+	run_prog(skel, skel->progs.prog_success);
+	run_prog(skel, skel->progs.prog_success_data_slice);
+	run_prog(skel, skel->progs.prog_success_ringbuf);
+
+	dynptr_success__destroy(skel);
+}
+
+void test_dynptr(void)
+{
+	char *obj_log_buf;
+
+	obj_log_buf = malloc(3 * log_buf_sz);
+	if (!ASSERT_OK_PTR(obj_log_buf, "obj_log_buf"))
+		return;
+	obj_log_buf[0] = '\0';
+
+	if (test__start_subtest("missing_free"))
+		verify_fail(MISSING_FREE, obj_log_buf,
+			    "spi=0 is an unreleased dynptr");
+
+	if (test__start_subtest("missing_free_callback"))
+		verify_fail(MISSING_FREE_CALLBACK, obj_log_buf,
+			    "spi=0 is an unreleased dynptr");
+
+	if (test__start_subtest("invalid_free1"))
+		verify_fail(INVALID_FREE1, obj_log_buf,
+			    "arg #1 is an unacquired reference and hence cannot be released");
+
+	if (test__start_subtest("invalid_free2"))
+		verify_fail(INVALID_FREE2, obj_log_buf, "type=alloc_mem_or_null expected=fp");
+
+	if (test__start_subtest("use_after_free"))
+		verify_fail(USE_AFTER_FREE, obj_log_buf,
+			    "Expected an initialized dynptr as arg #3");
+
+	if (test__start_subtest("malloc_twice"))
+		verify_fail(MALLOC_TWICE, obj_log_buf,
+			    "Arg #2 dynptr cannot be an initialized dynptr");
+
+	if (test__start_subtest("invalid_map_call1"))
+		verify_fail(INVALID_MAP_CALL1, obj_log_buf,
+			    "invalid indirect read from stack");
+
+	if (test__start_subtest("invalid_map_call2"))
+		verify_fail(INVALID_MAP_CALL2, obj_log_buf,
+			    "invalid indirect read from stack");
+
+	if (test__start_subtest("invalid_helper1"))
+		verify_fail(INVALID_HELPER1, obj_log_buf,
+			    "invalid indirect read from stack");
+
+	if (test__start_subtest("ringbuf_invalid_access"))
+		verify_fail(RINGBUF_INVALID_ACCESS, obj_log_buf,
+			    "invalid mem access 'scalar'");
+
+	if (test__start_subtest("ringbuf_invalid_api"))
+		verify_fail(RINGBUF_INVALID_API, obj_log_buf,
+			    "func bpf_ringbuf_submit#132 reference has not been acquired before");
+
+	if (test__start_subtest("ringbuf_out_of_bounds"))
+		verify_fail(RINGBUF_OUT_OF_BOUNDS, obj_log_buf,
+			    "value is outside of the allowed memory range");
+
+	if (test__start_subtest("data_slice_out_of_bounds"))
+		verify_fail(DATA_SLICE_OUT_OF_BOUNDS, obj_log_buf,
+			    "value is outside of the allowed memory range");
+
+	if (test__start_subtest("data_slice_use_after_free"))
+		verify_fail(DATA_SLICE_USE_AFTER_FREE, obj_log_buf,
+			    "invalid mem access 'scalar'");
+
+	if (test__start_subtest("invalid_helper2"))
+		verify_fail(INVALID_HELPER2, obj_log_buf,
+			    "Expected an initialized dynptr as arg #3");
+
+	if (test__start_subtest("invalid_write1"))
+		verify_fail(INVALID_WRITE1, obj_log_buf,
+			    "direct write into dynptr is not permitted");
+
+	if (test__start_subtest("invalid_write2"))
+		verify_fail(INVALID_WRITE2, obj_log_buf,
+			    "direct write into dynptr is not permitted");
+
+	if (test__start_subtest("invalid_write3"))
+		verify_fail(INVALID_WRITE3, obj_log_buf,
+			    "direct write into dynptr is not permitted");
+
+	if (test__start_subtest("invalid_write4"))
+		verify_fail(INVALID_WRITE4, obj_log_buf,
+			    "direct write into dynptr is not permitted");
+
+	if (test__start_subtest("invalid_read1"))
+		verify_fail(INVALID_READ1, obj_log_buf,
+			    "invalid read from stack");
+
+	if (test__start_subtest("invalid_read2"))
+		verify_fail(INVALID_READ2, obj_log_buf,
+			    "Expected an initialized dynptr as arg #3");
+
+	if (test__start_subtest("invalid_read3"))
+		verify_fail(INVALID_READ3, obj_log_buf,
+			    "invalid read from stack");
+
+	if (test__start_subtest("invalid_offset"))
+		verify_fail(INVALID_OFFSET, obj_log_buf,
+			    "invalid indirect access to stack");
+
+	if (test__start_subtest("global"))
+		verify_fail(GLOBAL, obj_log_buf,
+			    "R2 type=map_value expected=fp");
+
+	if (test__start_subtest("free_twice"))
+		verify_fail(FREE_TWICE, obj_log_buf,
+			    "arg #1 is an unacquired reference and hence cannot be released");
+
+	if (test__start_subtest("free_twice_callback"))
+		verify_fail(FREE_TWICE_CALLBACK, obj_log_buf,
+			    "arg #1 is an unacquired reference and hence cannot be released");
+
+	if (test__start_subtest("success"))
+		verify_success();
+
+	free(obj_log_buf);
+}
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..0b19eeb83e36
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/dynptr_fail.c
@@ -0,0 +1,527 @@ 
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2022 Facebook */
+
+#include <string.h>
+#include <linux/bpf.h>
+#include <bpf/bpf_helpers.h>
+#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;
+
+/* A dynptr can't be used after bpf_free has been called on it */
+SEC("raw_tp/sys_nanosleep")
+int use_after_free(void *ctx)
+{
+	struct bpf_dynptr ptr = {};
+	char read_data[64] = {};
+
+	bpf_malloc(8, &ptr);
+
+	bpf_dynptr_read(read_data, sizeof(read_data), &ptr, 0);
+
+	bpf_free(&ptr);
+
+	/* this should fail */
+	bpf_dynptr_read(read_data, sizeof(read_data), &ptr, 0);
+
+	return 0;
+}
+
+/* Every bpf_malloc call must have a corresponding bpf_free call */
+SEC("raw_tp/sys_nanosleep")
+int missing_free(void *ctx)
+{
+	struct bpf_dynptr mem;
+
+	bpf_malloc(8, &mem);
+
+	/* missing a call to bpf_free(&mem) */
+
+	return 0;
+}
+
+/* A non-malloc-ed dynptr can't be freed */
+SEC("raw_tp/sys_nanosleep")
+int invalid_free1(void *ctx)
+{
+	struct bpf_dynptr ptr;
+	__u32 x = 0;
+
+	bpf_dynptr_from_mem(&x, sizeof(x), &ptr);
+
+	/* this should fail */
+	bpf_free(&ptr);
+
+	return 0;
+}
+
+/* A data slice from a dynptr can't be freed */
+SEC("raw_tp/sys_nanosleep")
+int invalid_free2(void *ctx)
+{
+	struct bpf_dynptr ptr;
+	void *data;
+
+	bpf_malloc(8, &ptr);
+
+	data = bpf_dynptr_data(&ptr, 0, 8);
+
+	/* this should fail */
+	bpf_free(data);
+
+	return 0;
+}
+
+/*
+ * Can't bpf_malloc an existing malloc-ed bpf_dynptr that hasn't been
+ * freed yet
+ */
+SEC("raw_tp/sys_nanosleep")
+int malloc_twice(void *ctx)
+{
+	struct bpf_dynptr ptr;
+
+	bpf_malloc(8, &ptr);
+
+	/* this should fail */
+	bpf_malloc(2, &ptr);
+
+	bpf_free(&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);
+
+	return 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 invalid_map_call1(void *ctx)
+{
+	struct bpf_dynptr ptr = {};
+	char buf[64] = {};
+	int key = 0;
+
+	err = bpf_dynptr_from_mem(buf, sizeof(buf), &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 invalid_map_call2(void *ctx)
+{
+	struct info {
+		int x;
+		struct bpf_dynptr ptr;
+	};
+	struct info x;
+	int key = 0;
+
+	bpf_malloc(8, &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_malloc(8, &ptr);
+
+	/* this should fail */
+	bpf_strncmp((const char *)&ptr, sizeof(ptr), "hello!");
+
+	bpf_free(&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), &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("fentry/" SYS_PREFIX "sys_nanosleep")
+int data_slice_out_of_bounds(void *ctx)
+{
+	struct bpf_dynptr ptr = {};
+	void *data;
+
+	bpf_malloc(8, &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_free(&ptr);
+	return 0;
+}
+
+/* A data slice can't be used after it's freed */
+SEC("fentry/" SYS_PREFIX "sys_nanosleep")
+int data_slice_use_after_free(void *ctx)
+{
+	struct bpf_dynptr ptr = {};
+	void *data;
+
+	bpf_malloc(8, &ptr);
+
+	data = bpf_dynptr_data(&ptr, 0, 8);
+	if (!data)
+		goto done;
+
+	bpf_free(&ptr);
+
+	/* this should fail */
+	val = *(__u8 *)data;
+
+done:
+	bpf_free(&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_malloc(8, &ptr);
+
+	/* this should fail */
+	memcpy(&ptr, &x, sizeof(x));
+
+	bpf_free(&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), &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_malloc(8, &ptr);
+
+	memcpy(stack_buf, &val, sizeof(val));
+	len = stack_buf[0] & 0xf;
+
+	/* this should fail */
+	memcpy((void *)&ptr + len, &x, sizeof(x));
+
+	bpf_free(&ptr);
+
+	return 0;
+}
+
+static int invalid_write4_callback(__u32 index, void *data)
+{
+	/* this should fail */
+	*(__u32 *)data = 123;
+
+	bpf_free(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), &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_malloc(4, &global_dynptr);
+
+	bpf_free(&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), &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), &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), &ptr);
+	bpf_dynptr_from_mem(&x, sizeof(x), &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), &ptr + 1);
+
+	return 0;
+}
+
+/* A malloc can't be freed twice */
+SEC("raw_tp/sys_nanosleep")
+int free_twice(void *ctx)
+{
+	struct bpf_dynptr ptr;
+
+	bpf_malloc(8, &ptr);
+
+	bpf_free(&ptr);
+
+	/* this second free should fail */
+	bpf_free(&ptr);
+
+	return 0;
+}
+
+static int free_twice_callback_fn(__u32 index, void *data)
+{
+	/* this should fail */
+	bpf_free(data);
+	val = index;
+	return 0;
+}
+
+/* Test that freeing a malloc twice, where the 2nd free happens within a
+ * calback function, fails
+ */
+SEC("raw_tp/sys_nanosleep")
+int free_twice_callback(void *ctx)
+{
+	struct bpf_dynptr ptr;
+
+	bpf_malloc(8, &ptr);
+
+	bpf_free(&ptr);
+
+	bpf_loop(10, free_twice_callback_fn, &ptr, 0);
+
+	return 0;
+}
+
+static int missing_free_callback_fn(__u32 index, void *data)
+{
+	struct bpf_dynptr ptr;
+
+	bpf_malloc(8, &ptr);
+
+	val = index;
+
+	/* missing bpf_free(&ptr) */
+
+	return 0;
+}
+
+/* Any dynptr initialized within a callback must be freed */
+SEC("raw_tp/sys_nanosleep")
+int missing_free_callback(void *ctx)
+{
+	bpf_loop(10, missing_free_callback_fn, NULL, 0);
+	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..1b605bbc17f3
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/dynptr_success.c
@@ -0,0 +1,147 @@ 
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2022 Facebook */
+
+#include <string.h>
+#include <linux/bpf.h>
+#include <bpf/bpf_helpers.h>
+#include "bpf_misc.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 prog_success(void *ctx)
+{
+	char buf[64] = {};
+	char write_data[64] = "hello there, world!!";
+	struct bpf_dynptr ptr = {}, mem = {};
+	__u8 mem_allocated = 0;
+	char read_data[64] = {};
+	__u32 val = 0;
+	void *data;
+	int i;
+
+	if (bpf_get_current_pid_tgid() >> 32 != pid)
+		return 0;
+
+	err = bpf_dynptr_from_mem(buf, sizeof(buf), &ptr);
+	if (err)
+		goto done;
+
+	/* Write data into the dynptr */
+	err = bpf_dynptr_write(&ptr, 0, write_data, sizeof(write_data));
+	if (err)
+		goto done;
+
+	/* Read the data that was written into the dynptr */
+	err = bpf_dynptr_read(read_data, sizeof(read_data), &ptr, 0);
+	if (err)
+		goto done;
+
+	/* 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;
+			goto done;
+		}
+	}
+
+done:
+	if (mem_allocated)
+		bpf_free(&mem);
+	return 0;
+}
+
+SEC("tp/syscalls/sys_enter_nanosleep")
+int prog_success_data_slice(void *ctx)
+{
+	struct bpf_dynptr mem;
+	void *data;
+
+	if (bpf_get_current_pid_tgid() >> 32 != pid)
+		return 0;
+
+	err = bpf_malloc(16, &mem);
+	if (err)
+		goto done;
+
+	data = bpf_dynptr_data(&mem, 0, sizeof(__u32));
+	if (!data)
+		goto done;
+
+	*(__u32 *)data = 999;
+
+	err = bpf_probe_read_kernel(&val, sizeof(val), data);
+	if (err)
+		goto done;
+
+	if (val != *(__u32 *)data)
+		err = 2;
+
+done:
+	bpf_free(&mem);
+	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)
+		return 0;
+
+	sample->pid += val;
+
+	return 0;
+}
+
+SEC("tp/syscalls/sys_enter_nanosleep")
+int prog_success_ringbuf(void *ctx)
+{
+	struct bpf_dynptr ptr;
+	void *data;
+	struct sample *sample;
+
+	if (bpf_get_current_pid_tgid() >> 32 != pid)
+		return 0;
+
+	/* 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)
+		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;
+}