new file mode 100644
@@ -0,0 +1,272 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <test_progs.h>
+#include <network_helpers.h>
+
+#include "exceptions.skel.h"
+#include "exceptions_ext.skel.h"
+#include "exceptions_fail.skel.h"
+
+static char log_buf[1024 * 1024];
+
+static void test_exceptions_failure(void)
+{
+ RUN_TESTS(exceptions_fail);
+}
+
+static void test_exceptions_success(void)
+{
+ LIBBPF_OPTS(bpf_test_run_opts, ropts,
+ .data_in = &pkt_v4,
+ .data_size_in = sizeof(pkt_v4),
+ .repeat = 1,
+ );
+ struct exceptions_ext *eskel = NULL;
+ struct exceptions *skel;
+ int ret;
+
+ skel = exceptions__open();
+ if (!ASSERT_OK_PTR(skel, "exceptions__open"))
+ return;
+
+ ret = exceptions__load(skel);
+ if (!ASSERT_OK(ret, "exceptions__load"))
+ goto done;
+
+ if (!ASSERT_OK(bpf_map_update_elem(bpf_map__fd(skel->maps.jmp_table), &(int){0},
+ &(int){bpf_program__fd(skel->progs.exception_tail_call_target)}, BPF_ANY),
+ "bpf_map_update_elem jmp_table"))
+ goto done;
+
+#define RUN_SUCCESS(_prog, return_val) \
+ ret = bpf_prog_test_run_opts(bpf_program__fd(skel->progs._prog), &ropts); \
+ ASSERT_OK(ret, #_prog " prog run ret"); \
+ ASSERT_EQ(ropts.retval, return_val, #_prog " prog run retval");
+
+ RUN_SUCCESS(exception_throw_subprog, 16);
+ RUN_SUCCESS(exception_throw, 0);
+ RUN_SUCCESS(exception_throw_gfunc1, 1);
+ RUN_SUCCESS(exception_throw_gfunc2, 0);
+ RUN_SUCCESS(exception_throw_gfunc3, 1);
+ RUN_SUCCESS(exception_throw_gfunc4, 0);
+ RUN_SUCCESS(exception_throw_gfunc5, 1);
+ RUN_SUCCESS(exception_throw_gfunc6, 16);
+ RUN_SUCCESS(exception_throw_func1, 1);
+ RUN_SUCCESS(exception_throw_func2, 0);
+ RUN_SUCCESS(exception_throw_func3, 1);
+ RUN_SUCCESS(exception_throw_func4, 0);
+ RUN_SUCCESS(exception_throw_func5, 1);
+ RUN_SUCCESS(exception_throw_func6, 16);
+ RUN_SUCCESS(exception_tail_call, 50);
+ RUN_SUCCESS(exception_ext, 5);
+ RUN_SUCCESS(exception_throw_value, 60);
+ RUN_SUCCESS(exception_assert_eq, 16);
+ RUN_SUCCESS(exception_assert_ne, 16);
+ RUN_SUCCESS(exception_assert_lt, 16);
+ RUN_SUCCESS(exception_assert_gt, 16);
+ RUN_SUCCESS(exception_assert_le, 16);
+ RUN_SUCCESS(exception_assert_ge, 16);
+ RUN_SUCCESS(exception_assert_eq_ok, 6);
+ RUN_SUCCESS(exception_assert_ne_ok, 6);
+ RUN_SUCCESS(exception_assert_lt_ok, 6);
+ RUN_SUCCESS(exception_assert_gt_ok, 6);
+ RUN_SUCCESS(exception_assert_le_ok, 6);
+ RUN_SUCCESS(exception_assert_ge_ok, 6);
+ RUN_SUCCESS(exception_assert_eq_value, 42);
+ RUN_SUCCESS(exception_assert_ne_value, 42);
+ RUN_SUCCESS(exception_assert_lt_value, 42);
+ RUN_SUCCESS(exception_assert_gt_value, 42);
+ RUN_SUCCESS(exception_assert_le_value, 42);
+ RUN_SUCCESS(exception_assert_ge_value, 42);
+ RUN_SUCCESS(exception_assert_eq_ok_value, 5);
+ RUN_SUCCESS(exception_assert_ne_ok_value, 5);
+ RUN_SUCCESS(exception_assert_lt_ok_value, 5);
+ RUN_SUCCESS(exception_assert_gt_ok_value, 5);
+ RUN_SUCCESS(exception_assert_le_ok_value, 5);
+ RUN_SUCCESS(exception_assert_ge_ok_value, 5);
+
+#define RUN_EXT(load_ret, attach_err, expr, msg, after_link) \
+ { \
+ LIBBPF_OPTS(bpf_object_open_opts, o, .kernel_log_buf = log_buf, \
+ .kernel_log_size = sizeof(log_buf), \
+ .kernel_log_level = 2); \
+ exceptions_ext__destroy(eskel); \
+ eskel = exceptions_ext__open_opts(&o); \
+ struct bpf_program *prog = NULL; \
+ struct bpf_link *link = NULL; \
+ if (!ASSERT_OK_PTR(eskel, "exceptions_ext__open")) \
+ goto done; \
+ (expr); \
+ ASSERT_OK_PTR(bpf_program__name(prog), bpf_program__name(prog)); \
+ if (!ASSERT_EQ(exceptions_ext__load(eskel), load_ret, \
+ "exceptions_ext__load")) { \
+ printf("%s\n", log_buf); \
+ goto done; \
+ } \
+ if (load_ret != 0) { \
+ printf("%s\n", log_buf); \
+ if (!ASSERT_OK_PTR(strstr(log_buf, msg), "strstr")) \
+ goto done; \
+ } \
+ if (!load_ret && attach_err) { \
+ if (!ASSERT_ERR_PTR(link = bpf_program__attach(prog), "attach err")) \
+ goto done; \
+ } else if (!load_ret) { \
+ if (!ASSERT_OK_PTR(link = bpf_program__attach(prog), "attach ok")) \
+ goto done; \
+ (void)(after_link); \
+ bpf_link__destroy(link); \
+ } \
+ }
+
+ RUN_EXT(0, false, ({
+ prog = eskel->progs.throwing_extension;
+ bpf_program__set_autoload(prog, true);
+ if (!ASSERT_OK(bpf_program__set_attach_target(prog,
+ bpf_program__fd(skel->progs.exception_ext),
+ "exception_ext_global"), "set_attach_target"))
+ goto done;
+ }), "", ({ RUN_SUCCESS(exception_ext, 0); }));
+
+ /* non-throwing fexit -> non-throwing subprog : OK */
+ RUN_EXT(0, false, ({
+ prog = eskel->progs.pfexit;
+ bpf_program__set_autoload(prog, true);
+ if (!ASSERT_OK(bpf_program__set_attach_target(prog,
+ bpf_program__fd(skel->progs.exception_throw_subprog),
+ "subprog"), "set_attach_target"))
+ goto done;
+ }), "", (void)0);
+
+ /* throwing fexit -> non-throwing subprog : OK */
+ RUN_EXT(0, false, ({
+ prog = eskel->progs.throwing_fexit;
+ bpf_program__set_autoload(prog, true);
+ if (!ASSERT_OK(bpf_program__set_attach_target(prog,
+ bpf_program__fd(skel->progs.exception_throw_subprog),
+ "subprog"), "set_attach_target"))
+ goto done;
+ }), "", (void)0);
+
+ /* non-throwing fexit -> throwing subprog : OK */
+ RUN_EXT(0, false, ({
+ prog = eskel->progs.pfexit;
+ bpf_program__set_autoload(prog, true);
+ if (!ASSERT_OK(bpf_program__set_attach_target(prog,
+ bpf_program__fd(skel->progs.exception_throw_subprog),
+ "throwing_subprog"), "set_attach_target"))
+ goto done;
+ }), "", (void)0);
+
+ /* throwing fexit -> throwing subprog : OK */
+ RUN_EXT(0, false, ({
+ prog = eskel->progs.throwing_fexit;
+ bpf_program__set_autoload(prog, true);
+ if (!ASSERT_OK(bpf_program__set_attach_target(prog,
+ bpf_program__fd(skel->progs.exception_throw_subprog),
+ "throwing_subprog"), "set_attach_target"))
+ goto done;
+ }), "", (void)0);
+
+ /* fmod_ret not allowed for subprog - Check so we remember to handle its
+ * throwing specification compatibility with target when supported.
+ */
+ RUN_EXT(-EINVAL, false, ({
+ prog = eskel->progs.pfmod_ret;
+ bpf_program__set_autoload(prog, true);
+ if (!ASSERT_OK(bpf_program__set_attach_target(prog,
+ bpf_program__fd(skel->progs.exception_throw_subprog),
+ "subprog"), "set_attach_target"))
+ goto done;
+ }), "can't modify return codes of BPF program", (void)0);
+
+ /* fmod_ret not allowed for subprog - Check so we remember to handle its
+ * throwing specification compatibility with target when supported.
+ */
+ RUN_EXT(-EINVAL, false, ({
+ prog = eskel->progs.pfmod_ret;
+ bpf_program__set_autoload(prog, true);
+ if (!ASSERT_OK(bpf_program__set_attach_target(prog,
+ bpf_program__fd(skel->progs.exception_throw_subprog),
+ "global_subprog"), "set_attach_target"))
+ goto done;
+ }), "can't modify return codes of BPF program", (void)0);
+
+ /* non-throwing extension -> non-throwing subprog : BAD (!global) */
+ RUN_EXT(-EINVAL, true, ({
+ prog = eskel->progs.extension;
+ bpf_program__set_autoload(prog, true);
+ if (!ASSERT_OK(bpf_program__set_attach_target(prog,
+ bpf_program__fd(skel->progs.exception_throw_subprog),
+ "subprog"), "set_attach_target"))
+ goto done;
+ }), "subprog() is not a global function", (void)0);
+
+ /* non-throwing extension -> throwing subprog : BAD (!global) */
+ RUN_EXT(-EINVAL, true, ({
+ prog = eskel->progs.extension;
+ bpf_program__set_autoload(prog, true);
+ if (!ASSERT_OK(bpf_program__set_attach_target(prog,
+ bpf_program__fd(skel->progs.exception_throw_subprog),
+ "throwing_subprog"), "set_attach_target"))
+ goto done;
+ }), "throwing_subprog() is not a global function", (void)0);
+
+ /* non-throwing extension -> non-throwing global subprog : OK */
+ RUN_EXT(0, false, ({
+ prog = eskel->progs.extension;
+ bpf_program__set_autoload(prog, true);
+ if (!ASSERT_OK(bpf_program__set_attach_target(prog,
+ bpf_program__fd(skel->progs.exception_throw_subprog),
+ "global_subprog"), "set_attach_target"))
+ goto done;
+ }), "", (void)0);
+
+ /* non-throwing extension -> throwing global subprog : OK */
+ RUN_EXT(0, false, ({
+ prog = eskel->progs.extension;
+ bpf_program__set_autoload(prog, true);
+ if (!ASSERT_OK(bpf_program__set_attach_target(prog,
+ bpf_program__fd(skel->progs.exception_throw_subprog),
+ "throwing_global_subprog"), "set_attach_target"))
+ goto done;
+ }), "", (void)0);
+
+ /* throwing extension -> throwing global subprog : OK */
+ RUN_EXT(0, false, ({
+ prog = eskel->progs.throwing_extension;
+ bpf_program__set_autoload(prog, true);
+ if (!ASSERT_OK(bpf_program__set_attach_target(prog,
+ bpf_program__fd(skel->progs.exception_throw_subprog),
+ "throwing_global_subprog"), "set_attach_target"))
+ goto done;
+ }), "", (void)0);
+
+ /* throwing extension -> main subprog : OK */
+ RUN_EXT(0, false, ({
+ prog = eskel->progs.throwing_extension;
+ bpf_program__set_autoload(prog, true);
+ if (!ASSERT_OK(bpf_program__set_attach_target(prog,
+ bpf_program__fd(skel->progs.exception_throw_subprog),
+ "exception_throw_subprog"), "set_attach_target"))
+ goto done;
+ }), "", (void)0);
+
+ /* throwing extension -> non-throwing global subprog : OK */
+ RUN_EXT(0, false, ({
+ prog = eskel->progs.throwing_extension;
+ bpf_program__set_autoload(prog, true);
+ if (!ASSERT_OK(bpf_program__set_attach_target(prog,
+ bpf_program__fd(skel->progs.exception_throw_subprog),
+ "global_subprog"), "set_attach_target"))
+ goto done;
+ }), "", (void)0);
+done:
+ exceptions_ext__destroy(eskel);
+ exceptions__destroy(skel);
+}
+
+void test_exceptions(void)
+{
+ test_exceptions_success();
+ test_exceptions_failure();
+}
new file mode 100644
@@ -0,0 +1,450 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <vmlinux.h>
+#include <bpf/bpf_tracing.h>
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_core_read.h>
+#include <bpf/bpf_endian.h>
+#include "bpf_misc.h"
+#include "bpf_experimental.h"
+
+#ifndef ETH_P_IP
+#define ETH_P_IP 0x0800 /* Internet Protocol packet */
+#endif
+
+struct {
+ __uint(type, BPF_MAP_TYPE_PROG_ARRAY);
+ __uint(max_entries, 4);
+ __uint(key_size, sizeof(__u32));
+ __uint(value_size, sizeof(__u32));
+} jmp_table SEC(".maps");
+
+SEC("tc")
+int exception_throw(struct __sk_buff *ctx)
+{
+ volatile int ret = 1;
+
+ if (ctx->protocol)
+ throw;
+ return ret;
+}
+
+
+static __noinline int subprog(struct __sk_buff *ctx)
+{
+ return ctx->len;
+}
+
+static __noinline int throwing_subprog(struct __sk_buff *ctx)
+{
+ volatile int ret = 0;
+
+ if (ctx->protocol)
+ throw;
+ return ret;
+}
+
+__noinline int global_subprog(struct __sk_buff *ctx)
+{
+ return subprog(ctx) + 1;
+}
+
+__noinline int throwing_global_subprog(struct __sk_buff *ctx)
+{
+ volatile int ret = 0;
+
+ if (ctx->protocol)
+ throw;
+ return ret;
+}
+
+__noinline int throwing_global_subprog_value(struct __sk_buff *ctx, u64 value)
+{
+ volatile int ret = 0;
+
+ if (ctx->protocol)
+ throw_value(value);
+ return ret;
+}
+
+static __noinline int exception_cb(u64 c)
+{
+ volatile int ret = 16;
+
+ return ret;
+}
+
+SEC("tc")
+int exception_throw_subprog(struct __sk_buff *ctx)
+{
+ volatile int i;
+
+ bpf_set_exception_callback(exception_cb);
+ i = subprog(ctx);
+ i += global_subprog(ctx) - 1;
+ if (!i)
+ return throwing_global_subprog(ctx);
+ else
+ return throwing_subprog(ctx);
+ throw;
+ return 0;
+}
+
+__noinline int throwing_gfunc(volatile int i)
+{
+ volatile int ret = 1;
+
+ bpf_assert_eq(i, 0);
+ return ret;
+}
+
+__noinline static int throwing_func(volatile int i)
+{
+ volatile int ret = 1;
+
+ bpf_assert_lt(i, 1);
+ return ret;
+}
+
+SEC("tc")
+int exception_throw_gfunc1(void *ctx)
+{
+ return throwing_gfunc(0);
+}
+
+SEC("tc")
+__noinline int exception_throw_gfunc2()
+{
+ return throwing_gfunc(1);
+}
+
+__noinline int throwing_gfunc_2(volatile int i)
+{
+ return throwing_gfunc(i);
+}
+
+SEC("tc")
+int exception_throw_gfunc3(void *ctx)
+{
+ return throwing_gfunc_2(0);
+}
+
+SEC("tc")
+int exception_throw_gfunc4(void *ctx)
+{
+ return throwing_gfunc_2(1);
+}
+
+SEC("tc")
+int exception_throw_gfunc5(void *ctx)
+{
+ bpf_set_exception_callback(exception_cb);
+ return throwing_gfunc_2(0);
+}
+
+SEC("tc")
+int exception_throw_gfunc6(void *ctx)
+{
+ bpf_set_exception_callback(exception_cb);
+ return throwing_gfunc_2(1);
+}
+
+
+SEC("tc")
+int exception_throw_func1(void *ctx)
+{
+ return throwing_func(0);
+}
+
+SEC("tc")
+int exception_throw_func2(void *ctx)
+{
+ return throwing_func(1);
+}
+
+__noinline static int throwing_func_2(volatile int i)
+{
+ return throwing_func(i);
+}
+
+SEC("tc")
+int exception_throw_func3(void *ctx)
+{
+ return throwing_func_2(0);
+}
+
+SEC("tc")
+int exception_throw_func4(void *ctx)
+{
+ return throwing_func_2(1);
+}
+
+SEC("tc")
+int exception_throw_func5(void *ctx)
+{
+ bpf_set_exception_callback(exception_cb);
+ return throwing_func_2(0);
+}
+
+SEC("tc")
+int exception_throw_func6(void *ctx)
+{
+ bpf_set_exception_callback(exception_cb);
+ return throwing_func_2(1);
+}
+
+static int exception_cb_nz(u64 cookie)
+{
+ volatile int ret = 42;
+
+ return ret;
+}
+
+SEC("tc")
+int exception_tail_call_target(struct __sk_buff *ctx)
+{
+ bpf_set_exception_callback(exception_cb_nz);
+ throw;
+}
+
+static __noinline
+int exception_tail_call_subprog(struct __sk_buff *ctx)
+{
+ volatile int ret = 10;
+
+ bpf_tail_call_static(ctx, &jmp_table, 0);
+ return ret;
+}
+
+SEC("tc")
+int exception_tail_call(struct __sk_buff *ctx) {
+ volatile int ret = 0;
+
+ bpf_set_exception_callback(exception_cb);
+ ret = exception_tail_call_subprog(ctx);
+ return ret + 8;
+}
+
+__noinline int exception_ext_global(struct __sk_buff *ctx)
+{
+ volatile int ret = 5;
+
+ return ret;
+}
+
+static __noinline int exception_ext_static(struct __sk_buff *ctx)
+{
+ return exception_ext_global(ctx);
+}
+
+SEC("tc")
+int exception_ext(struct __sk_buff *ctx)
+{
+ bpf_set_exception_callback(exception_cb_nz);
+ return exception_ext_static(ctx);
+}
+
+static __noinline int exception_cb_value(u64 cookie)
+{
+ return cookie - 4;
+}
+
+SEC("tc")
+int exception_throw_value(struct __sk_buff *ctx)
+{
+ bpf_set_exception_callback(exception_cb_value);
+ return throwing_global_subprog_value(ctx, 64);
+}
+
+SEC("tc")
+int exception_assert_eq(struct __sk_buff *ctx)
+{
+ bpf_set_exception_callback(exception_cb);
+ bpf_assert_eq(ctx->protocol, IPPROTO_UDP);
+ return 6;
+}
+
+SEC("tc")
+int exception_assert_ne(struct __sk_buff *ctx)
+{
+ bpf_set_exception_callback(exception_cb);
+ bpf_assert_ne(ctx->protocol, __bpf_htons(ETH_P_IP));
+ return 6;
+}
+
+SEC("tc")
+int exception_assert_lt(struct __sk_buff *ctx)
+{
+ bpf_set_exception_callback(exception_cb);
+ bpf_assert_lt(ctx->protocol, __bpf_htons(ETH_P_IP) - 1);
+ return 6;
+}
+
+SEC("tc")
+int exception_assert_gt(struct __sk_buff *ctx)
+{
+ bpf_set_exception_callback(exception_cb);
+ bpf_assert_gt(ctx->protocol, __bpf_htons(ETH_P_IP) + 1);
+ return 6;
+}
+
+SEC("tc")
+int exception_assert_le(struct __sk_buff *ctx)
+{
+ bpf_set_exception_callback(exception_cb);
+ bpf_assert_le(ctx->protocol, __bpf_htons(ETH_P_IP) - 1);
+ return 6;
+}
+
+SEC("tc")
+int exception_assert_ge(struct __sk_buff *ctx)
+{
+ bpf_set_exception_callback(exception_cb);
+ bpf_assert_ge(ctx->protocol, __bpf_htons(ETH_P_IP) + 1);
+ return 6;
+}
+
+SEC("tc")
+int exception_assert_eq_ok(struct __sk_buff *ctx)
+{
+ bpf_set_exception_callback(exception_cb);
+ bpf_assert_eq(ctx->protocol, __bpf_htons(ETH_P_IP));
+ return 6;
+}
+
+SEC("tc")
+int exception_assert_ne_ok(struct __sk_buff *ctx)
+{
+ bpf_set_exception_callback(exception_cb);
+ bpf_assert_ne(ctx->protocol, IPPROTO_UDP);
+ return 6;
+}
+
+SEC("tc")
+int exception_assert_lt_ok(struct __sk_buff *ctx)
+{
+ bpf_set_exception_callback(exception_cb);
+ bpf_assert_lt(ctx->protocol, __bpf_htons(ETH_P_IP) + 1);
+ return 6;
+}
+
+SEC("tc")
+int exception_assert_gt_ok(struct __sk_buff *ctx)
+{
+ bpf_set_exception_callback(exception_cb);
+ bpf_assert_gt(ctx->protocol, __bpf_htons(ETH_P_IP) - 1);
+ return 6;
+}
+
+SEC("tc")
+int exception_assert_le_ok(struct __sk_buff *ctx)
+{
+ bpf_set_exception_callback(exception_cb);
+ bpf_assert_le(ctx->protocol, __bpf_htons(ETH_P_IP));
+ return 6;
+}
+
+SEC("tc")
+int exception_assert_ge_ok(struct __sk_buff *ctx)
+{
+ bpf_set_exception_callback(exception_cb);
+ bpf_assert_ge(ctx->protocol, __bpf_htons(ETH_P_IP));
+ return 6;
+}
+
+SEC("tc")
+int exception_assert_eq_value(struct __sk_buff *ctx)
+{
+ bpf_set_exception_callback(exception_cb_value);
+ bpf_assert_eq_value(ctx->protocol, IPPROTO_UDP, 46);
+ return 5;
+}
+
+SEC("tc")
+int exception_assert_ne_value(struct __sk_buff *ctx)
+{
+ bpf_set_exception_callback(exception_cb_value);
+ bpf_assert_ne_value(ctx->protocol, __bpf_htons(ETH_P_IP), 46);
+ return 5;
+}
+
+SEC("tc")
+int exception_assert_lt_value(struct __sk_buff *ctx)
+{
+ bpf_set_exception_callback(exception_cb_value);
+ bpf_assert_lt_value(ctx->protocol, __bpf_htons(ETH_P_IP) - 1, 46);
+ return 5;
+}
+
+SEC("tc")
+int exception_assert_gt_value(struct __sk_buff *ctx)
+{
+ bpf_set_exception_callback(exception_cb_value);
+ bpf_assert_gt_value(ctx->protocol, __bpf_htons(ETH_P_IP) + 1, 46);
+ return 5;
+}
+
+SEC("tc")
+int exception_assert_le_value(struct __sk_buff *ctx)
+{
+ bpf_set_exception_callback(exception_cb_value);
+ bpf_assert_le_value(ctx->protocol, __bpf_htons(ETH_P_IP) - 1, 46);
+ return 5;
+}
+
+SEC("tc")
+int exception_assert_ge_value(struct __sk_buff *ctx)
+{
+ bpf_set_exception_callback(exception_cb_value);
+ bpf_assert_ge_value(ctx->protocol, __bpf_htons(ETH_P_IP) + 1, 46);
+ return 5;
+}
+
+SEC("tc")
+int exception_assert_eq_ok_value(struct __sk_buff *ctx)
+{
+ bpf_set_exception_callback(exception_cb_value);
+ bpf_assert_eq_value(ctx->protocol, __bpf_htons(ETH_P_IP), 46);
+ return 5;
+}
+
+SEC("tc")
+int exception_assert_ne_ok_value(struct __sk_buff *ctx)
+{
+ bpf_set_exception_callback(exception_cb_value);
+ bpf_assert_ne_value(ctx->protocol, IPPROTO_UDP, 46);
+ return 5;
+}
+
+SEC("tc")
+int exception_assert_lt_ok_value(struct __sk_buff *ctx)
+{
+ bpf_set_exception_callback(exception_cb_value);
+ bpf_assert_lt_value(ctx->protocol, __bpf_htons(ETH_P_IP) + 1, 46);
+ return 5;
+}
+
+SEC("tc")
+int exception_assert_gt_ok_value(struct __sk_buff *ctx)
+{
+ bpf_set_exception_callback(exception_cb_value);
+ bpf_assert_gt_value(ctx->protocol, __bpf_htons(ETH_P_IP) - 1, 46);
+ return 5;
+}
+
+SEC("tc")
+int exception_assert_le_ok_value(struct __sk_buff *ctx)
+{
+ bpf_set_exception_callback(exception_cb_value);
+ bpf_assert_le_value(ctx->protocol, __bpf_htons(ETH_P_IP), 46);
+ return 5;
+}
+
+SEC("tc")
+int exception_assert_ge_ok_value(struct __sk_buff *ctx)
+{
+ bpf_set_exception_callback(exception_cb_value);
+ bpf_assert_ge_value(ctx->protocol, __bpf_htons(ETH_P_IP), 46);
+ return 5;
+}
+
+char _license[] SEC("license") = "GPL";
new file mode 100644
@@ -0,0 +1,42 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <vmlinux.h>
+#include <bpf/bpf_helpers.h>
+#include "bpf_experimental.h"
+
+SEC("?freplace")
+int extension(struct __sk_buff *ctx)
+{
+ return 0;
+}
+
+SEC("?freplace")
+int throwing_extension(struct __sk_buff *ctx)
+{
+ throw;
+}
+
+SEC("?fexit")
+int pfexit(void *ctx)
+{
+ return 0;
+}
+
+SEC("?fexit")
+int throwing_fexit(void *ctx)
+{
+ throw;
+}
+
+SEC("?fmod_ret")
+int pfmod_ret(void *ctx)
+{
+ return 1;
+}
+
+SEC("?fmod_ret")
+int throwing_fmod_ret(void *ctx)
+{
+ throw;
+}
+
+char _license[] SEC("license") = "GPL";
new file mode 100644
@@ -0,0 +1,311 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <vmlinux.h>
+#include <bpf/bpf_tracing.h>
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_core_read.h>
+
+#include "bpf_misc.h"
+#include "bpf_experimental.h"
+
+extern void bpf_rcu_read_lock(void) __ksym;
+
+#define private(name) SEC(".bss." #name) __hidden __attribute__((aligned(8)))
+
+struct foo {
+ struct bpf_rb_node node;
+};
+
+private(A) struct bpf_spin_lock lock;
+private(A) struct bpf_rb_root rbtree __contains(foo, node);
+
+__noinline static int subprog_lock(struct __sk_buff *ctx)
+{
+ volatile int ret = 0;
+
+ bpf_spin_lock(&lock);
+ if (ctx->len)
+ throw;
+ return ret;
+}
+
+SEC("?tc")
+__failure __msg("function calls are not allowed while holding a lock")
+int reject_with_lock(void *ctx)
+{
+ bpf_spin_lock(&lock);
+ throw;
+}
+
+SEC("?tc")
+__failure __msg("function calls are not allowed while holding a lock")
+int reject_subprog_with_lock(void *ctx)
+{
+ return subprog_lock(ctx);
+}
+
+SEC("?tc")
+__failure __msg("bpf_rcu_read_unlock is missing")
+int reject_with_rcu_read_lock(void *ctx)
+{
+ bpf_rcu_read_lock();
+ throw;
+}
+
+__noinline static int throwing_subprog(struct __sk_buff *ctx)
+{
+ if (ctx->len)
+ throw;
+ return 0;
+}
+
+SEC("?tc")
+__failure __msg("bpf_rcu_read_unlock is missing")
+int reject_subprog_with_rcu_read_lock(void *ctx)
+{
+ bpf_rcu_read_lock();
+ return throwing_subprog(ctx);
+}
+
+static bool rbless(struct bpf_rb_node *n1, const struct bpf_rb_node *n2)
+{
+ throw;
+}
+
+SEC("?tc")
+__failure __msg("function calls are not allowed while holding a lock")
+int reject_with_rbtree_add_throw(void *ctx)
+{
+ struct foo *f;
+
+ f = bpf_obj_new(typeof(*f));
+ if (!f)
+ return 0;
+ bpf_spin_lock(&lock);
+ bpf_rbtree_add(&rbtree, &f->node, rbless);
+ return 0;
+}
+
+SEC("?tc")
+__failure __msg("Unreleased reference")
+int reject_with_reference(void *ctx)
+{
+ struct foo *f;
+
+ f = bpf_obj_new(typeof(*f));
+ if (!f)
+ return 0;
+ throw;
+}
+
+__noinline static int subprog_ref(struct __sk_buff *ctx)
+{
+ struct foo *f;
+
+ f = bpf_obj_new(typeof(*f));
+ if (!f)
+ return 0;
+ throw;
+}
+
+__noinline static int subprog_cb_ref(u32 i, void *ctx)
+{
+ throw;
+}
+
+SEC("?tc")
+__failure __msg("Unreleased reference")
+int reject_with_cb_reference(void *ctx)
+{
+ struct foo *f;
+
+ f = bpf_obj_new(typeof(*f));
+ if (!f)
+ return 0;
+ bpf_loop(5, subprog_cb_ref, NULL, 0);
+ return 0;
+}
+
+SEC("?tc")
+__failure __msg("cannot be called from callback")
+int reject_with_cb(void *ctx)
+{
+ bpf_loop(5, subprog_cb_ref, NULL, 0);
+ return 0;
+}
+
+SEC("?tc")
+__failure __msg("Unreleased reference")
+int reject_with_subprog_reference(void *ctx)
+{
+ return subprog_ref(ctx) + 1;
+}
+
+static __noinline int throwing_exception_cb(u64 c)
+{
+ if (!c)
+ throw;
+ return c;
+}
+
+static __noinline int exception_cb1(u64 c)
+{
+ volatile int i = 0;
+
+ bpf_assert_eq(i, 0);
+ return i;
+}
+
+static __noinline int exception_cb2(u64 c)
+{
+ volatile int i = 0;
+
+ bpf_assert_eq(i, 0);
+ return i;
+}
+
+__noinline int throwing_exception_gfunc(struct __sk_buff *ctx)
+{
+ return throwing_exception_cb(ctx->protocol);
+}
+
+SEC("?tc")
+__failure __msg("cannot be called from callback")
+int reject_throwing_exception_cb_1(struct __sk_buff *ctx)
+{
+ bpf_set_exception_callback(throwing_exception_cb);
+ return 0;
+}
+
+SEC("?tc")
+__failure __msg("cannot call exception cb directly")
+int reject_throwing_exception_cb_2(struct __sk_buff *ctx)
+{
+ throwing_exception_gfunc(ctx);
+ bpf_set_exception_callback(throwing_exception_cb);
+ return 0;
+}
+
+SEC("?tc")
+__failure __msg("can only be called once to set exception callback")
+int reject_throwing_exception_cb_3(struct __sk_buff *ctx)
+{
+ if (ctx->protocol)
+ bpf_set_exception_callback(exception_cb1);
+ else
+ bpf_set_exception_callback(exception_cb2);
+ throw;
+}
+
+__noinline int gfunc_set_exception_cb(u64 c)
+{
+ bpf_set_exception_callback(exception_cb1);
+ return 0;
+}
+
+SEC("?tc")
+__failure __msg("can only be called from main prog")
+int reject_set_exception_cb_gfunc(struct __sk_buff *ctx)
+{
+ gfunc_set_exception_cb(0);
+ return 0;
+}
+
+static __noinline int exception_cb_rec(u64 c)
+{
+ bpf_set_exception_callback(exception_cb_rec);
+ return 0;
+}
+
+SEC("?tc")
+__failure __msg("can only be called from main prog")
+int reject_set_exception_cb_rec1(struct __sk_buff *ctx)
+{
+ bpf_set_exception_callback(exception_cb_rec);
+ return 0;
+}
+
+static __noinline int exception_cb_rec2(u64 c);
+
+static __noinline int exception_cb_rec1(u64 c)
+{
+ bpf_set_exception_callback(exception_cb_rec2);
+ return 0;
+}
+
+static __noinline int exception_cb_rec2(u64 c)
+{
+ bpf_set_exception_callback(exception_cb_rec2);
+ return 0;
+}
+
+SEC("?tc")
+__failure __msg("can only be called from main prog")
+int reject_set_exception_cb_rec2(struct __sk_buff *ctx)
+{
+ bpf_set_exception_callback(exception_cb_rec1);
+ return 0;
+}
+
+static __noinline int exception_cb_rec3(u64 c)
+{
+ bpf_set_exception_callback(exception_cb1);
+ return 0;
+}
+
+SEC("?tc")
+__failure __msg("can only be called from main prog")
+int reject_set_exception_cb_rec3(struct __sk_buff *ctx)
+{
+ bpf_set_exception_callback(exception_cb_rec3);
+ return 0;
+}
+
+static __noinline int exception_cb_bad_ret(u64 c)
+{
+ return 4242;
+}
+
+SEC("?fentry/bpf_check")
+__failure __msg("At program exit the register R0 has value")
+int reject_set_exception_cb_bad_ret(void *ctx)
+{
+ bpf_set_exception_callback(exception_cb_bad_ret);
+ return 0;
+}
+
+__noinline static int loop_cb1(u32 index, int *ctx)
+{
+ throw;
+ return 0;
+}
+
+__noinline static int loop_cb2(u32 index, int *ctx)
+{
+ throw;
+ return 0;
+}
+
+SEC("?tc")
+__failure __msg("cannot be called from callback")
+int reject_exception_throw_cb(struct __sk_buff *ctx)
+{
+ volatile int ret = 1;
+
+ bpf_loop(5, loop_cb1, NULL, 0);
+ return ret;
+}
+
+SEC("?tc")
+__failure __msg("cannot be called from callback")
+int exception_throw_cb_diff(struct __sk_buff *ctx)
+{
+ volatile int ret = 1;
+
+ if (ctx->protocol)
+ bpf_loop(5, loop_cb1, NULL, 0);
+ else
+ bpf_loop(5, loop_cb2, NULL, 0);
+ return ret;
+}
+
+char _license[] SEC("license") = "GPL";
Add selftests to cover success and failure cases of API usage, runtime behavior and invariants that need to be maintained for implementation correctness. Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com> --- .../selftests/bpf/prog_tests/exceptions.c | 272 +++++++++++ .../testing/selftests/bpf/progs/exceptions.c | 450 ++++++++++++++++++ .../selftests/bpf/progs/exceptions_ext.c | 42 ++ .../selftests/bpf/progs/exceptions_fail.c | 311 ++++++++++++ 4 files changed, 1075 insertions(+) create mode 100644 tools/testing/selftests/bpf/prog_tests/exceptions.c create mode 100644 tools/testing/selftests/bpf/progs/exceptions.c create mode 100644 tools/testing/selftests/bpf/progs/exceptions_ext.c create mode 100644 tools/testing/selftests/bpf/progs/exceptions_fail.c