summaryrefslogtreecommitdiff
path: root/tools/testing
diff options
context:
space:
mode:
authorDave Airlie <airlied@redhat.com>2026-08-20 10:58:44 +1000
committerDave Airlie <airlied@redhat.com>2026-08-20 10:58:44 +1000
commitc44e278ce02efd0c4be79a8eda1ea6885c1ce5ec (patch)
treeb6b7324a56fb1869a094da177d6a41f4d2efe8e0 /tools/testing
parent65b92bc671db7c222f8de1146c88e40fe8764d95 (diff)
parent8d3ae59288f1e7d58d76558a6ee96d533bc5019f (diff)
downloadlinux-next-c44e278ce02efd0c4be79a8eda1ea6885c1ce5ec.tar.gz
linux-next-c44e278ce02efd0c4be79a8eda1ea6885c1ce5ec.zip
BackMerge tag 'v7.2' into drm-next
Linux 7.2 There was a lot of conflicts this round between fixes and next, and I'd like to get the merge resolutions that we have in drm-tip. Signed-off-by: Dave Airlie <airlied@redhat.com>
Diffstat (limited to 'tools/testing')
-rw-r--r--tools/testing/selftests/Makefile1
-rw-r--r--tools/testing/selftests/alsa/mixer-test.c5
-rw-r--r--tools/testing/selftests/arm64/gcs/libc-gcs.c2
-rw-r--r--tools/testing/selftests/arm64/pauth/pac.c2
-rw-r--r--tools/testing/selftests/bpf/config1
-rw-r--r--tools/testing/selftests/bpf/prog_tests/kfunc_implicit_args_tracing.c36
-rw-r--r--tools/testing/selftests/bpf/prog_tests/raw_tp_writable_reject_bad_access.c57
-rw-r--r--tools/testing/selftests/bpf/prog_tests/raw_tp_writable_reject_nbd_invalid.c43
-rw-r--r--tools/testing/selftests/bpf/prog_tests/sockmap_basic.c45
-rw-r--r--tools/testing/selftests/bpf/prog_tests/sockmap_listen.c21
-rw-r--r--tools/testing/selftests/bpf/prog_tests/tc_qevent.c113
-rw-r--r--tools/testing/selftests/bpf/prog_tests/test_xsk.c50
-rw-r--r--tools/testing/selftests/bpf/prog_tests/verifier.c2
-rw-r--r--tools/testing/selftests/bpf/progs/dynptr_fail.c30
-rw-r--r--tools/testing/selftests/bpf/progs/kfunc_implicit_args_tracing.c77
-rw-r--r--tools/testing/selftests/bpf/progs/mem_rdonly_untrusted.c17
-rw-r--r--tools/testing/selftests/bpf/progs/test_tc_qevent.c23
-rw-r--r--tools/testing/selftests/bpf/progs/verifier_basic_stack.c41
-rw-r--r--tools/testing/selftests/bpf/progs/verifier_global_subprogs.c10
-rw-r--r--tools/testing/selftests/bpf/progs/verifier_ptr_to_buf.c27
-rw-r--r--tools/testing/selftests/bpf/progs/verifier_raw_tp_writable.c16
-rw-r--r--tools/testing/selftests/bpf/test_maps.c13
-rw-r--r--tools/testing/selftests/clone3/clone3_set_tid.c2
-rw-r--r--tools/testing/selftests/drivers/net/config3
-rwxr-xr-xtools/testing/selftests/drivers/net/netconsole/netcons_cmdline.sh2
-rwxr-xr-xtools/testing/selftests/drivers/net/netconsole/netcons_fragmented_msg.sh4
-rwxr-xr-xtools/testing/selftests/drivers/net/netconsole/netcons_resume.sh5
-rwxr-xr-xtools/testing/selftests/drivers/net/netconsole/netcons_sysdata.sh2
-rw-r--r--tools/testing/selftests/drivers/net/settings1
-rw-r--r--tools/testing/selftests/drivers/ntsync/config2
-rw-r--r--tools/testing/selftests/filesystems/fclog.c4
-rw-r--r--tools/testing/selftests/filesystems/fscontext_ns/Makefile10
-rw-r--r--tools/testing/selftests/filesystems/fscontext_ns/fscontext_ns_test.c239
-rw-r--r--tools/testing/selftests/filesystems/fuse/Makefile10
-rw-r--r--tools/testing/selftests/filesystems/fuse/fuse_acl_cache_test.c347
-rwxr-xr-xtools/testing/selftests/ftrace/ftracetest1
-rw-r--r--tools/testing/selftests/ftrace/test.d/dynevent/add_remove_eprobe.tc16
-rw-r--r--tools/testing/selftests/ftrace/test.d/dynevent/add_remove_fprobe_module.tc27
-rw-r--r--tools/testing/selftests/ftrace/test.d/dynevent/add_remove_uprobe.tc27
-rw-r--r--tools/testing/selftests/ftrace/test.d/trigger/trigger-hist-poll.tc4
-rw-r--r--tools/testing/selftests/gpio/.gitignore1
-rw-r--r--tools/testing/selftests/hid/Makefile2
-rw-r--r--tools/testing/selftests/hid/hid_bpf.c36
-rw-r--r--tools/testing/selftests/hid/progs/hid.c15
-rw-r--r--tools/testing/selftests/hid/tests/test_multitouch.py114
-rw-r--r--tools/testing/selftests/kvm/Makefile.kvm1
-rw-r--r--tools/testing/selftests/kvm/arm64/mmio_sign_ext.c255
-rw-r--r--tools/testing/selftests/kvm/x86/sev_init2_tests.c16
-rw-r--r--tools/testing/selftests/kvm/x86/sev_migrate_tests.c47
-rw-r--r--tools/testing/selftests/kvm/x86/sev_smoke_test.c9
-rw-r--r--tools/testing/selftests/landlock/net_test.c97
-rw-r--r--tools/testing/selftests/landlock/scoped_signal_test.c67
-rw-r--r--tools/testing/selftests/lkdtm/tests.txt2
-rw-r--r--tools/testing/selftests/mm/hmm-tests.c1
-rwxr-xr-xtools/testing/selftests/mm/ksft_process_madv.sh2
-rw-r--r--tools/testing/selftests/mm/mlock-random-test.c2
-rw-r--r--tools/testing/selftests/mm/pagemap_ioctl.c68
-rw-r--r--tools/testing/selftests/net/af_unix/.gitignore1
-rw-r--r--tools/testing/selftests/net/af_unix/Makefile1
-rw-r--r--tools/testing/selftests/net/af_unix/config1
-rw-r--r--tools/testing/selftests/net/af_unix/unix_listen.c187
-rwxr-xr-xtools/testing/selftests/net/bridge_vlan_dump.sh23
-rw-r--r--tools/testing/selftests/net/lib.sh25
-rwxr-xr-xtools/testing/selftests/net/mptcp/mptcp_join.sh4
-rwxr-xr-xtools/testing/selftests/net/mptcp/userspace_pm.sh2
-rwxr-xr-xtools/testing/selftests/net/netfilter/nft_flowtable.sh69
-rw-r--r--tools/testing/selftests/net/openvswitch/config16
-rw-r--r--tools/testing/selftests/net/ovpn/config2
-rw-r--r--tools/testing/selftests/net/ovpn/ovpn-cli.c4
-rw-r--r--tools/testing/selftests/net/ovpn/settings1
-rw-r--r--tools/testing/selftests/net/packetdrill/tcp_rfc5961_rst-syn-recv.pkt61
-rw-r--r--tools/testing/selftests/net/tcp_mmap.c4
-rw-r--r--tools/testing/selftests/net/tls.c169
-rw-r--r--tools/testing/selftests/net/tun.c8
-rw-r--r--tools/testing/selftests/pidfd/pidfd_file_handle_test.c1
-rw-r--r--tools/testing/selftests/riscv/vector/validate_v_ptrace.c15
-rw-r--r--tools/testing/selftests/rseq/Makefile6
-rw-r--r--tools/testing/selftests/sched_ext/Makefile1
-rw-r--r--tools/testing/selftests/sched_ext/nohz_tick.bpf.c65
-rw-r--r--tools/testing/selftests/sched_ext/nohz_tick.c347
-rw-r--r--tools/testing/selftests/sched_ext/numa.bpf.c13
-rw-r--r--tools/testing/selftests/seccomp/seccomp_bpf.c3
-rw-r--r--tools/testing/selftests/tc-testing/tc-tests/actions/ct.json40
-rw-r--r--tools/testing/selftests/tc-testing/tc-tests/qdiscs/depth.json74
-rw-r--r--tools/testing/selftests/user_events/abi_test.c29
-rw-r--r--tools/testing/selftests/user_events/perf_test.c26
-rw-r--r--tools/testing/vsock/vsock_test.c87
87 files changed, 3152 insertions, 206 deletions
diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile
index 8d4db2241cc2..b622052ec3e9 100644
--- a/tools/testing/selftests/Makefile
+++ b/tools/testing/selftests/Makefile
@@ -42,6 +42,7 @@ TARGETS += filesystems/fuse
TARGETS += filesystems/move_mount
TARGETS += filesystems/empty_mntns
TARGETS += filesystems/fsmount_ns
+TARGETS += filesystems/fscontext_ns
TARGETS += firmware
TARGETS += fpu
TARGETS += ftrace
diff --git a/tools/testing/selftests/alsa/mixer-test.c b/tools/testing/selftests/alsa/mixer-test.c
index d4f845c32804..a329f901c5ed 100644
--- a/tools/testing/selftests/alsa/mixer-test.c
+++ b/tools/testing/selftests/alsa/mixer-test.c
@@ -84,6 +84,7 @@ static void find_controls(void)
if (err < 0) {
ksft_print_msg("Failed to get hctl for card %d: %s\n",
card, snd_strerror(err));
+ free(card_data);
goto next_card;
}
@@ -339,9 +340,9 @@ static bool ctl_value_index_valid(struct ctl_data *ctl,
}
if (int64_val > snd_ctl_elem_info_get_max64(ctl->info)) {
- ksft_print_msg("%s.%d value %lld more than maximum %ld\n",
+ ksft_print_msg("%s.%d value %lld more than maximum %lld\n",
ctl->name, index, int64_val,
- snd_ctl_elem_info_get_max(ctl->info));
+ snd_ctl_elem_info_get_max64(ctl->info));
return false;
}
diff --git a/tools/testing/selftests/arm64/gcs/libc-gcs.c b/tools/testing/selftests/arm64/gcs/libc-gcs.c
index 72e82bfbecc9..ddb0b2b20155 100644
--- a/tools/testing/selftests/arm64/gcs/libc-gcs.c
+++ b/tools/testing/selftests/arm64/gcs/libc-gcs.c
@@ -130,7 +130,7 @@ TEST(gcs_find_terminator)
* We can access a GCS via ptrace
*
* This could usefully have a fixture but note that each test is
- * fork()ed into a new child whcih causes issues. Might be better to
+ * fork()ed into a new child which causes issues. Might be better to
* lift at least some of this out into a separate, non-harness, test
* program.
*/
diff --git a/tools/testing/selftests/arm64/pauth/pac.c b/tools/testing/selftests/arm64/pauth/pac.c
index 67d138057707..f4b859c75a5a 100644
--- a/tools/testing/selftests/arm64/pauth/pac.c
+++ b/tools/testing/selftests/arm64/pauth/pac.c
@@ -292,7 +292,7 @@ TEST(single_thread_different_keys)
/*
* fork() does not change keys. Only exec() does so call a worker program.
- * Its only job is to sign a value and report back the resutls
+ * Its only job is to sign a value and report back the results
*/
TEST(exec_changed_keys)
{
diff --git a/tools/testing/selftests/bpf/config b/tools/testing/selftests/bpf/config
index adb25146e88c..ea7044f30adc 100644
--- a/tools/testing/selftests/bpf/config
+++ b/tools/testing/selftests/bpf/config
@@ -82,6 +82,7 @@ CONFIG_NET_SCH_BPF=y
CONFIG_NET_SCH_FQ=y
CONFIG_NET_SCH_INGRESS=y
CONFIG_NET_SCH_HTB=y
+CONFIG_NET_SCH_RED=y
CONFIG_NET_SCHED=y
CONFIG_NETDEVSIM=y
CONFIG_NETFILTER=y
diff --git a/tools/testing/selftests/bpf/prog_tests/kfunc_implicit_args_tracing.c b/tools/testing/selftests/bpf/prog_tests/kfunc_implicit_args_tracing.c
new file mode 100644
index 000000000000..61cc5aaba025
--- /dev/null
+++ b/tools/testing/selftests/bpf/prog_tests/kfunc_implicit_args_tracing.c
@@ -0,0 +1,36 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */
+
+#include <test_progs.h>
+#include "kfunc_implicit_args_tracing.skel.h"
+
+void test_kfunc_implicit_args_tracing(void)
+{
+ struct kfunc_implicit_args_tracing *skel;
+ LIBBPF_OPTS(bpf_test_run_opts, topts);
+ int err, fd;
+
+ skel = kfunc_implicit_args_tracing__open_and_load();
+ if (!ASSERT_OK_PTR(skel, "open_and_load"))
+ return;
+
+ err = kfunc_implicit_args_tracing__attach(skel);
+ if (!ASSERT_OK(err, "attach"))
+ goto cleanup;
+
+ fd = bpf_program__fd(skel->progs.trigger_implicit_arg);
+ err = bpf_prog_test_run_opts(fd, &topts);
+ if (!ASSERT_OK(err, "test_run"))
+ goto cleanup;
+
+ ASSERT_EQ(topts.retval, 5, "kfunc_retval");
+ ASSERT_EQ(skel->bss->fentry_arg_cnt, 2, "fentry_arg_cnt");
+ ASSERT_NEQ(skel->bss->fentry_aux_arg, 0, "fentry_aux_arg");
+ ASSERT_EQ(skel->bss->fentry_result, 1, "fentry_result");
+ ASSERT_EQ(skel->bss->fexit_arg_cnt, 2, "fexit_arg_cnt");
+ ASSERT_NEQ(skel->bss->fexit_aux_arg, 0, "fexit_aux_arg");
+ ASSERT_EQ(skel->bss->fexit_result, 1, "fexit_result");
+
+cleanup:
+ kfunc_implicit_args_tracing__destroy(skel);
+}
diff --git a/tools/testing/selftests/bpf/prog_tests/raw_tp_writable_reject_bad_access.c b/tools/testing/selftests/bpf/prog_tests/raw_tp_writable_reject_bad_access.c
new file mode 100644
index 000000000000..b8538fc4fc3f
--- /dev/null
+++ b/tools/testing/selftests/bpf/prog_tests/raw_tp_writable_reject_bad_access.c
@@ -0,0 +1,57 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <test_progs.h>
+#include "test_kmods/bpf_testmod.h"
+#include "bpf_util.h"
+
+static void check_attach_reject(const struct bpf_insn *program, size_t prog_len)
+{
+ LIBBPF_OPTS(bpf_prog_load_opts, opts);
+ char error[4096];
+ int bpf_fd, tp_fd;
+
+ opts.log_level = 2;
+ opts.log_buf = error;
+ opts.log_size = sizeof(error);
+
+ bpf_fd = bpf_prog_load(BPF_PROG_TYPE_RAW_TRACEPOINT_WRITABLE, NULL, "GPL v2",
+ program, prog_len, &opts);
+ if (!ASSERT_GE(bpf_fd, 0, "prog_load"))
+ return;
+
+ tp_fd = bpf_raw_tracepoint_open("bpf_testmod_test_writable_bare_tp", bpf_fd);
+ ASSERT_EQ(tp_fd, -EINVAL, "bpf_raw_tracepoint_open");
+ if (tp_fd >= 0)
+ close(tp_fd);
+
+ close(bpf_fd);
+}
+
+void test_raw_tp_writable_reject_bad_access(void)
+{
+ const struct bpf_insn program[] = {
+ /* r6 is our tp buffer */
+ BPF_LDX_MEM(BPF_DW, BPF_REG_6, BPF_REG_1, 0),
+ /* one byte beyond the end of the writable context */
+ BPF_LDX_MEM(BPF_B, BPF_REG_0, BPF_REG_6,
+ sizeof(struct bpf_testmod_test_writable_ctx)),
+ BPF_EXIT_INSN(),
+ };
+
+ const struct bpf_insn negative_var_off_program[] = {
+ BPF_LDX_MEM(BPF_DW, BPF_REG_6, BPF_REG_1, 0),
+ /* make var_off negative, but keep the effective access offset non-negative */
+ BPF_ALU64_IMM(BPF_ADD, BPF_REG_6, -8),
+ /* one byte beyond the end of the writable context */
+ BPF_LDX_MEM(BPF_B, BPF_REG_0, BPF_REG_6,
+ sizeof(struct bpf_testmod_test_writable_ctx) + 8),
+ BPF_EXIT_INSN(),
+ };
+
+ if (test__start_subtest("past_end"))
+ check_attach_reject(program, ARRAY_SIZE(program));
+
+ if (test__start_subtest("negative_var_off_past_end"))
+ check_attach_reject(negative_var_off_program,
+ ARRAY_SIZE(negative_var_off_program));
+}
diff --git a/tools/testing/selftests/bpf/prog_tests/raw_tp_writable_reject_nbd_invalid.c b/tools/testing/selftests/bpf/prog_tests/raw_tp_writable_reject_nbd_invalid.c
deleted file mode 100644
index 216b0dfac0fe..000000000000
--- a/tools/testing/selftests/bpf/prog_tests/raw_tp_writable_reject_nbd_invalid.c
+++ /dev/null
@@ -1,43 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0
-
-#include <test_progs.h>
-#include <linux/nbd.h>
-#include "bpf_util.h"
-
-void test_raw_tp_writable_reject_nbd_invalid(void)
-{
- __u32 duration = 0;
- char error[4096];
- int bpf_fd = -1, tp_fd = -1;
-
- const struct bpf_insn program[] = {
- /* r6 is our tp buffer */
- BPF_LDX_MEM(BPF_DW, BPF_REG_6, BPF_REG_1, 0),
- /* one byte beyond the end of the nbd_request struct */
- BPF_LDX_MEM(BPF_B, BPF_REG_0, BPF_REG_6,
- sizeof(struct nbd_request)),
- BPF_EXIT_INSN(),
- };
-
- LIBBPF_OPTS(bpf_prog_load_opts, opts,
- .log_level = 2,
- .log_buf = error,
- .log_size = sizeof(error),
- );
-
- bpf_fd = bpf_prog_load(BPF_PROG_TYPE_RAW_TRACEPOINT_WRITABLE, NULL, "GPL v2",
- program, ARRAY_SIZE(program),
- &opts);
- if (CHECK(bpf_fd < 0, "bpf_raw_tracepoint_writable load",
- "failed: %d errno %d\n", bpf_fd, errno))
- return;
-
- tp_fd = bpf_raw_tracepoint_open("nbd_send_request", bpf_fd);
- if (CHECK(tp_fd >= 0, "bpf_raw_tracepoint_writable open",
- "erroneously succeeded\n"))
- goto out_bpffd;
-
- close(tp_fd);
-out_bpffd:
- close(bpf_fd);
-}
diff --git a/tools/testing/selftests/bpf/prog_tests/sockmap_basic.c b/tools/testing/selftests/bpf/prog_tests/sockmap_basic.c
index cb3229711f93..e5fc038d747b 100644
--- a/tools/testing/selftests/bpf/prog_tests/sockmap_basic.c
+++ b/tools/testing/selftests/bpf/prog_tests/sockmap_basic.c
@@ -853,7 +853,7 @@ static void test_sockmap_many_socket(void)
return;
}
- udp = xsocket(AF_INET, SOCK_DGRAM | SOCK_NONBLOCK, 0);
+ udp = socket_loopback(AF_INET, SOCK_DGRAM | SOCK_NONBLOCK);
if (udp < 0) {
close(dgram);
close(tcp);
@@ -922,7 +922,7 @@ static void test_sockmap_many_maps(void)
return;
}
- udp = xsocket(AF_INET, SOCK_DGRAM | SOCK_NONBLOCK, 0);
+ udp = socket_loopback(AF_INET, SOCK_DGRAM | SOCK_NONBLOCK);
if (udp < 0) {
close(dgram);
close(tcp);
@@ -993,7 +993,7 @@ static void test_sockmap_same_sock(void)
return;
}
- udp = xsocket(AF_INET, SOCK_DGRAM | SOCK_NONBLOCK, 0);
+ udp = socket_loopback(AF_INET, SOCK_DGRAM | SOCK_NONBLOCK);
if (udp < 0) {
close(dgram);
close(tcp);
@@ -1373,6 +1373,43 @@ end:
test_sockmap_pass_prog__destroy(skel);
}
+/* A socket in a sockmap without a verdict program keeps its ingress data
+ * in sk_receive_queue: FIONREAD must account for it.
+ */
+static void test_sockmap_no_verdict_fionread(void)
+{
+ int err, map, zero = 0, sent, avail;
+ int c0 = -1, c1 = -1, p0 = -1, p1 = -1;
+ struct test_sockmap_pass_prog *skel;
+ char buf[256] = "0123456789";
+
+ skel = test_sockmap_pass_prog__open_and_load();
+ if (!ASSERT_OK_PTR(skel, "open_and_load"))
+ return;
+ map = bpf_map__fd(skel->maps.sock_map_rx);
+
+ err = create_socket_pairs(AF_INET, SOCK_STREAM, &c0, &c1, &p0, &p1);
+ if (!ASSERT_OK(err, "create_socket_pairs()"))
+ goto out;
+
+ err = bpf_map_update_elem(map, &zero, &c1, BPF_NOEXIST);
+ if (!ASSERT_OK(err, "bpf_map_update_elem(c1)"))
+ goto out_close;
+
+ sent = xsend(p1, &buf, sizeof(buf), 0);
+ ASSERT_EQ(sent, sizeof(buf), "xsend(p1)");
+ avail = wait_for_fionread(c1, sizeof(buf), IO_TIMEOUT_SEC);
+ ASSERT_EQ(avail, sizeof(buf), "ioctl(FIONREAD)");
+
+out_close:
+ close(c0);
+ close(p0);
+ close(c1);
+ close(p1);
+out:
+ test_sockmap_pass_prog__destroy(skel);
+}
+
void test_sockmap_basic(void)
{
if (test__start_subtest("sockmap create_update_free"))
@@ -1415,6 +1452,8 @@ void test_sockmap_basic(void)
test_sockmap_skb_verdict_shutdown();
if (test__start_subtest("sockmap skb_verdict fionread"))
test_sockmap_skb_verdict_fionread(true);
+ if (test__start_subtest("sockmap no_verdict fionread"))
+ test_sockmap_no_verdict_fionread();
if (test__start_subtest("sockmap skb_verdict fionread on drop"))
test_sockmap_skb_verdict_fionread(false);
if (test__start_subtest("sockmap skb_verdict change tail"))
diff --git a/tools/testing/selftests/bpf/prog_tests/sockmap_listen.c b/tools/testing/selftests/bpf/prog_tests/sockmap_listen.c
index cc0c68bab907..1c96a3cf4b97 100644
--- a/tools/testing/selftests/bpf/prog_tests/sockmap_listen.c
+++ b/tools/testing/selftests/bpf/prog_tests/sockmap_listen.c
@@ -53,8 +53,8 @@ static void test_insert_opened(struct test_sockmap_listen *skel __always_unused,
int family, int sotype, int mapfd)
{
u32 key = 0;
- u64 value;
int err, s;
+ u64 value;
s = xsocket(family, sotype, 0);
if (s == -1)
@@ -63,11 +63,8 @@ static void test_insert_opened(struct test_sockmap_listen *skel __always_unused,
errno = 0;
value = s;
err = bpf_map_update_elem(mapfd, &key, &value, BPF_NOEXIST);
- if (sotype == SOCK_STREAM) {
- if (!err || errno != EOPNOTSUPP)
- FAIL_ERRNO("map_update: expected EOPNOTSUPP");
- } else if (err)
- FAIL_ERRNO("map_update: expected success");
+ ASSERT_ERR(err, "map_update");
+ ASSERT_EQ(errno, EOPNOTSUPP, "errno");
xclose(s);
}
@@ -77,8 +74,8 @@ static void test_insert_bound(struct test_sockmap_listen *skel __always_unused,
struct sockaddr_storage addr;
socklen_t len = 0;
u32 key = 0;
- u64 value;
int err, s;
+ u64 value;
init_addr_loopback(family, &addr, &len);
@@ -93,8 +90,12 @@ static void test_insert_bound(struct test_sockmap_listen *skel __always_unused,
errno = 0;
value = s;
err = bpf_map_update_elem(mapfd, &key, &value, BPF_NOEXIST);
- if (!err || errno != EOPNOTSUPP)
- FAIL_ERRNO("map_update: expected EOPNOTSUPP");
+ if (sotype == SOCK_STREAM) {
+ ASSERT_ERR(err, "map_update");
+ ASSERT_EQ(errno, EOPNOTSUPP, "errno");
+ } else {
+ ASSERT_OK(err, "map_update");
+ }
close:
xclose(s);
}
@@ -1289,7 +1290,7 @@ static void test_ops(struct test_sockmap_listen *skel, struct bpf_map *map,
/* insert */
TEST(test_insert_invalid),
TEST(test_insert_opened),
- TEST(test_insert_bound, SOCK_STREAM),
+ TEST(test_insert_bound),
TEST(test_insert),
/* delete */
TEST(test_delete_after_insert),
diff --git a/tools/testing/selftests/bpf/prog_tests/tc_qevent.c b/tools/testing/selftests/bpf/prog_tests/tc_qevent.c
new file mode 100644
index 000000000000..67e1d17567ab
--- /dev/null
+++ b/tools/testing/selftests/bpf/prog_tests/tc_qevent.c
@@ -0,0 +1,113 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <test_progs.h>
+#include <network_helpers.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <unistd.h>
+#include <string.h>
+
+#include "test_tc_qevent.skel.h"
+
+#define NS_TX "tc_qevent_tx"
+#define NS_RX "tc_qevent_rx"
+#define IP_TX "10.255.0.1"
+#define IP_RX "10.255.0.2"
+#define PIN_PATH "/sys/fs/bpf/tc_qevent_redirect"
+
+static void blast_udp(void)
+{
+ struct sockaddr_in dst = {};
+ char buf[1400] = {};
+ int fd, i;
+
+ fd = socket(AF_INET, SOCK_DGRAM, 0);
+ if (!ASSERT_GE(fd, 0, "udp socket"))
+ return;
+
+ dst.sin_family = AF_INET;
+ dst.sin_port = htons(12345);
+ inet_pton(AF_INET, IP_RX, &dst.sin_addr);
+
+ /*
+ * Push far more than the RED queue can hold. Once qavg crosses qth_min
+ * every further packet hits the congestion_drop / early_drop qevent.
+ */
+ for (i = 0; i < 50000; i++)
+ sendto(fd, buf, sizeof(buf), MSG_DONTWAIT,
+ (struct sockaddr *)&dst, sizeof(dst));
+
+ close(fd);
+}
+
+static void run_qevent_redirect(struct bpf_program *prog, __u64 *counter)
+{
+ struct nstoken *tok = NULL;
+ int err;
+
+ SYS_NOFAIL("ip netns del %s", NS_TX);
+ SYS_NOFAIL("ip netns del %s", NS_RX);
+ unlink(PIN_PATH);
+
+ err = bpf_program__pin(prog, PIN_PATH);
+ if (!ASSERT_OK(err, "pin prog"))
+ return;
+
+ SYS(unpin, "ip netns add %s", NS_TX);
+ SYS(del_tx, "ip netns add %s", NS_RX);
+ SYS(del_rx, "ip -n %s link add veth0 type veth peer name veth1 netns %s", NS_TX, NS_RX);
+ SYS(del_rx, "ip -n %s addr add %s/24 dev veth0", NS_TX, IP_TX);
+ SYS(del_rx, "ip -n %s link set veth0 up", NS_TX);
+ SYS(del_rx, "ip -n %s addr add %s/24 dev veth1", NS_RX, IP_RX);
+ SYS(del_rx, "ip -n %s link set veth1 up", NS_RX);
+
+ tok = open_netns(NS_TX);
+ if (!ASSERT_OK_PTR(tok, "open_netns"))
+ goto del_rx;
+
+ SYS(close_ns, "tc qdisc add dev veth0 root handle 1: htb default 1");
+ SYS(close_ns, "tc class add dev veth0 parent 1: classid 1:1 htb rate 1mbit ceil 1mbit");
+
+ if (system("tc qdisc add dev veth0 parent 1:1 handle 11: red "
+ "limit 500000 avpkt 1000 probability 1 min 5000 max 6000 "
+ "burst 6 qevent early_drop block 10 2>/dev/null")) {
+ test__skip();
+ goto close_ns;
+ }
+
+ if (system("tc filter add block 10 bpf da object-pinned "
+ PIN_PATH " 2>/dev/null")) {
+ test__skip();
+ goto close_ns;
+ }
+
+ blast_udp();
+ ASSERT_GT(*counter, 0, "qevent classifier ran");
+close_ns:
+ close_netns(tok);
+del_rx:
+ SYS_NOFAIL("ip netns del %s", NS_RX);
+del_tx:
+ SYS_NOFAIL("ip netns del %s", NS_TX);
+unpin:
+ bpf_program__unpin(prog, PIN_PATH);
+}
+
+void test_tc_qevent(void)
+{
+ struct test_tc_qevent *skel;
+
+ skel = test_tc_qevent__open_and_load();
+ if (!ASSERT_OK_PTR(skel, "open_and_load"))
+ return;
+
+ if (test__start_subtest("redirect_verdict"))
+ run_qevent_redirect(skel->progs.qevent_redirect_verdict,
+ &skel->bss->verdict_calls);
+ if (test__start_subtest("redirect_helper"))
+ run_qevent_redirect(skel->progs.qevent_redirect_helper,
+ &skel->bss->helper_calls);
+
+ test_tc_qevent__destroy(skel);
+}
diff --git a/tools/testing/selftests/bpf/prog_tests/test_xsk.c b/tools/testing/selftests/bpf/prog_tests/test_xsk.c
index 6eb9096d084c..38ce6060b8fa 100644
--- a/tools/testing/selftests/bpf/prog_tests/test_xsk.c
+++ b/tools/testing/selftests/bpf/prog_tests/test_xsk.c
@@ -427,14 +427,14 @@ static u32 pkt_nb_frags(u32 frame_size, struct pkt_stream *pkt_stream, struct pk
}
/* Search for the end of the packet in verbatim mode */
- if (!pkt_continues(pkt->options) || !pkt->valid)
+ if (!pkt_continues(pkt->options))
return nb_frags;
next_frag = pkt_stream->current_pkt_nb;
pkt++;
while (next_frag++ < pkt_stream->nb_pkts) {
nb_frags++;
- if (!pkt_continues(pkt->options) || !pkt->valid)
+ if (!pkt_continues(pkt->options))
break;
pkt++;
}
@@ -665,11 +665,11 @@ static struct pkt_stream *__pkt_stream_generate_custom(struct ifobject *ifobj, s
if (!frame->valid || !pkt_continues(frame->options))
payload++;
} else {
- if (frame->valid)
+ if (frame->valid) {
len += frame->len;
- if (frame->valid && pkt_continues(frame->options))
- continue;
-
+ if (pkt_continues(frame->options))
+ continue;
+ }
pkt->pkt_nb = pkt_nb;
pkt->len = len;
pkt->valid = frame->valid;
@@ -1250,10 +1250,9 @@ static int __send_pkts(struct ifobject *ifobject, struct xsk_socket_info *xsk,
}
}
- if (pkt && pkt->valid) {
+ if (pkt && pkt->valid)
valid_pkts++;
- valid_frags += nb_frags;
- }
+ valid_frags += nb_frags;
}
pthread_mutex_lock(&pacing_mutex);
@@ -2099,13 +2098,16 @@ int testapp_invalid_desc_mb(struct test_spec *test)
{0, 0, 0, false, 0},
/* Invalid address in the second frame */
{0, XSK_UMEM__LARGE_FRAME_SIZE, 0, false, XDP_PKT_CONTD},
- {umem_sz, XSK_UMEM__LARGE_FRAME_SIZE, 0, false, XDP_PKT_CONTD},
+ {umem_sz * 2, XSK_UMEM__LARGE_FRAME_SIZE, 0, false, XDP_PKT_CONTD},
+ {0, MIN_PKT_SIZE, 0, false, 0},
/* Invalid len in the middle */
{0, XSK_UMEM__LARGE_FRAME_SIZE, 0, false, XDP_PKT_CONTD},
{0, XSK_UMEM__INVALID_FRAME_SIZE, 0, false, XDP_PKT_CONTD},
+ {0, MIN_PKT_SIZE, 0, false, 0},
/* Invalid options in the middle */
{0, XSK_UMEM__LARGE_FRAME_SIZE, 0, false, XDP_PKT_CONTD},
{0, XSK_UMEM__LARGE_FRAME_SIZE, 0, false, XSK_DESC__INVALID_OPTION},
+ {0, MIN_PKT_SIZE, 0, false, 0},
/* Transmit 2 frags, receive 3 */
{0, XSK_UMEM__MAX_FRAME_SIZE, 0, true, XDP_PKT_CONTD},
{0, XSK_UMEM__MAX_FRAME_SIZE, 0, true, 0},
@@ -2117,8 +2119,8 @@ int testapp_invalid_desc_mb(struct test_spec *test)
if (umem->unaligned_mode) {
/* Crossing a chunk boundary allowed */
- pkts[12].valid = true;
- pkts[13].valid = true;
+ pkts[15].valid = true;
+ pkts[16].valid = true;
}
test->mtu = MAX_ETH_JUMBO_SIZE;
@@ -2270,7 +2272,7 @@ int testapp_too_many_frags(struct test_spec *test)
max_frags += 1;
}
- pkts = calloc(2 * max_frags + 2, sizeof(struct pkt));
+ pkts = calloc(2 * max_frags + 3, sizeof(struct pkt));
if (!pkts)
return TEST_FAILURE;
@@ -2288,24 +2290,30 @@ int testapp_too_many_frags(struct test_spec *test)
}
pkts[max_frags].options = 0;
- /* An invalid packet with the max amount of frags but signals packet
- * continues on the last frag
- */
- for (i = max_frags + 1; i < 2 * max_frags + 1; i++) {
+ /* An invalid packet with the max + 1 amount of frags */
+ for (i = max_frags + 1; i < 2 * max_frags + 2; i++) {
pkts[i].len = MIN_PKT_SIZE;
pkts[i].options = XDP_PKT_CONTD;
- pkts[i].valid = false;
+ pkts[i].valid = true;
}
+ pkts[2 * max_frags + 1].options = 0;
/* Valid packet for synch */
- pkts[2 * max_frags + 1].len = MIN_PKT_SIZE;
- pkts[2 * max_frags + 1].valid = true;
+ pkts[2 * max_frags + 2].len = MIN_PKT_SIZE;
+ pkts[2 * max_frags + 2].valid = true;
- if (pkt_stream_generate_custom(test, pkts, 2 * max_frags + 2)) {
+ if (pkt_stream_generate_custom(test, pkts, 2 * max_frags + 3)) {
free(pkts);
return TEST_FAILURE;
}
+ /* The generated Tx stream must keep the too-big packet valid so that
+ * __send_pkts() accounts its descriptors in outstanding_tx. The Rx
+ * stream, however, must not expect this packet on the wire.
+ */
+ test->ifobj_rx->xsk->pkt_stream->pkts[2].valid = false;
+ test->ifobj_rx->xsk->pkt_stream->nb_valid_entries--;
+
ret = testapp_validate_traffic(test);
free(pkts);
return ret;
diff --git a/tools/testing/selftests/bpf/prog_tests/verifier.c b/tools/testing/selftests/bpf/prog_tests/verifier.c
index 8a3d69e2453c..be97f6887f0e 100644
--- a/tools/testing/selftests/bpf/prog_tests/verifier.c
+++ b/tools/testing/selftests/bpf/prog_tests/verifier.c
@@ -78,6 +78,7 @@
#include "verifier_precision.skel.h"
#include "verifier_prevent_map_lookup.skel.h"
#include "verifier_private_stack.skel.h"
+#include "verifier_ptr_to_buf.skel.h"
#include "verifier_raw_stack.skel.h"
#include "verifier_raw_tp_writable.skel.h"
#include "verifier_reg_equal.skel.h"
@@ -230,6 +231,7 @@ void test_verifier_or_jmp32_k(void) { RUN(verifier_or_jmp32_k); }
void test_verifier_precision(void) { RUN(verifier_precision); }
void test_verifier_prevent_map_lookup(void) { RUN(verifier_prevent_map_lookup); }
void test_verifier_private_stack(void) { RUN(verifier_private_stack); }
+void test_verifier_ptr_to_buf(void) { RUN(verifier_ptr_to_buf); }
void test_verifier_raw_stack(void) { RUN(verifier_raw_stack); }
void test_verifier_raw_tp_writable(void) { RUN(verifier_raw_tp_writable); }
void test_verifier_reg_equal(void) { RUN(verifier_reg_equal); }
diff --git a/tools/testing/selftests/bpf/progs/dynptr_fail.c b/tools/testing/selftests/bpf/progs/dynptr_fail.c
index 344fb2aa0813..29c6361d8820 100644
--- a/tools/testing/selftests/bpf/progs/dynptr_fail.c
+++ b/tools/testing/selftests/bpf/progs/dynptr_fail.c
@@ -1635,6 +1635,36 @@ static int callback(__u32 index, void *data)
return 0;
}
+/* A commuted add should preserve the parent id of a dynptr data slice. */
+SEC("?raw_tp")
+__failure __msg("invalid mem access 'scalar'")
+int dynptr_slice_commuted_invalidate(void *ctx)
+{
+ struct bpf_dynptr ptr;
+ __u32 *slice, *derived;
+
+ bpf_ringbuf_reserve_dynptr(&ringbuf, sizeof(__u32), 0, &ptr);
+
+ slice = bpf_dynptr_data(&ptr, 0, sizeof(__u32));
+ if (!slice)
+ goto done;
+
+ asm volatile ("%[dst] = 0;"
+ "%[dst] += %[src];"
+ "%[src] = 0;"
+ : [dst]"=&r"(derived), [src]"+r"(slice)
+ :
+ : "memory");
+
+ bpf_ringbuf_discard_dynptr(&ptr, 0);
+ val = *derived;
+ return 0;
+
+done:
+ bpf_ringbuf_discard_dynptr(&ptr, 0);
+ return 0;
+}
+
/* If the dynptr is written into in a callback function, its data
* slices should be invalidated as well.
*/
diff --git a/tools/testing/selftests/bpf/progs/kfunc_implicit_args_tracing.c b/tools/testing/selftests/bpf/progs/kfunc_implicit_args_tracing.c
new file mode 100644
index 000000000000..995f8b8b5b9e
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/kfunc_implicit_args_tracing.c
@@ -0,0 +1,77 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */
+
+#include <vmlinux.h>
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_tracing.h>
+#include <errno.h>
+
+extern int bpf_kfunc_implicit_arg(int a) __weak __ksym;
+
+char _license[] SEC("license") = "GPL";
+
+/* Shared arg checks; reports arg count and aux, returns 1 on success. */
+static __always_inline __u64
+check_implicit_args(void *ctx, __u64 *arg_cnt, __u64 *aux_arg)
+{
+ __u64 a = 0, aux = 0, z = 0;
+ __u64 result;
+ __s64 err;
+
+ *arg_cnt = bpf_get_func_arg_cnt(ctx);
+ result = *arg_cnt == 2;
+
+ err = bpf_get_func_arg(ctx, 0, &a);
+ result &= err == 0 && (int)a == 5;
+
+ err = bpf_get_func_arg(ctx, 1, &aux);
+ *aux_arg = aux;
+ result &= err == 0 && aux != 0;
+
+ err = bpf_get_func_arg(ctx, 2, &z);
+ result &= err == -EINVAL;
+
+ return result;
+}
+
+__u64 fentry_result;
+__u64 fentry_arg_cnt;
+__u64 fentry_aux_arg;
+
+SEC("fentry/bpf_kfunc_implicit_arg")
+int BPF_PROG(trace_implicit_arg_fentry)
+{
+ __u64 ret = 0;
+ __s64 err;
+
+ fentry_result = check_implicit_args(ctx, &fentry_arg_cnt, &fentry_aux_arg);
+
+ err = bpf_get_func_ret(ctx, &ret);
+ fentry_result &= err == -EOPNOTSUPP;
+
+ return 0;
+}
+
+__u64 fexit_result;
+__u64 fexit_arg_cnt;
+__u64 fexit_aux_arg;
+
+SEC("fexit/bpf_kfunc_implicit_arg")
+int BPF_PROG(trace_implicit_arg_fexit)
+{
+ __u64 ret = 0;
+ __s64 err;
+
+ fexit_result = check_implicit_args(ctx, &fexit_arg_cnt, &fexit_aux_arg);
+
+ err = bpf_get_func_ret(ctx, &ret);
+ fexit_result &= err == 0 && ret == 5;
+
+ return 0;
+}
+
+SEC("syscall")
+int trigger_implicit_arg(void *ctx)
+{
+ return bpf_kfunc_implicit_arg(5);
+}
diff --git a/tools/testing/selftests/bpf/progs/mem_rdonly_untrusted.c b/tools/testing/selftests/bpf/progs/mem_rdonly_untrusted.c
index 5b4453747c23..f166fff8f217 100644
--- a/tools/testing/selftests/bpf/progs/mem_rdonly_untrusted.c
+++ b/tools/testing/selftests/bpf/progs/mem_rdonly_untrusted.c
@@ -226,4 +226,21 @@ int null_check(void *ctx)
return 0;
}
+SEC("socket")
+__success
+__retval(1)
+int ldx_is_ok_commuted_addr(void *ctx)
+{
+ int v, *p, *derived;
+
+ v = 1;
+ p = bpf_rdonly_cast(&v, 0);
+ asm volatile ("%[dst] = 0;"
+ "%[dst] += %[src];"
+ : [dst]"=&r"(derived)
+ : [src]"r"(p)
+ : "memory");
+ return *derived;
+}
+
char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/test_tc_qevent.c b/tools/testing/selftests/bpf/progs/test_tc_qevent.c
new file mode 100644
index 000000000000..1529c111f4aa
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/test_tc_qevent.c
@@ -0,0 +1,23 @@
+// SPDX-License-Identifier: GPL-2.0
+#include "vmlinux.h"
+#include <bpf/bpf_helpers.h>
+
+int redirect_ifindex = 1;
+__u64 verdict_calls = 0;
+__u64 helper_calls = 0;
+
+SEC("tc")
+int qevent_redirect_verdict(struct __sk_buff *skb)
+{
+ __sync_fetch_and_add(&verdict_calls, 1);
+ return TCX_REDIRECT;
+}
+
+SEC("tc")
+int qevent_redirect_helper(struct __sk_buff *skb)
+{
+ __sync_fetch_and_add(&helper_calls, 1);
+ return bpf_redirect(redirect_ifindex, 0);
+}
+
+char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/verifier_basic_stack.c b/tools/testing/selftests/bpf/progs/verifier_basic_stack.c
index fb62e09f2114..d3df7a9f1d8c 100644
--- a/tools/testing/selftests/bpf/progs/verifier_basic_stack.c
+++ b/tools/testing/selftests/bpf/progs/verifier_basic_stack.c
@@ -97,4 +97,45 @@ __naked void misaligned_read_from_stack(void)
" ::: __clobber_all);
}
+SEC("socket")
+__description("stack pointer arithmetic preserves frame number")
+__failure __msg("R7 invalid mem access 'scalar'")
+__naked void stack_ptr_arith_preserves_frameno(void)
+{
+ asm volatile ("\
+ r3 = 0; \
+ *(u64 *)(r10 - 8) = r3; \
+ r1 = %[map_hash_8b] ll; \
+ r2 = r10; \
+ r2 += -8; \
+ call %[bpf_map_lookup_elem]; \
+ if r0 != 0 goto +2; \
+ r0 = 0; \
+ exit; \
+ r1 = r0; \
+ r2 = 0; \
+ r3 = 0; \
+ call stack_ptr_arith_preserves_frameno_subprog;\
+ r0 = 0; \
+ exit; \
+ ":
+ : __imm(bpf_map_lookup_elem),
+ __imm_addr(map_hash_8b)
+ : __clobber_all);
+}
+
+static __used __naked void stack_ptr_arith_preserves_frameno_subprog(void)
+{
+ asm volatile ("\
+ *(u64 *)(r10 - 8) = r1; \
+ r6 = -8; \
+ r6 += r10; \
+ *(u64 *)(r6 + 0) = r2; \
+ r7 = *(u64 *)(r10 - 8); \
+ *(u64 *)(r7 + 0) = r3; \
+ r0 = 0; \
+ exit; \
+ "::: __clobber_all);
+}
+
char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/verifier_global_subprogs.c b/tools/testing/selftests/bpf/progs/verifier_global_subprogs.c
index 75a2e3f48d0f..67dc352addfd 100644
--- a/tools/testing/selftests/bpf/progs/verifier_global_subprogs.c
+++ b/tools/testing/selftests/bpf/progs/verifier_global_subprogs.c
@@ -185,6 +185,16 @@ int arg_tag_nonnull_ptr_good(void *ctx)
return subprog_nonnull_ptr_good(&x, &y);
}
+SEC("?raw_tp")
+__failure __log_level(2)
+__msg("R1 is expected to be non-NULL")
+int arg_tag_nonnull_ptr_null_bad(void *ctx)
+{
+ int y = 74;
+
+ return subprog_nonnull_ptr_good(NULL, &y);
+}
+
/* this global subprog can be now called from many types of entry progs, each
* with different context type
*/
diff --git a/tools/testing/selftests/bpf/progs/verifier_ptr_to_buf.c b/tools/testing/selftests/bpf/progs/verifier_ptr_to_buf.c
new file mode 100644
index 000000000000..12cf24db46a0
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/verifier_ptr_to_buf.c
@@ -0,0 +1,27 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <vmlinux.h>
+#include <bpf/bpf_helpers.h>
+#include "bpf_misc.h"
+
+SEC("iter/bpf_map_elem")
+__description("PTR_TO_BUF: reject negative const offset")
+__failure
+__msg("invalid negative rdwr buffer offset")
+__naked void ptr_to_buf_reject_negative_const_offset(void)
+{
+ asm volatile ("r0 = 0; \
+ r2 = *(u64 *)(r1 + %[value_off]); \
+ if r2 == 0 goto l0_%=; \
+ r2 += -8; \
+ r0 = *(u64 *)(r2 + 0); \
+l0_%=: \
+ exit; \
+ "
+ :
+ : __imm_const(value_off,
+ offsetof(struct bpf_iter__bpf_map_elem, value))
+ : __clobber_all);
+}
+
+char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/verifier_raw_tp_writable.c b/tools/testing/selftests/bpf/progs/verifier_raw_tp_writable.c
index 14a0172e2141..4055a6443bc2 100644
--- a/tools/testing/selftests/bpf/progs/verifier_raw_tp_writable.c
+++ b/tools/testing/selftests/bpf/progs/verifier_raw_tp_writable.c
@@ -47,4 +47,20 @@ l0_%=: /* shift the buffer pointer to a variable location */\
: __clobber_all);
}
+SEC("raw_tracepoint.w")
+__description("raw_tracepoint_writable: reject negative const offset")
+__failure
+__msg("invalid negative tracepoint buffer offset")
+__naked void tracepoint_writable_reject_negative_const_offset(void)
+{
+ asm volatile (" \
+ r6 = *(u64 *)(r1 + 0); \
+ r6 += -8; \
+ r0 = *(u64 *)(r6 + 0); \
+ exit; \
+" :
+ :
+ : __clobber_all);
+}
+
char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/test_maps.c b/tools/testing/selftests/bpf/test_maps.c
index c32da7bd8be2..6a2641ee7897 100644
--- a/tools/testing/selftests/bpf/test_maps.c
+++ b/tools/testing/selftests/bpf/test_maps.c
@@ -759,16 +759,15 @@ static void test_sockmap(unsigned int tasks, void *data)
goto out_sockmap;
}
- /* Test update with unsupported UDP socket */
+ /* Test update with unsupported unbound UDP socket */
udp = socket(AF_INET, SOCK_DGRAM, 0);
- i = 0;
- err = bpf_map_update_elem(fd, &i, &udp, BPF_ANY);
- if (err) {
- printf("Failed socket update SOCK_DGRAM '%i:%i'\n",
- i, udp);
+ CHECK(udp < 0, "socket(AF_INET, SOCK_DGRAM)", "errno:%d\n", errno);
+ err = bpf_map_update_elem(fd, &(int){0}, &udp, BPF_ANY);
+ close(udp);
+ if (!err) {
+ printf("Unexpectedly succeeded unbound UDP update '0:%i'\n", udp);
goto out_sockmap;
}
- close(udp);
/* Test update without programs */
for (i = 0; i < 6; i++) {
diff --git a/tools/testing/selftests/clone3/clone3_set_tid.c b/tools/testing/selftests/clone3/clone3_set_tid.c
index 5c944aee6b41..485efa7c9eed 100644
--- a/tools/testing/selftests/clone3/clone3_set_tid.c
+++ b/tools/testing/selftests/clone3/clone3_set_tid.c
@@ -141,7 +141,7 @@ int main(int argc, char *argv[])
{
FILE *f;
char buf;
- char *line;
+ char *line = NULL;
int status;
int ret = -1;
size_t len = 0;
diff --git a/tools/testing/selftests/drivers/net/config b/tools/testing/selftests/drivers/net/config
index 91d4fd410914..2070e890e064 100644
--- a/tools/testing/selftests/drivers/net/config
+++ b/tools/testing/selftests/drivers/net/config
@@ -4,6 +4,8 @@ CONFIG_DEBUG_INFO_BTF_MODULES=n
CONFIG_INET_PSP=y
CONFIG_IPV6=y
CONFIG_MACSEC=m
+CONFIG_NET_CLS_ACT=y
+CONFIG_NET_CLS_BPF=y
CONFIG_NETCONSOLE=m
CONFIG_NETCONSOLE_DYNAMIC=y
CONFIG_NETCONSOLE_EXTENDED_LOG=y
@@ -11,6 +13,7 @@ CONFIG_NETDEVSIM=m
CONFIG_NETKIT=y
CONFIG_NET_SCH_ETF=m
CONFIG_NET_SCH_FQ=m
+CONFIG_NET_SCH_INGRESS=y
CONFIG_PPP=y
CONFIG_PPPOE=y
CONFIG_VLAN_8021Q=m
diff --git a/tools/testing/selftests/drivers/net/netconsole/netcons_cmdline.sh b/tools/testing/selftests/drivers/net/netconsole/netcons_cmdline.sh
index 96d704b8d9d9..4436567abc94 100755
--- a/tools/testing/selftests/drivers/net/netconsole/netcons_cmdline.sh
+++ b/tools/testing/selftests/drivers/net/netconsole/netcons_cmdline.sh
@@ -50,7 +50,7 @@ do
# Send the message
echo "${MSG}: ${TARGET}" > /dev/kmsg
# Wait until socat saves the file to disk
- busywait "${BUSYWAIT_TIMEOUT}" test -s "${OUTPUT_FILE}"
+ busywait "${BUSYWAIT_TIMEOUT}" test -s "${OUTPUT_FILE}" || true
# Make sure the message was received in the dst part
# and exit
validate_msg "${OUTPUT_FILE}"
diff --git a/tools/testing/selftests/drivers/net/netconsole/netcons_fragmented_msg.sh b/tools/testing/selftests/drivers/net/netconsole/netcons_fragmented_msg.sh
index 0dc7280c3080..fc3db40c1df5 100755
--- a/tools/testing/selftests/drivers/net/netconsole/netcons_fragmented_msg.sh
+++ b/tools/testing/selftests/drivers/net/netconsole/netcons_fragmented_msg.sh
@@ -104,7 +104,7 @@ wait_local_port_listen "${NAMESPACE}" "${PORT}" udp
# Send the message
echo "${MSG}: ${TARGET}" > /dev/kmsg
# Wait until socat saves the file to disk
-busywait "${BUSYWAIT_TIMEOUT}" test -s "${OUTPUT_FILE}"
+busywait "${BUSYWAIT_TIMEOUT}" test -s "${OUTPUT_FILE}" || true
# Check if the message was not corrupted
validate_fragmented_result "${OUTPUT_FILE}"
@@ -117,6 +117,6 @@ disable_release_append
listen_port_and_save_to "${OUTPUT_FILE}" &
wait_local_port_listen "${NAMESPACE}" "${PORT}" udp
echo "${MSG}: ${TARGET}" > /dev/kmsg
-busywait "${BUSYWAIT_TIMEOUT}" test -s "${OUTPUT_FILE}"
+busywait "${BUSYWAIT_TIMEOUT}" test -s "${OUTPUT_FILE}" || true
validate_fragmented_result "${OUTPUT_FILE}"
exit "${ksft_pass}"
diff --git a/tools/testing/selftests/drivers/net/netconsole/netcons_resume.sh b/tools/testing/selftests/drivers/net/netconsole/netcons_resume.sh
index cb59cf436dd0..b379dff9087e 100755
--- a/tools/testing/selftests/drivers/net/netconsole/netcons_resume.sh
+++ b/tools/testing/selftests/drivers/net/netconsole/netcons_resume.sh
@@ -44,7 +44,8 @@ function trigger_reactivation() {
# Restore MACs
ip netns exec "${NAMESPACE}" ip link set "${DSTIF}" \
address "${SAVED_DSTMAC}"
- if [ "${BINDMODE}" == "mac" ]; then
+ if [ "${BINDMODE}" == "mac" ] &&
+ [ "$(mac_get "${SRCIF}")" != "${SAVED_SRCMAC}" ]; then
ip link set dev "${SRCIF}" down
ip link set dev "${SRCIF}" address "${SAVED_SRCMAC}"
# Rename device in order to trigger target resume, as initial
@@ -107,7 +108,7 @@ do
# Send the message
echo "${MSG}: ${TARGET}" > /dev/kmsg
# Wait until socat saves the file to disk
- busywait "${BUSYWAIT_TIMEOUT}" test -s "${OUTPUT_FILE}"
+ busywait "${BUSYWAIT_TIMEOUT}" test -s "${OUTPUT_FILE}" || true
# Make sure the message was received in the dst part
# and exit
validate_msg "${OUTPUT_FILE}"
diff --git a/tools/testing/selftests/drivers/net/netconsole/netcons_sysdata.sh b/tools/testing/selftests/drivers/net/netconsole/netcons_sysdata.sh
index 3fb8c4afe3d2..7089f7bd1e34 100755
--- a/tools/testing/selftests/drivers/net/netconsole/netcons_sysdata.sh
+++ b/tools/testing/selftests/drivers/net/netconsole/netcons_sysdata.sh
@@ -197,7 +197,7 @@ function runtest {
# Send the message
taskset -c "${CPU}" echo "${MSG}: ${TARGET}" > /dev/kmsg
# Wait until socat saves the file to disk
- busywait "${BUSYWAIT_TIMEOUT}" test -s "${OUTPUT_FILE}"
+ busywait "${BUSYWAIT_TIMEOUT}" test -s "${OUTPUT_FILE}" || true
}
# ========== #
diff --git a/tools/testing/selftests/drivers/net/settings b/tools/testing/selftests/drivers/net/settings
new file mode 100644
index 000000000000..eef533824a3c
--- /dev/null
+++ b/tools/testing/selftests/drivers/net/settings
@@ -0,0 +1 @@
+timeout=360
diff --git a/tools/testing/selftests/drivers/ntsync/config b/tools/testing/selftests/drivers/ntsync/config
index 60539c826d06..0aa68de147af 100644
--- a/tools/testing/selftests/drivers/ntsync/config
+++ b/tools/testing/selftests/drivers/ntsync/config
@@ -1 +1 @@
-CONFIG_WINESYNC=y
+CONFIG_NTSYNC=y
diff --git a/tools/testing/selftests/filesystems/fclog.c b/tools/testing/selftests/filesystems/fclog.c
index 551c4a0f395a..593a5136e991 100644
--- a/tools/testing/selftests/filesystems/fclog.c
+++ b/tools/testing/selftests/filesystems/fclog.c
@@ -6,10 +6,8 @@
#include <assert.h>
#include <errno.h>
+#include <fcntl.h>
#include <sched.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
#include <unistd.h>
#include <sys/mount.h>
diff --git a/tools/testing/selftests/filesystems/fscontext_ns/Makefile b/tools/testing/selftests/filesystems/fscontext_ns/Makefile
new file mode 100644
index 000000000000..7e3506294757
--- /dev/null
+++ b/tools/testing/selftests/filesystems/fscontext_ns/Makefile
@@ -0,0 +1,10 @@
+# SPDX-License-Identifier: GPL-2.0
+TEST_GEN_PROGS := fscontext_ns_test
+
+CFLAGS += -Wall -O0 -g $(KHDR_INCLUDES) $(TOOLS_INCLUDES)
+LDLIBS := -lcap
+
+include ../../lib.mk
+
+$(OUTPUT)/fscontext_ns_test: fscontext_ns_test.c ../utils.c
+ $(CC) $(CFLAGS) -o $@ $^ $(LDLIBS)
diff --git a/tools/testing/selftests/filesystems/fscontext_ns/fscontext_ns_test.c b/tools/testing/selftests/filesystems/fscontext_ns/fscontext_ns_test.c
new file mode 100644
index 000000000000..0f30a3e6e197
--- /dev/null
+++ b/tools/testing/selftests/filesystems/fscontext_ns/fscontext_ns_test.c
@@ -0,0 +1,239 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2026 Christian Brauner <brauner@kernel.org>
+ *
+ * Test that completing a filesystem context from another user namespace
+ * doesn't warn.
+ *
+ * fsopen() records the caller's user namespace in fc->user_ns and hands
+ * back an ordinary file descriptor. The task that issues
+ * FSCONFIG_CMD_CREATE need not be the one that created the context: the fd
+ * is inherited across fork() and exec() and it can be passed over a unix
+ * socket. vfs_cmd_create() authorizes the create with mount_capable(),
+ * which for FS_USERNS_MOUNT checks ns_capable(fc->user_ns, CAP_SYS_ADMIN),
+ * and that succeeds for a task holding CAP_SYS_ADMIN in an ancestor of
+ * fc->user_ns.
+ *
+ * binfmt_misc and overlayfs used to WARN_ON() that mismatch, which let an
+ * unprivileged user taint the kernel, flood the log and panic a kernel
+ * booted with panic_on_warn. The mount must still be refused, but it must
+ * not warn.
+ */
+#define _GNU_SOURCE
+
+#include <errno.h>
+#include <sched.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include "../wrappers.h"
+#include "../utils.h"
+#include "../../kselftest_harness.h"
+
+#ifndef FSCONFIG_CMD_CREATE
+#define FSCONFIG_CMD_CREATE 6
+#endif
+
+/* TAINT_WARN, i.e. bit 9 of /proc/sys/kernel/tainted. */
+#define TAINT_WARN_BIT 9
+
+static bool taint_warn_set(void)
+{
+ unsigned long taint = 0;
+ FILE *f;
+
+ f = fopen("/proc/sys/kernel/tainted", "r");
+ if (!f)
+ return false;
+ if (fscanf(f, "%lu", &taint) != 1)
+ taint = 0;
+ fclose(f);
+
+ return taint & (1UL << TAINT_WARN_BIT);
+}
+
+static int send_fd(int sock, int fd)
+{
+ char cmsgbuf[CMSG_SPACE(sizeof(int))] = {};
+ char b[1] = { 'x' };
+ struct iovec iov = { .iov_base = b, .iov_len = sizeof(b) };
+ struct msghdr msg = {
+ .msg_iov = &iov,
+ .msg_iovlen = 1,
+ .msg_control = cmsgbuf,
+ .msg_controllen = sizeof(cmsgbuf),
+ };
+ struct cmsghdr *cmsg;
+
+ cmsg = CMSG_FIRSTHDR(&msg);
+ cmsg->cmsg_level = SOL_SOCKET;
+ cmsg->cmsg_type = SCM_RIGHTS;
+ cmsg->cmsg_len = CMSG_LEN(sizeof(int));
+ memcpy(CMSG_DATA(cmsg), &fd, sizeof(int));
+
+ return sendmsg(sock, &msg, 0) < 0 ? -1 : 0;
+}
+
+static int recv_fd(int sock)
+{
+ char cmsgbuf[CMSG_SPACE(sizeof(int))] = {};
+ char b[1];
+ struct iovec iov = { .iov_base = b, .iov_len = sizeof(b) };
+ struct msghdr msg = {
+ .msg_iov = &iov,
+ .msg_iovlen = 1,
+ .msg_control = cmsgbuf,
+ .msg_controllen = sizeof(cmsgbuf),
+ };
+ struct cmsghdr *cmsg;
+ int fd = -1;
+
+ if (recvmsg(sock, &msg, 0) <= 0)
+ return -1;
+
+ cmsg = CMSG_FIRSTHDR(&msg);
+ if (!cmsg || cmsg->cmsg_type != SCM_RIGHTS)
+ return -1;
+ memcpy(&fd, CMSG_DATA(cmsg), sizeof(int));
+
+ return fd;
+}
+
+/*
+ * Create a context for @fsname in a child and complete it here. With @nest
+ * the child first creates its own user namespace, so that the context is
+ * created in a descendant of the namespace completing it. The child needs a
+ * mount namespace of its own as well: fsopen() gates on may_mount(), which
+ * asks for CAP_SYS_ADMIN in the user namespace owning the caller's mount
+ * namespace.
+ *
+ * Returns the result of FSCONFIG_CMD_CREATE with errno set, or -ENODATA if
+ * the child could not create the context at all.
+ */
+static int create_from_child(const char *fsname, bool nest)
+{
+ int sock[2], fd, ret, status;
+ pid_t pid;
+
+ if (socketpair(AF_UNIX, SOCK_STREAM, 0, sock))
+ return -ENODATA;
+
+ pid = fork();
+ if (pid < 0) {
+ close(sock[0]);
+ close(sock[1]);
+ return -ENODATA;
+ }
+
+ if (pid == 0) {
+ close(sock[0]);
+
+ if (nest && unshare(CLONE_NEWUSER | CLONE_NEWNS))
+ _exit(1);
+
+ fd = sys_fsopen(fsname, 0);
+ if (fd < 0)
+ _exit(1);
+ if (send_fd(sock[1], fd))
+ _exit(1);
+ _exit(0);
+ }
+
+ close(sock[1]);
+ fd = recv_fd(sock[0]);
+ close(sock[0]);
+ wait_for_pid(pid);
+ waitpid(pid, &status, WNOHANG);
+
+ if (fd < 0)
+ return -ENODATA;
+
+ errno = 0;
+ ret = sys_fsconfig(fd, FSCONFIG_CMD_CREATE, NULL, NULL, 0);
+ status = errno;
+ close(fd);
+ errno = status;
+
+ return ret;
+}
+
+FIXTURE(fscontext_ns) {
+ bool warn_before;
+};
+
+FIXTURE_SETUP(fscontext_ns)
+{
+ self->warn_before = taint_warn_set();
+
+ if (setup_userns() != 0)
+ SKIP(return, "setup_userns failed");
+}
+
+FIXTURE_TEARDOWN(fscontext_ns)
+{
+}
+
+/*
+ * The condition the kernel used to WARN about. It has to be refused, and it
+ * has to be refused quietly: an unprivileged task reaches this.
+ */
+FIXTURE_VARIANT(fscontext_ns) {
+ const char *fsname;
+ int expected_errno;
+};
+
+FIXTURE_VARIANT_ADD(fscontext_ns, binfmt_misc) {
+ .fsname = "binfmt_misc",
+ .expected_errno = EINVAL,
+};
+
+FIXTURE_VARIANT_ADD(fscontext_ns, overlay) {
+ .fsname = "overlay",
+ .expected_errno = EIO,
+};
+
+TEST_F(fscontext_ns, create_from_descendant_userns)
+{
+ int ret;
+
+ ret = create_from_child(variant->fsname, true);
+ if (ret == -ENODATA)
+ SKIP(return, "%s unavailable", variant->fsname);
+
+ ASSERT_EQ(-1, ret);
+ ASSERT_EQ(variant->expected_errno, errno);
+
+ /*
+ * Only meaningful if nothing had warned before us. Note that an
+ * unrelated warning racing this test would look like a failure.
+ */
+ if (self->warn_before)
+ TH_LOG("TAINT_WARN already set, not checking for a new warning");
+ else
+ ASSERT_FALSE(taint_warn_set());
+}
+
+/*
+ * The same handover within one user namespace is a supported thing to do and
+ * has to keep working. binfmt_misc takes no options, so the create succeeds
+ * outright and this also shows the test really drives the create path.
+ */
+TEST(create_from_same_userns)
+{
+ int ret;
+
+ if (setup_userns() != 0)
+ SKIP(return, "setup_userns failed");
+
+ ret = create_from_child("binfmt_misc", false);
+ if (ret == -ENODATA)
+ SKIP(return, "binfmt_misc unavailable");
+
+ ASSERT_EQ(0, ret);
+}
+
+TEST_HARNESS_MAIN
diff --git a/tools/testing/selftests/filesystems/fuse/Makefile b/tools/testing/selftests/filesystems/fuse/Makefile
index 612aad69a93a..f47141484275 100644
--- a/tools/testing/selftests/filesystems/fuse/Makefile
+++ b/tools/testing/selftests/filesystems/fuse/Makefile
@@ -5,6 +5,13 @@ CFLAGS += -Wall -O2 -g $(KHDR_INCLUDES)
TEST_GEN_PROGS := fusectl_test
TEST_GEN_FILES := fuse_mnt
+# fuse_acl_cache_test requires libfuse3; add it only when the library is present.
+ACL_CFLAGS := $(shell pkg-config fuse3 --cflags 2>/dev/null)
+ACL_LDLIBS := $(shell pkg-config fuse3 --libs 2>/dev/null)
+ifneq ($(ACL_CFLAGS),)
+TEST_GEN_PROGS += fuse_acl_cache_test
+endif
+
include ../../lib.mk
VAR_CFLAGS := $(shell pkg-config fuse --cflags 2>/dev/null)
@@ -19,3 +26,6 @@ endif
$(OUTPUT)/fuse_mnt: CFLAGS += $(VAR_CFLAGS)
$(OUTPUT)/fuse_mnt: LDLIBS += $(VAR_LDLIBS)
+
+$(OUTPUT)/fuse_acl_cache_test: CFLAGS += $(ACL_CFLAGS)
+$(OUTPUT)/fuse_acl_cache_test: LDLIBS += $(ACL_LDLIBS)
diff --git a/tools/testing/selftests/filesystems/fuse/fuse_acl_cache_test.c b/tools/testing/selftests/filesystems/fuse/fuse_acl_cache_test.c
new file mode 100644
index 000000000000..2411a6e285f1
--- /dev/null
+++ b/tools/testing/selftests/filesystems/fuse/fuse_acl_cache_test.c
@@ -0,0 +1,347 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Test: FUSE ACL caching bug triggered by AT_STATX_FORCE_SYNC
+ *
+ * A FUSE mount that does not negotiate FUSE_POSIX_ACL initialises every inode
+ * with i_acl = i_default_acl = ACL_DONT_CACHE. When a fresh stat is needed
+ * (e.g. AT_STATX_FORCE_SYNC), fuse_update_get_attr() calls
+ * forget_all_cached_acls() before issuing FUSE_GETATTR. On an unfixed kernel,
+ * __forget_cached_acl() replaces ACL_DONT_CACHE with ACL_NOT_CACHED,
+ * inadvertently enabling the kernel ACL cache for that inode. The next
+ * getxattr populates the cache. Because fuse_set_acl() skips
+ * forget_all_cached_acls() for !fc->posix_acl mounts, any subsequent change to
+ * the ACL leaves the stale kernel entry in place, and the next getxattr returns
+ * wrong data without ever reaching the FUSE daemon.
+ *
+ * Fix (fs/posix_acl.c): __forget_cached_acl() returns early when *p is
+ * ACL_DONT_CACHE, preserving the "never cache" invariant for the inode's
+ * lifetime.
+ *
+ * Test outline:
+ * 1. Mount a minimal FUSE fs (no FUSE_POSIX_ACL negotiated).
+ * 2. lgetxattr -> daemon called, ACL_A returned, NOT cached (ACL_DONT_CACHE).
+ * 3. statx(AT_STATX_FORCE_SYNC) -> forget_all_cached_acls() called.
+ * Buggy: ACL_DONT_CACHE -> ACL_NOT_CACHED (cache enabled).
+ * Fixed: ACL_DONT_CACHE preserved.
+ * 4. lgetxattr -> daemon called, ACL_A returned.
+ * Buggy: result now cached (ACL_NOT_CACHED -> cached).
+ * Fixed: result still not cached.
+ * 5. Daemon switches to ACL_B internally (different size).
+ * 6. lgetxattr -> should return ACL_B (44 bytes).
+ * Buggy: cache hit, returns stale ACL_A (28 bytes). FAIL.
+ * Fixed: no cache, daemon called, returns ACL_B (44 bytes). PASS.
+ */
+
+#define _GNU_SOURCE
+#include <errno.h>
+#include <fcntl.h>
+#include <linux/limits.h>
+#include <pthread.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/xattr.h>
+#include <unistd.h>
+
+#define FUSE_USE_VERSION 31
+#include <fuse_lowlevel.h>
+
+#include "kselftest_harness.h"
+
+/* ---- ACL binary encoding ------------------------------------------------ */
+/*
+ * POSIX ACL v2 xattr format (little-endian):
+ * header: u32 version (= 0x00000002)
+ * entry: u16 tag | u16 perm | u32 id
+ *
+ * Entries must appear in tag-ascending order; named USER/GROUP entries
+ * require a MASK entry. Both ACLs pass posix_acl_from_xattr() validation.
+ */
+
+/* ACL_A: 3 entries (USER_OBJ:rwx, GROUP_OBJ:r-x, OTHER:r-x) = 28 bytes */
+static const uint8_t acl_a[] = {
+ 0x02, 0x00, 0x00, 0x00, /* v2 header */
+ 0x01, 0x00, 0x07, 0x00, 0xff, 0xff, 0xff, 0xff, /* USER_OBJ rwx */
+ 0x04, 0x00, 0x05, 0x00, 0xff, 0xff, 0xff, 0xff, /* GROUP_OBJ r-x */
+ 0x20, 0x00, 0x05, 0x00, 0xff, 0xff, 0xff, 0xff, /* OTHER r-x */
+};
+
+/*
+ * ACL_B: 5 entries — adds USER uid=1 and MASK = 44 bytes.
+ * A named USER entry requires a MASK; all tags in ascending order.
+ */
+static const uint8_t acl_b[] = {
+ 0x02, 0x00, 0x00, 0x00, /* v2 header */
+ 0x01, 0x00, 0x07, 0x00, 0xff, 0xff, 0xff, 0xff, /* USER_OBJ rwx */
+ 0x02, 0x00, 0x07, 0x00, 0x01, 0x00, 0x00, 0x00, /* USER uid=1 rwx */
+ 0x04, 0x00, 0x05, 0x00, 0xff, 0xff, 0xff, 0xff, /* GROUP_OBJ r-x */
+ 0x10, 0x00, 0x07, 0x00, 0xff, 0xff, 0xff, 0xff, /* MASK rwx */
+ 0x20, 0x00, 0x05, 0x00, 0xff, 0xff, 0xff, 0xff, /* OTHER r-x */
+};
+
+/* ---- Shared state (daemon thread <-> test thread) ----------------------- */
+
+#define FILE_INO 2
+#define FILE_NAME "testfile"
+
+struct daemon_state {
+ pthread_mutex_t lock;
+ const uint8_t *acl;
+ size_t acl_size;
+ int getxattr_count;
+};
+
+/*
+ * Global: callbacks are stateless fns so we use a single global.
+ * Safe because only one test instance runs at a time.
+ */
+static struct daemon_state g_ds = {
+ .lock = PTHREAD_MUTEX_INITIALIZER,
+};
+
+/* ---- FUSE lowlevel callbacks -------------------------------------------- */
+
+static void fs_lookup(fuse_req_t req, fuse_ino_t parent, const char *name)
+{
+ if (parent != FUSE_ROOT_ID || strcmp(name, FILE_NAME)) {
+ fuse_reply_err(req, ENOENT);
+ return;
+ }
+ struct fuse_entry_param e = {};
+
+ /*
+ * Long attr/entry timeouts so that normal stat() calls do not
+ * expire and trigger forget_all_cached_acls() on their own;
+ * only the explicit AT_STATX_FORCE_SYNC should trigger it.
+ */
+ e.ino = FILE_INO;
+ e.generation = 1;
+ e.attr_timeout = 10.0;
+ e.entry_timeout = 10.0;
+ e.attr.st_ino = FILE_INO;
+ e.attr.st_mode = S_IFREG | 0644;
+ e.attr.st_nlink = 1;
+ fuse_reply_entry(req, &e);
+}
+
+static void fs_getattr(fuse_req_t req, fuse_ino_t ino,
+ struct fuse_file_info *fi)
+{
+ struct stat st = {};
+
+ (void)fi;
+ if (ino == FUSE_ROOT_ID) {
+ st.st_ino = FUSE_ROOT_ID;
+ st.st_mode = S_IFDIR | 0755;
+ st.st_nlink = 2;
+ } else if (ino == FILE_INO) {
+ st.st_ino = FILE_INO;
+ st.st_mode = S_IFREG | 0644;
+ st.st_nlink = 1;
+ } else {
+ fuse_reply_err(req, ENOENT);
+ return;
+ }
+ fuse_reply_attr(req, &st, 10);
+}
+
+static void fs_getxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
+ size_t size)
+{
+ if (ino != FILE_INO ||
+ strcmp(name, "system.posix_acl_access") != 0) {
+ fuse_reply_err(req, ENODATA);
+ return;
+ }
+
+ pthread_mutex_lock(&g_ds.lock);
+ const uint8_t *acl = g_ds.acl;
+ size_t acl_size = g_ds.acl_size;
+ g_ds.getxattr_count++;
+ pthread_mutex_unlock(&g_ds.lock);
+
+ if (size == 0)
+ fuse_reply_xattr(req, acl_size);
+ else if (size < acl_size)
+ fuse_reply_err(req, ERANGE);
+ else
+ fuse_reply_buf(req, (const char *)acl, acl_size);
+}
+
+static const struct fuse_lowlevel_ops fs_ops = {
+ .lookup = fs_lookup,
+ .getattr = fs_getattr,
+ .getxattr = fs_getxattr,
+};
+
+/* ---- Daemon thread ------------------------------------------------------- */
+
+static void *run_daemon(void *arg)
+{
+ fuse_session_loop((struct fuse_session *)arg);
+ return NULL;
+}
+
+/* ---- kselftest harness --------------------------------------------------- */
+
+FIXTURE(acl_cache) {
+ struct fuse_session *se;
+ char mountpoint[PATH_MAX];
+ char file_path[PATH_MAX];
+ pthread_t thread;
+};
+
+FIXTURE_SETUP(acl_cache)
+{
+ char *fuse_argv[] = { "fuse_acl_cache_test", NULL };
+ struct fuse_args args = FUSE_ARGS_INIT(1, fuse_argv);
+
+ g_ds.acl = acl_a;
+ g_ds.acl_size = sizeof(acl_a);
+ g_ds.getxattr_count = 0;
+
+ strcpy(self->mountpoint, "/tmp/acl_cache_test_XXXXXX");
+ if (!mkdtemp(self->mountpoint))
+ SKIP(return, "mkdtemp: %s", strerror(errno));
+
+ snprintf(self->file_path, sizeof(self->file_path),
+ "%s/" FILE_NAME, self->mountpoint);
+
+ self->se = fuse_session_new(&args, &fs_ops, sizeof(fs_ops), NULL);
+ if (!self->se) {
+ rmdir(self->mountpoint);
+ SKIP(return, "fuse_session_new failed");
+ }
+
+ if (fuse_session_mount(self->se, self->mountpoint)) {
+ fuse_session_destroy(self->se);
+ rmdir(self->mountpoint);
+ SKIP(return, "fuse_session_mount failed "
+ "(missing fusermount3 or insufficient privileges)");
+ }
+
+ if (pthread_create(&self->thread, NULL, run_daemon, self->se)) {
+ fuse_session_unmount(self->se);
+ fuse_session_destroy(self->se);
+ rmdir(self->mountpoint);
+ SKIP(return, "pthread_create: %s", strerror(errno));
+ }
+
+ fuse_opt_free_args(&args);
+}
+
+FIXTURE_TEARDOWN(acl_cache)
+{
+ fuse_session_exit(self->se);
+ fuse_session_unmount(self->se);
+ pthread_join(self->thread, NULL);
+ fuse_session_destroy(self->se);
+ rmdir(self->mountpoint);
+}
+
+static int do_force_statx(const char *path)
+{
+ struct statx stx;
+
+ return statx(AT_FDCWD, path, AT_STATX_FORCE_SYNC, STATX_BASIC_STATS,
+ &stx);
+}
+
+TEST_F(acl_cache, stale_after_force_sync)
+{
+ char buf[512];
+ ssize_t sz;
+ int count;
+
+ /*
+ * Step 1: two getxattr calls before any statx(FORCE_SYNC).
+ * i_acl == ACL_DONT_CACHE. __get_acl's cmpxchg(p, ACL_NOT_CACHED,
+ * sentinel) finds *p != ACL_NOT_CACHED on every call, so the sentinel
+ * is never placed and the result is never cached. Both calls must
+ * reach the daemon, proving ACL_DONT_CACHE suppresses caching.
+ */
+ sz = lgetxattr(self->file_path, "system.posix_acl_access",
+ buf, sizeof(buf));
+ ASSERT_EQ(sz, (ssize_t)sizeof(acl_a));
+
+ sz = lgetxattr(self->file_path, "system.posix_acl_access",
+ buf, sizeof(buf));
+ ASSERT_EQ(sz, (ssize_t)sizeof(acl_a));
+
+ pthread_mutex_lock(&g_ds.lock);
+ count = g_ds.getxattr_count;
+ pthread_mutex_unlock(&g_ds.lock);
+
+ ASSERT_EQ(count, 2);
+ TH_LOG("step 1 OK: both pre-trigger getxattrs reached daemon (count=%d), "
+ "ACL_DONT_CACHE is working", count);
+
+ /*
+ * Step 2: statx(AT_STATX_FORCE_SYNC).
+ * fuse_update_get_attr() calls forget_all_cached_acls() before sending
+ * FUSE_GETATTR.
+ * Buggy kernel: ACL_DONT_CACHE -> ACL_NOT_CACHED (cache enabled)
+ * Fixed kernel: ACL_DONT_CACHE preserved (no effect)
+ */
+ ASSERT_EQ(do_force_statx(self->file_path), 0);
+ TH_LOG("step 2 OK: statx(AT_STATX_FORCE_SYNC) succeeded");
+
+ /*
+ * Step 3: getxattr — cache population attempt after the trigger.
+ * Buggy: *p == ACL_NOT_CACHED -> sentinel placed -> fuse_get_inode_acl
+ * called -> ACL_A parsed and stored in the kernel cache.
+ * Fixed: *p == ACL_DONT_CACHE -> sentinel placement skipped ->
+ * fuse_get_inode_acl called but result not cached.
+ * Either way the correct ACL_A is returned here.
+ */
+ sz = lgetxattr(self->file_path, "system.posix_acl_access",
+ buf, sizeof(buf));
+ ASSERT_EQ(sz, (ssize_t)sizeof(acl_a));
+
+ pthread_mutex_lock(&g_ds.lock);
+ count = g_ds.getxattr_count;
+ pthread_mutex_unlock(&g_ds.lock);
+
+ ASSERT_EQ(count, 3);
+ TH_LOG("step 3 OK: post-trigger getxattr reached daemon (count=%d), "
+ "returned correct ACL_A (%zd bytes)", count, sz);
+
+ /*
+ * Step 4: switch daemon to ACL_B (different size: 44 vs 28 bytes).
+ * Simulates an ACL change that fuse_set_acl() would NOT invalidate for
+ * !fc->posix_acl mounts (it skips forget_all_cached_acls in that case).
+ * On a fixed kernel the ACL was never cached, so this is moot.
+ */
+ pthread_mutex_lock(&g_ds.lock);
+ g_ds.acl = acl_b;
+ g_ds.acl_size = sizeof(acl_b);
+ pthread_mutex_unlock(&g_ds.lock);
+ TH_LOG("step 4: daemon switched to ACL_B (%zu bytes)", sizeof(acl_b));
+
+ /*
+ * Step 5: getxattr — the decisive check.
+ * Buggy kernel: cache hit -> stale ACL_A (28 bytes), count stays 3.
+ * Fixed kernel: no cache -> daemon called -> ACL_B (44 bytes), count 4.
+ */
+ sz = lgetxattr(self->file_path, "system.posix_acl_access",
+ buf, sizeof(buf));
+
+ pthread_mutex_lock(&g_ds.lock);
+ count = g_ds.getxattr_count;
+ pthread_mutex_unlock(&g_ds.lock);
+
+ if (sz == (ssize_t)sizeof(acl_a))
+ TH_LOG("step 5 BUG: stale ACL_A (%zd bytes) from kernel cache "
+ "(count=%d); ACL_DONT_CACHE corrupted by "
+ "forget_all_cached_acls()", sz, count);
+ else
+ TH_LOG("step 5 OK: daemon reached (count=%d), "
+ "fresh ACL_B (%zd bytes)", count, sz);
+
+ EXPECT_EQ(sz, (ssize_t)sizeof(acl_b));
+ EXPECT_EQ(count, 4);
+}
+
+TEST_HARNESS_MAIN
diff --git a/tools/testing/selftests/ftrace/ftracetest b/tools/testing/selftests/ftrace/ftracetest
index 0a56bf209f6c..8ad2c385407e 100755
--- a/tools/testing/selftests/ftrace/ftracetest
+++ b/tools/testing/selftests/ftrace/ftracetest
@@ -503,6 +503,7 @@ for t in $TEST_CASES; do
done
# Test on instance loop
+(cd $TRACING_DIR; initialize_system)
INSTANCE=" (instance) "
for t in $TEST_CASES; do
test_on_instance $t || continue
diff --git a/tools/testing/selftests/ftrace/test.d/dynevent/add_remove_eprobe.tc b/tools/testing/selftests/ftrace/test.d/dynevent/add_remove_eprobe.tc
index c300eb020262..e2322693d0c3 100644
--- a/tools/testing/selftests/ftrace/test.d/dynevent/add_remove_eprobe.tc
+++ b/tools/testing/selftests/ftrace/test.d/dynevent/add_remove_eprobe.tc
@@ -1,16 +1,16 @@
#!/bin/sh
# SPDX-License-Identifier: GPL-2.0
# description: Generic dynamic event - add/remove eprobe events
-# requires: dynamic_events events/syscalls/sys_enter_openat "<attached-group>.<attached-event> [<args>]":README
+# requires: dynamic_events events/syscalls/sys_enter_chdir "<attached-group>.<attached-event> [<args>]":README
echo 0 > events/enable
clear_dynamic_events
SYSTEM="syscalls"
-EVENT="sys_enter_openat"
+EVENT="sys_enter_chdir"
FIELD="filename"
-EPROBE="eprobe_open"
+EPROBE="eprobe_chdir"
OPTIONS="file=+0(\$filename):ustring"
echo "e:$EPROBE $SYSTEM/$EVENT $OPTIONS" >> dynamic_events
@@ -18,20 +18,14 @@ grep -q "$EPROBE" dynamic_events
test -d events/eprobes/$EPROBE
echo 1 > events/eprobes/$EPROBE/enable
-ls
+cd /sys/kernel/tracing
echo 0 > events/eprobes/$EPROBE/enable
-content=`grep '^ *ls-' trace | grep 'file='`
-nocontent=`grep '^ *ls-' trace | grep 'file=' | grep -v -e '"/' -e '"."' -e '(fault)' ` || true
-
+content=`grep -e 'file="/sys/kernel/tracing"\|(fault)' trace`
if [ -z "$content" ]; then
exit_fail
fi
-if [ ! -z "$nocontent" ]; then
- exit_fail
-fi
-
echo "-:$EPROBE" >> dynamic_events
! grep -q "$EPROBE" dynamic_events
diff --git a/tools/testing/selftests/ftrace/test.d/dynevent/add_remove_fprobe_module.tc b/tools/testing/selftests/ftrace/test.d/dynevent/add_remove_fprobe_module.tc
index 2915206777b6..89660a9adf44 100644
--- a/tools/testing/selftests/ftrace/test.d/dynevent/add_remove_fprobe_module.tc
+++ b/tools/testing/selftests/ftrace/test.d/dynevent/add_remove_fprobe_module.tc
@@ -16,23 +16,32 @@ echo > dynamic_events
FUNC1='foo_bar*'
FUNC2='vfs_read'
+:;: "Save enabled functions count" ;:
+ecount=`cat enabled_functions | wc -l`
+
+count_enabled_functions() {
+ count=`cat enabled_functions | wc -l`
+ count=$(($count-$ecount))
+ echo $count
+}
+
:;: "Add an event on the test module" ;:
echo "f:test1 $FUNC1" >> dynamic_events
echo 1 > events/fprobes/test1/enable
:;: "Ensure it is enabled" ;:
-funcs=`cat enabled_functions | wc -l`
+funcs=`count_enabled_functions`
test $funcs -ne 0
:;: "Check the enabled_functions is cleared on unloading" ;:
rmmod trace-events-sample
-funcs=`cat enabled_functions | wc -l`
+funcs=`count_enabled_functions`
test $funcs -eq 0
:;: "Check it is kept clean" ;:
modprobe trace-events-sample
echo 1 > events/fprobes/test1/enable || echo "OK"
-funcs=`cat enabled_functions | wc -l`
+funcs=`count_enabled_functions`
test $funcs -eq 0
:;: "Add another event not on the test module" ;:
@@ -40,19 +49,19 @@ echo "f:test2 $FUNC2" >> dynamic_events
echo 1 > events/fprobes/test2/enable
:;: "Ensure it is enabled" ;:
-ofuncs=`cat enabled_functions | wc -l`
+ofuncs=`count_enabled_functions`
test $ofuncs -ne 0
:;: "Disable and remove the first event"
echo 0 > events/fprobes/test1/enable
echo "-:fprobes/test1" >> dynamic_events
-funcs=`cat enabled_functions | wc -l`
+funcs=`count_enabled_functions`
test $ofuncs -eq $funcs
:;: "Disable and remove other events" ;:
echo 0 > events/fprobes/enable
echo > dynamic_events
-funcs=`cat enabled_functions | wc -l`
+funcs=`count_enabled_functions`
test $funcs -eq 0
rmmod trace-events-sample
@@ -63,12 +72,12 @@ echo "f:test1 $FUNC1" >> dynamic_events
echo 1 > events/fprobes/test1/enable
echo "f:test2 $FUNC2" >> dynamic_events
echo 1 > events/fprobes/test2/enable
-ofuncs=`cat enabled_functions | wc -l`
+ofuncs=`count_enabled_functions`
test $ofuncs -ne 0
:;: "Unload module (ftrace entry should be removed)" ;:
rmmod trace-events-sample
-funcs=`cat enabled_functions | wc -l`
+funcs=`count_enabled_functions`
test $funcs -ne 0
test $ofuncs -ne $funcs
@@ -77,7 +86,7 @@ echo 0 > events/fprobes/test2/enable
echo "-:fprobes/test2" >> dynamic_events
:;: "Ensure ftrace is disabled." ;:
-funcs=`cat enabled_functions | wc -l`
+funcs=`count_enabled_functions`
test $funcs -eq 0
echo 0 > events/fprobes/enable
diff --git a/tools/testing/selftests/ftrace/test.d/dynevent/add_remove_uprobe.tc b/tools/testing/selftests/ftrace/test.d/dynevent/add_remove_uprobe.tc
index f2048c244526..19430bd5864c 100644
--- a/tools/testing/selftests/ftrace/test.d/dynevent/add_remove_uprobe.tc
+++ b/tools/testing/selftests/ftrace/test.d/dynevent/add_remove_uprobe.tc
@@ -12,9 +12,32 @@ echo 0 > events/enable
echo > dynamic_events
REALBIN=`readlink -f /bin/sh`
-ENTRYPOINT=`readelf -h ${REALBIN} | grep Entry | sed -e 's/[^0]*//'`
-echo "p:myevent ${REALBIN}:${ENTRYPOINT}" >> uprobe_events
+# Get the entry point virtual address from ELF header
+ENTRY=`readelf -hW ${REALBIN} | grep "Entry point" | awk '{print $NF}'`
+
+# Convert virtual address to file offset: find the LOAD segment containing
+# the entry point, then compute file_offset = e_entry - p_vaddr + p_offset.
+# For PIE binaries this is a no-op (vaddr == file offset), but for non-PIE
+# executables the virtual address is much larger than the file size and
+# must be converted, otherwise uprobe_register() rejects it with -EINVAL.
+ENTRY_DEC=$(printf '%d' "$ENTRY")
+OFFSET=$ENTRY
+while IFS= read -r line; do
+ set -- $line
+ [ "$1" = "LOAD" ] || continue
+ VA_DEC=$(printf '%d' "$3")
+ OFF_DEC=$(printf '%d' "$2")
+ FSZ_DEC=$(printf '%d' "$5")
+ if [ "$ENTRY_DEC" -ge "$VA_DEC" ] && [ "$ENTRY_DEC" -lt "$((VA_DEC + FSZ_DEC))" ]; then
+ OFFSET=$(printf '0x%x' "$((ENTRY_DEC - VA_DEC + OFF_DEC))")
+ break
+ fi
+done << EOF
+$(readelf -lW ${REALBIN} | grep LOAD)
+EOF
+
+echo "p:myevent ${REALBIN}:${OFFSET}" >> uprobe_events
grep -q myevent uprobe_events
test -d events/uprobes/myevent
diff --git a/tools/testing/selftests/ftrace/test.d/trigger/trigger-hist-poll.tc b/tools/testing/selftests/ftrace/test.d/trigger/trigger-hist-poll.tc
index 8d275e3238d9..04eb8546fc07 100644
--- a/tools/testing/selftests/ftrace/test.d/trigger/trigger-hist-poll.tc
+++ b/tools/testing/selftests/ftrace/test.d/trigger/trigger-hist-poll.tc
@@ -1,7 +1,7 @@
#!/bin/sh
# SPDX-License-Identifier: GPL-2.0
# description: event trigger - test poll wait on histogram
-# requires: set_event events/sched/sched_process_free/trigger events/sched/sched_process_free/hist
+# requires: set_event events/sched/sched_process_exit/trigger events/sched/sched_process_exit/hist
# flags: instance
POLL=${FTRACETEST_ROOT}/poll
@@ -11,7 +11,7 @@ if [ ! -x ${POLL} ]; then
exit_unresolved
fi
-EVENT=events/sched/sched_process_free/
+EVENT=events/sched/sched_process_exit/
# Check poll ops is supported. Before implementing poll on hist file, it
# returns soon with POLLIN | POLLOUT, but not POLLPRI.
diff --git a/tools/testing/selftests/gpio/.gitignore b/tools/testing/selftests/gpio/.gitignore
index ededb077a3a6..16f74de479f1 100644
--- a/tools/testing/selftests/gpio/.gitignore
+++ b/tools/testing/selftests/gpio/.gitignore
@@ -2,3 +2,4 @@
gpio-mockup-cdev
gpio-chip-info
gpio-line-name
+gpio-cdev-uaf
diff --git a/tools/testing/selftests/hid/Makefile b/tools/testing/selftests/hid/Makefile
index 96071b4800e8..2f423de83147 100644
--- a/tools/testing/selftests/hid/Makefile
+++ b/tools/testing/selftests/hid/Makefile
@@ -24,7 +24,7 @@ CXX ?= $(CROSS_COMPILE)g++
HOSTPKG_CONFIG := pkg-config
-CFLAGS += -g -O0 -rdynamic -Wall -Werror -I$(OUTPUT)
+CFLAGS += -g -O0 -rdynamic -Wall -Werror -I$(OUTPUT) $(KHDR_INCLUDES)
CFLAGS += -I$(OUTPUT)/tools/include
LDLIBS += -lelf -lz -lrt -lpthread
diff --git a/tools/testing/selftests/hid/hid_bpf.c b/tools/testing/selftests/hid/hid_bpf.c
index 1e979fb3542b..b851339308c2 100644
--- a/tools/testing/selftests/hid/hid_bpf.c
+++ b/tools/testing/selftests/hid/hid_bpf.c
@@ -86,6 +86,20 @@ static void load_programs(const struct test_program programs[],
self->skel = hid__open();
ASSERT_OK_PTR(self->skel) TEARDOWN_LOG("Error while calling hid__open");
+ /*
+ * Disable all struct_ops maps by default so libbpf does not autoload
+ * programs referenced by maps that are unrelated to the current test.
+ */
+ bpf_object__for_each_map(iter_map, *self->skel->skeleton->obj) {
+ if (bpf_map__type(iter_map) == BPF_MAP_TYPE_STRUCT_OPS) {
+ err = bpf_map__set_autocreate(iter_map, false);
+ ASSERT_OK(err) TH_LOG("can not disable struct_ops map '%s'",
+ bpf_map__name(iter_map));
+ }
+
+ bpf_map__set_autoattach(iter_map, false);
+ }
+
for (int i = 0; i < progs_count; i++) {
struct bpf_program *prog;
struct bpf_map *map;
@@ -102,6 +116,10 @@ static void load_programs(const struct test_program programs[],
ASSERT_OK_PTR(map) TH_LOG("can not find struct_ops by name '%s'",
programs[i].name + 4);
+ err = bpf_map__set_autocreate(map, true);
+ ASSERT_OK(err) TH_LOG("can not enable struct_ops map '%s'",
+ programs[i].name + 4);
+
/* hid_id is the first field of struct hid_bpf_ops */
ops_hid_id = bpf_map__initial_value(map, NULL);
ASSERT_OK_PTR(ops_hid_id) TH_LOG("unable to retrieve struct_ops data");
@@ -109,13 +127,6 @@ static void load_programs(const struct test_program programs[],
*ops_hid_id = self->hid.hid_id;
}
- /* we disable the auto-attach feature of all maps because we
- * only want the tested one to be manually attached in the next
- * call to bpf_map__attach_struct_ops()
- */
- bpf_object__for_each_map(iter_map, *self->skel->skeleton->obj)
- bpf_map__set_autoattach(iter_map, false);
-
err = hid__load(self->skel);
ASSERT_OK(err) TH_LOG("hid_skel_load failed: %d", err);
@@ -887,6 +898,17 @@ TEST_F(hid_bpf, test_rdesc_fixup)
ASSERT_EQ(rpt_desc.value[4], 0x42);
}
+TEST_F(hid_bpf, test_rdesc_fixup_get_data_overflow)
+{
+ const struct test_program progs[] = {
+ { .name = "hid_rdesc_fixup_get_data_overflow" },
+ };
+
+ LOAD_PROGRAMS(progs);
+
+ ASSERT_EQ(self->skel->bss->get_data_overflow_check, 1);
+}
+
static int libbpf_print_fn(enum libbpf_print_level level,
const char *format, va_list args)
{
diff --git a/tools/testing/selftests/hid/progs/hid.c b/tools/testing/selftests/hid/progs/hid.c
index 5ecc845ef792..b21fbb13c926 100644
--- a/tools/testing/selftests/hid/progs/hid.c
+++ b/tools/testing/selftests/hid/progs/hid.c
@@ -13,6 +13,7 @@ struct attach_prog_args {
__u64 callback_check = 52;
__u64 callback2_check = 52;
+__u64 get_data_overflow_check;
SEC("?struct_ops/hid_device_event")
int BPF_PROG(hid_first_event, struct hid_bpf_ctx *hid_ctx, enum hid_report_type type)
@@ -240,6 +241,20 @@ struct hid_bpf_ops rdesc_fixup = {
.hid_rdesc_fixup = (void *)hid_rdesc_fixup,
};
+SEC("?struct_ops.s/hid_rdesc_fixup")
+int BPF_PROG(hid_rdesc_fixup_get_data_overflow, struct hid_bpf_ctx *hid_ctx)
+{
+ if (!hid_bpf_get_data(hid_ctx, 2 /* offset */, ~0ULL /* size */))
+ get_data_overflow_check = 1;
+
+ return 0;
+}
+
+SEC(".struct_ops.link")
+struct hid_bpf_ops rdesc_fixup_get_data_overflow = {
+ .hid_rdesc_fixup = (void *)hid_rdesc_fixup_get_data_overflow,
+};
+
SEC("?struct_ops/hid_device_event")
int BPF_PROG(hid_test_insert1, struct hid_bpf_ctx *hid_ctx, enum hid_report_type type)
{
diff --git a/tools/testing/selftests/hid/tests/test_multitouch.py b/tools/testing/selftests/hid/tests/test_multitouch.py
index fa4fb2054bd4..7897340118b4 100644
--- a/tools/testing/selftests/hid/tests/test_multitouch.py
+++ b/tools/testing/selftests/hid/tests/test_multitouch.py
@@ -513,6 +513,79 @@ class SmartTechDigitizer(Digitizer):
return absinfo is not None and absinfo.resolution == 3
+class MinWin8TSParallelBigContactMax(Digitizer):
+ """A parallel Win8 touchscreen that advertises a ContactCountMaximum much
+ larger than the number of contacts it actually reports.
+
+ Such firmware makes the driver allocate that many input slots (up to 255)
+ while the input report only carries a few contacts. This is what used to
+ drive the per-slot bit operations on mt_io_flags out of bounds. The number
+ of contacts a HID report can describe is limited by the descriptor size,
+ so a large ContactCountMaximum can only be expressed this way, decoupled
+ from the number of finger collections."""
+
+ def __init__(self, n_fingers=5, contact_max=250):
+ self.phys_max = 120, 90
+ rdesc_finger_str = f"""
+ Usage Page (Digitizers)
+ Usage (Finger)
+ Collection (Logical)
+ Report Size (1)
+ Report Count (1)
+ Logical Minimum (0)
+ Logical Maximum (1)
+ Usage (Tip Switch)
+ Input (Data,Var,Abs)
+ Report Size (7)
+ Logical Maximum (127)
+ Input (Cnst,Var,Abs)
+ Report Size (8)
+ Logical Maximum (255)
+ Usage (Contact Id)
+ Input (Data,Var,Abs)
+ Report Size (16)
+ Unit Exponent (-1)
+ Unit (SILinear: cm)
+ Logical Maximum (4095)
+ Physical Minimum (0)
+ Physical Maximum ({self.phys_max[0]})
+ Usage Page (Generic Desktop)
+ Usage (X)
+ Input (Data,Var,Abs)
+ Physical Maximum ({self.phys_max[1]})
+ Usage (Y)
+ Input (Data,Var,Abs)
+ End Collection
+"""
+ rdesc_str = f"""
+ Usage Page (Digitizers)
+ Usage (Touch Screen)
+ Collection (Application)
+ Report ID (1)
+ {rdesc_finger_str * n_fingers}
+ Unit Exponent (-4)
+ Unit (SILinear: s)
+ Logical Maximum (65535)
+ Physical Maximum (65535)
+ Usage Page (Digitizers)
+ Usage (Scan Time)
+ Input (Data,Var,Abs)
+ Report Size (8)
+ Logical Maximum (255)
+ Usage (Contact Count)
+ Input (Data,Var,Abs)
+ Report ID (2)
+ Logical Maximum ({contact_max})
+ Usage (Contact Max)
+ Feature (Data,Var,Abs)
+ End Collection
+ {Digitizer.msCertificationBlob(68)}
+"""
+ super().__init__(
+ f"uhid test parallel big contact max {contact_max}", rdesc_str
+ )
+
+
class BaseTest:
class TestMultitouch(base.BaseTestCase.TestUhid):
kernel_modules = [KERNEL_MODULE]
@@ -1735,6 +1808,47 @@ class TestMinWin8TSParallel(BaseTest.TestWin8Multitouch):
return MinWin8TSParallel(10)
+class TestMinWin8TSParallelBigContactMax(base.BaseTestCase.TestUhid):
+ """Regression test for the out-of-bounds bit operations on
+ struct mt_device.mt_io_flags.
+
+ A Win8 touchscreen may advertise a ContactCountMaximum much larger than
+ the number of contacts it reports. The driver used to keep the per-slot
+ active state in the bits of a single unsigned long while indexing
+ set_bit()/clear_bit() by the slot number, so such a device drove those bit
+ operations out of bounds. The sticky-fingers release timer made it fatal:
+ mt_release_contacts() cleared one bit per slot, overwrote the adjacent
+ struct mt_device members and panicked the kernel.
+
+ Send a single contact, let the 100ms sticky-fingers timer release it, and
+ check that the kernel reports the release cleanly instead of crashing."""
+
+ kernel_modules = [KERNEL_MODULE]
+
+ def create_device(self):
+ return MinWin8TSParallelBigContactMax()
+
+ def test_sticky_fingers_release_big_contact_max(self):
+ uhdev = self.uhdev
+ evdev = uhdev.get_evdev()
+
+ assert evdev.num_slots == uhdev.max_contacts
+
+ t0 = Touch(1, 5, 10)
+ r = uhdev.event([t0])
+ events = uhdev.next_sync_events()
+ self.debug_reports(r, uhdev, events)
+ assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == 0
+
+ # do not release the contact; the sticky-fingers timer must do it
+ # after 100ms, which is where the out-of-bounds release used to hit
+ time.sleep(0.2)
+ events = uhdev.next_sync_events()
+ self.debug_reports(r, uhdev, events)
+ assert libevdev.InputEvent(libevdev.EV_KEY.BTN_TOUCH, 0) in events
+ assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == -1
+
+
class TestMinWin8TSHybrid(BaseTest.TestWin8Multitouch):
def create_device(self):
return MinWin8TSHybrid()
diff --git a/tools/testing/selftests/kvm/Makefile.kvm b/tools/testing/selftests/kvm/Makefile.kvm
index d28a057fa6c2..6fc34e9bf8e1 100644
--- a/tools/testing/selftests/kvm/Makefile.kvm
+++ b/tools/testing/selftests/kvm/Makefile.kvm
@@ -174,6 +174,7 @@ TEST_GEN_PROGS_arm64 += arm64/hello_el2
TEST_GEN_PROGS_arm64 += arm64/host_sve
TEST_GEN_PROGS_arm64 += arm64/hypercalls
TEST_GEN_PROGS_arm64 += arm64/external_aborts
+TEST_GEN_PROGS_arm64 += arm64/mmio_sign_ext
TEST_GEN_PROGS_arm64 += arm64/page_fault_test
TEST_GEN_PROGS_arm64 += arm64/psci_test
TEST_GEN_PROGS_arm64 += arm64/sea_to_user
diff --git a/tools/testing/selftests/kvm/arm64/mmio_sign_ext.c b/tools/testing/selftests/kvm/arm64/mmio_sign_ext.c
new file mode 100644
index 000000000000..25196f1e6322
--- /dev/null
+++ b/tools/testing/selftests/kvm/arm64/mmio_sign_ext.c
@@ -0,0 +1,255 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * mmio_sign_ext - Test sign-extending MMIO load emulation (LDRSB/LDRSH/LDRSW)
+ *
+ * Copyright (c) 2026 Google LLC
+ * Author: Fuad Tabba <fuad.tabba@linux.dev>
+ */
+
+#include <asm/ptrace.h>
+
+#include "processor.h"
+#include "test_util.h"
+
+#define MMIO_ADDR 0x8000000ULL
+
+/* AP[1]: allow unprivileged (EL0) access to a mapping. */
+#define PTE_USER BIT(6)
+
+/* SPSR for ERET to EL0t with DAIF masked. */
+#define SPSR_EL0 (PSR_MODE_EL0t | PSR_D_BIT | PSR_A_BIT | PSR_I_BIT | PSR_F_BIT)
+
+struct mmio_test {
+ const char *name;
+ uint64_t data; /* access-width value, host byte order */
+ uint8_t len;
+ uint64_t expected; /* sign-extended result; same for LE and BE */
+};
+
+/* Paired 1:1, in order, with the loads in guest_loads_le() and el0_be_loads. */
+static const struct mmio_test tests[] = {
+ /* LDRSB Xt: byte sign-extended to 64 bits */
+ { "LDRSB Xt 0xFF", 0xFF, 1, 0xFFFFFFFFFFFFFFFFULL },
+ { "LDRSB Xt 0x7F", 0x7F, 1, 0x7FULL },
+
+ /* LDRSB Wt: byte sign-extended to 32 bits, upper 32 bits zeroed */
+ { "LDRSB Wt 0xFF", 0xFF, 1, 0xFFFFFFFFULL },
+ { "LDRSB Wt 0x7F", 0x7F, 1, 0x7FULL },
+
+ /* LDRSH Xt: halfword sign-extended to 64 bits */
+ { "LDRSH Xt 0x8001", 0x8001, 2, 0xFFFFFFFFFFFF8001ULL },
+ { "LDRSH Xt 0x7FFF", 0x7FFF, 2, 0x7FFFULL },
+
+ /* LDRSH Wt: halfword sign-extended to 32 bits, upper 32 bits zeroed */
+ { "LDRSH Wt 0x8001", 0x8001, 2, 0xFFFF8001ULL },
+ { "LDRSH Wt 0x7FFF", 0x7FFF, 2, 0x7FFFULL },
+
+ /* LDRSW Xt: word sign-extended to 64 bits (no Wt form) */
+ { "LDRSW Xt 0x80000001", 0x80000001, 4, 0xFFFFFFFF80000001ULL },
+ { "LDRSW Xt 0x7FFFFFFF", 0x7FFFFFFF, 4, 0x7FFFFFFFULL },
+};
+
+/* Issue one sign-extending load from MMIO and report the result. */
+#define GUEST_LDRS(load) do { \
+ uint64_t val; \
+ \
+ asm volatile(load : "=r"(val) : "r"(MMIO_ADDR) : "memory"); \
+ GUEST_SYNC(val); \
+} while (0)
+
+/* Little-endian pass: loads issued at EL1. */
+static void guest_loads_le(void)
+{
+ GUEST_LDRS("ldrsb %0, [%1]");
+ GUEST_LDRS("ldrsb %0, [%1]");
+ GUEST_LDRS("ldrsb %w0, [%1]");
+ GUEST_LDRS("ldrsb %w0, [%1]");
+ GUEST_LDRS("ldrsh %0, [%1]");
+ GUEST_LDRS("ldrsh %0, [%1]");
+ GUEST_LDRS("ldrsh %w0, [%1]");
+ GUEST_LDRS("ldrsh %w0, [%1]");
+ GUEST_LDRS("ldrsw %0, [%1]");
+ GUEST_LDRS("ldrsw %0, [%1]");
+}
+
+/*
+ * Run the big-endian loads at EL0, where SCTLR_EL1.E0E flips only the data
+ * endianness; at EL1, SCTLR_EL1.EE would also flip the page-table walk and
+ * fault on the little-endian tables. x0 holds MMIO_ADDR; results return in
+ * x19..x28 (tests[] order) via a single SVC.
+ */
+extern char el0_be_loads[];
+asm(
+" .pushsection .text, \"ax\"\n"
+" .global el0_be_loads\n"
+"el0_be_loads:\n"
+" ldrsb x19, [x0]\n"
+" ldrsb x20, [x0]\n"
+" ldrsb w21, [x0]\n"
+" ldrsb w22, [x0]\n"
+" ldrsh x23, [x0]\n"
+" ldrsh x24, [x0]\n"
+" ldrsh w25, [x0]\n"
+" ldrsh w26, [x0]\n"
+" ldrsw x27, [x0]\n"
+" ldrsw x28, [x0]\n"
+" svc #0\n"
+" .popsection\n"
+);
+
+/* EL1 handler for the EL0 SVC: report the results, then finish. */
+static void el0_svc_handler(struct ex_regs *regs)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(tests); i++)
+ GUEST_SYNC(regs->regs[19 + i]);
+
+ GUEST_DONE();
+}
+
+static bool guest_mixed_endian_el0(void)
+{
+ uint64_t mmfr0 = read_sysreg(id_aa64mmfr0_el1);
+
+ return SYS_FIELD_GET(ID_AA64MMFR0_EL1, BIGEND, mmfr0) ||
+ SYS_FIELD_GET(ID_AA64MMFR0_EL1, BIGENDEL0, mmfr0);
+}
+
+static void guest_code(void)
+{
+ guest_loads_le();
+
+ if (guest_mixed_endian_el0()) {
+ write_sysreg(read_sysreg(sctlr_el1) | SCTLR_EL1_E0E, sctlr_el1);
+ isb();
+
+ asm volatile(
+ " msr elr_el1, %[pc]\n"
+ " msr spsr_el1, %[spsr]\n"
+ " mov x0, %[mmio]\n"
+ " isb\n"
+ " eret\n"
+ :
+ : [pc] "r"(el0_be_loads),
+ [spsr] "r"((uint64_t)SPSR_EL0),
+ [mmio] "r"(MMIO_ADDR)
+ : "x0", "memory");
+ __builtin_unreachable(); /* el0_svc_handler ends the test */
+ }
+
+ GUEST_DONE();
+}
+
+static void handle_mmio(struct kvm_run *run, const struct mmio_test *t, bool be)
+{
+ int i;
+
+ TEST_ASSERT_EQ(run->mmio.phys_addr, MMIO_ADDR);
+ TEST_ASSERT(!run->mmio.is_write, "Expected MMIO read for %s", t->name);
+ TEST_ASSERT_EQ(run->mmio.len, t->len);
+
+ memset(run->mmio.data, 0, sizeof(run->mmio.data));
+ if (be) {
+ /* The guest reads the device bytes most-significant first. */
+ for (i = 0; i < t->len; i++)
+ run->mmio.data[i] = t->data >> (8 * (t->len - 1 - i));
+ } else {
+ /* Works because arm64 KVM hosts are always little-endian. */
+ memcpy(run->mmio.data, &t->data, t->len);
+ }
+}
+
+static void expect_sync(struct kvm_vcpu *vcpu, struct ucall *uc,
+ const struct mmio_test *t)
+{
+ switch (get_ucall(vcpu, uc)) {
+ case UCALL_SYNC:
+ TEST_ASSERT(uc->args[1] == t->expected,
+ "%s: got %#lx, want %#lx", t->name,
+ (unsigned long)uc->args[1], (unsigned long)t->expected);
+ break;
+ case UCALL_ABORT:
+ REPORT_GUEST_ASSERT(*uc);
+ break;
+ default:
+ TEST_FAIL("Unexpected ucall for %s", t->name);
+ }
+}
+
+/* OR PTE_USER into the leaf descriptors covering [gva, gva + len). */
+static void make_el0_accessible(struct kvm_vm *vm, uint64_t gva, uint64_t len)
+{
+ uint64_t addr;
+
+ for (addr = gva & ~((uint64_t)vm->page_size - 1); addr < gva + len;
+ addr += vm->page_size)
+ *virt_get_pte_hva(vm, addr) |= PTE_USER;
+}
+
+static bool vcpu_mixed_endian_el0(struct kvm_vcpu *vcpu)
+{
+ uint64_t mmfr0 = vcpu_get_reg(vcpu, KVM_ARM64_SYS_REG(SYS_ID_AA64MMFR0_EL1));
+
+ return SYS_FIELD_GET(ID_AA64MMFR0_EL1, BIGEND, mmfr0) ||
+ SYS_FIELD_GET(ID_AA64MMFR0_EL1, BIGENDEL0, mmfr0);
+}
+
+int main(void)
+{
+ struct kvm_vcpu *vcpu;
+ struct kvm_vm *vm;
+ struct ucall uc;
+ unsigned int i;
+ bool be;
+
+ vm = vm_create_with_one_vcpu(&vcpu, guest_code);
+ virt_map(vm, MMIO_ADDR, MMIO_ADDR, 1);
+
+ vm_init_descriptor_tables(vm);
+ vcpu_init_descriptor_tables(vcpu);
+ vm_install_sync_handler(vm, VECTOR_SYNC_LOWER_64, ESR_ELx_EC_SVC64,
+ el0_svc_handler);
+
+ be = vcpu_mixed_endian_el0(vcpu);
+ if (be)
+ make_el0_accessible(vm, MMIO_ADDR, vm->page_size);
+
+ ksft_print_header();
+ ksft_set_plan(ARRAY_SIZE(tests) * (be ? 2 : 1));
+
+ /* Little-endian pass: one load and one result per iteration. */
+ for (i = 0; i < ARRAY_SIZE(tests); i++) {
+ const struct mmio_test *t = &tests[i];
+
+ vcpu_run(vcpu);
+ TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_MMIO);
+ handle_mmio(vcpu->run, t, false);
+
+ vcpu_run(vcpu);
+ expect_sync(vcpu, &uc, t);
+
+ ksft_test_result_pass("%s\n", t->name);
+ }
+
+ if (be) {
+ /* The EL0 stub issues all the loads, then reports the results. */
+ for (i = 0; i < ARRAY_SIZE(tests); i++) {
+ vcpu_run(vcpu);
+ TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_MMIO);
+ handle_mmio(vcpu->run, &tests[i], true);
+ }
+ for (i = 0; i < ARRAY_SIZE(tests); i++) {
+ vcpu_run(vcpu);
+ expect_sync(vcpu, &uc, &tests[i]);
+ ksft_test_result_pass("BE %s\n", tests[i].name);
+ }
+ }
+
+ vcpu_run(vcpu);
+ TEST_ASSERT(get_ucall(vcpu, &uc) == UCALL_DONE, "Expected UCALL_DONE");
+
+ kvm_vm_free(vm);
+
+ ksft_finished();
+}
diff --git a/tools/testing/selftests/kvm/x86/sev_init2_tests.c b/tools/testing/selftests/kvm/x86/sev_init2_tests.c
index 8db88c355f16..689390c10f7c 100644
--- a/tools/testing/selftests/kvm/x86/sev_init2_tests.c
+++ b/tools/testing/selftests/kvm/x86/sev_init2_tests.c
@@ -130,12 +130,18 @@ int main(int argc, char *argv[])
KVM_X86_SEV_VMSA_FEATURES,
&supported_vmsa_features);
- have_sev = kvm_cpu_has(X86_FEATURE_SEV);
- TEST_ASSERT(have_sev == !!(kvm_check_cap(KVM_CAP_VM_TYPES) & BIT(KVM_X86_SEV_VM)),
- "sev: KVM_CAP_VM_TYPES (%x) does not match cpuid (checking %x)",
- kvm_check_cap(KVM_CAP_VM_TYPES), 1 << KVM_X86_SEV_VM);
+ /*
+ * Whether a VM type is available depends on KVM, not just CPUID: e.g.
+ * when all SEV ASIDs are assigned to SEV-SNP, KVM does not offer the
+ * SEV VM type even though X86_FEATURE_SEV is set. Derive availability
+ * from KVM_CAP_VM_TYPES and only assert the one-way implication that a
+ * type offered by KVM must also be reported in CPUID.
+ */
+ have_sev = kvm_check_cap(KVM_CAP_VM_TYPES) & BIT(KVM_X86_SEV_VM);
+ TEST_ASSERT(!have_sev || kvm_cpu_has(X86_FEATURE_SEV),
+ "sev: SEV_VM supported without SEV in CPUID");
- TEST_REQUIRE(kvm_check_cap(KVM_CAP_VM_TYPES) & BIT(KVM_X86_SEV_VM));
+ TEST_REQUIRE(have_sev);
have_sev_es = kvm_check_cap(KVM_CAP_VM_TYPES) & BIT(KVM_X86_SEV_ES_VM);
TEST_ASSERT(!have_sev_es || kvm_cpu_has(X86_FEATURE_SEV_ES),
diff --git a/tools/testing/selftests/kvm/x86/sev_migrate_tests.c b/tools/testing/selftests/kvm/x86/sev_migrate_tests.c
index 42bc023d5193..d59abb198d86 100644
--- a/tools/testing/selftests/kvm/x86/sev_migrate_tests.c
+++ b/tools/testing/selftests/kvm/x86/sev_migrate_tests.c
@@ -313,6 +313,49 @@ out:
kvm_vm_free(vm_no_vcpu);
}
+static void test_sev_snp_migrate_reject(void)
+{
+ struct kvm_vm *src_vm, *dst_vm;
+ int ret;
+
+ src_vm = vm_create_barebones_type(KVM_X86_SNP_VM);
+ snp_vm_init(src_vm);
+ __vm_vcpu_add(src_vm, 0);
+ vm_sev_launch(src_vm, snp_default_policy(), NULL);
+
+ dst_vm = vm_create_barebones_type(KVM_X86_SNP_VM);
+ __vm_vcpu_add(dst_vm, 0);
+
+ ret = __sev_migrate_from(dst_vm, src_vm);
+ TEST_ASSERT(ret == -1 && errno == EINVAL,
+ "SNP VM migration should be rejected. ret: %d, errno: %d",
+ ret, errno);
+
+ kvm_vm_free(src_vm);
+ kvm_vm_free(dst_vm);
+}
+
+static void test_sev_snp_mirror_reject(void)
+{
+ struct kvm_vm *src_vm, *dst_vm;
+ int ret;
+
+ src_vm = vm_create_barebones_type(KVM_X86_SNP_VM);
+ snp_vm_init(src_vm);
+ __vm_vcpu_add(src_vm, 0);
+ vm_sev_launch(src_vm, snp_default_policy(), NULL);
+
+ dst_vm = aux_vm_create(false);
+
+ ret = __sev_mirror_create(dst_vm, src_vm);
+ TEST_ASSERT(ret == -1 && errno == EINVAL,
+ "SNP VM mirroring should be rejected. ret: %d, errno: %d",
+ ret, errno);
+
+ kvm_vm_free(src_vm);
+ kvm_vm_free(dst_vm);
+}
+
static void test_sev_move_copy(void)
{
struct kvm_vm *dst_vm, *dst2_vm, *dst3_vm, *sev_vm, *mirror_vm,
@@ -384,12 +427,16 @@ int main(int argc, char *argv[])
test_sev_migrate_parameters();
if (kvm_has_cap(KVM_CAP_VM_COPY_ENC_CONTEXT_FROM))
test_sev_move_copy();
+ if (kvm_cpu_has(X86_FEATURE_SEV_SNP))
+ test_sev_snp_migrate_reject();
}
if (kvm_has_cap(KVM_CAP_VM_COPY_ENC_CONTEXT_FROM)) {
test_sev_mirror(/* es= */ false);
if (have_sev_es)
test_sev_mirror(/* es= */ true);
test_sev_mirror_parameters();
+ if (kvm_cpu_has(X86_FEATURE_SEV_SNP))
+ test_sev_snp_mirror_reject();
}
return 0;
}
diff --git a/tools/testing/selftests/kvm/x86/sev_smoke_test.c b/tools/testing/selftests/kvm/x86/sev_smoke_test.c
index 6b2cbe2a90b7..bf27b6187afa 100644
--- a/tools/testing/selftests/kvm/x86/sev_smoke_test.c
+++ b/tools/testing/selftests/kvm/x86/sev_smoke_test.c
@@ -247,7 +247,14 @@ int main(int argc, char *argv[])
{
TEST_REQUIRE(kvm_cpu_has(X86_FEATURE_SEV));
- test_sev_smoke(guest_sev_code, KVM_X86_SEV_VM, 0);
+ /*
+ * Only exercise VM types the host actually offers. CPUID reporting
+ * SEV does not guarantee KVM offers the SEV VM type: when all SEV
+ * ASIDs are assigned to SEV-SNP, KVM_X86_SEV_VM is unavailable even
+ * though X86_FEATURE_SEV is set. Gate every type on KVM_CAP_VM_TYPES.
+ */
+ if (kvm_check_cap(KVM_CAP_VM_TYPES) & BIT(KVM_X86_SEV_VM))
+ test_sev_smoke(guest_sev_code, KVM_X86_SEV_VM, 0);
if (kvm_check_cap(KVM_CAP_VM_TYPES) & BIT(KVM_X86_SEV_ES_VM))
test_sev_smoke(guest_sev_es_code, KVM_X86_SEV_ES_VM, SEV_POLICY_ES);
diff --git a/tools/testing/selftests/landlock/net_test.c b/tools/testing/selftests/landlock/net_test.c
index 2ed1f76b7a8b..be2eb88092fb 100644
--- a/tools/testing/selftests/landlock/net_test.c
+++ b/tools/testing/selftests/landlock/net_test.c
@@ -1281,6 +1281,103 @@ TEST_F(protocol, connect_unspec)
EXPECT_EQ(0, close(bind_fd));
}
+TEST_F(protocol, tcp_fastopen)
+{
+ const bool restricted = variant->sandbox == TCP_SANDBOX &&
+ variant->prot.type == SOCK_STREAM &&
+ (variant->prot.protocol == IPPROTO_TCP ||
+ variant->prot.protocol == IPPROTO_IP) &&
+ (variant->prot.domain == AF_INET ||
+ variant->prot.domain == AF_INET6);
+ const struct landlock_ruleset_attr ruleset_attr = {
+ .handled_access_net = LANDLOCK_ACCESS_NET_CONNECT_TCP,
+ };
+ int bind_fd, client_fd, status;
+ char buf;
+ pid_t child;
+
+ bind_fd = socket_variant(&self->srv0);
+ ASSERT_LE(0, bind_fd);
+ EXPECT_EQ(0, bind_variant(bind_fd, &self->srv0));
+ if (self->srv0.protocol.type == SOCK_STREAM)
+ EXPECT_EQ(0, listen(bind_fd, backlog));
+
+ child = fork();
+ ASSERT_LE(0, child);
+ if (child == 0) {
+ int connect_fd, ret;
+
+ /* Closes listening socket for the child. */
+ EXPECT_EQ(0, close(bind_fd));
+
+ connect_fd = socket_variant(&self->srv0);
+ ASSERT_LE(0, connect_fd);
+
+ if (variant->sandbox == TCP_SANDBOX) {
+ const int ruleset_fd = landlock_create_ruleset(
+ &ruleset_attr, sizeof(ruleset_attr), 0);
+ ASSERT_LE(0, ruleset_fd);
+
+ enforce_ruleset(_metadata, ruleset_fd);
+ EXPECT_EQ(0, close(ruleset_fd));
+ }
+
+ /* Fast Open with no address. */
+ ret = sendto_variant(connect_fd, NULL, NULL, 0, MSG_FASTOPEN);
+ if (self->srv0.protocol.domain == AF_UNIX) {
+ EXPECT_EQ(-ENOTCONN, ret);
+ } else if (self->srv0.protocol.type == SOCK_DGRAM) {
+ EXPECT_EQ(-EDESTADDRREQ, ret);
+ } else {
+ EXPECT_EQ(-EINVAL, ret);
+ }
+
+ /* Fast Open to a denied address. */
+ ret = sendto_variant(connect_fd, &self->srv0, "A", 1,
+ MSG_FASTOPEN);
+ if (restricted) {
+ EXPECT_EQ(-EACCES, ret);
+ } else if (self->srv0.protocol.domain == AF_UNIX &&
+ self->srv0.protocol.type == SOCK_STREAM) {
+ EXPECT_EQ(-EOPNOTSUPP, ret);
+ } else {
+ EXPECT_EQ(0, ret);
+ }
+
+ EXPECT_EQ(0, close(connect_fd));
+ _exit(_metadata->exit_code);
+ return;
+ }
+
+ client_fd = bind_fd;
+ if (!restricted && self->srv0.protocol.type == SOCK_STREAM &&
+ self->srv0.protocol.domain != AF_UNIX) {
+ client_fd = accept(bind_fd, NULL, 0);
+ ASSERT_LE(0, client_fd);
+ }
+
+ if (restricted) {
+ EXPECT_EQ(-1, read(client_fd, &buf, 1));
+ EXPECT_EQ(ENOTCONN, errno);
+ } else if (self->srv0.protocol.domain == AF_UNIX &&
+ self->srv0.protocol.type == SOCK_STREAM) {
+ EXPECT_EQ(-1, read(client_fd, &buf, 1));
+ EXPECT_EQ(EINVAL, errno);
+ } else {
+ EXPECT_EQ(1, read(client_fd, &buf, 1));
+ EXPECT_EQ('A', buf);
+ }
+
+ EXPECT_EQ(child, waitpid(child, &status, 0));
+ EXPECT_EQ(1, WIFEXITED(status));
+ EXPECT_EQ(EXIT_SUCCESS, WEXITSTATUS(status));
+
+ if (client_fd != bind_fd)
+ EXPECT_LE(0, close(client_fd));
+
+ EXPECT_EQ(0, close(bind_fd));
+}
+
TEST_F(protocol, sendmsg_stream)
{
int srv0_fd, tmp_fd, client_fd, res;
diff --git a/tools/testing/selftests/landlock/scoped_signal_test.c b/tools/testing/selftests/landlock/scoped_signal_test.c
index f24f2c28f62e..2d37d0c06c06 100644
--- a/tools/testing/selftests/landlock/scoped_signal_test.c
+++ b/tools/testing/selftests/landlock/scoped_signal_test.c
@@ -249,12 +249,12 @@ TEST_F(scoped_domains, check_access_signal)
_metadata->exit_code = KSFT_FAIL;
}
-enum thread_return {
- THREAD_INVALID = 0,
- THREAD_SUCCESS = 1,
- THREAD_ERROR = 2,
- THREAD_TEST_FAILED = 3,
-};
+/* clang-format off */
+#define THREAD_INVALID ((void *)0)
+#define THREAD_SUCCESS ((void *)1)
+#define THREAD_ERROR ((void *)2)
+#define THREAD_TEST_FAILED ((void *)3)
+/* clang-format on */
static void *thread_sync(void *arg)
{
@@ -262,15 +262,15 @@ static void *thread_sync(void *arg)
char buf;
if (read(pipe_read, &buf, 1) != 1)
- return (void *)THREAD_ERROR;
+ return THREAD_ERROR;
- return (void *)THREAD_SUCCESS;
+ return THREAD_SUCCESS;
}
TEST(signal_scoping_thread_before)
{
pthread_t no_sandbox_thread;
- enum thread_return ret = THREAD_INVALID;
+ void *ret = THREAD_INVALID;
int thread_pipe[2];
drop_caps(_metadata);
@@ -285,7 +285,7 @@ TEST(signal_scoping_thread_before)
EXPECT_EQ(0, pthread_kill(no_sandbox_thread, 0));
EXPECT_EQ(1, write(thread_pipe[1], ".", 1));
- EXPECT_EQ(0, pthread_join(no_sandbox_thread, (void **)&ret));
+ EXPECT_EQ(0, pthread_join(no_sandbox_thread, &ret));
EXPECT_EQ(THREAD_SUCCESS, ret);
EXPECT_EQ(0, close(thread_pipe[0]));
@@ -295,7 +295,7 @@ TEST(signal_scoping_thread_before)
TEST(signal_scoping_thread_after)
{
pthread_t scoped_thread;
- enum thread_return ret = THREAD_INVALID;
+ void *ret = THREAD_INVALID;
int thread_pipe[2];
drop_caps(_metadata);
@@ -310,7 +310,7 @@ TEST(signal_scoping_thread_after)
EXPECT_EQ(0, pthread_kill(scoped_thread, 0));
EXPECT_EQ(1, write(thread_pipe[1], ".", 1));
- EXPECT_EQ(0, pthread_join(scoped_thread, (void **)&ret));
+ EXPECT_EQ(0, pthread_join(scoped_thread, &ret));
EXPECT_EQ(THREAD_SUCCESS, ret);
EXPECT_EQ(0, close(thread_pipe[0]));
@@ -327,20 +327,20 @@ void *thread_setuid(void *ptr)
char buf;
if (read(arg->pipe_read, &buf, 1) != 1)
- return (void *)THREAD_ERROR;
+ return THREAD_ERROR;
/* libc's setuid() should update all thread's credentials. */
if (getuid() != arg->new_uid)
- return (void *)THREAD_TEST_FAILED;
+ return THREAD_TEST_FAILED;
- return (void *)THREAD_SUCCESS;
+ return THREAD_SUCCESS;
}
TEST(signal_scoping_thread_setuid)
{
struct thread_setuid_args arg;
pthread_t no_sandbox_thread;
- enum thread_return ret = THREAD_INVALID;
+ void *ret = THREAD_INVALID;
int pipe_parent[2];
int prev_uid;
@@ -367,7 +367,7 @@ TEST(signal_scoping_thread_setuid)
EXPECT_EQ(arg.new_uid, getuid());
EXPECT_EQ(1, write(pipe_parent[1], ".", 1));
- EXPECT_EQ(0, pthread_join(no_sandbox_thread, (void **)&ret));
+ EXPECT_EQ(0, pthread_join(no_sandbox_thread, &ret));
EXPECT_EQ(THREAD_SUCCESS, ret);
clear_cap(_metadata, CAP_SETUID);
@@ -400,6 +400,24 @@ static int setup_signal_handler(int signal)
return sigaction(SIGURG, &sa, NULL);
}
+/*
+ * MSG_OOB might be disabled in the kernel via the CONFIG_AF_UNIX_OOB
+ * switch, so this function can be used for probing for its availability.
+ */
+static bool has_af_unix_oob(void)
+{
+ bool available = false;
+ int sp[2];
+
+ if (socketpair(AF_UNIX, SOCK_STREAM, 0, sp) == 0) {
+ available = (send(sp[0], ".", 1, MSG_OOB) == 1);
+ close(sp[0]);
+ close(sp[1]);
+ }
+
+ return available;
+}
+
/* clang-format off */
FIXTURE(fown) {};
/* clang-format on */
@@ -462,6 +480,9 @@ TEST_F(fown, sigurg_socket)
int pipe_parent[2], pipe_child[2];
pid_t child;
+ if (!has_af_unix_oob())
+ SKIP(return, "CONFIG_AF_UNIX_OOB / MSG_OOB not available");
+
memset(&server_address, 0, sizeof(server_address));
set_unix_address(&server_address, 0);
@@ -667,20 +688,20 @@ static void *thread_setown_scoped(void *arg)
ruleset_fd =
landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
if (ruleset_fd < 0)
- return (void *)THREAD_ERROR;
+ return THREAD_ERROR;
if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) ||
landlock_restrict_self(ruleset_fd, 0)) {
close(ruleset_fd);
- return (void *)THREAD_ERROR;
+ return THREAD_ERROR;
}
close(ruleset_fd);
/* Makes this process group own the SIGIO source. */
if (fcntl(fd, F_SETSIG, SIGURG) || fcntl(fd, F_SETOWN, -getpgrp()) ||
fcntl(fd, F_SETFL, O_ASYNC))
- return (void *)THREAD_ERROR;
+ return THREAD_ERROR;
- return (void *)THREAD_SUCCESS;
+ return THREAD_SUCCESS;
}
/*
@@ -702,7 +723,7 @@ TEST(sigio_to_pgid_self)
{
int trigger[2];
pthread_t thread;
- enum thread_return ret = THREAD_INVALID;
+ void *ret = THREAD_INVALID;
int i;
drop_caps(_metadata);
@@ -722,7 +743,7 @@ TEST(sigio_to_pgid_self)
*/
ASSERT_EQ(0, pthread_create(&thread, NULL, thread_setown_scoped,
&trigger[0]));
- ASSERT_EQ(0, pthread_join(thread, (void **)&ret));
+ ASSERT_EQ(0, pthread_join(thread, &ret));
ASSERT_EQ(THREAD_SUCCESS, ret);
/* Fans SIGURG out to the process group. */
diff --git a/tools/testing/selftests/lkdtm/tests.txt b/tools/testing/selftests/lkdtm/tests.txt
index d8180bbe31e8..bec57a02913a 100644
--- a/tools/testing/selftests/lkdtm/tests.txt
+++ b/tools/testing/selftests/lkdtm/tests.txt
@@ -78,7 +78,7 @@ USERCOPY_STACK_FRAME_TO
USERCOPY_STACK_FRAME_FROM
USERCOPY_STACK_BEYOND
USERCOPY_KERNEL
-STACKLEAK_ERASING OK: the rest of the thread stack is properly erased
+KSTACK_ERASE OK: the rest of the thread stack is properly erased
CFI_FORWARD_PROTO
CFI_BACKWARD call trace:|ok: control flow unchanged
FORTIFY_STRSCPY detected buffer overflow
diff --git a/tools/testing/selftests/mm/hmm-tests.c b/tools/testing/selftests/mm/hmm-tests.c
index e4c49699f3f7..2f2b9879d100 100644
--- a/tools/testing/selftests/mm/hmm-tests.c
+++ b/tools/testing/selftests/mm/hmm-tests.c
@@ -23,6 +23,7 @@
#include <time.h>
#include <pthread.h>
#include <limits.h>
+#include <linux/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
diff --git a/tools/testing/selftests/mm/ksft_process_madv.sh b/tools/testing/selftests/mm/ksft_process_madv.sh
index 2c3137ae8bc8..edad2d2d888f 100755
--- a/tools/testing/selftests/mm/ksft_process_madv.sh
+++ b/tools/testing/selftests/mm/ksft_process_madv.sh
@@ -1,4 +1,4 @@
#!/bin/sh -e
# SPDX-License-Identifier: GPL-2.0
-./run_vmtests.sh -t mmap
+./run_vmtests.sh -t process_madv
diff --git a/tools/testing/selftests/mm/mlock-random-test.c b/tools/testing/selftests/mm/mlock-random-test.c
index 9d349c151360..16294bc7dae6 100644
--- a/tools/testing/selftests/mm/mlock-random-test.c
+++ b/tools/testing/selftests/mm/mlock-random-test.c
@@ -84,7 +84,7 @@ int get_proc_locked_vm_size(void)
int get_proc_page_size(unsigned long addr)
{
FILE *smaps;
- char *line;
+ char *line = NULL;
unsigned long mmupage_size = 0;
size_t size;
diff --git a/tools/testing/selftests/mm/pagemap_ioctl.c b/tools/testing/selftests/mm/pagemap_ioctl.c
index 762306177ad8..f9bcff8e78fa 100644
--- a/tools/testing/selftests/mm/pagemap_ioctl.c
+++ b/tools/testing/selftests/mm/pagemap_ioctl.c
@@ -1051,6 +1051,57 @@ static void test_simple(void)
ksft_test_result(i == TEST_ITERATIONS, "Test %s\n", __func__);
}
+/*
+ * A range that was populated and then MADV_DONTNEED'd is genuine pte_none
+ * with no uffd-wp marker. Such a pte must read the same regardless of which
+ * PAGEMAP_SCAN path serves the request: both the PAGE_IS_WRITTEN fast path and
+ * the generic path (reached e.g. via category_anyof_mask) must report every
+ * page written.
+ */
+static void unpopulated_scan_test(void)
+{
+ int npages = 16, i;
+ long mem_size = npages * page_size;
+ struct page_region regions[16];
+ long fast = 0, slow = 0, ret;
+ char *mem;
+
+ mem = mmap(NULL, mem_size, PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+ if (mem == MAP_FAILED)
+ ksft_exit_fail_msg("%s mmap failed\n", __func__);
+
+ wp_init(mem, mem_size);
+
+ /* Populate, then drop: the ptes become pte_none without a marker. */
+ memset(mem, 1, mem_size);
+ if (madvise(mem, mem_size, MADV_DONTNEED))
+ ksft_exit_fail_msg("%s MADV_DONTNEED failed\n", __func__);
+
+ /* Fast path: category_mask == return_mask == PAGE_IS_WRITTEN. */
+ ret = pagemap_ioctl(mem, mem_size, regions, npages, 0, 0,
+ PAGE_IS_WRITTEN, 0, 0, PAGE_IS_WRITTEN);
+ if (ret < 0)
+ ksft_exit_fail_msg("%s fast scan failed\n", __func__);
+ for (i = 0; i < ret; i++)
+ fast += LEN(regions[i]);
+
+ /* Generic path: same query expressed via category_anyof_mask. */
+ ret = pagemap_ioctl(mem, mem_size, regions, npages, 0, 0,
+ 0, PAGE_IS_WRITTEN, 0, PAGE_IS_WRITTEN);
+ if (ret < 0)
+ ksft_exit_fail_msg("%s generic scan failed\n", __func__);
+ for (i = 0; i < ret; i++)
+ slow += LEN(regions[i]);
+
+ ksft_test_result(fast == npages && slow == npages,
+ "%s unpopulated ptes reported written by both paths (%ld, %ld of %d)\n",
+ __func__, fast, slow, npages);
+
+ wp_free(mem, mem_size);
+ munmap(mem, mem_size);
+}
+
int sanity_tests(void)
{
unsigned long long mem_size, vec_size;
@@ -1368,7 +1419,7 @@ void *thread_proc(void *mem)
ksft_exit_fail_msg("pthread_barrier_wait\n");
for (i = 0; i < access_per_thread; ++i)
- __atomic_add_fetch(m + i * (0x1000 / sizeof(*m)), 1, __ATOMIC_SEQ_CST);
+ __atomic_add_fetch(m + i * (page_size / sizeof(*m)), 1, __ATOMIC_SEQ_CST);
ret = pthread_barrier_wait(&end_barrier);
if (ret && ret != PTHREAD_BARRIER_SERIAL_THREAD)
@@ -1403,15 +1454,15 @@ static void transact_test(int page_size)
if (pthread_barrier_init(&end_barrier, NULL, nthreads + 1))
ksft_exit_fail_msg("pthread_barrier_init\n");
- mem = mmap(NULL, 0x1000 * nthreads * pages_per_thread, PROT_READ | PROT_WRITE,
+ mem = mmap(NULL, page_size * nthreads * pages_per_thread, PROT_READ | PROT_WRITE,
MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
if (mem == MAP_FAILED)
ksft_exit_fail_msg("Error mmap %s.\n", strerror(errno));
- wp_init(mem, 0x1000 * nthreads * pages_per_thread);
- wp_addr_range(mem, 0x1000 * nthreads * pages_per_thread);
+ wp_init(mem, page_size * nthreads * pages_per_thread);
+ wp_addr_range(mem, page_size * nthreads * pages_per_thread);
- memset(mem, 0, 0x1000 * nthreads * pages_per_thread);
+ memset(mem, 0, page_size * nthreads * pages_per_thread);
count = get_dirty_pages_reset(mem, nthreads * pages_per_thread, 1, page_size);
ksft_test_result(count > 0, "%s count %u\n", __func__, count);
@@ -1420,7 +1471,7 @@ static void transact_test(int page_size)
finish = 0;
for (i = 0; i < nthreads; ++i)
- pthread_create(&th, NULL, thread_proc, mem + 0x1000 * i * pages_per_thread);
+ pthread_create(&th, NULL, thread_proc, mem + page_size * i * pages_per_thread);
extra_pages = 0;
for (i = 0; i < iter_count; ++i) {
@@ -1559,7 +1610,7 @@ int main(int __attribute__((unused)) argc, char *argv[])
if (!hugetlb_setup_default(4))
ksft_print_msg("HugeTLB test will be skipped\n");
- ksft_set_plan(117);
+ ksft_set_plan(118);
page_size = getpagesize();
hpage_size = read_pmd_pagesize();
@@ -1737,6 +1788,9 @@ int main(int __attribute__((unused)) argc, char *argv[])
/* 17. ZEROPFN tests */
zeropfn_tests();
+ /* 18. Unpopulated pte scan-path consistency */
+ unpopulated_scan_test();
+
close(pagemap_fd);
ksft_finished();
}
diff --git a/tools/testing/selftests/net/af_unix/.gitignore b/tools/testing/selftests/net/af_unix/.gitignore
index 240b26740c9e..973176644103 100644
--- a/tools/testing/selftests/net/af_unix/.gitignore
+++ b/tools/testing/selftests/net/af_unix/.gitignore
@@ -6,3 +6,4 @@ scm_rights
so_peek_off
unix_connect
unix_connreset
+unix_listen
diff --git a/tools/testing/selftests/net/af_unix/Makefile b/tools/testing/selftests/net/af_unix/Makefile
index 4c0375e28bbe..57d159803a3a 100644
--- a/tools/testing/selftests/net/af_unix/Makefile
+++ b/tools/testing/selftests/net/af_unix/Makefile
@@ -14,6 +14,7 @@ TEST_GEN_PROGS := \
so_peek_off \
unix_connect \
unix_connreset \
+ unix_listen \
# end of TEST_GEN_PROGS
include ../../lib.mk
diff --git a/tools/testing/selftests/net/af_unix/config b/tools/testing/selftests/net/af_unix/config
index b5429c15a53c..41dbb03c747e 100644
--- a/tools/testing/selftests/net/af_unix/config
+++ b/tools/testing/selftests/net/af_unix/config
@@ -1,3 +1,4 @@
CONFIG_AF_UNIX_OOB=y
CONFIG_UNIX=y
CONFIG_UNIX_DIAG=m
+CONFIG_USER_NS=y
diff --git a/tools/testing/selftests/net/af_unix/unix_listen.c b/tools/testing/selftests/net/af_unix/unix_listen.c
new file mode 100644
index 000000000000..416fa3e5bfe9
--- /dev/null
+++ b/tools/testing/selftests/net/af_unix/unix_listen.c
@@ -0,0 +1,187 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Tests for the state checks in AF_UNIX listen().
+ *
+ * The central case is a regression test: listen() on a bound socket that
+ * is already connected (i.e. not in TCP_CLOSE or TCP_LISTEN state) must
+ * fail with EINVAL. A prior change accidentally let it return success
+ * without doing anything, because a helper called in between reset the
+ * error code to 0. The neighbouring checks (unbound, already listening)
+ * are tested too so they cannot silently regress the same way.
+ *
+ * Every case runs for both listenable socket types (SOCK_STREAM and
+ * SOCK_SEQPACKET) and both pathname and abstract addresses.
+ */
+#define _GNU_SOURCE
+
+#include <errno.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include "kselftest_harness.h"
+
+#define SK_NAME "unix_listen_sk"
+#define SRV_NAME "unix_listen_srv"
+
+FIXTURE(unix_listen)
+{
+ int sk; /* socket under test */
+ int server; /* a listening peer, when a test needs one */
+ struct sockaddr_un addr, srv_addr;
+ socklen_t addrlen, srv_addrlen;
+};
+
+FIXTURE_VARIANT(unix_listen)
+{
+ int type;
+ int abstract;
+};
+
+FIXTURE_VARIANT_ADD(unix_listen, stream_pathname)
+{
+ .type = SOCK_STREAM,
+ .abstract = 0,
+};
+
+FIXTURE_VARIANT_ADD(unix_listen, stream_abstract)
+{
+ .type = SOCK_STREAM,
+ .abstract = 1,
+};
+
+FIXTURE_VARIANT_ADD(unix_listen, seqpacket_pathname)
+{
+ .type = SOCK_SEQPACKET,
+ .abstract = 0,
+};
+
+FIXTURE_VARIANT_ADD(unix_listen, seqpacket_abstract)
+{
+ .type = SOCK_SEQPACKET,
+ .abstract = 1,
+};
+
+/* Fill @addr with a pathname or abstract address named @name. */
+static socklen_t unix_set_addr(struct sockaddr_un *addr, const char *name,
+ int abstract)
+{
+ size_t len = strlen(name);
+
+ memset(addr, 0, sizeof(*addr));
+ addr->sun_family = AF_UNIX;
+ /* An abstract address leads with a NUL and has no filesystem entry. */
+ memcpy(addr->sun_path + (abstract ? 1 : 0), name, len);
+
+ return offsetof(struct sockaddr_un, sun_path) + len + 1;
+}
+
+FIXTURE_SETUP(unix_listen)
+{
+ self->sk = -1;
+ self->server = -1;
+ self->addrlen = unix_set_addr(&self->addr, SK_NAME, variant->abstract);
+ self->srv_addrlen = unix_set_addr(&self->srv_addr, SRV_NAME,
+ variant->abstract);
+}
+
+FIXTURE_TEARDOWN(unix_listen)
+{
+ if (self->sk >= 0)
+ close(self->sk);
+ if (self->server >= 0)
+ close(self->server);
+
+ /* Pathname sockets leave a filesystem entry behind; abstract ones do not. */
+ if (!variant->abstract) {
+ remove(SK_NAME);
+ remove(SRV_NAME);
+ }
+}
+
+/* A bound socket in TCP_CLOSE is the normal, allowed case. */
+TEST_F(unix_listen, bound_is_ok)
+{
+ int err;
+
+ self->sk = socket(AF_UNIX, variant->type, 0);
+ ASSERT_LE(0, self->sk);
+
+ err = bind(self->sk, (struct sockaddr *)&self->addr, self->addrlen);
+ ASSERT_EQ(0, err);
+
+ err = listen(self->sk, 8);
+ EXPECT_EQ(0, err);
+}
+
+/* Listening again on an already-listening socket (TCP_LISTEN) is allowed. */
+TEST_F(unix_listen, relisten_is_ok)
+{
+ int err;
+
+ self->sk = socket(AF_UNIX, variant->type, 0);
+ ASSERT_LE(0, self->sk);
+
+ err = bind(self->sk, (struct sockaddr *)&self->addr, self->addrlen);
+ ASSERT_EQ(0, err);
+
+ err = listen(self->sk, 8);
+ ASSERT_EQ(0, err);
+
+ err = listen(self->sk, 16);
+ EXPECT_EQ(0, err);
+}
+
+/* listen() on an unbound socket fails: there is nothing to listen on. */
+TEST_F(unix_listen, unbound_is_einval)
+{
+ int err;
+
+ self->sk = socket(AF_UNIX, variant->type, 0);
+ ASSERT_LE(0, self->sk);
+
+ err = listen(self->sk, 8);
+ EXPECT_EQ(-1, err);
+ EXPECT_EQ(EINVAL, errno);
+}
+
+/*
+ * The regression: a bound socket that has already been connected is not in
+ * TCP_CLOSE or TCP_LISTEN, so listen() must reject it with EINVAL rather
+ * than quietly succeeding.
+ */
+TEST_F(unix_listen, connected_is_einval)
+{
+ int err;
+
+ self->server = socket(AF_UNIX, variant->type, 0);
+ ASSERT_LE(0, self->server);
+
+ err = bind(self->server, (struct sockaddr *)&self->srv_addr,
+ self->srv_addrlen);
+ ASSERT_EQ(0, err);
+
+ err = listen(self->server, 8);
+ ASSERT_EQ(0, err);
+
+ self->sk = socket(AF_UNIX, variant->type, 0);
+ ASSERT_LE(0, self->sk);
+
+ /* Bind first so the unbound check does not mask the state check. */
+ err = bind(self->sk, (struct sockaddr *)&self->addr, self->addrlen);
+ ASSERT_EQ(0, err);
+
+ err = connect(self->sk, (struct sockaddr *)&self->srv_addr,
+ self->srv_addrlen);
+ ASSERT_EQ(0, err);
+
+ err = listen(self->sk, 8);
+ EXPECT_EQ(-1, err);
+ EXPECT_EQ(EINVAL, errno);
+}
+
+TEST_HARNESS_MAIN
diff --git a/tools/testing/selftests/net/bridge_vlan_dump.sh b/tools/testing/selftests/net/bridge_vlan_dump.sh
index ad66731d2a6f..90e18e2104e3 100755
--- a/tools/testing/selftests/net/bridge_vlan_dump.sh
+++ b/tools/testing/selftests/net/bridge_vlan_dump.sh
@@ -13,6 +13,7 @@ ALL_TESTS="
vlan_range_mcast_max_groups
vlan_range_mcast_n_groups
vlan_range_mcast_enabled
+ vlan_range_pvid
"
setup_prepare()
@@ -191,6 +192,28 @@ vlan_range_mcast_enabled()
log_test "VLAN range grouping with mcast_enabled"
}
+vlan_range_pvid()
+{
+ RET=0
+
+ ip -n "$NS" link set dev br0 type bridge vlan_default_pvid 1
+ check_err $? "Failed to configure default PVID"
+ defer ip -n "$NS" link set dev br0 type bridge vlan_default_pvid 0
+
+ bridge -n "$NS" vlan add vid 2 dev dummy0 untagged
+ check_err $? "Failed to add VLAN 2"
+ defer bridge -n "$NS" vlan del vid 2 dev dummy0
+
+ bridge -n "$NS" -d vlan show dev dummy0 |
+ grep -Eq '(^|[[:space:]])2([[:space:]]|$)'
+ check_err $? "VLAN following PVID is missing from detailed dump"
+
+ bridge -n "$NS" -d vlan show dev dummy0 | grep -q "1-2"
+ check_fail $? "PVID was incorrectly included in a VLAN range"
+
+ log_test "PVID is isolated from VLAN dump ranges"
+}
+
# Verify the newest tested option is supported
if ! bridge vlan help 2>&1 | grep -q "neigh_suppress"; then
echo "SKIP: iproute2 too old, missing per-VLAN neighbor suppression support"
diff --git a/tools/testing/selftests/net/lib.sh b/tools/testing/selftests/net/lib.sh
index b3827b43782b..d46d2cec89e4 100644
--- a/tools/testing/selftests/net/lib.sh
+++ b/tools/testing/selftests/net/lib.sh
@@ -70,12 +70,33 @@ ksft_exit_status_merge()
$ksft_xfail $ksft_pass $ksft_skip $ksft_fail
}
+timestamp_ms()
+{
+ local now
+ local seconds
+ local nanoseconds
+
+ now=$(date -u +%s:%N) || return
+ seconds=${now%:*}
+ nanoseconds=${now#*:}
+
+ if [[ $nanoseconds =~ ^[0-9]+$ ]]; then
+ nanoseconds=${nanoseconds:0:9}
+ else
+ nanoseconds=0
+ fi
+
+ echo $((seconds * 1000 + 10#$nanoseconds / 1000000))
+}
+
loopy_wait()
{
local sleep_cmd=$1; shift
local timeout_ms=$1; shift
+ local start_time
+ local current_time
- local start_time="$(date -u +%s%3N)"
+ start_time=$(timestamp_ms) || return
while true
do
local out
@@ -84,7 +105,7 @@ loopy_wait()
return 0
fi
- local current_time="$(date -u +%s%3N)"
+ current_time=$(timestamp_ms) || return
if ((current_time - start_time > timeout_ms)); then
echo -n "$out"
return 1
diff --git a/tools/testing/selftests/net/mptcp/mptcp_join.sh b/tools/testing/selftests/net/mptcp/mptcp_join.sh
index c0aeffd5cb71..7dc91fac4917 100755
--- a/tools/testing/selftests/net/mptcp/mptcp_join.sh
+++ b/tools/testing/selftests/net/mptcp/mptcp_join.sh
@@ -584,7 +584,7 @@ check_transfer()
mv "$tmpfile" "$out"
tmpfile=""
fi
- cmp -l "$in" "$out" | while read -r i a b; do
+ while read -r i a b; do
local sum=$((0${a} + 0${b}))
if [ $check_invert -eq 0 ] || [ $sum -ne $((0xff)) ]; then
fail_test "$what does not match (in, out):"
@@ -595,7 +595,7 @@ check_transfer()
else
print_info "$what has inverted byte at ${i}"
fi
- done
+ done < <(cmp -l "$in" "$out")
return 0
}
diff --git a/tools/testing/selftests/net/mptcp/userspace_pm.sh b/tools/testing/selftests/net/mptcp/userspace_pm.sh
index e9ae1806ab07..30a809752d1b 100755
--- a/tools/testing/selftests/net/mptcp/userspace_pm.sh
+++ b/tools/testing/selftests/net/mptcp/userspace_pm.sh
@@ -212,7 +212,7 @@ make_connection()
./mptcp_connect -s MPTCP -w 300 -p $app_port -l $listen_addr > /dev/null 2>&1 &
local server_pid=$!
- mptcp_lib_wait_local_port_listen "${ns1}" "${port}"
+ mptcp_lib_wait_local_port_listen "${ns1}" "${app_port}"
# Run the client, transfer $file and stay connected to the server
# to conduct tests
diff --git a/tools/testing/selftests/net/netfilter/nft_flowtable.sh b/tools/testing/selftests/net/netfilter/nft_flowtable.sh
index 08ad07500e8a..449c518bd947 100755
--- a/tools/testing/selftests/net/netfilter/nft_flowtable.sh
+++ b/tools/testing/selftests/net/netfilter/nft_flowtable.sh
@@ -617,7 +617,11 @@ ip -6 -net "$nsr2" route add default via fee1:3::1
ip -net "$ns2" route add default via 10.0.2.1
ip -6 -net "$ns2" route add default via dead:2::1
+ip netns exec "$nsr1" nft -a insert rule inet filter forward \
+ 'meta oif tun0 tcp dport 12345 ct mark set 1 flow add @f1 counter name routed_orig accept'
ip netns exec "$nsr1" nft -a insert rule inet filter forward 'meta oif tun0 accept'
+ip netns exec "$nsr1" nft -a insert rule inet filter forward \
+ 'meta oif tun6 tcp dport 12345 ct mark set 1 flow add @f1 counter name routed_orig accept'
ip netns exec "$nsr1" nft -a insert rule inet filter forward 'meta oif tun6 accept'
ip netns exec "$nsr1" nft -a insert rule inet filter forward \
'meta oif "veth0" tcp sport 12345 ct mark set 1 flow add @f1 counter name routed_repl accept'
@@ -629,7 +633,7 @@ if ! test_tcp_forwarding_nat "$ns1" "$ns2" 1 "IPIP tunnel"; then
fi
if test_tcp_forwarding "$ns1" "$ns2" 1 6 "[dead:2::99]" 12345; then
- echo "PASS: flow offload for ns1/ns2 IP6IP6 tunnel"
+ check_counters "flow offload for ns1/ns2 IP6IP6 tunnel"
else
echo "FAIL: flow offload for ns1/ns2 with IP6IP6 tunnel" 1>&2
ip netns exec "$nsr1" nft list ruleset
@@ -642,6 +646,8 @@ ip -net "$nsr1" link set veth1.10 up
ip -net "$nsr1" addr add 192.168.20.1/24 dev veth1.10
ip -net "$nsr1" addr add fee1:4::1/64 dev veth1.10 nodad
ip netns exec "$nsr1" sysctl net.ipv4.conf.veth1/10.forwarding=1 > /dev/null
+ip netns exec "$nsr1" nft -a insert rule inet filter forward \
+ 'meta oif veth1.10 tcp dport 12345 ct mark set 1 flow add @f1 counter name routed_orig accept'
ip netns exec "$nsr1" nft -a insert rule inet filter forward 'meta oif veth1.10 accept'
ip -net "$nsr1" link add name tun0.10 type ipip local 192.168.20.1 remote 192.168.20.2
@@ -649,6 +655,8 @@ ip -net "$nsr1" link set tun0.10 up
ip -net "$nsr1" addr add 192.168.200.1/24 dev tun0.10
ip -net "$nsr1" route change default via 192.168.200.2
ip netns exec "$nsr1" sysctl net.ipv4.conf.tun0/10.forwarding=1 > /dev/null
+ip netns exec "$nsr1" nft -a insert rule inet filter forward \
+ 'meta oif tun0.10 tcp dport 12345 ct mark set 1 flow add @f1 counter name routed_orig accept'
ip netns exec "$nsr1" nft -a insert rule inet filter forward 'meta oif tun0.10 accept'
ip -net "$nsr1" link add name tun6.10 type ip6tnl local fee1:4::1 remote fee1:4::2 encaplimit none
@@ -656,6 +664,8 @@ ip -net "$nsr1" link set tun6.10 up
ip -net "$nsr1" addr add fee1:5::1/64 dev tun6.10 nodad
ip -6 -net "$nsr1" route delete default
ip -6 -net "$nsr1" route add default via fee1:5::2
+ip netns exec "$nsr1" nft -a insert rule inet filter forward \
+ 'meta oif tun6.10 tcp dport 12345 ct mark set 1 flow add @f1 counter name routed_orig accept'
ip netns exec "$nsr1" nft -a insert rule inet filter forward 'meta oif tun6.10 accept'
ip -net "$nsr2" link add link veth0 name veth0.10 type vlan id 10
@@ -683,7 +693,7 @@ if ! test_tcp_forwarding_nat "$ns1" "$ns2" 1 "IPIP tunnel over vlan"; then
fi
if test_tcp_forwarding "$ns1" "$ns2" 1 6 "[dead:2::99]" 12345; then
- echo "PASS: flow offload for ns1/ns2 IP6IP6 tunnel over vlan"
+ check_counters "flow offload for ns1/ns2 IP6IP6 tunnel over vlan"
else
echo "FAIL: flow offload for ns1/ns2 with IP6IP6 tunnel over vlan" 1>&2
ip netns exec "$nsr1" nft list ruleset
@@ -736,6 +746,61 @@ if ! test_tcp_forwarding_nat "$ns1" "$ns2" 1 "on bridge"; then
ret=1
fi
+if ip -net "$nsr1" link show tun0 > /dev/null 2>&1 &&
+ ip -net "$nsr2" link show tun0 > /dev/null 2>&1; then
+ ip -net "$nsr1" route change default via 192.168.100.2
+ ip -net "$nsr2" route change default via 192.168.100.1
+ ip -6 -net "$nsr1" route delete default
+ ip -6 -net "$nsr1" route add default via fee1:3::2
+ ip -6 -net "$nsr2" route delete default
+ ip -6 -net "$nsr2" route add default via fee1:3::1
+ ip -net "$ns2" route add default via 10.0.2.1
+ ip -6 -net "$ns2" route add default via dead:2::1
+
+ ip netns exec "$nsr1" nft -a insert rule inet filter forward \
+ 'meta oif "tun0" tcp dport 12345 ct mark set 1 flow add @f1 counter name routed_orig accept'
+ ip netns exec "$nsr1" nft -a insert rule inet filter forward \
+ 'meta oif "tun6" tcp dport 12345 ct mark set 1 flow add @f1 counter name routed_orig accept'
+ ip netns exec "$nsr1" nft -a insert rule inet filter forward \
+ 'meta oif "veth0" tcp sport 12345 ct mark set 1 flow add @f1 counter name routed_repl accept'
+ ip netns exec "$nsr1" nft -a insert rule inet filter forward \
+ 'meta oif "br0" tcp sport 12345 ct mark set 1 flow add @f1 counter name routed_repl accept'
+ ip netns exec "$nsr1" nft -a insert rule inet filter forward \
+ 'meta oif "tun0" accept'
+ ip netns exec "$nsr1" nft -a insert rule inet filter forward \
+ 'meta oif "tun6" accept'
+
+ ip netns exec "$nsr1" nft reset counters table inet filter >/dev/null
+
+ if test_tcp_forwarding "$ns1" "$ns2" 1 4 10.0.2.99 12345; then
+ check_counters "bridge + IPIP tunnel"
+ else
+ echo "FAIL: flow offload for ns1/ns2 with bridge + IPIP tunnel" 1>&2
+ ip netns exec "$nsr1" nft list ruleset
+ ret=1
+ fi
+
+ if test_tcp_forwarding "$ns1" "$ns2" 1 6 "[dead:2::99]" 12345; then
+ check_counters "bridge + IP6IP6 tunnel"
+ else
+ echo "FAIL: flow offload for ns1/ns2 with bridge + IP6IP6 tunnel" 1>&2
+ ip netns exec "$nsr1" nft list ruleset
+ ret=1
+ fi
+
+ ip -net "$nsr1" route change default via 192.168.10.2
+ ip -net "$nsr2" route change default via 192.168.10.1
+ ip -net "$ns2" route del default via 10.0.2.1
+ ip -6 -net "$nsr1" route delete default
+ ip -6 -net "$nsr1" route add default via fee1:2::2
+ ip -6 -net "$nsr2" route delete default
+ ip -6 -net "$nsr2" route add default via fee1:2::1
+ ip -6 -net "$ns2" route del default via dead:2::1
+else
+ echo "SKIP: bridge + tunnel flowtable regression (tun0 missing)"
+ [ "$ret" -eq 0 ] && ret=$ksft_skip
+fi
+
# Another test:
# Add bridge interface br0 to Router1, with NAT and VLAN.
diff --git a/tools/testing/selftests/net/openvswitch/config b/tools/testing/selftests/net/openvswitch/config
new file mode 100644
index 000000000000..c659749cd086
--- /dev/null
+++ b/tools/testing/selftests/net/openvswitch/config
@@ -0,0 +1,16 @@
+CONFIG_GENEVE=m
+CONFIG_INET_DIAG=y
+CONFIG_IPV6=y
+CONFIG_NETFILTER=y
+CONFIG_NET_IPGRE=m
+CONFIG_NET_IPGRE_DEMUX=m
+CONFIG_NF_CONNTRACK=m
+CONFIG_NF_CONNTRACK_OVS=y
+CONFIG_OPENVSWITCH=m
+CONFIG_OPENVSWITCH_GENEVE=m
+CONFIG_OPENVSWITCH_GRE=m
+CONFIG_OPENVSWITCH_VXLAN=m
+CONFIG_PSAMPLE=m
+CONFIG_VETH=y
+CONFIG_VLAN_8021Q=y
+CONFIG_VXLAN=m
diff --git a/tools/testing/selftests/net/ovpn/config b/tools/testing/selftests/net/ovpn/config
index d6cf033d555e..6b424762e46e 100644
--- a/tools/testing/selftests/net/ovpn/config
+++ b/tools/testing/selftests/net/ovpn/config
@@ -4,6 +4,7 @@ CONFIG_CRYPTO_CHACHA20POLY1305=y
CONFIG_CRYPTO_GCM=y
CONFIG_DST_CACHE=y
CONFIG_INET=y
+CONFIG_IPV6=y
CONFIG_NET=y
CONFIG_NETFILTER=y
CONFIG_NET_UDP_TUNNEL=y
@@ -11,3 +12,4 @@ CONFIG_NF_TABLES=m
CONFIG_NF_TABLES_INET=y
CONFIG_OVPN=m
CONFIG_STREAM_PARSER=y
+CONFIG_VETH=y
diff --git a/tools/testing/selftests/net/ovpn/ovpn-cli.c b/tools/testing/selftests/net/ovpn/ovpn-cli.c
index d40953375c86..f4effa7580c0 100644
--- a/tools/testing/selftests/net/ovpn/ovpn-cli.c
+++ b/tools/testing/selftests/net/ovpn/ovpn-cli.c
@@ -1785,7 +1785,7 @@ static int ovpn_parse_remote(struct ovpn_ctx *ovpn, const char *host,
const char *service, const char *vpnip)
{
int ret;
- struct addrinfo *result;
+ struct addrinfo *result = NULL;
struct addrinfo hints = {
.ai_family = ovpn->sa_family,
.ai_socktype = SOCK_DGRAM,
@@ -1809,6 +1809,8 @@ static int ovpn_parse_remote(struct ovpn_ctx *ovpn, const char *host,
}
memcpy(&ovpn->remote, result->ai_addr, result->ai_addrlen);
+ freeaddrinfo(result);
+ result = NULL;
}
if (vpnip) {
diff --git a/tools/testing/selftests/net/ovpn/settings b/tools/testing/selftests/net/ovpn/settings
new file mode 100644
index 000000000000..ba4d85f74cd6
--- /dev/null
+++ b/tools/testing/selftests/net/ovpn/settings
@@ -0,0 +1 @@
+timeout=90
diff --git a/tools/testing/selftests/net/packetdrill/tcp_rfc5961_rst-syn-recv.pkt b/tools/testing/selftests/net/packetdrill/tcp_rfc5961_rst-syn-recv.pkt
new file mode 100644
index 000000000000..3fc2de03658a
--- /dev/null
+++ b/tools/testing/selftests/net/packetdrill/tcp_rfc5961_rst-syn-recv.pkt
@@ -0,0 +1,61 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// RFC 9293 Section 3.10.7.4: in SYN-RECEIVED, an exact RST resets
+// the connection. A non-exact in-window RST elicits a challenge ACK,
+// while an out-of-window RST is silently discarded.
+
+`./defaults.sh`
+
+// An exact RST removes the request socket.
+ 0 socket(..., SOCK_STREAM|SOCK_NONBLOCK, IPPROTO_TCP) = 3
+ +0 setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0
+ +0 bind(3, ..., ...) = 0
+ +0 listen(3, 1) = 0
+ +0 < S 0:0(0) win 1000 <mss 1000,sackOK,nop,nop,nop,wscale 0>
+ +0 > S. 0:0(0) ack 1 <...>
+ +0 < R 1:1(0) win 1000
+ +.1 < . 1:1(0) ack 1 win 1000
+ +0 > R 1:1(0)
+ +0 close(3) = 0
+
+// A non-exact in-window RST gets a challenge ACK and the request survives.
+ +0 socket(..., SOCK_STREAM|SOCK_NONBLOCK, IPPROTO_TCP) = 3
+ +0 setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0
+ +0 bind(3, ..., ...) = 0
+ +0 listen(3, 1) = 0
+ +0 < S 0:0(0) win 1000 <mss 1000,sackOK,nop,nop,nop,wscale 0>
+ +0 > S. 0:0(0) ack 1 <...>
+ +0 < R 2:2(0) win 1000
+ +0 > . 1:1(0) ack 1
+ +0 < . 1:1(0) ack 1 win 1000
+ +0 accept(3, ..., ...) = 4
+ +0 close(4) = 0
+ +0 close(3) = 0
+
+// RST sequence validation precedes ACK validation. Even an RST|ACK
+// with an unacceptable ACK value gets a challenge ACK.
+ +0 socket(..., SOCK_STREAM|SOCK_NONBLOCK, IPPROTO_TCP) = 3
+ +0 setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0
+ +0 bind(3, ..., ...) = 0
+ +0 listen(3, 1) = 0
+ +0 < S 0:0(0) win 1000 <mss 1000,sackOK,nop,nop,nop,wscale 0>
+ +0 > S. 0:0(0) ack 1 <...>
+ +0 < R. 2:2(0) ack 100 win 1000
+ +0 > . 1:1(0) ack 1
+ +0 < . 1:1(0) ack 1 win 1000
+ +0 accept(3, ..., ...) = 4
+ +0 close(4) = 0
+ +0 close(3) = 0
+
+// An out-of-window RST is silent and does not remove the request.
+ +0 socket(..., SOCK_STREAM|SOCK_NONBLOCK, IPPROTO_TCP) = 3
+ +0 setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0
+ +0 bind(3, ..., ...) = 0
+ +0 listen(3, 1) = 0
+ +0 < S 0:0(0) win 1000 <mss 1000,sackOK,nop,nop,nop,wscale 0>
+ +0 > S. 0:0(0) ack 1 <...>
+ +0 < R 100001:100001(0) win 1000
+ +.1 < . 1:1(0) ack 1 win 1000
+ +0 accept(3, ..., ...) = 4
+ +0 close(4) = 0
+ +0 close(3) = 0
diff --git a/tools/testing/selftests/net/tcp_mmap.c b/tools/testing/selftests/net/tcp_mmap.c
index 4fcce5150850..2544ae35d07a 100644
--- a/tools/testing/selftests/net/tcp_mmap.c
+++ b/tools/testing/selftests/net/tcp_mmap.c
@@ -313,6 +313,8 @@ end:
tcp_info_get_rcv_mss(fd));
}
error:
+ if (ctx)
+ EVP_MD_CTX_free(ctx);
munmap(buffer, buffer_sz);
close(fd);
if (zflg)
@@ -606,6 +608,8 @@ int main(int argc, char *argv[])
EVP_DigestFinal_ex(ctx, digest, &digest_len);
send(fd, digest, (size_t)SHA256_DIGEST_LENGTH, 0);
}
+ if (ctx)
+ EVP_MD_CTX_free(ctx);
close(fd);
munmap(buffer, buffer_sz);
return 0;
diff --git a/tools/testing/selftests/net/tls.c b/tools/testing/selftests/net/tls.c
index cbdd3ea28b99..9d3cd4fff062 100644
--- a/tools/testing/selftests/net/tls.c
+++ b/tools/testing/selftests/net/tls.c
@@ -24,6 +24,7 @@
#include "kselftest_harness.h"
#define TLS_PAYLOAD_MAX_LEN 16384
+#define TLS_HDR_LEN 5
#define SOL_TLS 282
static int fips_enabled;
@@ -835,6 +836,43 @@ TEST_F(tls, send_and_splice)
EXPECT_EQ(memcmp(mem_send, mem_recv, send_len), 0);
}
+TEST_F(tls, splice_onto_full_record)
+{
+ char mem_send[4608];
+ char mem_recv[4608];
+ int frag_len = 100;
+ int nfrags, i, off;
+ int p[2];
+
+ memrnd(mem_send, sizeof(mem_send));
+ ASSERT_GE(pipe(p), 0);
+
+ for (nfrags = 16; nfrags <= 44; nfrags++) {
+ for (i = 0, off = 0; i < nfrags; i++, off += frag_len) {
+ EXPECT_EQ(write(p[1], mem_send + off, frag_len), frag_len);
+ EXPECT_EQ(splice(p[0], NULL, self->fd, NULL, frag_len,
+ SPLICE_F_MORE), frag_len);
+ }
+
+ EXPECT_EQ(send(self->fd, mem_send + off, 1, MSG_MORE), 1);
+ off++;
+
+ EXPECT_EQ(write(p[1], mem_send + off, frag_len), frag_len);
+ EXPECT_EQ(splice(p[0], NULL, self->fd, NULL, frag_len,
+ SPLICE_F_MORE), frag_len);
+ off += frag_len;
+
+ EXPECT_EQ(send(self->fd, mem_send + off, 1, 0), 1);
+ off++;
+
+ EXPECT_EQ(recv(self->cfd, mem_recv, off, MSG_WAITALL), off);
+ EXPECT_EQ(memcmp(mem_send, mem_recv, off), 0);
+ }
+
+ close(p[0]);
+ close(p[1]);
+}
+
TEST_F(tls, splice_to_pipe)
{
int send_len = TLS_PAYLOAD_MAX_LEN;
@@ -1802,6 +1840,63 @@ TEST_F(tls, recv_efault)
}
#define TLS_RECORD_TYPE_HANDSHAKE 0x16
+
+TEST_F(tls_basic, recvmsg_nopad_retry_iov)
+{
+ char payload[32];
+ char first_iov[sizeof(payload)];
+ char later_iov[sizeof(payload) * 2];
+ char expected_later_iov[sizeof(later_iov)];
+ char cbuf[CMSG_SPACE(sizeof(char))];
+ struct tls_crypto_info_keys tls13;
+ struct iovec iov[] = {
+ { .iov_base = first_iov, .iov_len = sizeof(first_iov) },
+ { .iov_base = later_iov, .iov_len = sizeof(later_iov) },
+ };
+ struct msghdr msg = {
+ .msg_iov = iov,
+ .msg_iovlen = ARRAY_SIZE(iov),
+ .msg_control = cbuf,
+ .msg_controllen = sizeof(cbuf),
+ };
+ int one = 1;
+ int ret;
+ int i;
+
+ if (self->notls)
+ SKIP(return, "no TLS support");
+
+ tls_crypto_info_init(TLS_1_3_VERSION, TLS_CIPHER_AES_GCM_128,
+ &tls13, 0);
+
+ ret = setsockopt(self->fd, SOL_TLS, TLS_TX, &tls13, tls13.len);
+ ASSERT_EQ(ret, 0);
+
+ ret = setsockopt(self->cfd, SOL_TLS, TLS_RX, &tls13, tls13.len);
+ ASSERT_EQ(ret, 0);
+
+ ret = setsockopt(self->cfd, SOL_TLS, TLS_RX_EXPECT_NO_PAD,
+ &one, sizeof(one));
+ ASSERT_EQ(ret, 0);
+
+ for (i = 0; i < sizeof(payload); i++)
+ payload[i] = 0x40 + i;
+ memset(first_iov, 0xa5, sizeof(first_iov));
+ memset(later_iov, 0x5a, sizeof(later_iov));
+ memset(expected_later_iov, 0x5a, sizeof(expected_later_iov));
+
+ /* A control record forces optimistic TLS 1.3 RX to retry. */
+ ret = tls_send_cmsg(self->fd, TLS_RECORD_TYPE_HANDSHAKE,
+ payload, sizeof(payload), 0);
+ ASSERT_EQ(ret, sizeof(payload));
+
+ ret = recvmsg(self->cfd, &msg, 0);
+ ASSERT_EQ(ret, sizeof(payload));
+ EXPECT_EQ(memcmp(first_iov, payload, sizeof(payload)), 0);
+ EXPECT_EQ(memcmp(later_iov, expected_later_iov,
+ sizeof(later_iov)), 0);
+}
+
/* key_update, length 1, update_not_requested */
static const char key_update_msg[] = "\x18\x00\x00\x01\x00";
static void tls_send_keyupdate(struct __test_metadata *_metadata, int fd)
@@ -2638,28 +2733,83 @@ TEST_F(tls_err, bad_rec)
EXPECT_EQ(errno, EAGAIN);
}
+/* cfd carries a byte stream, so one recv() can return part of a
+ * record. Take the fragment length from the record header and wait
+ * for the remainder.
+ */
+static void tls_send_bad_auth(struct __test_metadata *_metadata,
+ int fd, int cfd, int fd2)
+{
+ char buf[128];
+ int len;
+
+ memrnd(buf, sizeof(buf) / 2);
+ ASSERT_EQ(send(fd, buf, sizeof(buf) / 2, 0), sizeof(buf) / 2);
+
+ ASSERT_EQ(recv(cfd, buf, TLS_HDR_LEN, MSG_WAITALL), TLS_HDR_LEN);
+
+ len = ((unsigned char)buf[3] << 8) | (unsigned char)buf[4];
+ ASSERT_GT(len, 0);
+ ASSERT_LE(len, (int)sizeof(buf) - TLS_HDR_LEN);
+
+ ASSERT_EQ(recv(cfd, buf + TLS_HDR_LEN, len, MSG_WAITALL), len);
+
+ buf[TLS_HDR_LEN + len - 1]++;
+
+ ASSERT_EQ(send(fd2, buf, TLS_HDR_LEN + len, 0), TLS_HDR_LEN + len);
+}
+
TEST_F(tls_err, bad_auth)
{
char buf[128];
- int n;
if (self->notls)
SKIP(return, "no TLS support");
- memrnd(buf, sizeof(buf) / 2);
- EXPECT_EQ(send(self->fd, buf, sizeof(buf) / 2, 0), sizeof(buf) / 2);
- n = recv(self->cfd, buf, sizeof(buf), 0);
- EXPECT_GT(n, sizeof(buf) / 2);
-
- buf[n - 1]++;
+ tls_send_bad_auth(_metadata, self->fd, self->cfd, self->fd2);
- EXPECT_EQ(send(self->fd2, buf, n, 0), n);
EXPECT_EQ(recv(self->cfd2, buf, sizeof(buf), 0), -1);
EXPECT_EQ(errno, EBADMSG);
EXPECT_EQ(recv(self->cfd2, buf, sizeof(buf), 0), -1);
EXPECT_EQ(errno, EBADMSG);
}
+/* A record that did not authenticate breaks the connection for every
+ * reader, splice included.
+ *
+ * The two decrypt paths reach that result differently. A synchronous
+ * decrypt leaves the record parsed, so the splice re-runs the decrypt
+ * and fails on the record itself; the ctx->async_wait.err check in
+ * tls_sw_splice_read() is not what stops it. Only an asynchronous
+ * decrypt, which needs a TLS 1.2 socket and an AEAD advertising
+ * CRYPTO_ALG_ASYNC, consumes the record before the failure is
+ * recorded, leaving that check the sole reason the splice fails.
+ */
+TEST_F(tls_err, bad_auth_splice)
+{
+ char buf[128];
+ ssize_t ret;
+ int p[2];
+
+ if (self->notls)
+ SKIP(return, "no TLS support");
+
+ tls_send_bad_auth(_metadata, self->fd, self->cfd, self->fd2);
+
+ EXPECT_EQ(recv(self->cfd2, buf, sizeof(buf), 0), -1);
+ EXPECT_EQ(errno, EBADMSG);
+
+ ASSERT_GE(pipe(p), 0);
+
+ ret = splice(self->cfd2, NULL, p[1], NULL, sizeof(buf),
+ SPLICE_F_NONBLOCK);
+ EXPECT_EQ(ret, -1);
+ EXPECT_EQ(errno, EBADMSG);
+
+ close(p[0]);
+ close(p[1]);
+}
+
TEST_F(tls_err, bad_in_large_read)
{
char txt[3][64];
@@ -2915,7 +3065,6 @@ static size_t parse_tls_records(struct __test_metadata *_metadata,
{
const __u8 *rec = rx_buf;
size_t total_plaintext_rx = 0;
- const __u8 rec_header_len = 5;
while (rec < rx_buf + rx_len) {
__u16 record_payload_len;
@@ -2935,7 +3084,7 @@ static size_t parse_tls_records(struct __test_metadata *_metadata,
/* Plaintext must not exceed the specified limit */
ASSERT_LE(plaintext_len, max_payload_len);
- rec += rec_header_len + record_payload_len;
+ rec += TLS_HDR_LEN + record_payload_len;
}
return total_plaintext_rx;
diff --git a/tools/testing/selftests/net/tun.c b/tools/testing/selftests/net/tun.c
index cf106a49b55e..abe488bac50b 100644
--- a/tools/testing/selftests/net/tun.c
+++ b/tools/testing/selftests/net/tun.c
@@ -42,19 +42,19 @@ static struct in_addr param_ipaddr4_inner_src = {
};
static struct in6_addr param_ipaddr6_outer_dst = {
- { { 0x20, 0x02, 0x0d, 0xb8, 0x01, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 } },
+ { { 0xfd, 0x00, 0x0d, 0xb8, 0x01, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 } },
};
static struct in6_addr param_ipaddr6_outer_src = {
- { { 0x20, 0x02, 0x0d, 0xb8, 0x01, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2 } },
+ { { 0xfd, 0x00, 0x0d, 0xb8, 0x01, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2 } },
};
static struct in6_addr param_ipaddr6_inner_dst = {
- { { 0x20, 0x02, 0x0d, 0xb8, 0x02, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 } },
+ { { 0xfd, 0x00, 0x0d, 0xb8, 0x02, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 } },
};
static struct in6_addr param_ipaddr6_inner_src = {
- { { 0x20, 0x02, 0x0d, 0xb8, 0x02, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2 } },
+ { { 0xfd, 0x00, 0x0d, 0xb8, 0x02, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2 } },
};
#ifndef BIT
diff --git a/tools/testing/selftests/pidfd/pidfd_file_handle_test.c b/tools/testing/selftests/pidfd/pidfd_file_handle_test.c
index 68918734dcf3..1e03ae9575fe 100644
--- a/tools/testing/selftests/pidfd/pidfd_file_handle_test.c
+++ b/tools/testing/selftests/pidfd/pidfd_file_handle_test.c
@@ -373,6 +373,7 @@ TEST_F(file_handle, open_by_handle_at_valid_flags)
O_CLOEXEC |
O_EXCL);
ASSERT_GE(pidfd, 0);
+ ASSERT_NE(fcntl(pidfd, F_GETFL) & PIDFD_THREAD, 0);
ASSERT_EQ(fstat(pidfd, &st2), 0);
ASSERT_TRUE(st1.st_dev == st2.st_dev && st1.st_ino == st2.st_ino);
diff --git a/tools/testing/selftests/riscv/vector/validate_v_ptrace.c b/tools/testing/selftests/riscv/vector/validate_v_ptrace.c
index 74b6f6bcf067..a388b7963d47 100644
--- a/tools/testing/selftests/riscv/vector/validate_v_ptrace.c
+++ b/tools/testing/selftests/riscv/vector/validate_v_ptrace.c
@@ -1,5 +1,6 @@
// SPDX-License-Identifier: GPL-2.0-only
#include <sys/ptrace.h>
+#include <sys/syscall.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/uio.h>
@@ -25,9 +26,9 @@ TEST(ptrace_v_not_enabled)
SKIP(return, "Vector not supported");
chld_lock = 1;
- pid = fork();
+ pid = (pid_t)syscall(SYS_clone, SIGCHLD, 0, NULL, 0, NULL);
ASSERT_LE(0, pid)
- TH_LOG("fork: %m");
+ TH_LOG("clone: %m");
if (pid == 0) {
while (chld_lock == 1)
@@ -74,7 +75,7 @@ TEST(ptrace_v_not_enabled)
ASSERT_EQ(-1, ret);
/* cleanup */
-
+ free(regset_data);
ASSERT_EQ(0, kill(pid, SIGKILL));
}
}
@@ -206,7 +207,7 @@ TEST(ptrace_v_early_debug)
EXPECT_EQ(vl_csr, regset_data->vl);
/* cleanup */
-
+ free(regset_data);
ASSERT_EQ(0, kill(pid, SIGKILL));
}
}
@@ -330,7 +331,7 @@ TEST(ptrace_v_syscall_clobbering)
EXPECT_EQ(0UL, regset_data->vl);
/* cleanup */
-
+ free(regset_data);
ASSERT_EQ(0, kill(pid, SIGKILL));
}
}
@@ -648,7 +649,7 @@ TEST_F(v_csr_invalid, ptrace_v_invalid_values)
ASSERT_EQ(ret, -1);
/* cleanup */
-
+ free(regset_data);
ASSERT_EQ(0, kill(pid, SIGKILL));
}
}
@@ -910,7 +911,7 @@ TEST_F(v_csr_valid, ptrace_v_valid_values)
EXPECT_EQ(regset_data->vlenb, vlenb);
/* cleanup */
-
+ free(regset_data);
ASSERT_EQ(0, kill(pid, SIGKILL));
}
}
diff --git a/tools/testing/selftests/rseq/Makefile b/tools/testing/selftests/rseq/Makefile
index 50d69e22ee7a..aba6317f6cb8 100644
--- a/tools/testing/selftests/rseq/Makefile
+++ b/tools/testing/selftests/rseq/Makefile
@@ -5,9 +5,13 @@ CLANG_FLAGS += -no-integrated-as
endif
top_srcdir = ../../../..
+include $(top_srcdir)/scripts/subarch.include
+ARCH ?= $(SUBARCH)
+LINUX_TOOL_ARCH_INCLUDE = $(top_srcdir)/tools/arch/$(ARCH)/include
CFLAGS += -O2 -Wall -g -I./ $(KHDR_INCLUDES) -L$(OUTPUT) -Wl,-rpath=./ \
- $(CLANG_FLAGS) -I$(top_srcdir)/tools/include
+ $(CLANG_FLAGS) -I$(top_srcdir)/tools/include \
+ -I$(LINUX_TOOL_ARCH_INCLUDE)
LDLIBS += -lpthread -ldl
# Own dependencies because we only want to build against 1st prerequisite, but
diff --git a/tools/testing/selftests/sched_ext/Makefile b/tools/testing/selftests/sched_ext/Makefile
index 5d2dffca0e91..3cfe90e0f34f 100644
--- a/tools/testing/selftests/sched_ext/Makefile
+++ b/tools/testing/selftests/sched_ext/Makefile
@@ -176,6 +176,7 @@ auto-test-targets := \
maybe_null \
minimal \
non_scx_kfunc_deny \
+ nohz_tick \
numa \
allowed_cpus \
peek_dsq \
diff --git a/tools/testing/selftests/sched_ext/nohz_tick.bpf.c b/tools/testing/selftests/sched_ext/nohz_tick.bpf.c
new file mode 100644
index 000000000000..6998c5dd6bcb
--- /dev/null
+++ b/tools/testing/selftests/sched_ext/nohz_tick.bpf.c
@@ -0,0 +1,65 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES
+ *
+ * Exercise tick dependency transitions between infinite and finite slices.
+ */
+#include <scx/common.bpf.h>
+
+char _license[] SEC("license") = "GPL";
+
+const volatile s32 test_cpu;
+bool finite_phase;
+u64 nr_inf_running;
+u64 nr_finite_running;
+u64 nr_finite_ticks;
+
+UEI_DEFINE(uei);
+
+s32 BPF_STRUCT_OPS(nohz_tick_select_cpu, struct task_struct *p, s32 prev_cpu,
+ u64 wake_flags)
+{
+ return prev_cpu;
+}
+
+void BPF_STRUCT_OPS(nohz_tick_enqueue, struct task_struct *p, u64 enq_flags)
+{
+ u64 slice = finite_phase ? 1000000ULL : SCX_SLICE_INF;
+
+ scx_bpf_dsq_insert(p, SCX_DSQ_GLOBAL, slice, enq_flags);
+ if (enq_flags & SCX_ENQ_LAST)
+ scx_bpf_kick_cpu(test_cpu, SCX_KICK_IDLE);
+}
+
+void BPF_STRUCT_OPS(nohz_tick_running, struct task_struct *p)
+{
+ if (bpf_get_smp_processor_id() != test_cpu)
+ return;
+
+ if (finite_phase)
+ __sync_fetch_and_add(&nr_finite_running, 1);
+ else
+ __sync_fetch_and_add(&nr_inf_running, 1);
+}
+
+void BPF_STRUCT_OPS(nohz_tick_tick, struct task_struct *p)
+{
+ if (bpf_get_smp_processor_id() == test_cpu && finite_phase)
+ __sync_fetch_and_add(&nr_finite_ticks, 1);
+}
+
+void BPF_STRUCT_OPS(nohz_tick_exit, struct scx_exit_info *ei)
+{
+ UEI_RECORD(uei, ei);
+}
+
+SEC(".struct_ops.link")
+struct sched_ext_ops nohz_tick_ops = {
+ .select_cpu = (void *)nohz_tick_select_cpu,
+ .enqueue = (void *)nohz_tick_enqueue,
+ .running = (void *)nohz_tick_running,
+ .tick = (void *)nohz_tick_tick,
+ .exit = (void *)nohz_tick_exit,
+ .name = "nohz_tick",
+ .timeout_ms = 1000U,
+};
diff --git a/tools/testing/selftests/sched_ext/nohz_tick.c b/tools/testing/selftests/sched_ext/nohz_tick.c
new file mode 100644
index 000000000000..028f54391c2c
--- /dev/null
+++ b/tools/testing/selftests/sched_ext/nohz_tick.c
@@ -0,0 +1,347 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES
+ *
+ * Validate that a finite-slice EXT task restarts the scheduler tick when it
+ * follows an infinite-slice EXT task and an idle interval on a NOHZ_FULL CPU.
+ */
+#define _GNU_SOURCE
+
+#include <bpf/bpf.h>
+#include <errno.h>
+#include <sched.h>
+#include <signal.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/prctl.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include <scx/common.h>
+
+#include "nohz_tick.bpf.skel.h"
+#include "scx_test.h"
+
+#ifndef SCHED_EXT
+#define SCHED_EXT 7
+#endif
+
+#define MIN_FINITE_TICKS 3
+#define PHASE_TIMEOUT_MS 1000
+
+struct nohz_tick_ctx {
+ struct nohz_tick *skel;
+ cpu_set_t original_mask;
+ int test_cpu;
+};
+
+static int first_allowed_cpu(const cpu_set_t *mask, int first, int last)
+{
+ int cpu;
+
+ for (cpu = first; cpu <= last && cpu < CPU_SETSIZE; cpu++)
+ if (CPU_ISSET(cpu, mask))
+ return cpu;
+
+ return -1;
+}
+
+static int find_nohz_full_cpu(const cpu_set_t *allowed)
+{
+ char buf[4096], *cur, *end;
+ FILE *file;
+
+ file = fopen("/sys/devices/system/cpu/nohz_full", "r");
+ if (!file)
+ return -1;
+ if (!fgets(buf, sizeof(buf), file)) {
+ fclose(file);
+ return -1;
+ }
+ fclose(file);
+
+ cur = buf;
+ while (*cur) {
+ long first, last;
+ int cpu;
+
+ while (*cur == ' ' || *cur == '\t' || *cur == ',')
+ cur++;
+ if (*cur < '0' || *cur > '9')
+ break;
+
+ errno = 0;
+ first = strtol(cur, &end, 10);
+ if (errno || end == cur || first < 0 || first >= CPU_SETSIZE)
+ return -1;
+ cur = end;
+ last = first;
+ if (*cur == '-') {
+ cur++;
+ errno = 0;
+ last = strtol(cur, &end, 10);
+ if (errno || end == cur || last < first)
+ return -1;
+ cur = end;
+ }
+
+ cpu = first_allowed_cpu(allowed, first, last);
+ if (cpu >= 0)
+ return cpu;
+ }
+
+ return -1;
+}
+
+static pid_t start_worker(int cpu)
+{
+ struct sched_param param = {};
+ cpu_set_t mask;
+ pid_t parent;
+ pid_t pid;
+
+ parent = getpid();
+ pid = fork();
+ if (pid != 0)
+ return pid;
+ if (prctl(PR_SET_PDEATHSIG, SIGKILL) || getppid() != parent)
+ _exit(1);
+
+ /*
+ * Become EXT before touching the target so it stays idle until wakeup.
+ */
+ if (sched_setscheduler(0, SCHED_EXT, &param))
+ _exit(1);
+
+ CPU_ZERO(&mask);
+ CPU_SET(cpu, &mask);
+ if (sched_setaffinity(0, sizeof(mask), &mask))
+ _exit(1);
+
+ for (;;)
+ asm volatile("" ::: "memory");
+}
+
+static void stop_worker(pid_t pid)
+{
+ if (pid <= 0)
+ return;
+
+ kill(pid, SIGKILL);
+ waitpid(pid, NULL, 0);
+}
+
+static int pause_worker(pid_t pid)
+{
+ int status;
+
+ if (kill(pid, SIGSTOP))
+ return -errno;
+ if (waitpid(pid, &status, WUNTRACED) != pid)
+ return -errno;
+ if (!WIFSTOPPED(status))
+ return -ECHILD;
+
+ return 0;
+}
+
+static bool wait_for_counter(const u64 *counter, u64 value, int timeout_ms)
+{
+ int elapsed;
+
+ for (elapsed = 0; elapsed < timeout_ms; elapsed++) {
+ if (__atomic_load_n(counter, __ATOMIC_RELAXED) >= value)
+ return true;
+ usleep(1000);
+ }
+
+ return false;
+}
+
+static enum scx_test_status setup(void **ctx_ptr)
+{
+ struct nohz_tick_ctx *ctx;
+ cpu_set_t controller_mask;
+ int cpu;
+
+ ctx = calloc(1, sizeof(*ctx));
+ SCX_FAIL_IF(!ctx, "Failed to allocate context");
+ if (sched_getaffinity(0, sizeof(ctx->original_mask),
+ &ctx->original_mask)) {
+ free(ctx);
+ SCX_FAIL("Failed to get affinity (%d)", errno);
+ }
+
+ cpu = find_nohz_full_cpu(&ctx->original_mask);
+ if (cpu < 0) {
+ fprintf(stderr, "SKIP: no allowed NOHZ_FULL CPU\n");
+ free(ctx);
+ return SCX_TEST_SKIP;
+ }
+
+ controller_mask = ctx->original_mask;
+ CPU_CLR(cpu, &controller_mask);
+ if (CPU_COUNT(&controller_mask) == 0) {
+ fprintf(stderr, "SKIP: no housekeeping CPU available\n");
+ free(ctx);
+ return SCX_TEST_SKIP;
+ }
+
+ ctx->test_cpu = cpu;
+ ctx->skel = nohz_tick__open();
+ if (!ctx->skel) {
+ free(ctx);
+ SCX_FAIL("Failed to open skeleton");
+ }
+
+ SCX_ENUM_INIT(ctx->skel);
+ ctx->skel->rodata->test_cpu = cpu;
+ ctx->skel->struct_ops.nohz_tick_ops->flags |= SCX_OPS_SWITCH_PARTIAL |
+ SCX_OPS_ENQ_LAST;
+ if (nohz_tick__load(ctx->skel)) {
+ nohz_tick__destroy(ctx->skel);
+ free(ctx);
+ SCX_FAIL("Failed to load skeleton");
+ }
+
+ if (sched_setaffinity(0, sizeof(controller_mask), &controller_mask)) {
+ nohz_tick__destroy(ctx->skel);
+ free(ctx);
+ SCX_FAIL("Failed to move controller off CPU %d (%d)", cpu, errno);
+ }
+
+ *ctx_ptr = ctx;
+ return SCX_TEST_PASS;
+}
+
+static enum scx_test_status run(void *ctx_ptr)
+{
+ struct nohz_tick_ctx *ctx = ctx_ptr;
+ struct nohz_tick *skel = ctx->skel;
+ struct bpf_link *link = NULL;
+ enum scx_test_status status = SCX_TEST_FAIL;
+ pid_t finite_worker = -1;
+ pid_t inf_worker = -1;
+ u64 finite_running;
+ u64 finite_ticks;
+ int ret;
+
+ link = bpf_map__attach_struct_ops(skel->maps.nohz_tick_ops);
+ if (!link) {
+ SCX_ERR("Failed to attach scheduler");
+ goto out;
+ }
+
+ /*
+ * Establish SCX_RQ_CAN_STOP_TICK with an infinite-slice task.
+ */
+ inf_worker = start_worker(ctx->test_cpu);
+ if (inf_worker < 0) {
+ SCX_ERR("Failed to start infinite-slice worker (%d)", errno);
+ goto out;
+ }
+ if (!wait_for_counter(&skel->bss->nr_inf_running, 1,
+ PHASE_TIMEOUT_MS)) {
+ SCX_ERR("Infinite-slice worker was not scheduled");
+ goto out;
+ }
+
+ /* Block without exiting so the rq retains the infinite-slice state. */
+ ret = pause_worker(inf_worker);
+ if (ret) {
+ SCX_ERR("Failed to stop infinite-slice worker (%d)", ret);
+ goto out;
+ }
+
+ /* Let the target enter idle with its tick stopped. */
+ usleep(100000);
+
+ /*
+ * The next EXT task receives a finite slice and must restart the tick.
+ */
+ __atomic_store_n(&skel->bss->finite_phase, true, __ATOMIC_RELEASE);
+ finite_worker = start_worker(ctx->test_cpu);
+ if (finite_worker < 0) {
+ SCX_ERR("Failed to start finite-slice worker (%d)", errno);
+ goto out;
+ }
+ if (!wait_for_counter(&skel->bss->nr_finite_running, 1,
+ PHASE_TIMEOUT_MS)) {
+ SCX_ERR("Finite-slice worker was not scheduled");
+ goto out;
+ }
+ if (!wait_for_counter(&skel->bss->nr_finite_ticks, MIN_FINITE_TICKS,
+ PHASE_TIMEOUT_MS)) {
+ SCX_ERR("Finite-slice worker received only %llu scheduler ticks",
+ (unsigned long long)skel->bss->nr_finite_ticks);
+ goto out;
+ }
+ stop_worker(finite_worker);
+ finite_worker = -1;
+
+ /*
+ * Leave the CPU idle after a finite-slice task. The next finite-slice
+ * task must restart the tick even though the slice type is unchanged.
+ */
+ usleep(100000);
+ finite_running = __atomic_load_n(&skel->bss->nr_finite_running,
+ __ATOMIC_RELAXED);
+ finite_ticks = __atomic_load_n(&skel->bss->nr_finite_ticks,
+ __ATOMIC_RELAXED);
+
+ finite_worker = start_worker(ctx->test_cpu);
+ if (finite_worker < 0) {
+ SCX_ERR("Failed to start second finite-slice worker (%d)", errno);
+ goto out;
+ }
+ if (!wait_for_counter(&skel->bss->nr_finite_running,
+ finite_running + 1, PHASE_TIMEOUT_MS)) {
+ SCX_ERR("Second finite-slice worker was not scheduled");
+ goto out;
+ }
+ if (!wait_for_counter(&skel->bss->nr_finite_ticks,
+ finite_ticks + MIN_FINITE_TICKS,
+ PHASE_TIMEOUT_MS)) {
+ SCX_ERR("Second finite-slice worker received only %llu scheduler ticks",
+ (unsigned long long)(skel->bss->nr_finite_ticks -
+ finite_ticks));
+ goto out;
+ }
+
+ if (skel->data->uei.kind != EXIT_KIND(SCX_EXIT_NONE)) {
+ SCX_ERR("Scheduler exited unexpectedly (kind=%llu code=%lld)",
+ (unsigned long long)skel->data->uei.kind,
+ (long long)skel->data->uei.exit_code);
+ goto out;
+ }
+
+ fprintf(stderr, "CPU %d received %llu finite-slice ticks\n",
+ ctx->test_cpu,
+ (unsigned long long)skel->bss->nr_finite_ticks);
+ status = SCX_TEST_PASS;
+out:
+ stop_worker(finite_worker);
+ stop_worker(inf_worker);
+ if (link)
+ bpf_link__destroy(link);
+ return status;
+}
+
+static void cleanup(void *ctx_ptr)
+{
+ struct nohz_tick_ctx *ctx = ctx_ptr;
+
+ sched_setaffinity(0, sizeof(ctx->original_mask), &ctx->original_mask);
+ nohz_tick__destroy(ctx->skel);
+ free(ctx);
+}
+
+struct scx_test nohz_tick = {
+ .name = "nohz_tick",
+ .description = "Verify finite EXT slices restart the NOHZ_FULL tick",
+ .setup = setup,
+ .run = run,
+ .cleanup = cleanup,
+};
+REGISTER_SCX_TEST(&nohz_tick)
diff --git a/tools/testing/selftests/sched_ext/numa.bpf.c b/tools/testing/selftests/sched_ext/numa.bpf.c
index 78cc49a7f9a6..6b4515c28aa0 100644
--- a/tools/testing/selftests/sched_ext/numa.bpf.c
+++ b/tools/testing/selftests/sched_ext/numa.bpf.c
@@ -34,7 +34,8 @@ static bool is_cpu_idle(s32 cpu, int node)
s32 BPF_STRUCT_OPS(numa_select_cpu,
struct task_struct *p, s32 prev_cpu, u64 wake_flags)
{
- int node = __COMPAT_scx_bpf_cpu_node(scx_bpf_task_cpu(p));
+ s32 task_cpu = scx_bpf_task_cpu(p);
+ int node = __COMPAT_scx_bpf_cpu_node(task_cpu);
s32 cpu;
/*
@@ -48,6 +49,16 @@ s32 BPF_STRUCT_OPS(numa_select_cpu,
cpu = __COMPAT_scx_bpf_pick_any_cpu_node(p->cpus_ptr, node,
__COMPAT_SCX_PICK_IDLE_IN_NODE);
+ /*
+ * @task_cpu may be outside of p->cpus_ptr if @p's affinity
+ * changed while it was sleeping. This means it's possible for
+ * p->cpus_ptr to not include any CPUs from @node.
+ * If we failed to find a cpu in @node, check if @task_cpu
+ * is outside of p->cpus_ptr and just return @prev_cpu if it is.
+ */
+ if (cpu < 0 && !bpf_cpumask_test_cpu(task_cpu, p->cpus_ptr))
+ return prev_cpu;
+
if (is_cpu_idle(cpu, node))
scx_bpf_error("CPU %d should be marked as busy", cpu);
diff --git a/tools/testing/selftests/seccomp/seccomp_bpf.c b/tools/testing/selftests/seccomp/seccomp_bpf.c
index 358b6c65e120..0622bc2acad4 100644
--- a/tools/testing/selftests/seccomp/seccomp_bpf.c
+++ b/tools/testing/selftests/seccomp/seccomp_bpf.c
@@ -5178,7 +5178,8 @@ FIXTURE_SETUP(UPROBE)
ASSERT_GE(bit, 0);
}
- offset = get_uprobe_offset(variant->uretprobe ? probed_uretprobe : probed_uprobe);
+ offset = get_uprobe_offset(variant->uretprobe ? (void *)probed_uretprobe
+ : (void *)probed_uprobe);
ASSERT_GE(offset, 0);
if (variant->uretprobe)
diff --git a/tools/testing/selftests/tc-testing/tc-tests/actions/ct.json b/tools/testing/selftests/tc-testing/tc-tests/actions/ct.json
index da65f838bd52..8ab48def89b6 100644
--- a/tools/testing/selftests/tc-testing/tc-tests/actions/ct.json
+++ b/tools/testing/selftests/tc-testing/tc-tests/actions/ct.json
@@ -702,5 +702,45 @@
"$TC qdisc del dev $DUMMY clsact",
"$TC qdisc del dev $DUMMY root handle 1:"
]
+ },
+ {
+ "id": "c7a3",
+ "name": "Verify act_ct drops a packet whose header checks fail",
+ "category": [
+ "actions",
+ "ct",
+ "scapy"
+ ],
+ "plugins": {
+ "requires": [
+ "nsPlugin",
+ "scapyPlugin"
+ ]
+ },
+ "setup": [
+ [
+ "$TC qdisc del dev $DEV1 clsact",
+ 0,
+ 1,
+ 2,
+ 255
+ ],
+ "$TC qdisc add dev $DEV1 clsact"
+ ],
+ "cmdUnderTest": "$TC filter add dev $DEV1 ingress protocol all prio 1 matchall action ct",
+ "scapy": [
+ {
+ "iface": "$DEV0",
+ "count": 10,
+ "packet": "Ether(type=0x86dd)/IPv6(nh=0, plen=0, src='::1', dst='::2')"
+ }
+ ],
+ "expExitCode": "0",
+ "verifyCmd": "$TC -s qdisc show dev $DEV1 clsact",
+ "matchPattern": "dropped 10",
+ "matchCount": "1",
+ "teardown": [
+ "$TC qdisc del dev $DEV1 clsact"
+ ]
}
]
diff --git a/tools/testing/selftests/tc-testing/tc-tests/qdiscs/depth.json b/tools/testing/selftests/tc-testing/tc-tests/qdiscs/depth.json
new file mode 100644
index 000000000000..48f8683f0d1a
--- /dev/null
+++ b/tools/testing/selftests/tc-testing/tc-tests/qdiscs/depth.json
@@ -0,0 +1,74 @@
+[
+ {
+ "id": "ad43",
+ "name": "Create qdisc hierarchy at maximum depth",
+ "category": [
+ "qdisc",
+ "depth"
+ ],
+ "plugins": {
+ "requires": "nsPlugin"
+ },
+ "setup": [
+ "$TC qdisc add dev $DUMMY root handle 1: htb default 1",
+ "$TC class add dev $DUMMY parent 1: classid 1:1 htb rate 10mbit",
+ "$TC qdisc add dev $DUMMY parent 1:1 handle 2: htb default 1",
+ "$TC class add dev $DUMMY parent 2: classid 2:1 htb rate 10mbit",
+ "$TC qdisc add dev $DUMMY parent 2:1 handle 3: htb default 1",
+ "$TC class add dev $DUMMY parent 3: classid 3:1 htb rate 10mbit",
+ "$TC qdisc add dev $DUMMY parent 3:1 handle 4: htb default 1",
+ "$TC class add dev $DUMMY parent 4: classid 4:1 htb rate 10mbit",
+ "$TC qdisc add dev $DUMMY parent 4:1 handle 5: htb default 1",
+ "$TC class add dev $DUMMY parent 5: classid 5:1 htb rate 10mbit",
+ "$TC qdisc add dev $DUMMY parent 5:1 handle 6: htb default 1",
+ "$TC class add dev $DUMMY parent 6: classid 6:1 htb rate 10mbit",
+ "$TC qdisc add dev $DUMMY parent 6:1 handle 7: htb default 1",
+ "$TC class add dev $DUMMY parent 7: classid 7:1 htb rate 10mbit"
+ ],
+ "cmdUnderTest": "$TC qdisc add dev $DUMMY parent 7:1 handle 8: htb default 1",
+ "expExitCode": "0",
+ "verifyCmd": "$TC qdisc show dev $DUMMY",
+ "matchPattern": "qdisc htb 8: parent 7:1",
+ "matchCount": "1",
+ "teardown": [
+ "$TC qdisc del dev $DUMMY root"
+ ]
+ },
+ {
+ "id": "c519",
+ "name": "Reject qdisc hierarchy above maximum depth",
+ "category": [
+ "qdisc",
+ "depth"
+ ],
+ "plugins": {
+ "requires": "nsPlugin"
+ },
+ "setup": [
+ "$TC qdisc add dev $DUMMY root handle 1: htb default 1",
+ "$TC class add dev $DUMMY parent 1: classid 1:1 htb rate 10mbit",
+ "$TC qdisc add dev $DUMMY parent 1:1 handle 2: htb default 1",
+ "$TC class add dev $DUMMY parent 2: classid 2:1 htb rate 10mbit",
+ "$TC qdisc add dev $DUMMY parent 2:1 handle 3: htb default 1",
+ "$TC class add dev $DUMMY parent 3: classid 3:1 htb rate 10mbit",
+ "$TC qdisc add dev $DUMMY parent 3:1 handle 4: htb default 1",
+ "$TC class add dev $DUMMY parent 4: classid 4:1 htb rate 10mbit",
+ "$TC qdisc add dev $DUMMY parent 4:1 handle 5: htb default 1",
+ "$TC class add dev $DUMMY parent 5: classid 5:1 htb rate 10mbit",
+ "$TC qdisc add dev $DUMMY parent 5:1 handle 6: htb default 1",
+ "$TC class add dev $DUMMY parent 6: classid 6:1 htb rate 10mbit",
+ "$TC qdisc add dev $DUMMY parent 6:1 handle 7: htb default 1",
+ "$TC class add dev $DUMMY parent 7: classid 7:1 htb rate 10mbit",
+ "$TC qdisc add dev $DUMMY parent 7:1 handle 8: htb default 1",
+ "$TC class add dev $DUMMY parent 8: classid 8:1 htb rate 10mbit"
+ ],
+ "cmdUnderTest": "$TC qdisc add dev $DUMMY parent 8:1 handle 9: htb default 1",
+ "expExitCode": "2",
+ "verifyCmd": "$TC qdisc show dev $DUMMY",
+ "matchPattern": "qdisc htb 9:",
+ "matchCount": "0",
+ "teardown": [
+ "$TC qdisc del dev $DUMMY root"
+ ]
+ }
+]
diff --git a/tools/testing/selftests/user_events/abi_test.c b/tools/testing/selftests/user_events/abi_test.c
index 85892b3b719c..b71813eaf5c0 100644
--- a/tools/testing/selftests/user_events/abi_test.c
+++ b/tools/testing/selftests/user_events/abi_test.c
@@ -132,6 +132,33 @@ static int event_delete(void)
return ret;
}
+/*
+ * Deleting an event drops its last reference, but an unregister may defer
+ * that put (and the freeing of the associated enabler) past an RCU grace
+ * period. The delete can therefore transiently fail with -EBUSY while the
+ * previous reference is still being dropped. Retry only on that transient
+ * failure; treat an already-deleted event (-ENOENT) as success and return
+ * any other error immediately rather than spinning for the full timeout.
+ */
+static int wait_for_event_delete(void)
+{
+ int i, ret;
+
+ for (i = 0; i < 10000; ++i) {
+ ret = event_delete();
+
+ if (ret == 0 || errno == ENOENT)
+ return 0;
+
+ if (errno != EBUSY)
+ return ret;
+
+ usleep(1000);
+ }
+
+ return ret;
+}
+
static int reg_enable_multi(void *enable, int size, int bit, int flags,
char *args)
{
@@ -262,7 +289,7 @@ TEST_F(user, flags) {
ASSERT_TRUE(event_exists());
/* Ensure we can delete it */
- ASSERT_EQ(0, event_delete());
+ ASSERT_EQ(0, wait_for_event_delete());
/* USER_EVENT_REG_MAX or above is not allowed */
ASSERT_EQ(-1, reg_enable_flags(&self->check, sizeof(int), 0,
diff --git a/tools/testing/selftests/user_events/perf_test.c b/tools/testing/selftests/user_events/perf_test.c
index cafec0e52eb3..5727cb5b914c 100644
--- a/tools/testing/selftests/user_events/perf_test.c
+++ b/tools/testing/selftests/user_events/perf_test.c
@@ -85,6 +85,7 @@ static int get_offset(void)
static int clear(int *check)
{
struct user_unreg unreg = {0};
+ int i, ret;
unreg.size = sizeof(unreg);
unreg.disable_bit = 31;
@@ -99,13 +100,32 @@ static int clear(int *check)
if (errno != ENOENT)
return -1;
- if (ioctl(fd, DIAG_IOCSDEL, "__test_event") == -1)
- if (errno != ENOENT)
+ /*
+ * Deleting the event drops its last reference, but the unregister
+ * above defers that put (and the freeing of the enabler) past an RCU
+ * grace period. The delete can therefore transiently fail with -EBUSY
+ * until that reference is dropped. Retry for up to ~10 seconds so the
+ * event is actually gone before the next test registers the same name.
+ */
+ for (i = 0; i < 10000; ++i) {
+ ret = ioctl(fd, DIAG_IOCSDEL, "__test_event");
+
+ if (ret == 0 || errno == ENOENT) {
+ ret = 0;
+ break;
+ }
+
+ if (errno != EBUSY) {
+ close(fd);
return -1;
+ }
+
+ usleep(1000);
+ }
close(fd);
- return 0;
+ return ret;
}
FIXTURE(user) {
diff --git a/tools/testing/vsock/vsock_test.c b/tools/testing/vsock/vsock_test.c
index 76be0e4a7f0e..b4ff9f946565 100644
--- a/tools/testing/vsock/vsock_test.c
+++ b/tools/testing/vsock/vsock_test.c
@@ -2347,6 +2347,88 @@ static void test_stream_tx_credit_bounds_server(const struct test_opts *opts)
close(fd);
}
+/* Test that many small packets don't cause a connection reset under pressure
+ * and that data integrity is preserved. Packet sizes vary randomly between
+ * 129 and 512 bytes, above GOOD_COPY_LEN (128) to bypass in-place coalescing
+ * in recv_enqueue, forcing each one into its own skb. Without receive queue
+ * collapsing, the per-skb overhead eventually exceeds buf_alloc and the
+ * connection is reset.
+ */
+#define COLLAPSE_PKT_MIN 129
+#define COLLAPSE_PKT_MAX 512
+#define COLLAPSE_TOTAL (2 * 1024 * 1024)
+
+static void test_stream_collapse_client(const struct test_opts *opts)
+{
+ unsigned char *data;
+ unsigned long hash;
+ size_t offset = 0;
+ int i, fd;
+
+ data = malloc(COLLAPSE_TOTAL);
+ if (!data) {
+ perror("malloc");
+ exit(EXIT_FAILURE);
+ }
+
+ for (i = 0; i < COLLAPSE_TOTAL; i++)
+ data[i] = rand() & 0xff;
+
+ fd = vsock_stream_connect(opts->peer_cid, opts->peer_port);
+ if (fd < 0) {
+ perror("connect");
+ exit(EXIT_FAILURE);
+ }
+
+ while (offset < COLLAPSE_TOTAL) {
+ size_t pkt_size = COLLAPSE_PKT_MIN +
+ rand() % (COLLAPSE_PKT_MAX - COLLAPSE_PKT_MIN + 1);
+
+ pkt_size = min(pkt_size, COLLAPSE_TOTAL - offset);
+
+ send_buf(fd, data + offset, pkt_size, 0, pkt_size);
+ offset += pkt_size;
+ }
+
+ hash = hash_djb2(data, COLLAPSE_TOTAL);
+ control_writeulong(hash);
+
+ free(data);
+ close(fd);
+}
+
+static void test_stream_collapse_server(const struct test_opts *opts)
+{
+ unsigned long hash, remote_hash;
+ unsigned char *data;
+ int fd;
+
+ data = malloc(COLLAPSE_TOTAL);
+ if (!data) {
+ perror("malloc");
+ exit(EXIT_FAILURE);
+ }
+
+ fd = vsock_stream_accept(VMADDR_CID_ANY, opts->peer_port, NULL);
+ if (fd < 0) {
+ perror("accept");
+ exit(EXIT_FAILURE);
+ }
+
+ recv_buf(fd, data, COLLAPSE_TOTAL, 0, COLLAPSE_TOTAL);
+
+ hash = hash_djb2(data, COLLAPSE_TOTAL);
+ remote_hash = control_readulong();
+ if (hash != remote_hash) {
+ fprintf(stderr, "hash mismatch: local %lu remote %lu\n",
+ hash, remote_hash);
+ exit(EXIT_FAILURE);
+ }
+
+ free(data);
+ close(fd);
+}
+
static struct test_case test_cases[] = {
{
.name = "SOCK_STREAM connection reset",
@@ -2546,6 +2628,11 @@ static struct test_case test_cases[] = {
.run_client = test_stream_msg_peek_client,
.run_server = test_stream_peek_after_recv_server,
},
+ {
+ .name = "SOCK_STREAM small packets backpressure",
+ .run_client = test_stream_collapse_client,
+ .run_server = test_stream_collapse_server,
+ },
{},
};