summaryrefslogtreecommitdiff
path: root/tools/testing
diff options
context:
space:
mode:
Diffstat (limited to 'tools/testing')
-rw-r--r--tools/testing/selftests/Makefile1
-rw-r--r--tools/testing/selftests/alsa/.gitignore1
-rw-r--r--tools/testing/selftests/alsa/Makefile2
-rw-r--r--tools/testing/selftests/alsa/aloop-test.c345
-rw-r--r--tools/testing/selftests/alsa/mixer-test.c235
-rw-r--r--tools/testing/selftests/bpf/Makefile12
-rw-r--r--tools/testing/selftests/bpf/Makefile.buildvars25
-rw-r--r--tools/testing/selftests/bpf/Makefile.skel18
-rw-r--r--tools/testing/selftests/bpf/prog_tests/arena_scalar_blinded.c21
-rw-r--r--tools/testing/selftests/bpf/prog_tests/bpf_ma_ttrace.c60
-rw-r--r--tools/testing/selftests/bpf/prog_tests/btf.c132
-rw-r--r--tools/testing/selftests/bpf/prog_tests/btf_rust.c142
-rw-r--r--tools/testing/selftests/bpf/prog_tests/data_in_arena.c216
-rw-r--r--tools/testing/selftests/bpf/prog_tests/file_reader.c15
-rw-r--r--tools/testing/selftests/bpf/prog_tests/verifier.c2
-rw-r--r--tools/testing/selftests/bpf/progs/bpf_ma_ttrace.c50
-rw-r--r--tools/testing/selftests/bpf/progs/data_in_arena.c112
-rw-r--r--tools/testing/selftests/bpf/progs/data_in_arena_decl.c37
-rw-r--r--tools/testing/selftests/bpf/progs/data_in_arena_extern.c20
-rw-r--r--tools/testing/selftests/bpf/progs/data_in_arena_fail.c20
-rw-r--r--tools/testing/selftests/bpf/progs/data_in_arena_nomap.c20
-rw-r--r--tools/testing/selftests/bpf/progs/data_in_arena_rust.rs73
-rw-r--r--tools/testing/selftests/bpf/progs/file_reader.c129
-rw-r--r--tools/testing/selftests/bpf/progs/verifier_align.c6
-rw-r--r--tools/testing/selftests/bpf/progs/verifier_arena_large.c64
-rw-r--r--tools/testing/selftests/bpf/progs/verifier_arena_scalar.c912
-rw-r--r--tools/testing/selftests/bpf/progs/verifier_direct_packet_access.c178
-rw-r--r--tools/testing/selftests/bpf/progs/verifier_meta_access.c30
-rw-r--r--tools/testing/selftests/bpf/progs/verifier_value_illegal_alu.c173
-rw-r--r--tools/testing/selftests/bpf/test_loader.c2
-rw-r--r--tools/testing/selftests/ftrace/Makefile4
-rwxr-xr-xtools/testing/selftests/ftrace/boottime-ktap6
-rw-r--r--tools/testing/selftests/ftrace/boottime/Makefile10
-rw-r--r--tools/testing/selftests/ftrace/boottime/README74
-rw-r--r--tools/testing/selftests/ftrace/boottime/bootconfigs/01-kprobe.bconf4
-rw-r--r--tools/testing/selftests/ftrace/boottime/bootconfigs/02-synth.bconf4
-rw-r--r--tools/testing/selftests/ftrace/boottime/bootconfigs/03-eprobe.bconf4
-rw-r--r--tools/testing/selftests/ftrace/boottime/bootconfigs/04-fprobe.bconf4
-rw-r--r--tools/testing/selftests/ftrace/boottime/bootconfigs/05-tprobe.bconf4
-rw-r--r--tools/testing/selftests/ftrace/boottime/bootconfigs/06-instance.bconf5
-rw-r--r--tools/testing/selftests/ftrace/boottime/cmdlines/cmdline-01-ftrace.cmdline1
-rw-r--r--tools/testing/selftests/ftrace/boottime/cmdlines/cmdline-02-trace-event.cmdline1
-rw-r--r--tools/testing/selftests/ftrace/boottime/cmdlines/cmdline-03-trace-buf-size.cmdline1
-rw-r--r--tools/testing/selftests/ftrace/boottime/cmdlines/cmdline-04-trace-options.cmdline1
-rw-r--r--tools/testing/selftests/ftrace/boottime/cmdlines/cmdline-05-trace-clock.cmdline1
-rw-r--r--tools/testing/selftests/ftrace/boottime/cmdlines/cmdline-06-trace-instance.cmdline1
-rw-r--r--tools/testing/selftests/ftrace/boottime/cmdlines/persistent-01-reserve-mem.cmdline1
-rw-r--r--tools/testing/selftests/ftrace/boottime/cmdlines/persistent-02-backup-instance.cmdline1
-rwxr-xr-xtools/testing/selftests/ftrace/boottime/run_boottime_test.sh403
-rw-r--r--tools/testing/selftests/ftrace/boottime/tests/01-kprobe.sh25
-rw-r--r--tools/testing/selftests/ftrace/boottime/tests/02-synth.sh25
-rw-r--r--tools/testing/selftests/ftrace/boottime/tests/03-eprobe.sh25
-rw-r--r--tools/testing/selftests/ftrace/boottime/tests/04-fprobe.sh25
-rw-r--r--tools/testing/selftests/ftrace/boottime/tests/05-tprobe.sh25
-rw-r--r--tools/testing/selftests/ftrace/boottime/tests/06-instance.sh30
-rw-r--r--tools/testing/selftests/ftrace/boottime/tests/cmdline-01-ftrace.sh19
-rw-r--r--tools/testing/selftests/ftrace/boottime/tests/cmdline-02-trace-event.sh26
-rw-r--r--tools/testing/selftests/ftrace/boottime/tests/cmdline-03-trace-buf-size.sh29
-rw-r--r--tools/testing/selftests/ftrace/boottime/tests/cmdline-04-trace-options.sh23
-rw-r--r--tools/testing/selftests/ftrace/boottime/tests/cmdline-05-trace-clock.sh19
-rw-r--r--tools/testing/selftests/ftrace/boottime/tests/cmdline-06-trace-instance.sh24
-rw-r--r--tools/testing/selftests/ftrace/boottime/tests/persistent-01-reserve-mem.sh28
-rw-r--r--tools/testing/selftests/ftrace/boottime/tests/persistent-02-backup-instance.sh40
-rw-r--r--tools/testing/selftests/ftrace/config6
-rw-r--r--tools/testing/selftests/kselftest/runner.sh6
-rw-r--r--tools/testing/selftests/kvm/arm64/vgic_init.c116
-rw-r--r--tools/testing/selftests/mm/split_huge_page_test.c7
-rw-r--r--tools/testing/selftests/mm/vm_util.c108
-rwxr-xr-xtools/testing/selftests/net/amt.sh29
-rwxr-xr-xtools/testing/selftests/net/drop_monitor_tests.sh13
-rwxr-xr-xtools/testing/selftests/net/fcnal-test.sh29
-rwxr-xr-xtools/testing/selftests/net/fdb_flush.sh35
-rwxr-xr-xtools/testing/selftests/net/fib-onlink-tests.sh18
-rwxr-xr-xtools/testing/selftests/net/fib_nexthop_multiprefix.sh21
-rwxr-xr-xtools/testing/selftests/net/fib_nexthop_nongw.sh21
-rwxr-xr-xtools/testing/selftests/net/fib_rule_tests.sh19
-rwxr-xr-xtools/testing/selftests/net/fib_tests.sh66
-rwxr-xr-xtools/testing/selftests/net/gre_gso.sh26
-rwxr-xr-xtools/testing/selftests/net/icmp_redirect.sh19
-rwxr-xr-xtools/testing/selftests/net/l2tp.sh19
-rw-r--r--tools/testing/selftests/net/lib.sh34
-rwxr-xr-xtools/testing/selftests/net/ndisc_unsolicited_na_test.sh26
-rwxr-xr-xtools/testing/selftests/net/srv6_encap_lookup_l3vpn_test.sh19
-rwxr-xr-xtools/testing/selftests/net/srv6_end_dt46_l3vpn_test.sh19
-rwxr-xr-xtools/testing/selftests/net/srv6_end_dt4_l3vpn_test.sh19
-rwxr-xr-xtools/testing/selftests/net/srv6_end_dt6_l3vpn_test.sh19
-rwxr-xr-xtools/testing/selftests/net/srv6_end_dx4_netfilter_test.sh23
-rwxr-xr-xtools/testing/selftests/net/srv6_end_dx6_netfilter_test.sh23
-rwxr-xr-xtools/testing/selftests/net/srv6_end_flavors_test.sh23
-rwxr-xr-xtools/testing/selftests/net/srv6_end_next_csid_l3vpn_test.sh19
-rwxr-xr-xtools/testing/selftests/net/srv6_end_x_next_csid_l3vpn_test.sh19
-rwxr-xr-xtools/testing/selftests/net/srv6_hencap_red_l3vpn_test.sh19
-rwxr-xr-xtools/testing/selftests/net/srv6_hl2encap_red_l2vpn_test.sh19
-rwxr-xr-xtools/testing/selftests/net/test_bridge_backup_port.sh32
-rwxr-xr-xtools/testing/selftests/net/test_bridge_neigh_suppress.sh34
-rwxr-xr-xtools/testing/selftests/net/test_vxlan_mdb.sh32
-rwxr-xr-xtools/testing/selftests/net/test_vxlan_nolocalbypass.sh32
-rwxr-xr-xtools/testing/selftests/net/test_vxlan_vnifiltering.sh26
-rwxr-xr-xtools/testing/selftests/net/vrf-xfrm-tests.sh19
-rwxr-xr-xtools/testing/selftests/net/vrf_route_leaking.sh19
-rwxr-xr-xtools/testing/selftests/net/vrf_strict_mode_test.sh19
-rw-r--r--tools/testing/selftests/nfsd/.gitignore1
-rw-r--r--tools/testing/selftests/nfsd/Makefile6
-rw-r--r--tools/testing/selftests/nfsd/config14
-rw-r--r--tools/testing/selftests/nfsd/nfsd_netlink_listener.c1323
-rw-r--r--tools/testing/selftests/nfsd/settings1
-rw-r--r--tools/testing/selftests/nommu/Makefile8
-rw-r--r--tools/testing/selftests/nommu/local.mk7
-rw-r--r--tools/testing/selftests/nommu/nommu_mmap_test.c261
-rw-r--r--tools/testing/selftests/nommu/nommu_mremap_test.c366
110 files changed, 6173 insertions, 812 deletions
diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile
index 79a00e9ee46d..37a45349f149 100644
--- a/tools/testing/selftests/Makefile
+++ b/tools/testing/selftests/Makefile
@@ -97,6 +97,7 @@ TARGETS += net/packetdrill
TARGETS += net/ppp
TARGETS += net/rds
TARGETS += net/tcp_ao
+TARGETS += nfsd
TARGETS += nolibc
TARGETS += pci_endpoint
TARGETS += pcie_bwctrl
diff --git a/tools/testing/selftests/alsa/.gitignore b/tools/testing/selftests/alsa/.gitignore
index 3dd8e1176b89..7b0e1e9ebf1b 100644
--- a/tools/testing/selftests/alsa/.gitignore
+++ b/tools/testing/selftests/alsa/.gitignore
@@ -1,3 +1,4 @@
+aloop-test
global-timer
mixer-test
pcm-test
diff --git a/tools/testing/selftests/alsa/Makefile b/tools/testing/selftests/alsa/Makefile
index 8dab90ad22bb..afd64a679dc1 100644
--- a/tools/testing/selftests/alsa/Makefile
+++ b/tools/testing/selftests/alsa/Makefile
@@ -16,7 +16,7 @@ LDLIBS+=-lpthread
OVERRIDE_TARGETS = 1
-TEST_GEN_PROGS := mixer-test pcm-test test-pcmtest-driver utimer-test
+TEST_GEN_PROGS := aloop-test mixer-test pcm-test test-pcmtest-driver utimer-test
TEST_GEN_PROGS_EXTENDED := libatest.so global-timer
diff --git a/tools/testing/selftests/alsa/aloop-test.c b/tools/testing/selftests/alsa/aloop-test.c
new file mode 100644
index 000000000000..a58b9fdbdc17
--- /dev/null
+++ b/tools/testing/selftests/alsa/aloop-test.c
@@ -0,0 +1,345 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Tests for the hw constraints between the two ends of an snd-aloop cable,
+ * with and without the "PCM Notify" control, and for the "PCM Slave"
+ * controls that report the playback side's parameters.
+ *
+ * Needs snd-aloop loaded with the default card id "Loopback". The tests use
+ * the cable between hw:Loopback,0,0 (playback) and hw:Loopback,1,0 (capture).
+ */
+#include <errno.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <string.h>
+#include <alsa/asoundlib.h>
+#include "kselftest_harness.h"
+
+#define FRAMES 1024
+#define MAX_CHANNELS 4
+
+struct stream_params {
+ snd_pcm_access_t access;
+ snd_pcm_format_t format;
+ unsigned int channels;
+ unsigned int rate;
+};
+
+/* The playback params differ from the capture params in every field. */
+static const struct stream_params capture_params = {
+ SND_PCM_ACCESS_RW_INTERLEAVED, SND_PCM_FORMAT_S16_LE, 2, 44100
+};
+
+static const struct stream_params playback_params = {
+ SND_PCM_ACCESS_RW_NONINTERLEAVED, SND_PCM_FORMAT_S32_LE, 4, 96000
+};
+
+struct probe_result {
+ unsigned int rate_min, rate_max;
+ unsigned int channels_min, channels_max;
+ bool format_ok;
+};
+
+/* Value events seen on the cable's controls */
+enum {
+ EV_ACTIVE = 1 << 0,
+ EV_FORMAT = 1 << 1,
+ EV_RATE = 1 << 2,
+ EV_CHANNELS = 1 << 3,
+ EV_ACCESS = 1 << 4,
+};
+
+FIXTURE(aloop) {
+ char play_name[32];
+ char capt_name[32];
+ snd_ctl_t *ctl;
+ bool saved_notify;
+ bool notify_saved;
+};
+
+/* The cable's controls are on the capture side device. */
+static void cable_ctl_id(snd_ctl_elem_value_t *value, const char *name)
+{
+ snd_ctl_elem_value_set_interface(value, SND_CTL_ELEM_IFACE_PCM);
+ snd_ctl_elem_value_set_name(value, name);
+ snd_ctl_elem_value_set_device(value, 1);
+ snd_ctl_elem_value_set_subdevice(value, 0);
+}
+
+static long cable_ctl_get(snd_ctl_t *ctl, const char *name)
+{
+ snd_ctl_elem_value_t *value;
+
+ snd_ctl_elem_value_alloca(&value);
+ cable_ctl_id(value, name);
+ if (snd_ctl_elem_read(ctl, value) < 0)
+ return -1;
+ if (!strcmp(name, "PCM Slave Access Mode"))
+ return snd_ctl_elem_value_get_enumerated(value, 0);
+ return snd_ctl_elem_value_get_integer(value, 0);
+}
+
+static int set_notify(snd_ctl_t *ctl, bool on)
+{
+ snd_ctl_elem_value_t *value;
+
+ snd_ctl_elem_value_alloca(&value);
+ cable_ctl_id(value, "PCM Notify");
+ snd_ctl_elem_value_set_boolean(value, 0, on);
+ return snd_ctl_elem_write(ctl, value);
+}
+
+/* Read all pending control events and return the EV_* bits for the cable's controls. */
+static unsigned int read_events(snd_ctl_t *ctl)
+{
+ static const struct {
+ const char *name;
+ unsigned int bit;
+ } names[] = {
+ { "PCM Slave Active", EV_ACTIVE },
+ { "PCM Slave Format", EV_FORMAT },
+ { "PCM Slave Rate", EV_RATE },
+ { "PCM Slave Channels", EV_CHANNELS },
+ { "PCM Slave Access Mode", EV_ACCESS },
+ };
+ snd_ctl_event_t *event;
+ unsigned int seen = 0;
+ int i;
+
+ snd_ctl_event_alloca(&event);
+ while (snd_ctl_read(ctl, event) > 0) {
+ if (snd_ctl_event_get_type(event) != SND_CTL_EVENT_ELEM ||
+ !(snd_ctl_event_elem_get_mask(event) & SND_CTL_EVENT_MASK_VALUE) ||
+ snd_ctl_event_elem_get_device(event) != 1 ||
+ snd_ctl_event_elem_get_subdevice(event) != 0)
+ continue;
+ for (i = 0; i < ARRAY_SIZE(names); i++)
+ if (!strcmp(snd_ctl_event_elem_get_name(event), names[i].name))
+ seen |= names[i].bit;
+ }
+ return seen;
+}
+
+/* Open and configure a stream. snd_pcm_hw_params() also prepares it. */
+static int open_pcm(snd_pcm_t **pcm, const char *name, snd_pcm_stream_t stream,
+ const struct stream_params *p)
+{
+ unsigned int buffer_time = 100000;
+ snd_pcm_hw_params_t *hw;
+ int err;
+
+ snd_pcm_hw_params_alloca(&hw);
+ err = snd_pcm_open(pcm, name, stream, 0);
+ if (err < 0)
+ return err;
+ err = snd_pcm_hw_params_any(*pcm, hw);
+ if (err >= 0)
+ err = snd_pcm_hw_params_set_access(*pcm, hw, p->access);
+ if (err >= 0)
+ err = snd_pcm_hw_params_set_format(*pcm, hw, p->format);
+ if (err >= 0)
+ err = snd_pcm_hw_params_set_channels(*pcm, hw, p->channels);
+ if (err >= 0)
+ err = snd_pcm_hw_params_set_rate(*pcm, hw, p->rate, 0);
+ if (err >= 0)
+ err = snd_pcm_hw_params_set_buffer_time_near(*pcm, hw, &buffer_time, NULL);
+ if (err >= 0)
+ err = snd_pcm_hw_params(*pcm, hw);
+ if (err < 0) {
+ snd_pcm_close(*pcm);
+ *pcm = NULL;
+ }
+ return err;
+}
+
+/* What a client probing the device sees, and whether it may use the given format */
+static int probe_pcm(const char *name, snd_pcm_stream_t stream, snd_pcm_format_t format,
+ struct probe_result *res)
+{
+ snd_pcm_hw_params_t *hw;
+ snd_pcm_t *pcm;
+ int err;
+
+ snd_pcm_hw_params_alloca(&hw);
+ err = snd_pcm_open(&pcm, name, stream, 0);
+ if (err < 0)
+ return err;
+ err = snd_pcm_hw_params_any(pcm, hw);
+ if (err >= 0)
+ err = snd_pcm_hw_params_get_rate_min(hw, &res->rate_min, NULL);
+ if (err >= 0)
+ err = snd_pcm_hw_params_get_rate_max(hw, &res->rate_max, NULL);
+ if (err >= 0)
+ err = snd_pcm_hw_params_get_channels_min(hw, &res->channels_min);
+ if (err >= 0)
+ err = snd_pcm_hw_params_get_channels_max(hw, &res->channels_max);
+ if (err >= 0)
+ res->format_ok = !snd_pcm_hw_params_test_format(pcm, hw, format);
+ snd_pcm_close(pcm);
+ return err;
+}
+
+static int start_playback(snd_pcm_t *pcm, const struct stream_params *p)
+{
+ static char silence[FRAMES * MAX_CHANNELS * 4];
+ void *bufs[MAX_CHANNELS];
+ snd_pcm_sframes_t written;
+ unsigned int i;
+
+ if (p->access == SND_PCM_ACCESS_RW_NONINTERLEAVED) {
+ for (i = 0; i < p->channels; i++)
+ bufs[i] = silence + i * FRAMES * 4;
+ written = snd_pcm_writen(pcm, bufs, FRAMES);
+ } else {
+ written = snd_pcm_writei(pcm, silence, FRAMES);
+ }
+ if (written < 0)
+ return written;
+ if (snd_pcm_state(pcm) == SND_PCM_STATE_PREPARED)
+ return snd_pcm_start(pcm);
+ return 0;
+}
+
+FIXTURE_SETUP(aloop) {
+ char ctl_name[32];
+ snd_pcm_t *pcm;
+ int card, err;
+
+ card = snd_card_get_index("Loopback");
+ if (card < 0)
+ SKIP(return, "No Loopback card, snd-aloop is probably not loaded");
+
+ sprintf(ctl_name, "hw:%d", card);
+ sprintf(self->play_name, "hw:%d,0,0", card);
+ sprintf(self->capt_name, "hw:%d,1,0", card);
+
+ err = snd_pcm_open(&pcm, self->capt_name, SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK);
+ if (err == -EBUSY)
+ SKIP(return, "%s is in use", self->capt_name);
+ ASSERT_EQ(err, 0);
+ snd_pcm_close(pcm);
+ err = snd_pcm_open(&pcm, self->play_name, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK);
+ if (err == -EBUSY)
+ SKIP(return, "%s is in use", self->play_name);
+ ASSERT_EQ(err, 0);
+ snd_pcm_close(pcm);
+
+ ASSERT_EQ(snd_ctl_open(&self->ctl, ctl_name, SND_CTL_NONBLOCK), 0);
+ ASSERT_EQ(snd_ctl_subscribe_events(self->ctl, 1), 0);
+ self->saved_notify = cable_ctl_get(self->ctl, "PCM Notify");
+ self->notify_saved = true;
+}
+
+FIXTURE_TEARDOWN(aloop) {
+ if (self->notify_saved)
+ set_notify(self->ctl, self->saved_notify);
+ if (self->ctl)
+ snd_ctl_close(self->ctl);
+}
+
+/* Without notify, a playback opened while a capture is set up is pinned to its parameters. */
+TEST_F(aloop, playback_constrained_without_notify) {
+ struct probe_result res;
+ snd_pcm_t *capt;
+
+ ASSERT_EQ(set_notify(self->ctl, false), 0);
+ ASSERT_EQ(open_pcm(&capt, self->capt_name, SND_PCM_STREAM_CAPTURE, &capture_params), 0);
+
+ ASSERT_EQ(probe_pcm(self->play_name, SND_PCM_STREAM_PLAYBACK, playback_params.format,
+ &res), 0);
+ EXPECT_EQ(res.rate_min, capture_params.rate);
+ EXPECT_EQ(res.rate_max, capture_params.rate);
+ EXPECT_EQ(res.channels_min, capture_params.channels);
+ EXPECT_EQ(res.channels_max, capture_params.channels);
+ EXPECT_FALSE(res.format_ok);
+
+ snd_pcm_close(capt);
+}
+
+/* With notify, the playback side is free to pick other parameters. */
+TEST_F(aloop, playback_unconstrained_with_notify) {
+ struct probe_result res;
+ snd_pcm_t *capt;
+
+ ASSERT_EQ(set_notify(self->ctl, true), 0);
+ ASSERT_EQ(open_pcm(&capt, self->capt_name, SND_PCM_STREAM_CAPTURE, &capture_params), 0);
+
+ ASSERT_EQ(probe_pcm(self->play_name, SND_PCM_STREAM_PLAYBACK, playback_params.format,
+ &res), 0);
+ EXPECT_LE(res.rate_min, capture_params.rate);
+ EXPECT_GE(res.rate_max, playback_params.rate);
+ EXPECT_LE(res.channels_min, capture_params.channels);
+ EXPECT_GE(res.channels_max, playback_params.channels);
+ EXPECT_TRUE(res.format_ok);
+
+ snd_pcm_close(capt);
+}
+
+/*
+ * With notify, starting a playback with different parameters stops the
+ * running capture, and the "PCM Slave" controls report the new parameters
+ * with a value event for each one that changed.
+ */
+TEST_F(aloop, params_change_stops_capture_with_notify) {
+ snd_pcm_t *capt, *play;
+ unsigned int events;
+
+ /*
+ * The controls keep the last playback's parameters, and only notify on
+ * a change. Start a playback with the capture's parameters while no
+ * capture is open, so that every control changes below.
+ */
+ ASSERT_EQ(open_pcm(&play, self->play_name, SND_PCM_STREAM_PLAYBACK, &capture_params), 0);
+ ASSERT_EQ(start_playback(play, &capture_params), 0);
+ snd_pcm_close(play);
+
+ ASSERT_EQ(set_notify(self->ctl, true), 0);
+ ASSERT_EQ(open_pcm(&capt, self->capt_name, SND_PCM_STREAM_CAPTURE, &capture_params), 0);
+ ASSERT_EQ(snd_pcm_start(capt), 0);
+ ASSERT_EQ(snd_pcm_state(capt), SND_PCM_STATE_RUNNING);
+ EXPECT_EQ(cable_ctl_get(self->ctl, "PCM Slave Active"), 0);
+ read_events(self->ctl);
+
+ ASSERT_EQ(open_pcm(&play, self->play_name, SND_PCM_STREAM_PLAYBACK, &playback_params), 0)
+ TH_LOG("Playback refused other parameters while the capture is running");
+ ASSERT_EQ(start_playback(play, &playback_params), 0);
+
+ /* loopback_check_format() stops the capture from the playback's start trigger. */
+ EXPECT_NE(snd_pcm_state(capt), SND_PCM_STATE_RUNNING);
+
+ EXPECT_EQ(cable_ctl_get(self->ctl, "PCM Slave Active"), 1);
+ EXPECT_EQ(cable_ctl_get(self->ctl, "PCM Slave Format"), playback_params.format);
+ EXPECT_EQ(cable_ctl_get(self->ctl, "PCM Slave Rate"), playback_params.rate);
+ EXPECT_EQ(cable_ctl_get(self->ctl, "PCM Slave Channels"), playback_params.channels);
+ EXPECT_EQ(cable_ctl_get(self->ctl, "PCM Slave Access Mode"), 1);
+
+ events = read_events(self->ctl);
+ EXPECT_TRUE(events & EV_ACTIVE);
+ EXPECT_TRUE(events & EV_FORMAT);
+ EXPECT_TRUE(events & EV_RATE);
+ EXPECT_TRUE(events & EV_CHANNELS);
+ EXPECT_TRUE(events & EV_ACCESS);
+
+ snd_pcm_close(play);
+ snd_pcm_close(capt);
+}
+
+/* With notify, a capture opened second is still pinned to the playback's parameters. */
+TEST_F(aloop, capture_constrained_with_notify) {
+ struct probe_result res;
+ snd_pcm_t *play;
+
+ ASSERT_EQ(set_notify(self->ctl, true), 0);
+ ASSERT_EQ(open_pcm(&play, self->play_name, SND_PCM_STREAM_PLAYBACK, &playback_params), 0);
+
+ ASSERT_EQ(probe_pcm(self->capt_name, SND_PCM_STREAM_CAPTURE, capture_params.format,
+ &res), 0);
+ EXPECT_EQ(res.rate_min, playback_params.rate);
+ EXPECT_EQ(res.rate_max, playback_params.rate);
+ EXPECT_EQ(res.channels_min, playback_params.channels);
+ EXPECT_EQ(res.channels_max, playback_params.channels);
+ EXPECT_FALSE(res.format_ok);
+
+ snd_pcm_close(play);
+}
+
+TEST_HARNESS_MAIN
diff --git a/tools/testing/selftests/alsa/mixer-test.c b/tools/testing/selftests/alsa/mixer-test.c
index 53a72753bb08..966ef83354b9 100644
--- a/tools/testing/selftests/alsa/mixer-test.c
+++ b/tools/testing/selftests/alsa/mixer-test.c
@@ -28,7 +28,7 @@
#include "kselftest.h"
#include "alsa-local.h"
-#define TESTS_PER_CONTROL 7
+#define TESTS_PER_CONTROL 8
/* Suffixes of the SNDRV_CTL_NAME_IEC958() names, not exported to userspace */
#define IEC958_DEFAULT "Default"
@@ -52,9 +52,14 @@ struct ctl_data {
snd_ctl_elem_id_t *id;
snd_ctl_elem_info_t *info;
snd_ctl_elem_value_t *def_val;
+ snd_ctl_elem_value_t *snapshot;
+ bool snapshot_valid;
+ bool moved;
int elem;
int event_missing;
int event_spurious;
+ int side_effects;
+ unsigned int ev_mask;
struct card_data *card;
struct ctl_data *next;
};
@@ -159,6 +164,10 @@ static void find_controls(void)
if (err < 0)
ksft_exit_fail_msg("Out of memory\n");
+ err = snd_ctl_elem_value_malloc(&ctl_data->snapshot);
+ if (err < 0)
+ ksft_exit_fail_msg("Out of memory\n");
+
snd_ctl_elem_list_get_id(card_data->ctls, ctl,
ctl_data->id);
snd_ctl_elem_info_set_id(ctl_data->info, ctl_data->id);
@@ -207,6 +216,20 @@ static void find_controls(void)
snd_config_delete(config);
}
+/* The control on the same card that an event's numid refers to */
+static struct ctl_data *find_ctl_by_numid(struct card_data *card,
+ unsigned int numid)
+{
+ struct ctl_data *ctl;
+
+ for (ctl = ctl_list; ctl != NULL; ctl = ctl->next)
+ if (ctl->card == card &&
+ snd_ctl_elem_info_get_numid(ctl->info) == numid)
+ return ctl;
+
+ return NULL;
+}
+
/*
* Block for up to timeout ms for an event, returns a negative value
* on error, 0 for no event and 1 for an event.
@@ -265,8 +288,21 @@ static int wait_for_event(struct ctl_data *ctl, int timeout)
mask = snd_ctl_event_elem_get_mask(event);
ev_id = snd_ctl_event_elem_get_numid(event);
if (ev_id != snd_ctl_elem_info_get_numid(ctl->info)) {
+ struct ctl_data *other = find_ctl_by_numid(ctl->card,
+ ev_id);
+
+ /*
+ * Remember that the driver announced this one.
+ * test_ctl_write_side_effects() uses that to tell a
+ * deliberate link from a silent register collision.
+ */
+ if (other)
+ other->ev_mask |= mask;
+
ksft_print_msg("Event for unexpected ctl %s\n",
snd_ctl_event_elem_get_name(event));
+ /* The loop condition must not see the other control's mask */
+ mask = 0;
continue;
}
@@ -1147,6 +1183,202 @@ static void test_ctl_write_valid(struct ctl_data *ctl)
ctl->card->card_name, ctl->elem);
}
+/*
+ * Build the smallest or the largest value the control offers. The smallest
+ * clears the control's register field and the largest sets its top bit, which
+ * is the bit a mask one bit too wide puts in its neighbour.
+ */
+static bool set_limit_value(struct ctl_data *ctl, snd_ctl_elem_value_t *val,
+ bool max)
+{
+ int i, count = snd_ctl_elem_info_get_count(ctl->info);
+
+ snd_ctl_elem_value_set_id(val, ctl->id);
+
+ switch (snd_ctl_elem_info_get_type(ctl->info)) {
+ case SND_CTL_ELEM_TYPE_BOOLEAN:
+ for (i = 0; i < count; i++)
+ snd_ctl_elem_value_set_boolean(val, i, max);
+ return true;
+
+ case SND_CTL_ELEM_TYPE_INTEGER:
+ for (i = 0; i < count; i++)
+ snd_ctl_elem_value_set_integer(val, i, max ?
+ snd_ctl_elem_info_get_max(ctl->info) :
+ snd_ctl_elem_info_get_min(ctl->info));
+ return true;
+
+ case SND_CTL_ELEM_TYPE_INTEGER64:
+ for (i = 0; i < count; i++)
+ snd_ctl_elem_value_set_integer64(val, i, max ?
+ snd_ctl_elem_info_get_max64(ctl->info) :
+ snd_ctl_elem_info_get_min64(ctl->info));
+ return true;
+
+ case SND_CTL_ELEM_TYPE_ENUMERATED:
+ for (i = 0; i < count; i++)
+ snd_ctl_elem_value_set_enumerated(val, i, max ?
+ snd_ctl_elem_info_get_items(ctl->info) - 1 : 0);
+ return true;
+
+ default:
+ /* Nothing sensible to write for the rest */
+ return false;
+ }
+}
+
+/* Note every control on the card that no longer reads as it did */
+static void find_moved_ctls(struct ctl_data *ctl, snd_ctl_elem_value_t *val)
+{
+ struct ctl_data *other;
+ int err;
+
+ for (other = ctl_list; other != NULL; other = other->next) {
+ if (!other->snapshot_valid)
+ continue;
+
+ /*
+ * The buffer is shared and compare() looks at all of it, so
+ * clear what the last control left in the slots this one
+ * does not use.
+ */
+ snd_ctl_elem_value_clear(val);
+ snd_ctl_elem_value_set_id(val, other->id);
+ err = snd_ctl_elem_read(ctl->card->handle, val);
+ if (err < 0) {
+ ksft_print_msg("snd_ctl_elem_read() failed for %s: %s\n",
+ other->name, snd_strerror(err));
+ continue;
+ }
+
+ if (snd_ctl_elem_value_compare(other->snapshot, val))
+ other->moved = true;
+ }
+}
+
+/*
+ * Write one control and look for others on the same card that moved with it.
+ * A driver that links two controls on purpose tells userspace about both, so
+ * only an unannounced change is counted. That is what a control whose
+ * register mask covers bits belonging to its neighbour looks like from here.
+ */
+static void test_ctl_write_side_effects(struct ctl_data *ctl)
+{
+ struct ctl_data *other;
+ snd_ctl_elem_value_t *min_val, *max_val, *read_val;
+ int err;
+
+ snd_ctl_elem_value_alloca(&min_val);
+ snd_ctl_elem_value_alloca(&max_val);
+ snd_ctl_elem_value_alloca(&read_val);
+
+ /* Without a readable default there is nothing to put back */
+ if (snd_ctl_elem_info_is_inactive(ctl->info) ||
+ !snd_ctl_elem_info_is_writable(ctl->info) ||
+ !snd_ctl_elem_info_is_readable(ctl->info) ||
+ !set_limit_value(ctl, min_val, false) ||
+ !set_limit_value(ctl, max_val, true)) {
+ ksft_test_result_skip("write_side_effects.%s.%d\n",
+ ctl->card->card_name, ctl->elem);
+ return;
+ }
+
+ /* Drain first, a stale event would look like the driver announced it */
+ drop_events(ctl);
+
+ /*
+ * Record what the rest of the card reads as. A volatile control can
+ * move on its own so there is nothing to compare it against.
+ */
+ for (other = ctl_list; other != NULL; other = other->next) {
+ other->snapshot_valid = false;
+ other->moved = false;
+ other->ev_mask = 0;
+
+ if (other == ctl || other->card != ctl->card)
+ continue;
+ if (!snd_ctl_elem_info_is_readable(other->info) ||
+ snd_ctl_elem_info_is_volatile(other->info))
+ continue;
+
+ snd_ctl_elem_value_clear(other->snapshot);
+ snd_ctl_elem_value_set_id(other->snapshot, other->id);
+ err = snd_ctl_elem_read(ctl->card->handle, other->snapshot);
+ if (err < 0) {
+ ksft_print_msg("snd_ctl_elem_read() failed for %s: %s\n",
+ other->name, snd_strerror(err));
+ continue;
+ }
+
+ other->snapshot_valid = true;
+ }
+
+ /*
+ * Compare against the snapshot after each write, before anything is
+ * put back. Restoring the control we wrote goes through the same
+ * mask, so doing it first would hide the change we are looking for.
+ */
+ err = snd_ctl_elem_write(ctl->card->handle, min_val);
+ if (err >= 0) {
+ drop_events(ctl);
+ find_moved_ctls(ctl, read_val);
+ err = snd_ctl_elem_write(ctl->card->handle, max_val);
+ }
+ if (err < 0) {
+ ksft_print_msg("snd_ctl_elem_write() failed for %s: %s\n",
+ ctl->name, snd_strerror(err));
+ } else {
+ drop_events(ctl);
+ find_moved_ctls(ctl, read_val);
+ }
+
+ for (other = ctl_list; other != NULL; other = other->next) {
+ if (!other->moved)
+ continue;
+
+ if (other->ev_mask & SND_CTL_EVENT_MASK_VALUE) {
+ ksft_print_msg("Writing %s changed %s, the driver said so\n",
+ ctl->name, other->name);
+ } else {
+ ksft_print_msg("Writing %s silently changed %s\n",
+ ctl->name, other->name);
+ ctl->side_effects++;
+ }
+ }
+
+ /*
+ * The control we wrote goes back first so its mask stops moving the
+ * rest. A plain write keeps this out of the event counters, they
+ * belong to the tests that check them.
+ */
+ snd_ctl_elem_write(ctl->card->handle, ctl->def_val);
+
+ for (other = ctl_list; other != NULL; other = other->next) {
+ if (!other->snapshot_valid ||
+ !snd_ctl_elem_info_is_writable(other->info))
+ continue;
+
+ snd_ctl_elem_value_clear(read_val);
+ snd_ctl_elem_value_set_id(read_val, other->id);
+ if (snd_ctl_elem_read(ctl->card->handle, read_val) < 0)
+ continue;
+
+ if (snd_ctl_elem_value_compare(other->snapshot, read_val))
+ snd_ctl_elem_write(ctl->card->handle, other->snapshot);
+ }
+
+ /* Our own restores queue events, the next test must not see them */
+ drop_events(ctl);
+
+ if (err < 0)
+ ksft_test_result_skip("write_side_effects.%s.%d\n",
+ ctl->card->card_name, ctl->elem);
+ else
+ ksft_test_result(!ctl->side_effects,
+ "write_side_effects.%s.%d\n",
+ ctl->card->card_name, ctl->elem);
+}
+
static bool test_ctl_write_invalid_value(struct ctl_data *ctl,
snd_ctl_elem_value_t *val)
{
@@ -1390,6 +1622,7 @@ int main(void)
test_ctl_name(ctl);
test_ctl_write_default(ctl);
test_ctl_write_valid(ctl);
+ test_ctl_write_side_effects(ctl);
test_ctl_write_invalid(ctl);
test_ctl_event_missing(ctl);
test_ctl_event_spurious(ctl);
diff --git a/tools/testing/selftests/bpf/Makefile b/tools/testing/selftests/bpf/Makefile
index afa589a27b15..a22be7efd1fa 100644
--- a/tools/testing/selftests/bpf/Makefile
+++ b/tools/testing/selftests/bpf/Makefile
@@ -422,6 +422,16 @@ $(LIBARENA_ASAN_SKEL): $(INCLUDE_DIR)/vmlinux.h $(BPFOBJ) $(LIBARENA_BPF_DEPS)
+$(MAKE) -C libarena libarena_asan.skel.h $(LIBARENA_MAKE_ARGS)
endif
+# #![no_std] looks for compiler_builtins too. Nothing of it is used.
+ifneq ($(RUST_CORE),)
+$(RUST_CORE): $(RUST_CORE_SRC)
+ $(call msg,RUSTC,,$@)
+ $(Q)mkdir -p $(@D)
+ +$(Q)$(RUSTC_BPF) -A warnings --edition 2024 --crate-name core --out-dir $(@D) $<
+ +$(Q)echo '#![feature(compiler_builtins)] #![compiler_builtins] #![no_std]' | \
+ $(RUSTC_BPF) -A warnings --crate-name compiler_builtins --out-dir $(@D) -
+endif
+
# Generated test list headers
define gen_tests_hdr
@@ -457,7 +467,7 @@ RUNNER_PREREQS := $(INCLUDE_DIR)/vmlinux.h $(BPFOBJ) $(BPFTOOL) \
$(VERIFY_SIG_HDR) $(PRIVATE_KEY) $(VERIFICATION_CERT) \
$(LIBARENA_SKEL) $(LIBARENA_ASAN_SKEL) \
prog_tests/tests.h map_tests/tests.h \
- $(RUNNER_OBJS)
+ $(RUNNER_OBJS) $(RUST_CORE)
# Runtime fixtures for each test_progs flavor.
RUNNER_EXTRA_FILES := $(OUTPUT)/urandom_read \
diff --git a/tools/testing/selftests/bpf/Makefile.buildvars b/tools/testing/selftests/bpf/Makefile.buildvars
index d2a0c0031b87..ff3476bc40a4 100644
--- a/tools/testing/selftests/bpf/Makefile.buildvars
+++ b/tools/testing/selftests/bpf/Makefile.buildvars
@@ -109,6 +109,31 @@ HOST_INCLUDE_DIR := $(INCLUDE_DIR)
endif
RESOLVE_BTFIDS := $(HOST_BUILD_DIR)/resolve_btfids/resolve_btfids
+# Programs in Rust are built by upstream rustc. It has no prebuilt core for
+# the bpf target, so it has to come with the source of core:
+# rustup component add rust-src
+# core is built as edition 2024, which it is since rustc 1.87.
+# rustc emits LLVM bitcode and clang makes the object of it, so clang has to be
+# 23 or newer and not older than LLVM of rustc.
+# Otherwise RUST_CORE is empty and the tests are skipped.
+RUSTC ?= rustc
+RUST_CORE_SRC := $(wildcard $(shell $(RUSTC) --print sysroot 2>/dev/null)$\
+ /lib/rustlib/src/rust/library/core/src/lib.rs)
+ifneq ($(RUST_CORE_SRC),)
+ifeq ($(shell { clang=$$(echo __clang_major__ | $(CLANG) -E -P -x c -) && \
+ llvm=$$($(srctree)/scripts/rustc-llvm-version.sh $(RUSTC)) && \
+ [ $$($(srctree)/scripts/rustc-version.sh $(RUSTC)) -ge 108700 ] && \
+ [ $$clang -ge 23 ] && [ $$clang -ge $$((llvm / 10000)) ]; } \
+ 2>/dev/null && echo y),y)
+RUST_CORE := $(BUILD_DIR)/rust/libcore.rlib
+endif
+endif
+# RUSTC_BOOTSTRAP=1 is to build core with a stable rustc, like the kernel does.
+# panic=abort is a stop gap until panic=unwind is supported.
+RUSTC_BPF = RUSTC_BOOTSTRAP=1 $(RUSTC) -O -C panic=abort --crate-type rlib \
+ --target $(if $(IS_LITTLE_ENDIAN),bpfel,bpfeb)-unknown-none \
+ -L $(dir $(RUST_CORE))
+
DEFAULT_BPFTOOL := $(HOST_SCRATCH_DIR)/sbin/bpftool
ifneq ($(CROSS_COMPILE),)
CROSS_BPFTOOL := $(SCRATCH_DIR)/sbin/bpftool
diff --git a/tools/testing/selftests/bpf/Makefile.skel b/tools/testing/selftests/bpf/Makefile.skel
index 2e22bb901bf3..06e297a17156 100644
--- a/tools/testing/selftests/bpf/Makefile.skel
+++ b/tools/testing/selftests/bpf/Makefile.skel
@@ -20,7 +20,8 @@ ifneq ($(BPF_CC),)
BPF_SRCS := $(notdir $(wildcard progs/*.c))
BPF_OBJS := $(patsubst %.c,$(RDIR)/%.bpf.o,$(BPF_SRCS))
-SKEL_BLACKLIST := btf__% test_pinning_invalid.c test_sk_assign.c
+SKEL_BLACKLIST := btf__% test_pinning_invalid.c test_sk_assign.c \
+ data_in_arena_extern.c data_in_arena_nomap.c
LINKED_SKELS := test_static_linked.skel.h linked_funcs.skel.h \
linked_vars.skel.h linked_maps.skel.h linked_arena.skel.h \
@@ -137,4 +138,19 @@ $(LINKED_SKELS_H): $(RDIR)/%.skel.h: $$(addprefix $(RDIR)/,$$($$*.skel.h-deps))
$(BPFTOOL) $(GEN_SKEL) | $(RDIR)
$(Q)$(cmd_bpf_link_skel)
+# Programs in Rust, see Makefile.buildvars. No skeletons: the objects may be absent.
+ifneq ($(RUST_CORE),)
+ifeq ($(BPF_CC),$(CLANG))
+RUST_OBJS := $(patsubst progs/%.rs,$(RDIR)/%.bpf.o,$(wildcard progs/*.rs))
+BPF_OBJS += $(RUST_OBJS)
+
+$(RUST_OBJS): $(RDIR)/%.bpf.o: progs/%.rs $(RUST_CORE) | $(RDIR)
+ $(call msg,RUSTC,$(BINARY),$@)
+ +$(Q)$(RUSTC_BPF) --edition 2021 -C debuginfo=2 --emit=llvm-bc \
+ $(patsubst -mcpu=%,-C target-cpu=%,$(filter -mcpu=%,$(BPF_CC_FLAGS))) \
+ -o $(dir $(RUST_CORE))$(BINARY)-$*.bc $< && \
+ $(BPF_CC) $(BPF_CC_FLAGS) -c $(dir $(RUST_CORE))$(BINARY)-$*.bc -o $@ $(call skip_on_fail,BPF)
+endif
+endif
+
endif # BPF_CC
diff --git a/tools/testing/selftests/bpf/prog_tests/arena_scalar_blinded.c b/tools/testing/selftests/bpf/prog_tests/arena_scalar_blinded.c
new file mode 100644
index 000000000000..2ac2e9a652fe
--- /dev/null
+++ b/tools/testing/selftests/bpf/prog_tests/arena_scalar_blinded.c
@@ -0,0 +1,21 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <test_progs.h>
+#include "sysctl_helpers.h"
+#include "verifier_arena_scalar.skel.h"
+
+/* The same tests with constants of the programs blinded */
+void serial_test_arena_scalar_blinded(void)
+{
+ const char *harden = "/proc/sys/net/core/bpf_jit_harden";
+ char old[16] = {};
+
+ if (!is_jit_enabled()) {
+ test__skip();
+ return;
+ }
+ if (sysctl_set_or_fail(harden, old, "2"))
+ return;
+ RUN_TESTS(verifier_arena_scalar);
+ sysctl_set_or_fail(harden, NULL, old);
+}
diff --git a/tools/testing/selftests/bpf/prog_tests/bpf_ma_ttrace.c b/tools/testing/selftests/bpf/prog_tests/bpf_ma_ttrace.c
new file mode 100644
index 000000000000..a1d41b109940
--- /dev/null
+++ b/tools/testing/selftests/bpf/prog_tests/bpf_ma_ttrace.c
@@ -0,0 +1,60 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <test_progs.h>
+#include "bpf_ma_ttrace.skel.h"
+
+#define NR_ELEMS 4096
+
+/*
+ * The first free_bulk() starts RCU tasks trace GP. The rest of the elements are
+ * deleted while it's in flight. They should be freed without further alloc or
+ * free from this map.
+ */
+void test_bpf_ma_ttrace(void)
+{
+ LIBBPF_OPTS(bpf_test_run_opts, opts);
+ struct bpf_ma_ttrace *skel;
+ __u32 cnt = NR_ELEMS;
+ long *vals = NULL;
+ int *keys = NULL;
+ int i, err, fd, nr_cpus;
+
+ skel = bpf_ma_ttrace__open_and_load();
+ if (!ASSERT_OK_PTR(skel, "open_and_load"))
+ return;
+ nr_cpus = libbpf_num_possible_cpus();
+ if (!ASSERT_GT(nr_cpus, 0, "nr_cpus"))
+ goto out;
+ skel->bss->nr_cpus = nr_cpus;
+
+ keys = calloc(NR_ELEMS, sizeof(*keys));
+ vals = calloc(NR_ELEMS, sizeof(*vals));
+ if (!ASSERT_OK_PTR(keys, "keys") || !ASSERT_OK_PTR(vals, "vals"))
+ goto out;
+ for (i = 0; i < NR_ELEMS; i++)
+ keys[i] = i;
+
+ fd = bpf_map__fd(skel->maps.htab);
+ err = bpf_map_update_batch(fd, keys, vals, &cnt, NULL);
+ if (!ASSERT_OK(err, "update_batch") || !ASSERT_EQ(cnt, NR_ELEMS, "update_cnt"))
+ goto out;
+ err = bpf_map_delete_batch(fd, keys, &cnt, NULL);
+ if (!ASSERT_OK(err, "delete_batch") || !ASSERT_EQ(cnt, NR_ELEMS, "delete_cnt"))
+ goto out;
+
+ /* Wait for all __free_rcu() callbacks to finish */
+ for (i = 0; i < 300; i++) {
+ err = bpf_prog_test_run_opts(bpf_program__fd(skel->progs.check_ttrace), &opts);
+ if (!ASSERT_OK(err, "test_run") || !ASSERT_OK(opts.retval, "retval"))
+ goto out;
+ if (!skel->bss->in_progress)
+ break;
+ usleep(100000);
+ }
+ ASSERT_EQ(skel->bss->nr_caches, nr_cpus, "nr_caches");
+ ASSERT_EQ(skel->bss->in_progress, 0, "in_progress");
+ ASSERT_EQ(skel->bss->not_freed, 0, "not_freed");
+out:
+ free(keys);
+ free(vals);
+ bpf_ma_ttrace__destroy(skel);
+}
diff --git a/tools/testing/selftests/bpf/prog_tests/btf.c b/tools/testing/selftests/bpf/prog_tests/btf.c
index df6ad38d287d..24ab62b2834a 100644
--- a/tools/testing/selftests/bpf/prog_tests/btf.c
+++ b/tools/testing/selftests/bpf/prog_tests/btf.c
@@ -424,7 +424,7 @@ static struct btf_raw_test raw_tests[] = {
.err_str = "Invalid type",
},
{
- .descr = "global data test #8, invalid var size",
+ .descr = "global data test #8, var is smaller than its type",
.raw_types = {
/* int */
BTF_TYPE_INT_ENC(0, BTF_INT_SIGNED, 0, 32, 4), /* [1] */
@@ -457,8 +457,6 @@ static struct btf_raw_test raw_tests[] = {
.key_type_id = 0,
.value_type_id = 7,
.max_entries = 1,
- .btf_load_err = true,
- .err_str = "Invalid size",
},
{
.descr = "global data test #9, invalid var size",
@@ -498,7 +496,7 @@ static struct btf_raw_test raw_tests[] = {
.err_str = "Invalid size",
},
{
- .descr = "global data test #10, invalid var size",
+ .descr = "global data test #10, section is smaller than map value",
.raw_types = {
/* int */
BTF_TYPE_INT_ENC(0, BTF_INT_SIGNED, 0, 32, 4), /* [1] */
@@ -531,8 +529,7 @@ static struct btf_raw_test raw_tests[] = {
.key_type_id = 0,
.value_type_id = 7,
.max_entries = 1,
- .btf_load_err = true,
- .err_str = "Invalid size",
+ .map_create_err = true,
},
{
.descr = "global data test #11, multiple section members",
@@ -1987,14 +1984,14 @@ static struct btf_raw_test raw_tests[] = {
},
{
- .descr = "typedef (invalid name, invalid identifier)",
+ .descr = "typedef (invalid name, not printable)",
.raw_types = {
BTF_TYPE_INT_ENC(0, BTF_INT_SIGNED, 0, 32, 4), /* [1] */
BTF_TYPEDEF_ENC(NAME_TBD, 1), /* [2] */
BTF_END_RAW,
},
- .str_sec = "\0__!int",
- .str_sec_size = sizeof("\0__!int"),
+ .str_sec = "\0__\7int",
+ .str_sec_size = sizeof("\0__\7int"),
.map_type = BPF_MAP_TYPE_ARRAY,
.map_name = "typedef_check_btf",
.key_size = sizeof(int),
@@ -2112,15 +2109,15 @@ static struct btf_raw_test raw_tests[] = {
},
{
- .descr = "fwd type (invalid name, invalid identifier)",
+ .descr = "fwd type (invalid name, not printable)",
.raw_types = {
BTF_TYPE_INT_ENC(0, BTF_INT_SIGNED, 0, 32, 4), /* [1] */
BTF_TYPE_ENC(NAME_TBD,
BTF_INFO_ENC(BTF_KIND_FWD, 0, 0), 0), /* [2] */
BTF_END_RAW,
},
- .str_sec = "\0__!skb",
- .str_sec_size = sizeof("\0__!skb"),
+ .str_sec = "\0__\7skb",
+ .str_sec_size = sizeof("\0__\7skb"),
.map_type = BPF_MAP_TYPE_ARRAY,
.map_name = "fwd_type_check_btf",
.key_size = sizeof(int),
@@ -2175,7 +2172,7 @@ static struct btf_raw_test raw_tests[] = {
},
{
- .descr = "struct type (invalid name, invalid identifier)",
+ .descr = "struct type (invalid name, not printable)",
.raw_types = {
BTF_TYPE_INT_ENC(0, BTF_INT_SIGNED, 0, 32, 4), /* [1] */
BTF_TYPE_ENC(NAME_TBD,
@@ -2183,8 +2180,8 @@ static struct btf_raw_test raw_tests[] = {
BTF_MEMBER_ENC(NAME_TBD, 1, 0),
BTF_END_RAW,
},
- .str_sec = "\0A!\0B",
- .str_sec_size = sizeof("\0A!\0B"),
+ .str_sec = "\0A\7\0B",
+ .str_sec_size = sizeof("\0A\7\0B"),
.map_type = BPF_MAP_TYPE_ARRAY,
.map_name = "struct_type_check_btf",
.key_size = sizeof(int),
@@ -2217,7 +2214,7 @@ static struct btf_raw_test raw_tests[] = {
},
{
- .descr = "struct member (invalid name, invalid identifier)",
+ .descr = "struct member (invalid name, not printable)",
.raw_types = {
BTF_TYPE_INT_ENC(0, BTF_INT_SIGNED, 0, 32, 4), /* [1] */
BTF_TYPE_ENC(NAME_TBD,
@@ -2225,8 +2222,8 @@ static struct btf_raw_test raw_tests[] = {
BTF_MEMBER_ENC(NAME_TBD, 1, 0),
BTF_END_RAW,
},
- .str_sec = "\0A\0B*",
- .str_sec_size = sizeof("\0A\0B*"),
+ .str_sec = "\0A\0B\7",
+ .str_sec_size = sizeof("\0A\0B\7"),
.map_type = BPF_MAP_TYPE_ARRAY,
.map_name = "struct_type_check_btf",
.key_size = sizeof(int),
@@ -2260,7 +2257,7 @@ static struct btf_raw_test raw_tests[] = {
},
{
- .descr = "enum type (invalid name, invalid identifier)",
+ .descr = "enum type (invalid name, not printable)",
.raw_types = {
BTF_TYPE_INT_ENC(0, BTF_INT_SIGNED, 0, 32, 4), /* [1] */
BTF_TYPE_ENC(NAME_TBD,
@@ -2269,8 +2266,8 @@ static struct btf_raw_test raw_tests[] = {
BTF_ENUM_ENC(NAME_TBD, 0),
BTF_END_RAW,
},
- .str_sec = "\0A!\0B",
- .str_sec_size = sizeof("\0A!\0B"),
+ .str_sec = "\0A\7\0B",
+ .str_sec_size = sizeof("\0A\7\0B"),
.map_type = BPF_MAP_TYPE_ARRAY,
.map_name = "enum_type_check_btf",
.key_size = sizeof(int),
@@ -2306,7 +2303,7 @@ static struct btf_raw_test raw_tests[] = {
},
{
- .descr = "enum member (invalid name, invalid identifier)",
+ .descr = "enum member (invalid name, not printable)",
.raw_types = {
BTF_TYPE_INT_ENC(0, BTF_INT_SIGNED, 0, 32, 4), /* [1] */
BTF_TYPE_ENC(0,
@@ -2315,8 +2312,8 @@ static struct btf_raw_test raw_tests[] = {
BTF_ENUM_ENC(NAME_TBD, 0),
BTF_END_RAW,
},
- .str_sec = "\0A!",
- .str_sec_size = sizeof("\0A!"),
+ .str_sec = "\0A\7",
+ .str_sec_size = sizeof("\0A\7"),
.map_type = BPF_MAP_TYPE_ARRAY,
.map_name = "enum_type_check_btf",
.key_size = sizeof(int),
@@ -2625,14 +2622,14 @@ static struct btf_raw_test raw_tests[] = {
.raw_types = {
BTF_TYPE_INT_ENC(0, BTF_INT_SIGNED, 0, 32, 4), /* [1] */
BTF_TYPE_INT_ENC(0, 0, 0, 32, 4), /* [2] */
- /* void (*)(int a, unsigned int !!!) */
+ /* void (*)(int a, unsigned int \7) */
BTF_FUNC_PROTO_ENC(0, 2), /* [3] */
BTF_FUNC_PROTO_ARG_ENC(NAME_TBD, 1),
BTF_FUNC_PROTO_ARG_ENC(NAME_TBD, 2),
BTF_END_RAW,
},
- .str_sec = "\0a\0!!!",
- .str_sec_size = sizeof("\0a\0!!!"),
+ .str_sec = "\0a\0\7",
+ .str_sec_size = sizeof("\0a\0\7"),
.map_type = BPF_MAP_TYPE_ARRAY,
.map_name = "func_proto_type_check_btf",
.key_size = sizeof(int),
@@ -2775,12 +2772,12 @@ static struct btf_raw_test raw_tests[] = {
BTF_FUNC_PROTO_ENC(0, 2), /* [3] */
BTF_FUNC_PROTO_ARG_ENC(NAME_TBD, 1),
BTF_FUNC_PROTO_ARG_ENC(NAME_TBD, 2),
- /* void !!!(int a, unsigned int b) */
+ /* void \7(int a, unsigned int b) */
BTF_FUNC_ENC(NAME_TBD, 3), /* [4] */
BTF_END_RAW,
},
- .str_sec = "\0a\0b\0!!!",
- .str_sec_size = sizeof("\0a\0b\0!!!"),
+ .str_sec = "\0a\0b\0\7",
+ .str_sec_size = sizeof("\0a\0b\0\7"),
.map_type = BPF_MAP_TYPE_ARRAY,
.map_name = "func_type_check_btf",
.key_size = sizeof(int),
@@ -2793,7 +2790,7 @@ static struct btf_raw_test raw_tests[] = {
},
{
- .descr = "func (Some arg has no name)",
+ .descr = "func (Some arg of global func has no name)",
.raw_types = {
BTF_TYPE_INT_ENC(0, BTF_INT_SIGNED, 0, 32, 4), /* [1] */
BTF_TYPE_INT_ENC(0, 0, 0, 32, 4), /* [2] */
@@ -2802,7 +2799,8 @@ static struct btf_raw_test raw_tests[] = {
BTF_FUNC_PROTO_ARG_ENC(NAME_TBD, 1),
BTF_FUNC_PROTO_ARG_ENC(0, 2),
/* void func(int a, unsigned int) */
- BTF_FUNC_ENC(NAME_TBD, 3), /* [4] */
+ BTF_TYPE_ENC(NAME_TBD, /* [4] */
+ BTF_INFO_ENC(BTF_KIND_FUNC, 0, BTF_FUNC_GLOBAL), 3),
BTF_END_RAW,
},
.str_sec = "\0a\0func",
@@ -2819,6 +2817,57 @@ static struct btf_raw_test raw_tests[] = {
},
{
+ .descr = "func (Some arg of static func has no name)",
+ .raw_types = {
+ /* int */
+ BTF_TYPE_INT_ENC(0, BTF_INT_SIGNED, 0, 32, 4), /* [1] */
+ /* unsigned int */
+ BTF_TYPE_INT_ENC(0, 0, 0, 32, 4), /* [2] */
+ /* void (*)(int a, unsigned int) */
+ BTF_FUNC_PROTO_ENC(0, 2), /* [3] */
+ BTF_FUNC_PROTO_ARG_ENC(NAME_TBD, 1),
+ BTF_FUNC_PROTO_ARG_ENC(0, 2),
+ /* static void func(int a, unsigned int) */
+ BTF_FUNC_ENC(NAME_TBD, 3), /* [4] */
+ BTF_END_RAW,
+ },
+ .str_sec = "\0a\0func",
+ .str_sec_size = sizeof("\0a\0func"),
+ .map_type = BPF_MAP_TYPE_ARRAY,
+ .map_name = "func_type_check_btf",
+ .key_size = sizeof(int),
+ .value_size = sizeof(int),
+ .key_type_id = 1,
+ .value_type_id = 1,
+ .max_entries = 4,
+},
+
+{
+ .descr = "func (vararg of global func has no name)",
+ .raw_types = {
+ /* int */
+ BTF_TYPE_INT_ENC(0, BTF_INT_SIGNED, 0, 32, 4), /* [1] */
+ /* void (*)(int a, ...) */
+ BTF_FUNC_PROTO_ENC(0, 2), /* [2] */
+ BTF_FUNC_PROTO_ARG_ENC(NAME_TBD, 1),
+ BTF_FUNC_PROTO_ARG_ENC(0, 0),
+ /* void func(int a, ...) */
+ BTF_TYPE_ENC(NAME_TBD, /* [3] */
+ BTF_INFO_ENC(BTF_KIND_FUNC, 0, BTF_FUNC_GLOBAL), 2),
+ BTF_END_RAW,
+ },
+ .str_sec = "\0a\0func",
+ .str_sec_size = sizeof("\0a\0func"),
+ .map_type = BPF_MAP_TYPE_ARRAY,
+ .map_name = "func_type_check_btf",
+ .key_size = sizeof(int),
+ .value_size = sizeof(int),
+ .key_type_id = 1,
+ .value_type_id = 1,
+ .max_entries = 4,
+},
+
+{
.descr = "func (Non zero vlen)",
.raw_types = {
BTF_TYPE_INT_ENC(0, BTF_INT_SIGNED, 0, 32, 4), /* [1] */
@@ -3585,15 +3634,28 @@ static struct btf_raw_test raw_tests[] = {
.btf_load_err = true,
},
{
- .descr = "type name '?foo' is not ok",
+ .descr = "type name '?foo' is ok",
.raw_types = {
/* union ?foo; */
BTF_TYPE_ENC(1, BTF_INFO_ENC(BTF_KIND_FWD, 1, 0), 0), /* [1] */
BTF_END_RAW,
},
BTF_STR_SEC("\0?foo"),
- .err_str = "Invalid name",
- .btf_load_err = true,
+},
+{
+ .descr = "names of Rust types and functions are ok",
+ .raw_types = {
+ BTF_TYPE_INT_ENC(NAME_NTH(1), 0, 0, 32, 4), /* [1] */
+ BTF_STRUCT_ENC(NAME_NTH(2), 1, 4), /* [2] */
+ BTF_MEMBER_ENC(NAME_NTH(3), 1, 0),
+ BTF_FWD_ENC(NAME_NTH(4), 0), /* [3] */
+ BTF_TYPEDEF_ENC(NAME_NTH(5), 2), /* [4] */
+ BTF_FUNC_PROTO_ENC(0, 1), /* [5] */
+ BTF_FUNC_PROTO_ARG_ENC(NAME_NTH(6), 1),
+ BTF_FUNC_ENC(NAME_NTH(7), 5), /* [6] */
+ BTF_END_RAW,
+ },
+ BTF_STR_SEC("\0u32\0Option<&str>\0__0\0*const str\0{impl#9}<[u8; 4]>\0self\0fmt<str>"),
},
{
diff --git a/tools/testing/selftests/bpf/prog_tests/btf_rust.c b/tools/testing/selftests/bpf/prog_tests/btf_rust.c
new file mode 100644
index 000000000000..daf777cadfda
--- /dev/null
+++ b/tools/testing/selftests/bpf/prog_tests/btf_rust.c
@@ -0,0 +1,142 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <test_progs.h>
+#include <bpf/btf.h>
+#include "bpftool_helpers.h"
+
+#define FUNC_NAME "write_fmt<scx_simple::BpfStream>"
+#define KSYM_NAME "write_fmt_scx_simple__BpfStream_"
+
+/* The name of a function of Rust is a part of the name of the program in kallsyms */
+static void test_func_name(void)
+{
+ struct bpf_insn insns[] = {
+ BPF_MOV64_IMM(BPF_REG_0, 0),
+ BPF_EXIT_INSN(),
+ };
+ LIBBPF_OPTS(bpf_prog_load_opts, opts);
+ int int_id, proto_id, prog_fd = -1, i;
+ struct bpf_func_info func_info = {};
+ struct bpf_prog_info info = {};
+ unsigned long long addr;
+ __u32 len = sizeof(info);
+ char sym[128], *p = sym;
+ struct btf *btf;
+
+ btf = btf__new_empty();
+ if (!ASSERT_OK_PTR(btf, "btf"))
+ return;
+ int_id = btf__add_int(btf, "i32", 4, BTF_INT_SIGNED);
+ ASSERT_GT(int_id, 0, "int");
+ proto_id = btf__add_func_proto(btf, int_id);
+ ASSERT_GT(proto_id, 0, "proto");
+ ASSERT_OK(btf__add_func_param(btf, "ctx", int_id), "param");
+ func_info.type_id = btf__add_func(btf, FUNC_NAME, BTF_FUNC_STATIC, proto_id);
+ ASSERT_GT(func_info.type_id, 0, "func");
+ if (!ASSERT_OK(btf__load_into_kernel(btf), "btf load"))
+ goto out;
+
+ opts.prog_btf_fd = btf__fd(btf);
+ opts.func_info = &func_info;
+ opts.func_info_cnt = 1;
+ opts.func_info_rec_size = sizeof(func_info);
+ prog_fd = bpf_prog_load(BPF_PROG_TYPE_SOCKET_FILTER, NULL, "GPL", insns,
+ ARRAY_SIZE(insns), &opts);
+ if (!ASSERT_GE(prog_fd, 0, "prog load"))
+ goto out;
+ if (!ASSERT_OK(bpf_prog_get_info_by_fd(prog_fd, &info, &len), "prog info"))
+ goto out;
+ if (!info.jited_prog_len) {
+ test__skip();
+ goto out;
+ }
+
+ p += sprintf(p, "bpf_prog_");
+ for (i = 0; i < BPF_TAG_SIZE; i++)
+ p += sprintf(p, "%02x", info.tag[i]);
+ sprintf(p, "_%s", KSYM_NAME);
+ ASSERT_OK(kallsyms_find(sym, &addr), sym);
+out:
+ if (prog_fd >= 0)
+ close(prog_fd);
+ btf__free(btf);
+}
+
+#define PIN_PATH "/sys/fs/bpf/btf_rust_piece"
+
+/*
+ * A piece of a static that LLVM split has the type of the whole static.
+ * It's the last variable in the section, so its type ends past the map value.
+ */
+static void test_piece(void)
+{
+ LIBBPF_OPTS(bpf_map_create_opts, opts);
+ int int_id, struct_id, var_id, piece_id, sec_id, map_fd = -1, key = 0;
+ __u32 value[2] = { 0x11111111, 0x22222222 };
+ char line[256] = {}, out[1024] = {};
+ struct btf *btf;
+ FILE *f = NULL;
+
+ btf = btf__new_empty();
+ if (!ASSERT_OK_PTR(btf, "btf"))
+ return;
+ int_id = btf__add_int(btf, "u32", 4, 0);
+ ASSERT_GT(int_id, 0, "int");
+ struct_id = btf__add_struct(btf, "Whole", 8);
+ ASSERT_GT(struct_id, 0, "struct");
+ ASSERT_OK(btf__add_field(btf, "a", int_id, 0, 0), "field");
+ ASSERT_OK(btf__add_field(btf, "b", int_id, 32, 0), "field");
+ var_id = btf__add_var(btf, "CNT", BTF_VAR_STATIC, int_id);
+ ASSERT_GT(var_id, 0, "var");
+ piece_id = btf__add_var(btf, "WHOLE.1", BTF_VAR_STATIC, struct_id);
+ ASSERT_GT(piece_id, 0, "piece");
+ sec_id = btf__add_datasec(btf, ".bss", sizeof(value));
+ ASSERT_GT(sec_id, 0, "datasec");
+ ASSERT_OK(btf__add_datasec_var_info(btf, var_id, 0, 4), "var info");
+ ASSERT_OK(btf__add_datasec_var_info(btf, piece_id, 4, 4), "piece info");
+ if (!ASSERT_OK(btf__load_into_kernel(btf), "btf load"))
+ goto out;
+
+ opts.btf_fd = btf__fd(btf);
+ opts.btf_value_type_id = sec_id;
+ map_fd = bpf_map_create(BPF_MAP_TYPE_ARRAY, ".bss", sizeof(key), sizeof(value), 1, &opts);
+ if (!ASSERT_GE(map_fd, 0, "map create"))
+ goto out;
+ if (!ASSERT_OK(bpf_map_update_elem(map_fd, &key, value, 0), "map update"))
+ goto out;
+
+ /* the variable is printed, the piece is not */
+ unlink(PIN_PATH);
+ if (!ASSERT_OK(bpf_obj_pin(map_fd, PIN_PATH), "pin"))
+ goto out;
+ f = fopen(PIN_PATH, "r");
+ if (!ASSERT_OK_PTR(f, "open"))
+ goto out;
+ while (fgets(line, sizeof(line), f) && line[0] == '#')
+ ;
+ ASSERT_HAS_SUBSTR(line, "286331153", "var");
+ ASSERT_NULL(strstr(line, "572662306"), "piece");
+
+ /* the same for bpftool */
+ if (!ASSERT_OK(get_bpftool_command_output("map dump pinned " PIN_PATH, out, sizeof(out)),
+ "bpftool"))
+ goto out;
+ ASSERT_HAS_SUBSTR(out, "CNT", "var");
+ ASSERT_NULL(strstr(out, "WHOLE.1"), "piece");
+out:
+ if (f)
+ fclose(f);
+ unlink(PIN_PATH);
+ if (map_fd >= 0)
+ close(map_fd);
+ btf__free(btf);
+}
+
+/* Serial: programs are not in kallsyms while another test sets bpf_jit_harden */
+void serial_test_btf_rust(void)
+{
+ if (test__start_subtest("func_name"))
+ test_func_name();
+ if (test__start_subtest("piece"))
+ test_piece();
+}
diff --git a/tools/testing/selftests/bpf/prog_tests/data_in_arena.c b/tools/testing/selftests/bpf/prog_tests/data_in_arena.c
new file mode 100644
index 000000000000..cb1023507c01
--- /dev/null
+++ b/tools/testing/selftests/bpf/prog_tests/data_in_arena.c
@@ -0,0 +1,216 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <test_progs.h>
+#include "data_in_arena.skel.h"
+#include "data_in_arena_decl.skel.h"
+#include "data_in_arena_fail.skel.h"
+
+static int run_prog(struct bpf_program *prog)
+{
+ LIBBPF_OPTS(bpf_test_run_opts, topts);
+
+ if (!ASSERT_OK(bpf_prog_test_run_opts(bpf_program__fd(prog), &topts), "test_run"))
+ return -1;
+ return topts.retval;
+}
+
+static void run(struct data_in_arena *skel, int counter)
+{
+ int i;
+
+ ASSERT_EQ(run_prog(skel->progs.use_data), counter + 7 + 1 + 2, "retval");
+ ASSERT_EQ(skel->bss->sum, counter + 7 + 1 + 2, "sum");
+ ASSERT_EQ(skel->data->counter, counter + 1, "counter");
+ ASSERT_EQ(skel->data->pair[1], 5, "pair[1]");
+ for (i = 0; i < 4; i++)
+ ASSERT_EQ(skel->bss->table[i], 10 * (i + 1) + i, "table");
+}
+
+static void test_in_arena(void)
+{
+ struct bpf_map_info info = {};
+ struct data_in_arena *skel;
+ __u32 len = sizeof(info);
+ struct bpf_map *arena;
+ size_t sz;
+
+ skel = data_in_arena__open();
+ if (!ASSERT_OK_PTR(skel, "open"))
+ return;
+
+ arena = bpf_object__find_map_by_name(skel->obj, "arena");
+ if (!ASSERT_OK_PTR(arena, "arena"))
+ goto out;
+ ASSERT_EQ(bpf_map__type(arena), BPF_MAP_TYPE_ARENA, "arena type");
+ ASSERT_EQ(bpf_map__max_entries(arena), 1, "arena pages");
+ ASSERT_OK(bpf_map__set_max_entries(arena, 8), "arena resize");
+ ASSERT_FALSE(bpf_map__autocreate(skel->maps.data), "data autocreate");
+ ASSERT_FALSE(bpf_map__autocreate(skel->maps.bss), "bss autocreate");
+ ASSERT_FALSE(bpf_map__autocreate(skel->maps.rodata), "rodata autocreate");
+ ASSERT_EQ(bpf_map__set_autocreate(skel->maps.data, true), -EOPNOTSUPP, "set_autocreate");
+ ASSERT_EQ(bpf_map__set_value_size(skel->maps.bss, 4096), -EOPNOTSUPP, "set_value_size");
+ ASSERT_EQ(bpf_map__initial_value(skel->maps.data, &sz), skel->data, "initial_value");
+ ASSERT_EQ(sz, sizeof(*skel->data), "initial_value size");
+
+ /* initial values are set the usual way */
+ skel->data->counter = 100;
+
+ if (!ASSERT_OK(data_in_arena__load(skel), "load"))
+ goto out;
+ /* there are no maps behind the sections */
+ ASSERT_ERR(bpf_map_get_info_by_fd(bpf_map__fd(skel->maps.data), &info, &len), "data map");
+ ASSERT_ERR(bpf_map_get_info_by_fd(bpf_map__fd(skel->maps.bss), &info, &len), "bss map");
+ ASSERT_ERR(bpf_map_get_info_by_fd(bpf_map__fd(skel->maps.rodata), &info, &len),
+ "rodata map");
+ run(skel, 100);
+
+ /* pointers to data next to pointers to functions */
+ ASSERT_EQ(run_prog(skel->progs.use_ops), 42 + 1 + 'e', "use_ops");
+ ASSERT_EQ(skel->data->counter, 102, "counter");
+
+ /* alignment of sections and pointers to data in data */
+ ASSERT_EQ((unsigned long)&skel->bss->aligned64 % 64, 0, "alignment");
+ ASSERT_EQ(run_prog(skel->progs.use_ptrs), 0, "use_ptrs");
+ ASSERT_EQ(skel->data->x, 43, "x");
+ ASSERT_EQ(skel->bss->aligned64.v[7], 7, "aligned64");
+ ASSERT_EQ(skel->data->px, &skel->data->x, "px");
+
+ /* format strings of bpf_printk() and BPF_SNPRINTF() */
+ ASSERT_EQ(run_prog(skel->progs.use_printk), sizeof("43-7"), "use_printk");
+ ASSERT_STREQ(skel->bss->out, "43-7", "out");
+out:
+ data_in_arena__destroy(skel);
+}
+
+/* The object has an arena map and __arena variables */
+static void test_declared_arena(void)
+{
+ struct data_in_arena_decl *skel;
+ struct bpf_map *map;
+ int arenas = 0;
+
+ skel = data_in_arena_decl__open();
+ if (!ASSERT_OK_PTR(skel, "open"))
+ return;
+ bpf_object__for_each_map(map, skel->obj)
+ arenas += bpf_map__type(map) == BPF_MAP_TYPE_ARENA;
+ ASSERT_EQ(arenas, 1, "no second arena");
+ skel->data->counter = 6;
+ if (!ASSERT_OK(data_in_arena_decl__load(skel), "load"))
+ goto out;
+ ASSERT_EQ(run_prog(skel->progs.use_data), 6 + 7 + 11, "retval");
+ ASSERT_EQ(run_prog(skel->progs.use_data), 7 + 7 + 12, "retval");
+ ASSERT_EQ(skel->bss->sum, 7 + 7 + 12, "sum");
+ ASSERT_EQ(skel->data->counter, 8, "counter");
+out:
+ data_in_arena_decl__destroy(skel);
+}
+
+/* A pointer in data that can't be made an address of arena fails the load */
+static void test_ptr_to_map(void)
+{
+ struct data_in_arena_fail *skel;
+
+ skel = data_in_arena_fail__open();
+ if (!ASSERT_OK_PTR(skel, "open"))
+ return;
+ ASSERT_ERR(data_in_arena_fail__load(skel), "load");
+ data_in_arena_fail__destroy(skel);
+}
+
+/* So does a pointer to a variable of the kernel. There is no skeleton: the open fails. */
+static void test_ptr_to_extern(void)
+{
+ struct bpf_object *obj;
+
+ obj = bpf_object__open_file("./data_in_arena_extern.bpf.o", NULL);
+ if (!ASSERT_ERR_PTR(obj, "open"))
+ bpf_object__close(obj);
+}
+
+/* __arena variables and no arena map. There is no skeleton: the open fails. */
+static void test_arena_var_no_map(void)
+{
+ struct bpf_object *obj;
+
+ obj = bpf_object__open_file("./data_in_arena_nomap.bpf.o", NULL);
+ if (!ASSERT_ERR_PTR(obj, "open"))
+ bpf_object__close(obj);
+}
+
+/* The address of an arena that libbpf doesn't create is not known. No pointers in data then. */
+static void test_not_my_arena(bool pin)
+{
+ LIBBPF_OPTS(bpf_map_create_opts, opts, .map_flags = BPF_F_MMAPABLE);
+ struct data_in_arena *skel;
+ struct bpf_map *arena;
+ int fd = -1;
+
+ skel = data_in_arena__open();
+ if (!ASSERT_OK_PTR(skel, "open"))
+ return;
+ arena = bpf_object__find_map_by_name(skel->obj, "arena");
+ if (!ASSERT_OK_PTR(arena, "arena"))
+ goto out;
+ if (pin) {
+ ASSERT_OK(bpf_map__set_pin_path(arena, "/sys/fs/bpf/data_in_arena"), "pin_path");
+ } else {
+ fd = bpf_map_create(BPF_MAP_TYPE_ARENA, "arena", 0, 0, 1, &opts);
+ if (!ASSERT_GE(fd, 0, "map_create"))
+ goto out;
+ ASSERT_OK(bpf_map__reuse_fd(arena, fd), "reuse_fd");
+ }
+ ASSERT_EQ(data_in_arena__load(skel), -ENOTSUP, "load");
+out:
+ if (fd >= 0)
+ close(fd);
+ data_in_arena__destroy(skel);
+}
+
+/* The object is there if rustc and clang can build it, see Makefile.buildvars */
+static void test_rust(void)
+{
+ const char *file = "./data_in_arena_rust.bpf.o";
+ struct bpf_object *obj;
+
+ if (access(file, R_OK)) {
+ test__skip();
+ return;
+ }
+ obj = bpf_object__open_file(file, NULL);
+ if (!ASSERT_OK_PTR(obj, "open"))
+ return;
+ if (!ASSERT_OK(bpf_object__load(obj), "load"))
+ goto out;
+ /* libbpf goes on without BTF when the kernel doesn't take it */
+ ASSERT_GE(bpf_object__btf_fd(obj), 0, "btf_fd");
+ ASSERT_EQ(run_prog(bpf_object__find_program_by_name(obj, "list_in_data")),
+ 100 + 20 + 3, "retval");
+out:
+ bpf_object__close(obj);
+}
+
+void test_data_in_arena(void)
+{
+#if !defined(__x86_64__) && !defined(__aarch64__)
+ /* other JITs don't take BPF_F_ARENA_SCALAR */
+ test__skip();
+ return;
+#endif
+ if (test__start_subtest("arena"))
+ test_in_arena();
+ if (test__start_subtest("declared_arena"))
+ test_declared_arena();
+ if (test__start_subtest("ptr_to_map"))
+ test_ptr_to_map();
+ if (test__start_subtest("ptr_to_extern"))
+ test_ptr_to_extern();
+ if (test__start_subtest("arena_var_no_map"))
+ test_arena_var_no_map();
+ if (test__start_subtest("pinned_arena"))
+ test_not_my_arena(true);
+ if (test__start_subtest("reused_arena"))
+ test_not_my_arena(false);
+ if (test__start_subtest("rust"))
+ test_rust();
+}
diff --git a/tools/testing/selftests/bpf/prog_tests/file_reader.c b/tools/testing/selftests/bpf/prog_tests/file_reader.c
index 48aae7ea0e4b..e59c6c87e9d5 100644
--- a/tools/testing/selftests/bpf/prog_tests/file_reader.c
+++ b/tools/testing/selftests/bpf/prog_tests/file_reader.c
@@ -7,10 +7,12 @@
#include "file_reader_fail.skel.h"
#include <dlfcn.h>
#include <sys/mman.h>
+#include <sys/stat.h>
const char *user_ptr = "hello world";
char file_contents[256000];
void *addr;
+__u64 beyond_eof_offset;
void *get_executable_base_addr(void)
{
@@ -26,12 +28,18 @@ void *get_executable_base_addr(void)
static int initialize_file_contents(void)
{
+ struct stat st;
int fd, page_sz = sysconf(_SC_PAGESIZE);
ssize_t n = 0, cur;
fd = open("/proc/self/exe", O_RDONLY);
if (!ASSERT_OK_FD(fd, "Open /proc/self/exe\n"))
return 1;
+ if (!ASSERT_OK(fstat(fd, &st), "fstat /proc/self/exe")) {
+ close(fd);
+ return 1;
+ }
+ beyond_eof_offset = st.st_size + (1ULL << 30);
do {
cur = read(fd, file_contents + n, sizeof(file_contents) - n);
@@ -75,6 +83,7 @@ static void run_test(const char *prog_name)
memcpy(skel->bss->user_buf, file_contents, sizeof(file_contents));
skel->bss->pid = getpid();
+ skel->bss->beyond_eof_offset = beyond_eof_offset;
err = file_reader__load(skel);
if (!ASSERT_OK(err, "file_reader__load"))
@@ -110,6 +119,12 @@ void test_file_reader(void)
if (test__start_subtest("on_open_validate_file_read"))
run_test("on_open_validate_file_read");
+ if (test__start_subtest("on_open_non_sleepable_first"))
+ run_test("on_open_non_sleepable_first");
+
+ if (test__start_subtest("on_open_sleepable_first"))
+ run_test("on_open_sleepable_first");
+
if (test__start_subtest("negative"))
RUN_TESTS(file_reader_fail);
}
diff --git a/tools/testing/selftests/bpf/prog_tests/verifier.c b/tools/testing/selftests/bpf/prog_tests/verifier.c
index 8a6d341b754a..460ad10ddc02 100644
--- a/tools/testing/selftests/bpf/prog_tests/verifier.c
+++ b/tools/testing/selftests/bpf/prog_tests/verifier.c
@@ -12,6 +12,7 @@
#include "verifier_and.skel.h"
#include "verifier_arena.skel.h"
#include "verifier_arena_large.skel.h"
+#include "verifier_arena_scalar.skel.h"
#include "verifier_arena_globals1.skel.h"
#include "verifier_arena_globals2.skel.h"
#include "verifier_array_access.skel.h"
@@ -198,6 +199,7 @@ void test_verifier_align(void) { RUN(verifier_align); }
void test_verifier_and(void) { RUN(verifier_and); }
void test_verifier_arena(void) { RUN(verifier_arena); }
void test_verifier_arena_large(void) { RUN(verifier_arena_large); }
+void test_verifier_arena_scalar(void) { RUN(verifier_arena_scalar); }
void test_verifier_arena_globals1(void) { RUN(verifier_arena_globals1); }
void test_verifier_arena_globals2(void) { RUN(verifier_arena_globals2); }
void test_verifier_basic_stack(void) { RUN(verifier_basic_stack); }
diff --git a/tools/testing/selftests/bpf/progs/bpf_ma_ttrace.c b/tools/testing/selftests/bpf/progs/bpf_ma_ttrace.c
new file mode 100644
index 000000000000..31b2a962e339
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/bpf_ma_ttrace.c
@@ -0,0 +1,50 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <vmlinux.h>
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_core_read.h>
+#include "bpf_kfuncs.h"
+
+struct {
+ __uint(type, BPF_MAP_TYPE_HASH);
+ __uint(map_flags, BPF_F_NO_PREALLOC);
+ __uint(max_entries, 4096);
+ __type(key, int);
+ __type(value, long);
+} htab SEC(".maps");
+
+extern const void __per_cpu_offset __ksym;
+
+int nr_cpus;
+int nr_caches;
+int in_progress;
+int not_freed;
+
+/* Look at bpf_mem_cache of every cpu that htab allocates its elements from */
+SEC("syscall")
+int check_ttrace(void *ctx)
+{
+ struct bpf_htab *h = bpf_core_cast(&htab, struct bpf_htab);
+ unsigned long cache = (unsigned long)BPF_CORE_READ(h, ma.cache);
+ const unsigned long *offsets = &__per_cpu_offset;
+ struct bpf_mem_cache *c;
+ unsigned long off;
+ int cpu;
+
+ nr_caches = 0;
+ in_progress = 0;
+ not_freed = 0;
+ bpf_for(cpu, 0, nr_cpus) {
+ if (bpf_probe_read_kernel(&off, sizeof(off), offsets + cpu))
+ return -1;
+ c = bpf_core_cast((void *)(cache + off), struct bpf_mem_cache);
+ if (c->unit_size)
+ nr_caches++;
+ if (c->call_rcu_ttrace_in_progress.counter)
+ in_progress++;
+ if (c->free_by_rcu_ttrace.first || c->waiting_for_gp_ttrace.first)
+ not_freed++;
+ }
+ return 0;
+}
+
+char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/data_in_arena.c b/tools/testing/selftests/bpf/progs/data_in_arena.c
new file mode 100644
index 000000000000..18f38103c0fc
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/data_in_arena.c
@@ -0,0 +1,112 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <linux/bpf.h>
+#include <bpf/bpf_helpers.h>
+
+/* global data of the object is in arena */
+char data_in_arena SEC(".arena.data");
+
+int counter = 5;
+long pair[2] = { 1, 2 };
+int x = 42;
+long sum;
+long table[4];
+struct {
+ long v[8];
+} aligned64 __attribute__((aligned(64)));
+const volatile int ro = 7;
+const volatile long ro_table[4] = { 10, 20, 30, 40 };
+
+/* const strings stay in a map for helpers and kfuncs, a copy of them is in arena */
+const char hello[] SEC(".rodata.str.hello") = "hello";
+
+/* pointers to data that are stored in data */
+int *px = &x;
+const char *str = hello;
+int *const volatile cpx SEC(".data.rel.ro") = &x;
+
+SEC("syscall")
+int use_data(void *ctx)
+{
+ int i;
+
+ for (i = 0; i < 4; i++)
+ table[i] = ro_table[i] + i;
+ sum = counter + ro + pair[0] + pair[1];
+ counter++;
+ __sync_fetch_and_add(&pair[1], 3);
+ return sum;
+}
+
+typedef int (*op_fn)(int);
+
+static __noinline int add1(int v)
+{
+ return v + 1;
+}
+
+/*
+ * Pointers to functions and to data in read-only data of a program with callx.
+ * Volatile, so that the compiler doesn't replace the pointers with what
+ * they point to.
+ */
+static const volatile struct {
+ op_fn fn;
+ int *data;
+ const char *name;
+} ops SEC(".data.rel.ro") = { add1, &x, hello };
+
+SEC("syscall")
+int use_ops(void *ctx)
+{
+ /* a program has the arena when its code refers to it */
+ counter++;
+#ifdef __clang__
+ return ops.fn(*ops.data) + ops.name[1];
+#else
+ /* gcc doesn't support indirect calls */
+ return add1(*ops.data) + ops.name[1];
+#endif
+}
+
+SEC("syscall")
+int use_ptrs(void *ctx)
+{
+ unsigned long addr = (unsigned long)&aligned64;
+ char local[4] = "abc";
+
+ /* hide the address from the compiler, it knows that '& 63' is 0 */
+ asm volatile ("" : "+r"(addr));
+ if (addr & 63)
+ return 1;
+ aligned64.v[7] = 7;
+ if (*px != 42)
+ return 2;
+ *px = 43;
+ if (x != 43 || *cpx != 43)
+ return 3;
+ if (str[0] != 'h' || str[4] != 'o' || str[5])
+ return 4;
+ /* the literal is in a map */
+ if (bpf_strncmp(local, sizeof(local), "abc"))
+ return 5;
+ return 0;
+}
+
+char out[16];
+
+/* format strings are in a map */
+SEC("syscall")
+int use_printk(void *ctx)
+{
+ char buf[sizeof(out)];
+ int i, n;
+
+ bpf_printk("counter %d", counter);
+ n = BPF_SNPRINTF(buf, sizeof(buf), "%d-%d", x, ro);
+ for (i = 0; i < sizeof(out); i++)
+ out[i] = buf[i];
+ return n;
+}
+
+char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/data_in_arena_decl.c b/tools/testing/selftests/bpf/progs/data_in_arena_decl.c
new file mode 100644
index 000000000000..01bc4fea8f0c
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/data_in_arena_decl.c
@@ -0,0 +1,37 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#define BPF_NO_KFUNC_PROTOTYPES
+#include <vmlinux.h>
+#include <bpf/bpf_helpers.h>
+#include "bpf_experimental.h"
+#include <bpf_arena_common.h>
+
+struct {
+ __uint(type, BPF_MAP_TYPE_ARENA);
+ __uint(map_flags, BPF_F_MMAPABLE);
+ __uint(max_entries, 4);
+} arena SEC(".maps");
+
+/* global data of the object is in arena */
+char data_in_arena SEC(".arena.data");
+
+int counter = 5;
+long sum;
+const volatile int ro = 7;
+
+#if defined(__BPF_FEATURE_ADDR_SPACE_CAST)
+int __arena avar = 11;
+#else
+int avar = 11;
+#endif
+
+SEC("syscall")
+int use_data(void *ctx)
+{
+ sum = counter + ro + avar;
+ counter++;
+ avar++;
+ return sum;
+}
+
+char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/data_in_arena_extern.c b/tools/testing/selftests/bpf/progs/data_in_arena_extern.c
new file mode 100644
index 000000000000..dc1ad5ca3bdd
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/data_in_arena_extern.c
@@ -0,0 +1,20 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <linux/bpf.h>
+#include <bpf/bpf_helpers.h>
+
+/* global data of the object is in arena */
+char data_in_arena SEC(".arena.data");
+
+extern const int bpf_prog_active __ksym;
+
+/* the variable of the kernel is not in arena */
+const void *kp = &bpf_prog_active;
+
+SEC("syscall")
+int ptr_to_extern(void *ctx)
+{
+ return *(int *)kp;
+}
+
+char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/data_in_arena_fail.c b/tools/testing/selftests/bpf/progs/data_in_arena_fail.c
new file mode 100644
index 000000000000..ad8afe9afc96
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/data_in_arena_fail.c
@@ -0,0 +1,20 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <linux/bpf.h>
+#include <bpf/bpf_helpers.h>
+
+/* global data of the object is in arena */
+char data_in_arena SEC(".arena.data");
+
+int x = 42;
+/* the table is read-only data with pointers. It's not in arena. */
+int *const volatile tbl[1] SEC(".data.rel.ro") = { &x };
+int *const volatile *pp = tbl;
+
+SEC("syscall")
+int ptr_to_map(void *ctx)
+{
+ return **pp;
+}
+
+char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/data_in_arena_nomap.c b/tools/testing/selftests/bpf/progs/data_in_arena_nomap.c
new file mode 100644
index 000000000000..ccf66321eb36
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/data_in_arena_nomap.c
@@ -0,0 +1,20 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <vmlinux.h>
+#include <bpf/bpf_helpers.h>
+#include "bpf_arena_common.h"
+
+/* global data of the object is in arena */
+char data_in_arena SEC(".arena.data");
+
+int counter = 5;
+/* needs an arena map that is declared. The one that libbpf creates won't do. */
+int __arena_global avar = 11;
+
+SEC("syscall")
+int arena_var(void *ctx)
+{
+ return avar + counter;
+}
+
+char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/data_in_arena_rust.rs b/tools/testing/selftests/bpf/progs/data_in_arena_rust.rs
new file mode 100644
index 000000000000..3dd9d4208232
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/data_in_arena_rust.rs
@@ -0,0 +1,73 @@
+// SPDX-License-Identifier: GPL-2.0
+
+// Why .data, .bss and .rodata of a program in Rust are in arena.
+//
+// A reference in Rust is an address. It doesn't say what it points to and it
+// can be stored in data. The list below has a node in each of the sections.
+// The nodes are linked by references that are in the data:
+// IN_BSS.next is stored by the program,
+// IN_DATA.next is a relocation in .data against .rodata.
+// sum() loads the references back and reads the three nodes with the same insn.
+//
+// When the sections are array maps libbpf skips the relocation in .data, and
+// what sum() loads from a node is a number that can't be dereferenced:
+// R1 invalid mem access 'scalar'
+// In arena the address of a node is a number to begin with.
+
+#![no_std]
+#![no_main]
+
+// Tell libbpf to keep .data, .bss and .rodata in arena.
+#[used]
+#[link_section = ".arena.data"]
+static DATA_IN_ARENA: u8 = 0;
+
+#[used]
+#[link_section = "license"]
+static LICENSE: [u8; 4] = *b"GPL\0";
+
+// panic=abort is a stop gap until panic=unwind is supported.
+// Nothing here panics, so the handler is not a part of the program.
+#[panic_handler]
+fn panic(_info: &core::panic::PanicInfo) -> ! {
+ loop {}
+}
+
+pub struct Node {
+ val: u32,
+ next: Option<&'static Node>,
+}
+
+// no_mangle makes them visible outside, so LLVM can't fold the list into a constant.
+#[no_mangle]
+static IN_RODATA: Node = Node { val: 3, next: None };
+#[no_mangle]
+static mut IN_DATA: Node = Node {
+ val: 20,
+ next: Some(&IN_RODATA),
+};
+#[no_mangle]
+static mut IN_BSS: Node = Node { val: 0, next: None };
+
+#[inline(never)]
+fn sum(mut node: Option<&Node>) -> u32 {
+ let mut sum = 0;
+ // The verifier wants a bound.
+ for _ in 0..8 {
+ let Some(n) = node else { break };
+ sum += n.val;
+ node = n.next;
+ }
+ sum
+}
+
+#[no_mangle]
+#[link_section = "syscall"]
+pub extern "C" fn list_in_data(_ctx: *mut u8) -> u32 {
+ unsafe {
+ let head = &mut *&raw mut IN_BSS;
+ head.val = 100;
+ head.next = Some(&*&raw const IN_DATA);
+ sum(Some(head))
+ }
+}
diff --git a/tools/testing/selftests/bpf/progs/file_reader.c b/tools/testing/selftests/bpf/progs/file_reader.c
index aa2c05cce2b3..8b972fd26d73 100644
--- a/tools/testing/selftests/bpf/progs/file_reader.c
+++ b/tools/testing/selftests/bpf/progs/file_reader.c
@@ -27,9 +27,14 @@ char tmp_buf[256000];
int pid = 0;
int err, run_success = 0;
+__u64 beyond_eof_offset;
static int validate_file_read(struct file *file);
static int task_work_callback(struct bpf_map *map, void *key, void *value);
+static int sleepable_second_callback(struct bpf_map *map, void *key, void *value);
+
+void bpf_rcu_read_lock(void) __ksym;
+void bpf_rcu_read_unlock(void) __ksym;
SEC("lsm/file_open")
int on_open_expect_fault(void *c)
@@ -81,6 +86,101 @@ int on_open_validate_file_read(void *c)
return 0;
}
+/*
+ * Exercise bpf_dynptr_from_file() first from a non-sleepable LSM program and
+ * then from its sleepable task-work callback. Reading beyond EOF makes the two
+ * backing implementations return different errors.
+ */
+SEC("lsm/file_open")
+int on_open_non_sleepable_first(void *c)
+{
+ struct task_struct *task = bpf_get_current_task_btf();
+ struct bpf_dynptr dynptr;
+ struct elem *work;
+ struct file *file;
+ int key = 0;
+ int ret;
+
+ if (bpf_get_current_pid_tgid() >> 32 != pid)
+ return 0;
+
+ file = bpf_get_task_exe_file(task);
+ if (!file) {
+ err = 1;
+ return 0;
+ }
+
+ /* The non-sleepable reader cannot fault in an uncached folio. */
+ ret = bpf_dynptr_from_file(file, 0, &dynptr);
+ if (!ret)
+ ret = bpf_dynptr_read(tmp_buf, 1, &dynptr, beyond_eof_offset, 0);
+ bpf_dynptr_file_discard(&dynptr);
+ bpf_put_file(file);
+ if (ret != -EFAULT) {
+ err = 2;
+ return 0;
+ }
+
+ work = bpf_map_lookup_elem(&arrmap, &key);
+ if (!work) {
+ err = 3;
+ return 0;
+ }
+
+ ret = bpf_task_work_schedule_signal(task, &work->tw, &arrmap,
+ sleepable_second_callback);
+ if (ret)
+ err = 4;
+ return 0;
+}
+
+/*
+ * Exercise the opposite fixup order: the first call is made from a sleepable
+ * LSM program, while the RCU read-side section makes the second non-sleepable.
+ */
+SEC("lsm.s/file_open")
+int on_open_sleepable_first(void *c)
+{
+ struct task_struct *task = bpf_get_current_task_btf();
+ struct bpf_dynptr dynptr;
+ struct file *file;
+ int ret;
+
+ if (bpf_get_current_pid_tgid() >> 32 != pid)
+ return 0;
+
+ file = bpf_get_task_exe_file(task);
+ if (!file) {
+ err = 7;
+ return 0;
+ }
+
+ ret = bpf_dynptr_from_file(file, 0, &dynptr);
+ if (!ret)
+ ret = bpf_dynptr_read(tmp_buf, 1, &dynptr, beyond_eof_offset, 0);
+ bpf_dynptr_file_discard(&dynptr);
+ if (ret != -EIO) {
+ err = 8;
+ goto out;
+ }
+
+ bpf_rcu_read_lock();
+ ret = bpf_dynptr_from_file(file, 0, &dynptr);
+ bpf_rcu_read_unlock();
+ if (!ret)
+ ret = bpf_dynptr_read(tmp_buf, 1, &dynptr, beyond_eof_offset, 0);
+ bpf_dynptr_file_discard(&dynptr);
+ if (ret != -EFAULT) {
+ err = 9;
+ goto out;
+ }
+
+ run_success = 1;
+out:
+ bpf_put_file(file);
+ return 0;
+}
+
/* Called in a sleepable context, read 256K bytes, cross check with user space read data */
static int task_work_callback(struct bpf_map *map, void *key, void *value)
{
@@ -97,6 +197,35 @@ static int task_work_callback(struct bpf_map *map, void *key, void *value)
return 0;
}
+/* Task-work callbacks are verified as sleepable. */
+static int sleepable_second_callback(struct bpf_map *map, void *key, void *value)
+{
+ struct task_struct *task = bpf_get_current_task_btf();
+ struct bpf_dynptr dynptr;
+ struct file *file;
+ int ret;
+
+ file = bpf_get_task_exe_file(task);
+ if (!file) {
+ err = 5;
+ return 0;
+ }
+
+ /* freader_fetch() converts __kernel_read()'s short read at EOF to -EIO. */
+ ret = bpf_dynptr_from_file(file, 0, &dynptr);
+ if (!ret)
+ ret = bpf_dynptr_read(tmp_buf, 1, &dynptr, beyond_eof_offset, 0);
+ bpf_dynptr_file_discard(&dynptr);
+ bpf_put_file(file);
+ if (ret != -EIO) {
+ err = 6;
+ return 0;
+ }
+
+ run_success = 1;
+ return 0;
+}
+
static int verify_dynptr_read(struct bpf_dynptr *ptr, u32 off, char *user_buf, u32 len)
{
int i;
diff --git a/tools/testing/selftests/bpf/progs/verifier_align.c b/tools/testing/selftests/bpf/progs/verifier_align.c
index 3e52686515ca..0083ef8b8ba7 100644
--- a/tools/testing/selftests/bpf/progs/verifier_align.c
+++ b/tools/testing/selftests/bpf/progs/verifier_align.c
@@ -284,7 +284,7 @@ __msg("26: {{.*}} R5=pkt(r=8,imm=14)")
*/
__msg("28: {{.*}} R4={{[^)]*}}var_off=(0x2; 0x7fc){{.*}} R5={{[^)]*}}var_off=(0x2; 0x7fc)")
/* Constant is added to R5 again, setting reg->off to 18. */
-__msg("29: {{.*}} R5=pkt(id=3,{{[^)]*}}var_off=(0x2; 0x7fc)")
+__msg("29: {{.*}} R5=pkt(id=3,{{[^)]*}}var_off=(0x2; 0xffc)")
/* And once more we add a variable; resulting {{[^)]*}}var_off
* is still (4n), fixed offset is not changed.
* Also, we create a new reg->id.
@@ -359,7 +359,7 @@ __msg("7: {{.*}} R6={{[^)]*}}var_off=(0x0; 0x3fc)")
__msg("8: {{.*}} R6={{[^)]*}}var_off=(0x2; 0x7fc)")
/* Packet pointer has (4n+2) offset */
__msg("11: {{.*}} R5={{[^)]*}}var_off=(0x2; 0x7fc)")
-__msg("12: {{.*}} R4={{[^)]*}}var_off=(0x2; 0x7fc)")
+__msg("12: {{.*}} R4={{[^)]*}}var_off=(0x2; 0xffc)")
/* At the time the word size load is performed from R5,
* its total fixed offset is NET_IP_ALIGN + reg->off (0)
* which is 2. Then the variable offset is (4n+2), so
@@ -375,7 +375,7 @@ __msg("17: {{.*}} R6={{[^)]*}}var_off=(0x0; 0x3fc)")
* another (4n+2).
*/
__msg("19: {{.*}} R5={{[^)]*}}var_off=(0x2; 0xffc)")
-__msg("20: {{.*}} R4={{[^)]*}}var_off=(0x2; 0xffc)")
+__msg("20: {{.*}} R4={{[^)]*}}var_off=(0x2; 0x1ffc)")
/* At the time the word size load is performed from R5,
* its total fixed offset is NET_IP_ALIGN + reg->off (0)
* which is 2. Then the variable offset is (4n+2), so
diff --git a/tools/testing/selftests/bpf/progs/verifier_arena_large.c b/tools/testing/selftests/bpf/progs/verifier_arena_large.c
index 6ab8730d4878..dbdea14ca76f 100644
--- a/tools/testing/selftests/bpf/progs/verifier_arena_large.c
+++ b/tools/testing/selftests/bpf/progs/verifier_arena_large.c
@@ -10,6 +10,9 @@
#include <bpf_arena_common.h>
#define ARENA_SIZE (1ull << 32)
+#define LARGE_PAGE_CNT 1025
+
+volatile int zero = 0;
struct {
__uint(type, BPF_MAP_TYPE_ARENA);
@@ -284,6 +287,7 @@ int big_alloc2(void *ctx)
return 0;
}
+/* Nonsleepable because it binds to a socket program. */
SEC("socket")
__success __retval(0)
int big_alloc3(void *ctx)
@@ -291,24 +295,56 @@ int big_alloc3(void *ctx)
#if defined(__BPF_FEATURE_ADDR_SPACE_CAST)
char __arena *pages;
u64 i;
+ int err = 0;
- /*
- * Allocate 2051 pages in one go to check how kmalloc_nolock() handles large requests.
- * Since kmalloc_nolock() can allocate up to 1024 struct page * at a time, this call should
- * result in three batches: two batches of 1024 pages each, followed by a final batch of 3
- * pages.
- */
- pages = bpf_arena_alloc_pages(&arena, NULL, 2051, NUMA_NO_NODE, 0);
+ /* Verify that a nonsleepable allocation larger than 1024 pages succeeds. */
+ pages = bpf_arena_alloc_pages(&arena, NULL, LARGE_PAGE_CNT, NUMA_NO_NODE, 0);
if (!pages)
- return 0;
+ return 1;
+
+ for (i = zero; i < LARGE_PAGE_CNT && can_loop; i++)
+ pages[i * PAGE_SIZE] = 123;
+
+ for (i = zero; i < LARGE_PAGE_CNT && can_loop; i++) {
+ if (pages[i * PAGE_SIZE] == 123)
+ continue;
+ err = 2;
+ break;
+ }
+
+ bpf_arena_free_pages(&arena, pages, LARGE_PAGE_CNT);
+ return err;
+#endif
+ return 0;
+}
- bpf_for(i, 0, 2051)
- pages[i * PAGE_SIZE] = 123;
- bpf_for(i, 0, 2051)
- if (pages[i * PAGE_SIZE] != 123)
- return i;
+/* SYSCALL programs are always sleepable. */
+SEC("syscall")
+__success __retval(0)
+int big_alloc4(void *ctx)
+{
+#if defined(__BPF_FEATURE_ADDR_SPACE_CAST)
+ char __arena *pages;
+ u64 i;
+ int err = 0;
+
+ /* Verify that a sleepable allocation larger than 1024 pages succeeds. */
+ pages = bpf_arena_alloc_pages(&arena, NULL, LARGE_PAGE_CNT, NUMA_NO_NODE, 0);
+ if (!pages)
+ return 1;
+
+ for (i = zero; i < LARGE_PAGE_CNT && can_loop; i++)
+ pages[i * PAGE_SIZE] = 123;
+
+ for (i = zero; i < LARGE_PAGE_CNT && can_loop; i++) {
+ if (pages[i * PAGE_SIZE] == 123)
+ continue;
+ err = 2;
+ break;
+ }
- bpf_arena_free_pages(&arena, pages, 2051);
+ bpf_arena_free_pages(&arena, pages, LARGE_PAGE_CNT);
+ return err;
#endif
return 0;
}
diff --git a/tools/testing/selftests/bpf/progs/verifier_arena_scalar.c b/tools/testing/selftests/bpf/progs/verifier_arena_scalar.c
new file mode 100644
index 000000000000..bebc37f501b7
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/verifier_arena_scalar.c
@@ -0,0 +1,912 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <linux/bpf.h>
+#include <bpf/bpf_helpers.h>
+#include "../../../include/linux/filter.h"
+#include "bpf_misc.h"
+
+void *bpf_arena_alloc_pages(void *map, void *addr, __u32 page_cnt, int node_id,
+ __u64 flags) __ksym;
+
+#ifdef __TARGET_ARCH_arm64
+#define ARENA_VM_START (1ull << 32)
+#else
+#define ARENA_VM_START (1ull << 44)
+#endif
+
+struct {
+ __uint(type, BPF_MAP_TYPE_ARENA);
+ __uint(map_flags, BPF_F_MMAPABLE);
+ __uint(max_entries, 4);
+ __ulong(map_extra, ARENA_VM_START);
+} arena SEC(".maps");
+
+struct {
+ __uint(type, BPF_MAP_TYPE_HASH);
+ __uint(max_entries, 1);
+ __type(key, int);
+ __type(value, long long);
+} hash SEC(".maps");
+
+/* JITs that take BPF_F_ARENA_SCALAR */
+#define __arena_scalar __flag(BPF_F_ARENA_SCALAR) __arch_x86_64 __arch_arm64
+
+/* BTF FUNC records are not generated for kfuncs referenced from inline assembly */
+void __kfunc_btf_root(void)
+{
+ bpf_arena_alloc_pages(0, 0, 0, 0, 0);
+}
+
+/* Tests start with r6 = address of a new page as the user space sees it, a number */
+
+SEC("syscall")
+__arena_scalar
+__description("arena_scalar: load and store of every size")
+__success __retval(0)
+__load_if_JITed()
+__naked void ld_st_sizes(void)
+{
+ asm volatile (" \
+ r1 = %[arena] ll; \
+ r2 = 0; \
+ r3 = 1; \
+ r4 = -1; \
+ r5 = 0; \
+ call %[bpf_arena_alloc_pages]; \
+ r6 = r0; \
+ r0 = 100; \
+ if r6 == 0 goto 9f; \
+ r7 = r6; \
+ r1 = 0x1122334455667788 ll; \
+ *(u64 *)(r6 + 0) = r1; \
+ *(u32 *)(r6 + 8) = r1; \
+ *(u16 *)(r6 + 12) = r1; \
+ *(u8 *)(r6 + 14) = r1; \
+ *(u64 *)(r6 + 16) = 0x1234; \
+ *(u32 *)(r6 + 24) = 0x5678; \
+ *(u16 *)(r6 + 28) = 0x9a; \
+ *(u8 *)(r6 + 30) = 0xbc; \
+ r0 = 1; \
+ r2 = *(u64 *)(r6 + 0); \
+ if r2 != r1 goto 9f; \
+ r0 = 2; \
+ r2 = *(u32 *)(r6 + 8); \
+ if r2 != 0x55667788 goto 9f; \
+ r0 = 3; \
+ r2 = *(u16 *)(r6 + 12); \
+ if r2 != 0x7788 goto 9f; \
+ r0 = 4; \
+ r2 = *(u8 *)(r6 + 14); \
+ if r2 != 0x88 goto 9f; \
+ r0 = 5; \
+ r2 = *(u64 *)(r6 + 16); \
+ if r2 != 0x1234 goto 9f; \
+ r0 = 6; \
+ r2 = *(u32 *)(r6 + 24); \
+ if r2 != 0x5678 goto 9f; \
+ r0 = 7; \
+ r2 = *(u16 *)(r6 + 28); \
+ if r2 != 0x9a goto 9f; \
+ r0 = 8; \
+ r2 = *(u8 *)(r6 + 30); \
+ if r2 != 0xbc goto 9f; \
+ /* the address is what it was */ \
+ r0 = 9; \
+ if r6 != r7 goto 9f; \
+ r0 = 10; \
+ r7 >>= 32; \
+ if r7 == 0 goto 9f; \
+ r0 = 0; \
+9: exit; \
+" :
+ : __imm_addr(arena),
+ __imm(bpf_arena_alloc_pages)
+ : __clobber_all);
+}
+
+SEC("syscall")
+__arena_scalar
+__description("arena_scalar: load into the register that holds the address")
+__success __retval(0)
+__load_if_JITed()
+__naked void ld_into_base(void)
+{
+ asm volatile (" \
+ r1 = %[arena] ll; \
+ r2 = 0; \
+ r3 = 1; \
+ r4 = -1; \
+ r5 = 0; \
+ call %[bpf_arena_alloc_pages]; \
+ r6 = r0; \
+ r0 = 100; \
+ if r6 == 0 goto 9f; \
+ r1 = 77; \
+ *(u64 *)(r6 + 0) = r1; \
+ r6 = *(u64 *)(r6 + 0); \
+ r0 = 1; \
+ if r6 != 77 goto 9f; \
+ r0 = 0; \
+9: exit; \
+" :
+ : __imm_addr(arena),
+ __imm(bpf_arena_alloc_pages)
+ : __clobber_all);
+}
+
+SEC("syscall")
+__arena_scalar
+__description("arena_scalar: sign extending load")
+__success __retval(0)
+__load_if_JITed()
+__naked void ldsx(void)
+{
+ asm volatile (" \
+ r1 = %[arena] ll; \
+ r2 = 0; \
+ r3 = 1; \
+ r4 = -1; \
+ r5 = 0; \
+ call %[bpf_arena_alloc_pages]; \
+ r6 = r0; \
+ r0 = 100; \
+ if r6 == 0 goto 9f; \
+ r7 = r6; \
+ *(u64 *)(r6 + 0) = 0x80; \
+ .8byte %[ldsx_insn]; /* r2 = *(s8 *)(r6 + 0) */ \
+ r0 = 1; \
+ if r2 != -128 goto 9f; \
+ r0 = 2; \
+ if r6 != r7 goto 9f; \
+ r0 = 0; \
+9: exit; \
+" :
+ : __imm_addr(arena),
+ __imm(bpf_arena_alloc_pages),
+ __imm_insn(ldsx_insn, BPF_RAW_INSN(BPF_LDX | BPF_MEMSX | BPF_B,
+ BPF_REG_2, BPF_REG_6, 0, 0))
+ : __clobber_all);
+}
+
+SEC("syscall")
+__arena_scalar
+__description("arena_scalar: address loaded from arena")
+__success __retval(0)
+__load_if_JITed()
+__naked void ptr_chase(void)
+{
+ asm volatile (" \
+ r1 = %[arena] ll; \
+ r2 = 0; \
+ r3 = 1; \
+ r4 = -1; \
+ r5 = 0; \
+ call %[bpf_arena_alloc_pages]; \
+ r6 = r0; \
+ r0 = 100; \
+ if r6 == 0 goto 9f; \
+ /* page[0] = &page[64]; page[64] = 5; */ \
+ r1 = r6; \
+ r1 += 64; \
+ *(u64 *)(r6 + 0) = r1; \
+ *(u64 *)(r1 + 0) = 5; \
+ r2 = *(u64 *)(r6 + 0); \
+ r0 = 1; \
+ if r2 != r1 goto 9f; \
+ r3 = *(u64 *)(r2 + 0); \
+ r0 = 2; \
+ if r3 != 5 goto 9f; \
+ r0 = 0; \
+9: exit; \
+" :
+ : __imm_addr(arena),
+ __imm(bpf_arena_alloc_pages)
+ : __clobber_all);
+}
+
+SEC("syscall")
+__arena_scalar
+__description("arena_scalar: atomics")
+__success __retval(0)
+__load_if_JITed()
+__naked void atomics(void)
+{
+ asm volatile (" \
+ r1 = %[arena] ll; \
+ r2 = 0; \
+ r3 = 1; \
+ r4 = -1; \
+ r5 = 0; \
+ call %[bpf_arena_alloc_pages]; \
+ r6 = r0; \
+ r0 = 100; \
+ if r6 == 0 goto 9f; \
+ r7 = r6; \
+ *(u64 *)(r6 + 8) = 1; \
+ r1 = 2; \
+ lock *(u64 *)(r6 + 8) += r1; \
+ r1 = 4; \
+ .8byte %[fetch_add_insn]; /* r1 = atomic_fetch_add((u64 *)(r6 + 8), r1) */ \
+ r0 = 1; \
+ if r1 != 3 goto 9f; \
+ r1 = 8; \
+ .8byte %[xchg_insn]; /* r1 = xchg_64(r6 + 8, r1) */ \
+ r0 = 2; \
+ if r1 != 7 goto 9f; \
+ r0 = 8; \
+ r1 = 16; \
+ .8byte %[cmpxchg_insn]; /* r0 = cmpxchg_64(r6 + 8, r0, r1) */ \
+ r2 = r0; \
+ r0 = 3; \
+ if r2 != 8 goto 9f; \
+ r2 = *(u64 *)(r6 + 8); \
+ r0 = 4; \
+ if r2 != 16 goto 9f; \
+ r0 = 5; \
+ if r6 != r7 goto 9f; \
+ r0 = 0; \
+9: exit; \
+" :
+ : __imm_addr(arena),
+ __imm(bpf_arena_alloc_pages),
+ __imm_insn(fetch_add_insn, BPF_ATOMIC_OP(BPF_DW, BPF_ADD | BPF_FETCH,
+ BPF_REG_6, BPF_REG_1, 8)),
+ __imm_insn(xchg_insn, BPF_ATOMIC_OP(BPF_DW, BPF_XCHG, BPF_REG_6, BPF_REG_1, 8)),
+ __imm_insn(cmpxchg_insn, BPF_ATOMIC_OP(BPF_DW, BPF_CMPXCHG, BPF_REG_6, BPF_REG_1, 8))
+ : __clobber_all);
+}
+
+SEC("syscall")
+__arena_scalar
+__description("arena_scalar: cmpxchg through r0")
+__success __retval(0)
+__load_if_JITed()
+__naked void cmpxchg_r0(void)
+{
+ asm volatile (" \
+ r1 = %[arena] ll; \
+ r2 = 0; \
+ r3 = 1; \
+ r4 = -1; \
+ r5 = 0; \
+ call %[bpf_arena_alloc_pages]; \
+ r6 = r0; \
+ r0 = 100; \
+ if r6 == 0 goto 9f; \
+ /* The address is in r0. The value at the address is not equal to it. */ \
+ *(u64 *)(r6 + 0) = 3; \
+ r0 = r6; \
+ r1 = 5; \
+ .8byte %[cmpxchg_insn]; /* r0 = cmpxchg_64(r0 + 0, r0, r1) */ \
+ r2 = r0; \
+ r0 = 1; \
+ if r2 != 3 goto 9f; \
+ r2 = *(u64 *)(r6 + 0); \
+ r0 = 2; \
+ if r2 != 3 goto 9f; \
+ r0 = 0; \
+9: exit; \
+" :
+ : __imm_addr(arena),
+ __imm(bpf_arena_alloc_pages),
+ __imm_insn(cmpxchg_insn, BPF_ATOMIC_OP(BPF_DW, BPF_CMPXCHG, BPF_REG_0, BPF_REG_1, 0))
+ : __clobber_all);
+}
+
+SEC("syscall")
+__arena_scalar
+__description("arena_scalar: xchg into the register that holds the address")
+__success __retval(0)
+__load_if_JITed()
+__naked void xchg_into_base(void)
+{
+ asm volatile (" \
+ r1 = %[arena] ll; \
+ r2 = 0; \
+ r3 = 1; \
+ r4 = -1; \
+ r5 = 0; \
+ call %[bpf_arena_alloc_pages]; \
+ r6 = r0; \
+ r0 = 100; \
+ if r6 == 0 goto 9f; \
+ *(u64 *)(r6 + 0) = 3; \
+ r1 = r6; \
+ .8byte %[xchg_insn]; /* r1 = xchg_64(r1 + 0, r1) */ \
+ r0 = 1; \
+ if r1 != 3 goto 9f; \
+ r2 = *(u64 *)(r6 + 0); \
+ r0 = 2; \
+ if r2 != r6 goto 9f; \
+ r0 = 0; \
+9: exit; \
+" :
+ : __imm_addr(arena),
+ __imm(bpf_arena_alloc_pages),
+ __imm_insn(xchg_insn, BPF_ATOMIC_OP(BPF_DW, BPF_XCHG, BPF_REG_1, BPF_REG_1, 0))
+ : __clobber_all);
+}
+
+SEC("syscall")
+__arena_scalar
+__description("arena_scalar: xchg into the register that holds a pointer to stack")
+__failure __msg("misaligned access off (0x0; 0xffffffffffffffff)+0 size 8")
+__naked void xchg_into_stack_ptr(void)
+{
+ asm volatile (" \
+ r1 = %[arena] ll; \
+ r1 = 0; \
+ *(u64 *)(r10 - 8) = r1; \
+ r1 = r10; \
+ r1 += -8; \
+ .8byte %[xchg_insn]; /* r1 = xchg_64(r1 + 0, r1) */ \
+ r0 = 0; \
+ exit; \
+" :
+ : __imm_addr(arena),
+ __imm_insn(xchg_insn, BPF_ATOMIC_OP(BPF_DW, BPF_XCHG, BPF_REG_1, BPF_REG_1, 0))
+ : __clobber_all);
+}
+
+#ifdef CAN_USE_LOAD_ACQ_STORE_REL
+
+SEC("syscall")
+__arena_scalar
+__description("arena_scalar: load-acquire and store-release")
+__success __retval(0)
+__load_if_JITed()
+__naked void load_acq_store_rel(void)
+{
+ asm volatile (" \
+ r1 = %[arena] ll; \
+ r2 = 0; \
+ r3 = 1; \
+ r4 = -1; \
+ r5 = 0; \
+ call %[bpf_arena_alloc_pages]; \
+ r6 = r0; \
+ r0 = 100; \
+ if r6 == 0 goto 9f; \
+ r7 = r6; \
+ r1 = 0x1234; \
+ .8byte %[store_release_insn]; /* store_release((u64 *)(r6 + 8), r1) */ \
+ .8byte %[load_acquire_insn]; /* r2 = load_acquire((u64 *)(r6 + 8)) */ \
+ r0 = 1; \
+ if r2 != 0x1234 goto 9f; \
+ .8byte %[load_acquire8_insn]; /* w2 = load_acquire((u8 *)(r6 + 8)) */ \
+ r0 = 2; \
+ if r2 != 0x34 goto 9f; \
+ r0 = 3; \
+ if r6 != r7 goto 9f; \
+ r0 = 0; \
+9: exit; \
+" :
+ : __imm_addr(arena),
+ __imm(bpf_arena_alloc_pages),
+ __imm_insn(store_release_insn,
+ BPF_ATOMIC_OP(BPF_DW, BPF_STORE_REL, BPF_REG_6, BPF_REG_1, 8)),
+ __imm_insn(load_acquire_insn,
+ BPF_ATOMIC_OP(BPF_DW, BPF_LOAD_ACQ, BPF_REG_2, BPF_REG_6, 8)),
+ __imm_insn(load_acquire8_insn,
+ BPF_ATOMIC_OP(BPF_B, BPF_LOAD_ACQ, BPF_REG_2, BPF_REG_6, 8))
+ : __clobber_all);
+}
+
+#endif /* CAN_USE_LOAD_ACQ_STORE_REL */
+
+SEC("syscall")
+__arena_scalar
+__description("arena_scalar: number that is not an address in arena")
+__success __retval(0)
+__load_if_JITed()
+__naked void not_in_arena(void)
+{
+ asm volatile (" \
+ r1 = %[arena] ll; \
+ /* nothing is allocated: all loads read 0, stores are dropped */ \
+ r6 = 0xdeadbeef00000000 ll; \
+ r0 = 1; \
+ r2 = *(u64 *)(r6 + 0); \
+ if r2 != 0 goto 9f; \
+ r0 = 2; \
+ r2 = *(u8 *)(r6 - 32768); \
+ if r2 != 0 goto 9f; \
+ r6 = 0x12345678ffffffff ll; \
+ r0 = 3; \
+ r2 = *(u64 *)(r6 + 32760); \
+ if r2 != 0 goto 9f; \
+ *(u64 *)(r6 + 32760) = 1; \
+ *(u8 *)(r6 + 32767) = r2; \
+ r1 = 1; \
+ lock *(u64 *)(r6 + 32760) += r1; \
+ r0 = 0; \
+9: exit; \
+" :
+ : __imm_addr(arena)
+ : __clobber_all);
+}
+
+SEC("syscall")
+__arena_scalar
+__description("arena_scalar: store through the address of the stack as a number")
+__success __retval(0)
+__load_if_JITed()
+__naked void stack_addr_as_number(void)
+{
+ asm volatile (" \
+ r1 = %[arena] ll; \
+ *(u64 *)(r10 - 8) = 5; \
+ r6 = r10; \
+ r6 |= 0; \
+ /* r6 is a number now. The store goes to arena, not to the stack. */ \
+ *(u64 *)(r6 - 8) = 7; \
+ r1 = 9; \
+ *(u64 *)(r6 - 8) = r1; \
+ lock *(u64 *)(r6 - 8) += r1; \
+ r2 = *(u64 *)(r10 - 8); \
+ r0 = 1; \
+ if r2 != 5 goto 9f; \
+ r0 = 0; \
+9: exit; \
+" :
+ : __imm_addr(arena)
+ : __clobber_all);
+}
+
+SEC("syscall")
+__arena_scalar
+__description("arena_scalar: 64-bit math on the address stays 64-bit")
+__success __retval(0)
+__load_if_JITed()
+__naked void alu64_after_access(void)
+{
+ asm volatile (" \
+ r1 = %[arena] ll; \
+ r2 = 0; \
+ r3 = 1; \
+ r4 = -1; \
+ r5 = 0; \
+ call %[bpf_arena_alloc_pages]; \
+ r6 = r0; \
+ r0 = 100; \
+ if r6 == 0 goto 9f; \
+ r2 = *(u64 *)(r6 + 0); \
+ r7 = r6; \
+ r7 += 8; \
+ r7 -= r6; \
+ r0 = 1; \
+ if r7 != 8 goto 9f; \
+ r7 = r6; \
+ r7 >>= 32; \
+ r0 = 2; \
+ if r7 == 0 goto 9f; \
+ r0 = 0; \
+9: exit; \
+" :
+ : __imm_addr(arena),
+ __imm(bpf_arena_alloc_pages)
+ : __clobber_all);
+}
+
+SEC("syscall")
+__arena_scalar
+__description("arena_scalar: st of an immediate changes no register")
+__success __retval(0)
+__load_if_JITed()
+__naked void st_keeps_regs(void)
+{
+ asm volatile (" \
+ r1 = %[arena] ll; \
+ r2 = 0; \
+ r3 = 1; \
+ r4 = -1; \
+ r5 = 0; \
+ call %[bpf_arena_alloc_pages]; \
+ r6 = r0; \
+ r0 = 100; \
+ if r6 == 0 goto 9f; \
+ r0 = r6; \
+ r1 = 0x1111; \
+ r2 = 0x2222; \
+ *(u64 *)(r0 + 0) = 5; \
+ r3 = r0; \
+ r0 = 1; \
+ if r3 != r6 goto 9f; \
+ r0 = 2; \
+ if r1 != 0x1111 goto 9f; \
+ r0 = 3; \
+ if r2 != 0x2222 goto 9f; \
+ r0 = 0x3333; \
+ r1 = r6; \
+ *(u32 *)(r1 + 8) = -7; \
+ r3 = r0; \
+ r0 = 4; \
+ if r3 != 0x3333 goto 9f; \
+ r0 = 5; \
+ if r1 != r6 goto 9f; \
+ r0 = 6; \
+ r3 = *(u64 *)(r6 + 0); \
+ if r3 != 5 goto 9f; \
+ r0 = 7; \
+ r3 = *(u64 *)(r6 + 8); \
+ r4 = 0xfffffff9 ll; \
+ if r3 != r4 goto 9f; \
+ r0 = 0; \
+9: exit; \
+" :
+ : __imm_addr(arena),
+ __imm(bpf_arena_alloc_pages)
+ : __clobber_all);
+}
+
+SEC("syscall")
+__arena_scalar
+__description("arena_scalar: access through a number with garbage in the upper half")
+__success __retval(0)
+__load_if_JITed()
+__naked void st_value_garbage(void)
+{
+ asm volatile (" \
+ r1 = %[arena] ll; \
+ r2 = 0; \
+ r3 = 1; \
+ r4 = -1; \
+ r5 = 0; \
+ call %[bpf_arena_alloc_pages]; \
+ r6 = r0; \
+ r0 = 100; \
+ if r6 == 0 goto 9f; \
+ r1 = 0xdeadbeef00001000 ll; \
+ *(u64 *)(r6 + 2048) = r1; \
+ r7 = *(u64 *)(r6 + 2048); \
+ r8 = *(u64 *)(r6 + 2048); \
+ r1 = 3; \
+ *(u64 *)(r8 + 0) = 1; \
+ r0 = 1; \
+ if r8 != r7 goto 9f; \
+ *(u8 *)(r8 + 1) = 1; \
+ r0 = 2; \
+ if r8 != r7 goto 9f; \
+ *(u32 *)(r8 + 4) = r1; \
+ r0 = 3; \
+ if r8 != r7 goto 9f; \
+ r2 = *(u16 *)(r8 + 2); \
+ r0 = 4; \
+ if r8 != r7 goto 9f; \
+ r0 = 5; \
+ if r1 != 3 goto 9f; \
+ r0 = 0; \
+9: exit; \
+" :
+ : __imm_addr(arena),
+ __imm(bpf_arena_alloc_pages)
+ : __clobber_all);
+}
+
+SEC("syscall")
+__arena_scalar
+__description("arena_scalar: access through a number without the upper half")
+__success __retval(0)
+__load_if_JITed()
+__naked void st_value_small(void)
+{
+ asm volatile (" \
+ r1 = %[arena] ll; \
+ r2 = 0; \
+ r3 = 1; \
+ r4 = -1; \
+ r5 = 0; \
+ call %[bpf_arena_alloc_pages]; \
+ r6 = r0; \
+ r0 = 100; \
+ if r6 == 0 goto 9f; \
+ r1 = 0x1000 ll; \
+ *(u64 *)(r6 + 2048) = r1; \
+ r7 = *(u64 *)(r6 + 2048); \
+ r8 = *(u64 *)(r6 + 2048); \
+ r1 = 3; \
+ *(u64 *)(r8 + 0) = 1; \
+ r0 = 1; \
+ if r8 != r7 goto 9f; \
+ *(u8 *)(r8 + 1) = 1; \
+ r0 = 2; \
+ if r8 != r7 goto 9f; \
+ *(u32 *)(r8 + 4) = r1; \
+ r0 = 3; \
+ if r8 != r7 goto 9f; \
+ r2 = *(u16 *)(r8 + 2); \
+ r0 = 4; \
+ if r8 != r7 goto 9f; \
+ r0 = 5; \
+ if r1 != 3 goto 9f; \
+ r0 = 0; \
+9: exit; \
+" :
+ : __imm_addr(arena),
+ __imm(bpf_arena_alloc_pages)
+ : __clobber_all);
+}
+
+SEC("syscall")
+__arena_scalar
+__description("arena_scalar: atomics through a number that is not an address in arena")
+__success __retval(0)
+__load_if_JITed()
+__naked void atomic_value(void)
+{
+ asm volatile (" \
+ r1 = %[arena] ll; \
+ r2 = 0; \
+ r3 = 1; \
+ r4 = -1; \
+ r5 = 0; \
+ call %[bpf_arena_alloc_pages]; \
+ r6 = r0; \
+ r0 = 100; \
+ if r6 == 0 goto 9f; \
+ r1 = 0xdeadbeef00001000 ll; \
+ *(u64 *)(r6 + 2048) = r1; \
+ r7 = *(u64 *)(r6 + 2048); \
+ r8 = *(u64 *)(r6 + 2048); \
+ r1 = 3; \
+ lock *(u64 *)(r8 + 0) += r1; \
+ r0 = 1; \
+ if r8 != r7 goto 9f; \
+ .8byte %[fetch_add_insn]; \
+ r0 = 2; \
+ if r8 != r7 goto 9f; \
+ .8byte %[xchg_insn]; \
+ r0 = 3; \
+ if r8 != r7 goto 9f; \
+ r0 = 0; \
+ .8byte %[cmpxchg_insn]; \
+ r0 = 4; \
+ if r8 != r7 goto 9f; \
+ r0 = 0; \
+9: exit; \
+" :
+ : __imm_addr(arena),
+ __imm(bpf_arena_alloc_pages),
+ __imm_insn(fetch_add_insn, BPF_ATOMIC_OP(BPF_DW, BPF_ADD | BPF_FETCH,
+ BPF_REG_8, BPF_REG_1, 0)),
+ __imm_insn(xchg_insn, BPF_ATOMIC_OP(BPF_DW, BPF_XCHG, BPF_REG_8, BPF_REG_1, 0)),
+ __imm_insn(cmpxchg_insn, BPF_ATOMIC_OP(BPF_DW, BPF_CMPXCHG, BPF_REG_8, BPF_REG_1, 0))
+ : __clobber_all);
+}
+
+SEC("syscall")
+__arena_scalar
+__description("arena_scalar: number and pointer to arena at the same insn")
+__success __retval(0)
+__load_if_JITed()
+__naked void mixed_number_arena(void)
+{
+ asm volatile (" \
+ r1 = %[arena] ll; \
+ r2 = 0; \
+ r3 = 1; \
+ r4 = -1; \
+ r5 = 0; \
+ call %[bpf_arena_alloc_pages]; \
+ r6 = r0; \
+ r0 = 100; \
+ if r6 == 0 goto 9f; \
+ *(u64 *)(r6 + 8) = 0x1234; \
+ /* a number that the verifier does not know, 0 at run time */ \
+ r7 = *(u64 *)(r6 + 16); \
+ r8 = r6; \
+ if r7 != 0 goto 1f; \
+ .8byte %[cast_kern_insn]; \
+1: *(u8 *)(r8 + 0) = 1; \
+ *(u8 *)(r8 + 1) = r7; \
+ r2 = *(u8 *)(r8 + 1); \
+ lock *(u64 *)(r8 + 24) += r7; \
+ r0 = 0; \
+ if r7 != 0 goto 9f; \
+ /* pointer to arena only: JIT adds all 64 bits of r8 to the base */ \
+ r0 = 2; \
+ r2 = *(u64 *)(r8 + 8); \
+ if r2 != 0x1234 goto 9f; \
+ r0 = 3; \
+ r2 = *(u8 *)(r6 + 0); \
+ if r2 != 1 goto 9f; \
+ r0 = 0; \
+9: exit; \
+" :
+ : __imm_addr(arena),
+ __imm(bpf_arena_alloc_pages),
+ __imm_insn(cast_kern_insn, BPF_RAW_INSN(BPF_ALU64 | BPF_MOV | BPF_X,
+ BPF_REG_8, BPF_REG_8, 1, 1))
+ : __clobber_all);
+}
+
+SEC("syscall")
+__description("arena_scalar: no flag, no access through a number")
+__failure __msg("R6 invalid mem access 'scalar'")
+__naked void no_flag(void)
+{
+ asm volatile (" \
+ r1 = %[arena] ll; \
+ r6 = 0x100000000000 ll; \
+ r0 = *(u64 *)(r6 + 0); \
+ exit; \
+" :
+ : __imm_addr(arena)
+ : __clobber_all);
+}
+
+SEC("syscall")
+__arena_scalar
+__description("arena_scalar: no arena, no access through a number")
+__failure __msg("R6 invalid mem access 'scalar'")
+__naked void no_arena(void)
+{
+ asm volatile (" \
+ r6 = 0x100000000000 ll; \
+ r0 = *(u64 *)(r6 + 0); \
+ exit; \
+" ::: __clobber_all);
+}
+
+SEC("syscall")
+__arena_scalar
+__description("arena_scalar: pointer that is NULL is an address in arena")
+__success __retval(0)
+__load_if_JITed()
+__naked void null_ptr(void)
+{
+ asm volatile (" \
+ r1 = %[arena] ll; \
+ r1 = 0; \
+ *(u32 *)(r10 - 4) = r1; \
+ r2 = r10; \
+ r2 += -4; \
+ r1 = %[hash] ll; \
+ call %[bpf_map_lookup_elem]; \
+ r1 = r0; \
+ r0 = 1; \
+ if r1 != 0 goto 9f; \
+ /* nothing is allocated: the load reads 0 */ \
+ r0 = *(u64 *)(r1 + 0); \
+9: exit; \
+" :
+ : __imm_addr(arena),
+ __imm_addr(hash),
+ __imm(bpf_map_lookup_elem)
+ : __clobber_all);
+}
+
+SEC("syscall")
+__arena_scalar
+__description("arena_scalar: pointer that is NULL with an offset is an address in arena")
+__success __retval(0)
+__load_if_JITed()
+__naked void null_ptr_off(void)
+{
+ asm volatile (" \
+ r1 = %[arena] ll; \
+ r1 = 0; \
+ *(u32 *)(r10 - 4) = r1; \
+ r2 = r10; \
+ r2 += -4; \
+ r1 = %[hash] ll; \
+ call %[bpf_map_lookup_elem]; \
+ r1 = r0; \
+ r0 = 1; \
+ if r1 != 0 goto 9f; \
+ r1 += 8; \
+ *(u64 *)(r1 + 0) = 5; \
+ r0 = *(u64 *)(r1 + 0); \
+9: exit; \
+" :
+ : __imm_addr(arena),
+ __imm_addr(hash),
+ __imm(bpf_map_lookup_elem)
+ : __clobber_all);
+}
+
+SEC("syscall")
+__arena_scalar
+__description("arena_scalar: number that is less than a page is an address in arena")
+__success __retval(0)
+__load_if_JITed()
+__naked void small_number(void)
+{
+ asm volatile (" \
+ r1 = %[arena] ll; \
+ call %[bpf_get_prandom_u32]; \
+ r1 = r0; \
+ r1 &= 0xfff; \
+ r0 = *(u64 *)(r1 + 0); \
+ exit; \
+" :
+ : __imm_addr(arena),
+ __imm(bpf_get_prandom_u32)
+ : __clobber_all);
+}
+
+SEC("syscall")
+__arena_scalar
+__description("arena_scalar: number is not a pointer for a helper")
+__failure __msg("R2 type=scalar expected=")
+__naked void helper_arg(void)
+{
+ asm volatile (" \
+ r1 = %[arena] ll; \
+ r2 = 0; \
+ r3 = 1; \
+ r4 = -1; \
+ r5 = 0; \
+ call %[bpf_arena_alloc_pages]; \
+ r6 = r0; \
+ r0 = 100; \
+ if r6 == 0 goto 9f; \
+ r1 = %[hash] ll; \
+ r2 = r6; \
+ call %[bpf_map_lookup_elem]; \
+ r0 = 0; \
+9: exit; \
+" :
+ : __imm_addr(arena),
+ __imm_addr(hash),
+ __imm(bpf_arena_alloc_pages),
+ __imm(bpf_map_lookup_elem)
+ : __clobber_all);
+}
+
+SEC("syscall")
+__arena_scalar
+__description("arena_scalar: number and pointer to stack at the same insn")
+__failure __msg("same insn cannot be used with different pointers")
+__load_if_JITed()
+__naked void mixed_number_stack(void)
+{
+ asm volatile (" \
+ r1 = %[arena] ll; \
+ r2 = 0; \
+ r3 = 1; \
+ r4 = -1; \
+ r5 = 0; \
+ call %[bpf_arena_alloc_pages]; \
+ r6 = r0; \
+ r0 = 100; \
+ if r6 == 0 goto 9f; \
+ *(u64 *)(r10 - 8) = 0; \
+ call %[bpf_get_prandom_u32]; \
+ if w0 != 0 goto 1f; \
+ r6 = r10; \
+ r6 += -8; \
+1: r0 = *(u64 *)(r6 + 0); \
+ r0 = 0; \
+9: exit; \
+" :
+ : __imm_addr(arena),
+ __imm(bpf_arena_alloc_pages),
+ __imm(bpf_get_prandom_u32)
+ : __clobber_all);
+}
+
+static int st_cb(__u64 idx, void *ctx)
+{
+ volatile long *p = *(volatile long **)ctx;
+
+ p[idx] = 7;
+ return 0;
+}
+
+SEC("syscall")
+__arena_scalar
+__description("arena_scalar: store through a number in a callback")
+__success __retval(0)
+__load_if_JITed()
+int st_in_callback(void *unused)
+{
+ volatile long *p = bpf_arena_alloc_pages(&arena, NULL, 1, -1, 0);
+
+ if (!p)
+ return 100;
+ bpf_loop(4, st_cb, &p, 0);
+ return p[0] + p[3] + p[4] - 14;
+}
+
+char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/verifier_direct_packet_access.c b/tools/testing/selftests/bpf/progs/verifier_direct_packet_access.c
index 915a9707298b..139ff019d87d 100644
--- a/tools/testing/selftests/bpf/progs/verifier_direct_packet_access.c
+++ b/tools/testing/selftests/bpf/progs/verifier_direct_packet_access.c
@@ -920,4 +920,182 @@ l1_%=: r0 = *(u8*)(r9 + 0); \
: __clobber_all);
}
+SEC("tc")
+__description("direct packet access: 8-aligned offset, check p + 8, load 8 bytes at p")
+__success __retval(0) __flag(BPF_F_ANY_ALIGNMENT)
+__naked void pkt_same_id_check_copy_load_base(void)
+{
+ asm volatile (" \
+ r2 = *(u32*)(r1 + %[__sk_buff_data]); \
+ r3 = *(u32*)(r1 + %[__sk_buff_data_end]); \
+ r4 = *(u32*)(r1 + %[__sk_buff_mark]); \
+ r4 &= 0x38; \
+ if r4 > 50 goto l0_%=; \
+ /* r4 is a multiple of 8, at most 48 */ \
+ r5 = r2; \
+ r5 += r4; \
+ r6 = r5; \
+ r6 += 8; \
+ if r6 > r3 goto l0_%=; \
+ /* [r5, r5 + 8) is in the packet */ \
+ r0 = *(u64*)(r5 + 0); \
+l0_%=: r0 = 0; \
+ exit; \
+" :
+ : __imm_const(__sk_buff_data, offsetof(struct __sk_buff, data)),
+ __imm_const(__sk_buff_data_end, offsetof(struct __sk_buff, data_end)),
+ __imm_const(__sk_buff_mark, offsetof(struct __sk_buff, mark))
+ : __clobber_all);
+}
+
+SEC("tc")
+__description("direct packet access: 8-aligned offset, check p, load past it via p + 8")
+__failure __msg("invalid access to packet, off=51 size=1")
+__naked void pkt_same_id_check_base_load_via_copy(void)
+{
+ asm volatile (" \
+ r2 = *(u32*)(r1 + %[__sk_buff_data]); \
+ r3 = *(u32*)(r1 + %[__sk_buff_data_end]); \
+ r4 = *(u32*)(r1 + %[__sk_buff_mark]); \
+ r4 &= 0x38; \
+ if r4 > 50 goto l0_%=; \
+ /* r4 is a multiple of 8, at most 48 */ \
+ r5 = r2; \
+ r5 += r4; \
+ r6 = r5; \
+ r6 += 8; \
+ if r5 > r3 goto l0_%=; \
+ /* r6 - 7 is r5 + 1 */ \
+ r0 = *(u8*)(r6 - 7); \
+l0_%=: r0 = 0; \
+ exit; \
+" :
+ : __imm_const(__sk_buff_data, offsetof(struct __sk_buff, data)),
+ __imm_const(__sk_buff_data_end, offsetof(struct __sk_buff, data_end)),
+ __imm_const(__sk_buff_mark, offsetof(struct __sk_buff, mark))
+ : __clobber_all);
+}
+
+SEC("tc")
+__description("direct packet access: 8-aligned offset, check p + 4, 4-byte load at p + 2")
+__failure __msg("invalid access to packet, off=52 size=4")
+__naked void pkt_same_id_check_base_4_load_via_copy(void)
+{
+ asm volatile (" \
+ r2 = *(u32*)(r1 + %[__sk_buff_data]); \
+ r3 = *(u32*)(r1 + %[__sk_buff_data_end]); \
+ r4 = *(u32*)(r1 + %[__sk_buff_mark]); \
+ r4 &= 0x38; \
+ if r4 > 50 goto l0_%=; \
+ /* r4 is a multiple of 8, at most 48 */ \
+ r5 = r2; \
+ r5 += r4; \
+ r6 = r5; \
+ r6 += 8; \
+ r7 = r5; \
+ r7 += 4; \
+ if r7 > r3 goto l0_%=; \
+ /* [r5, r5 + 4) is in the packet, [r5 + 2, r5 + 6) may not be */ \
+ r0 = *(u32*)(r6 - 6); \
+l0_%=: r0 = 0; \
+ exit; \
+" :
+ : __imm_const(__sk_buff_data, offsetof(struct __sk_buff, data)),
+ __imm_const(__sk_buff_data_end, offsetof(struct __sk_buff, data_end)),
+ __imm_const(__sk_buff_mark, offsetof(struct __sk_buff, mark))
+ : __clobber_all);
+}
+
+SEC("tc")
+__description("direct packet access: checked pointer minus non-negative unknown keeps range")
+__success __retval(0) __flag(BPF_F_ANY_ALIGNMENT)
+__naked void pkt_sub_unknown_keeps_range(void)
+{
+ asm volatile (" \
+ r2 = *(u32*)(r1 + %[__sk_buff_data]); \
+ r3 = *(u32*)(r1 + %[__sk_buff_data_end]); \
+ r4 = *(u32*)(r1 + %[__sk_buff_mark]); \
+ r4 &= 0x1f; \
+ r4 += 8; \
+ r5 = r2; \
+ r5 += 40; \
+ if r5 > r3 goto l0_%=; \
+ /* r5 is 8 to 39 bytes below the checked pointer */ \
+ r5 -= r4; \
+ r0 = *(u64*)(r5 + 0); \
+l0_%=: r0 = 0; \
+ exit; \
+" :
+ : __imm_const(__sk_buff_data, offsetof(struct __sk_buff, data)),
+ __imm_const(__sk_buff_data_end, offsetof(struct __sk_buff, data_end)),
+ __imm_const(__sk_buff_mark, offsetof(struct __sk_buff, mark))
+ : __clobber_all);
+}
+
+SEC("tc")
+__description("direct packet access: no pruning of a path whose checks prove fewer bytes")
+__failure __msg("invalid access to packet, off=255 size=8")
+__flag(BPF_F_ANY_ALIGNMENT) __flag(BPF_F_TEST_STATE_FREQ)
+__naked void pkt_same_id_pruning(void)
+{
+ asm volatile (" \
+ r2 = *(u32*)(r1 + %[__sk_buff_data]); \
+ r3 = *(u32*)(r1 + %[__sk_buff_data_end]); \
+ r4 = *(u32*)(r1 + %[__sk_buff_mark]); \
+ r0 = *(u32*)(r1 + %[__sk_buff_priority]); \
+ r4 &= 0xff; \
+ r5 = r2; \
+ r5 += r4; \
+ if r0 != 0 goto l1_%=; \
+ /* this path proves [r5, r5 + 8) */ \
+ r6 = r5; \
+ r6 += 8; \
+ if r6 > r3 goto l0_%=; \
+ goto l2_%=; \
+l1_%=: /* this path proves [r5, r5 + 7) */ \
+ if r5 > r3 goto l0_%=; \
+ r6 = r5; \
+ r6 += 7; \
+ if r6 > r3 goto l0_%=; \
+l2_%=: r0 = *(u64*)(r5 + 0); \
+l0_%=: r0 = 0; \
+ exit; \
+" :
+ : __imm_const(__sk_buff_data, offsetof(struct __sk_buff, data)),
+ __imm_const(__sk_buff_data_end, offsetof(struct __sk_buff, data_end)),
+ __imm_const(__sk_buff_mark, offsetof(struct __sk_buff, mark)),
+ __imm_const(__sk_buff_priority, offsetof(struct __sk_buff, priority))
+ : __clobber_all);
+}
+
+SEC("tc")
+__description("direct packet access: spilled copy of checked pointer, load past range")
+__failure __msg("invalid access to packet, off=262 size=1, R7(id={{[0-9]+}},off=262,r=262)")
+__naked void pkt_spilled_copy_gets_range(void)
+{
+ asm volatile (" \
+ r2 = *(u32*)(r1 + %[__sk_buff_data]); \
+ r3 = *(u32*)(r1 + %[__sk_buff_data_end]); \
+ r4 = *(u32*)(r1 + %[__sk_buff_mark]); \
+ r4 &= 0xff; \
+ r5 = r2; \
+ r5 += r4; \
+ *(u64*)(r10 - 8) = r5; \
+ /* proves only bytes before r5 */ \
+ if r5 > r3 goto l0_%=; \
+ r6 = r5; \
+ r6 += 7; \
+ if r6 > r3 goto l0_%=; \
+ /* [r5, r5 + 7) is in the packet */ \
+ r7 = *(u64*)(r10 - 8); \
+ r0 = *(u8*)(r7 + 7); \
+l0_%=: r0 = 0; \
+ exit; \
+" :
+ : __imm_const(__sk_buff_data, offsetof(struct __sk_buff, data)),
+ __imm_const(__sk_buff_data_end, offsetof(struct __sk_buff, data_end)),
+ __imm_const(__sk_buff_mark, offsetof(struct __sk_buff, mark))
+ : __clobber_all);
+}
+
char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/verifier_meta_access.c b/tools/testing/selftests/bpf/progs/verifier_meta_access.c
index 62235f032ffe..c87e0be4b2ff 100644
--- a/tools/testing/selftests/bpf/progs/verifier_meta_access.c
+++ b/tools/testing/selftests/bpf/progs/verifier_meta_access.c
@@ -281,4 +281,34 @@ l0_%=: r0 = 0; \
: __clobber_all);
}
+SEC("xdp")
+__description("meta access, 8-aligned offset, check p, load a byte at p + 1 via p + 8")
+__failure __msg("invalid access to packet, off=51 size=1")
+__naked void meta_access_check_base_load_via_copy(void)
+{
+ asm volatile (" \
+ r9 = r1; \
+ call %[bpf_get_prandom_u32]; \
+ r4 = r0; \
+ r4 &= 0x38; \
+ if r4 > 50 goto l0_%=; \
+ /* r4 is a multiple of 8, at most 48 */ \
+ r2 = *(u32*)(r9 + %[xdp_md_data_meta]); \
+ r3 = *(u32*)(r9 + %[xdp_md_data]); \
+ r5 = r2; \
+ r5 += r4; \
+ r6 = r5; \
+ r6 += 8; \
+ if r5 > r3 goto l0_%=; \
+ /* r6 - 7 is r5 + 1 */ \
+ r0 = *(u8*)(r6 - 7); \
+l0_%=: r0 = 0; \
+ exit; \
+" :
+ : __imm(bpf_get_prandom_u32),
+ __imm_const(xdp_md_data, offsetof(struct xdp_md, data)),
+ __imm_const(xdp_md_data_meta, offsetof(struct xdp_md, data_meta))
+ : __clobber_all);
+}
+
char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/verifier_value_illegal_alu.c b/tools/testing/selftests/bpf/progs/verifier_value_illegal_alu.c
index 4d8273c258d5..31663338866d 100644
--- a/tools/testing/selftests/bpf/progs/verifier_value_illegal_alu.c
+++ b/tools/testing/selftests/bpf/progs/verifier_value_illegal_alu.c
@@ -22,8 +22,8 @@ struct {
SEC("socket")
__description("map element value illegal alu op, 1")
-__failure __msg("R0 bitwise operator &= on pointer")
-__failure_unpriv
+__failure __msg("R0 invalid mem access 'scalar'")
+__failure_unpriv __msg_unpriv("R0 bitwise operator &= on pointer")
__naked void value_illegal_alu_op_1(void)
{
asm volatile (" \
@@ -70,8 +70,8 @@ l0_%=: exit; \
SEC("socket")
__description("map element value illegal alu op, 3")
-__failure __msg("R0 pointer arithmetic with /= operator")
-__failure_unpriv
+__failure __msg("R0 invalid mem access 'scalar'")
+__failure_unpriv __msg_unpriv("R0 pointer arithmetic with /= operator")
__naked void value_illegal_alu_op_3(void)
{
asm volatile (" \
@@ -165,6 +165,171 @@ __naked void map_ptr_illegal_alu_op(void)
: __clobber_all);
}
+SEC("socket")
+__description("tag in the low bit of a pointer, and, shift")
+__success __retval(0)
+__failure_unpriv __msg_unpriv("R1 bitwise operator &= on pointer")
+__naked void ptr_tag_and_shift(void)
+{
+ asm volatile (" \
+ r2 = r10; \
+ r2 += -8; \
+ r1 = 0; \
+ *(u64*)(r2 + 0) = r1; \
+ r1 = %[map_hash_48b] ll; \
+ call %[bpf_map_lookup_elem]; \
+ if r0 == 0 goto l0_%=; \
+ r1 = r0; \
+ r1 &= 1; \
+ r2 = r0; \
+ r2 >>= 1; \
+ r3 = r0; \
+ r3 |= 1; \
+ r3 ^= 1; \
+ r0 = *(u32*)(r0 + 0); \
+ r0 = 0; \
+l0_%=: exit; \
+" :
+ : __imm(bpf_map_lookup_elem),
+ __imm_addr(map_hash_48b)
+ : __clobber_all);
+}
+
+SEC("socket")
+__description("tag in the low bit of a pointer, CAP_BPF without CAP_PERFMON")
+__success __retval(0)
+__failure_unpriv __msg_unpriv("R1 bitwise operator &= on pointer")
+__caps_unpriv(CAP_BPF)
+__naked void ptr_tag_cap_bpf(void)
+{
+ asm volatile (" \
+ r2 = r10; \
+ r2 += -8; \
+ r1 = 0; \
+ *(u64*)(r2 + 0) = r1; \
+ r1 = %[map_hash_48b] ll; \
+ call %[bpf_map_lookup_elem]; \
+ if r0 == 0 goto l0_%=; \
+ r1 = r0; \
+ r1 &= 1; \
+ r0 = 0; \
+l0_%=: exit; \
+" :
+ : __imm(bpf_map_lookup_elem),
+ __imm_addr(map_hash_48b)
+ : __clobber_all);
+}
+
+SEC("socket")
+__description("number op= pointer")
+__success __retval(0)
+__failure_unpriv __msg_unpriv("R1 pointer arithmetic with *= operator")
+__naked void number_mul_ptr(void)
+{
+ asm volatile (" \
+ r2 = r10; \
+ r2 += -8; \
+ r1 = 0; \
+ *(u64*)(r2 + 0) = r1; \
+ r1 = %[map_hash_48b] ll; \
+ call %[bpf_map_lookup_elem]; \
+ if r0 == 0 goto l0_%=; \
+ r1 = 7; \
+ r1 *= r0; \
+ r0 = 0; \
+l0_%=: exit; \
+" :
+ : __imm(bpf_map_lookup_elem),
+ __imm_addr(map_hash_48b)
+ : __clobber_all);
+}
+
+SEC("socket")
+__description("pointer with the tag cleared is a number")
+__failure __msg("R0 invalid mem access 'scalar'")
+__failure_unpriv __msg_unpriv("R0 bitwise operator |= on pointer")
+__naked void ptr_tag_cleared_deref(void)
+{
+ asm volatile (" \
+ r2 = r10; \
+ r2 += -8; \
+ r1 = 0; \
+ *(u64*)(r2 + 0) = r1; \
+ r1 = %[map_hash_48b] ll; \
+ call %[bpf_map_lookup_elem]; \
+ if r0 == 0 goto l0_%=; \
+ r0 |= 1; \
+ r0 ^= 1; \
+ r0 = *(u32*)(r0 + 0); \
+l0_%=: r0 = 0; \
+ exit; \
+" :
+ : __imm(bpf_map_lookup_elem),
+ __imm_addr(map_hash_48b)
+ : __clobber_all);
+}
+
+SEC("socket")
+__description("shift of a pointer that may be NULL")
+__failure __msg("R0 pointer arithmetic on map_value_or_null prohibited, null-check it first")
+__failure_unpriv
+__naked void ptr_or_null_shift(void)
+{
+ asm volatile (" \
+ r2 = r10; \
+ r2 += -8; \
+ r1 = 0; \
+ *(u64*)(r2 + 0) = r1; \
+ r1 = %[map_hash_48b] ll; \
+ call %[bpf_map_lookup_elem]; \
+ r0 >>= 1; \
+ r0 = 0; \
+ exit; \
+" :
+ : __imm(bpf_map_lookup_elem),
+ __imm_addr(map_hash_48b)
+ : __clobber_all);
+}
+
+SEC("socket")
+__description("and of a pointer to map")
+__failure __msg("R0 pointer arithmetic on map_ptr prohibited")
+__failure_unpriv
+__naked void map_ptr_and(void)
+{
+ asm volatile (" \
+ r0 = %[map_hash_48b] ll; \
+ r0 &= 1; \
+ r0 = 0; \
+ exit; \
+" :
+ : __imm_addr(map_hash_48b)
+ : __clobber_all);
+}
+
+SEC("socket")
+__description("32-bit and of a pointer")
+__success __retval(0)
+__failure_unpriv __msg_unpriv("R0 32-bit pointer arithmetic prohibited")
+__naked void ptr_and32(void)
+{
+ asm volatile (" \
+ r2 = r10; \
+ r2 += -8; \
+ r1 = 0; \
+ *(u64*)(r2 + 0) = r1; \
+ r1 = %[map_hash_48b] ll; \
+ call %[bpf_map_lookup_elem]; \
+ if r0 == 0 goto l0_%=; \
+ w0 &= 1; \
+l0_%=: r0 = 0; \
+ exit; \
+" :
+ : __imm(bpf_map_lookup_elem),
+ __imm_addr(map_hash_48b)
+ : __clobber_all);
+}
+
SEC("flow_dissector")
__description("flow_keys illegal alu op with variable offset")
__failure __msg("R7 pointer arithmetic on flow_keys prohibited")
diff --git a/tools/testing/selftests/bpf/test_loader.c b/tools/testing/selftests/bpf/test_loader.c
index 25eeb1c1248b..a89890cd56d8 100644
--- a/tools/testing/selftests/bpf/test_loader.c
+++ b/tools/testing/selftests/bpf/test_loader.c
@@ -580,6 +580,8 @@ static int parse_test_spec(struct test_loader *tester,
update_flags(&spec->prog_flags, BPF_F_XDP_HAS_FRAGS, clear);
} else if (strcmp(val, "BPF_F_TEST_REG_INVARIANTS") == 0) {
update_flags(&spec->prog_flags, BPF_F_TEST_REG_INVARIANTS, clear);
+ } else if (strcmp(val, "BPF_F_ARENA_SCALAR") == 0) {
+ update_flags(&spec->prog_flags, BPF_F_ARENA_SCALAR, clear);
} else /* assume numeric value */ {
err = parse_int(val, &flags, "test prog flags");
if (err)
diff --git a/tools/testing/selftests/ftrace/Makefile b/tools/testing/selftests/ftrace/Makefile
index 7c12263f8260..3d41f9545683 100644
--- a/tools/testing/selftests/ftrace/Makefile
+++ b/tools/testing/selftests/ftrace/Makefile
@@ -2,8 +2,8 @@
all:
TEST_PROGS_EXTENDED := ftracetest
-TEST_PROGS := ftracetest-ktap
-TEST_FILES := test.d settings
+TEST_PROGS := ftracetest-ktap boottime-ktap
+TEST_FILES := test.d settings boottime
EXTRA_CLEAN := $(OUTPUT)/logs/*
TEST_GEN_FILES := poll
diff --git a/tools/testing/selftests/ftrace/boottime-ktap b/tools/testing/selftests/ftrace/boottime-ktap
new file mode 100755
index 000000000000..1fed68c8a944
--- /dev/null
+++ b/tools/testing/selftests/ftrace/boottime-ktap
@@ -0,0 +1,6 @@
+#!/bin/sh -e
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# boottime-ktap: Wrapper to integrate boottime tracing test framework with kselftest runner
+
+exec ./boottime/run_boottime_test.sh "$@"
diff --git a/tools/testing/selftests/ftrace/boottime/Makefile b/tools/testing/selftests/ftrace/boottime/Makefile
new file mode 100644
index 000000000000..fd0975350f10
--- /dev/null
+++ b/tools/testing/selftests/ftrace/boottime/Makefile
@@ -0,0 +1,10 @@
+# SPDX-License-Identifier: GPL-2.0
+
+all:
+
+run_tests:
+ @./run_boottime_test.sh
+
+clean:
+
+.PHONY: all run_tests clean
diff --git a/tools/testing/selftests/ftrace/boottime/README b/tools/testing/selftests/ftrace/boottime/README
new file mode 100644
index 000000000000..1eb0191d75d4
--- /dev/null
+++ b/tools/testing/selftests/ftrace/boottime/README
@@ -0,0 +1,74 @@
+Boot Tracing Test Suite
+=======================
+
+This directory contains an automated test framework to verify kernel boot
+tracing configurations (bootconfig, kernel command-line tracing options, and
+persistent ring buffers) during early boot.
+
+Overview
+--------
+The test runner (`run_boottime_test.sh`) constructs a lightweight initramfs with
+busybox and a tracefs checker script from `tests/`, attaches optional bootconfigs
+from `bootconfigs/` using the `bootconfig` tool, applies optional command-line
+parameters from `cmdlines/`, boots the target kernel image under QEMU, and reports
+results according to TAP version 13 format.
+
+Test Specification Format
+-------------------------
+For each test script `tests/<name>.sh`, the harness dynamically detects:
+- `bootconfigs/<name>.bconf` or `<name>.bconf`: Bootconfig configuration file.
+- `cmdlines/<name>.cmdline` or `# CMDLINE: <opts>` header in script: Kernel boot parameters.
+- `qemuopts/<name>.qemuopts` or `# QEMUOPTS: <opts>` header in script: Additional QEMU CLI arguments.
+- `# APPLETS: <applets>` header in script: Additional BusyBox applet symlinks to create in initramfs.
+- `# REBOOT: 1` header in script: Multi-boot reboot/crash test mode (omits `-no-reboot` and doubles timeout).
+
+Required Tools
+--------------
+- Host Architecture: x86 (x86_64/i686/i386/x86; tests skip on non-x86 hosts)
+- `qemu-system-x86_64` (or custom QEMU executable)
+- `busybox` (host binary at `/usr/bin/busybox` or custom path)
+- `bootconfig` (compiled from `tools/bootconfig/bootconfig` or in PATH)
+- `file` (standard file architecture identifier)
+- `cpio` (standard archiver)
+- `timeout` (standard coreutils execution timer)
+
+Required Kernel Configurations
+------------------------------
+The kernel binary under test must be compiled with:
+- `CONFIG_BOOT_CONFIG=y`
+- `CONFIG_BOOTTIME_TRACING=y`
+- `CONFIG_MAGIC_SYSRQ=y` (for guest reboot and poweroff)
+
+Additional feature-specific config options enable respective testcases:
+- `CONFIG_KPROBE_EVENTS=y` (for kprobe tests)
+- `CONFIG_SYNTH_EVENTS=y` (for synthetic event tests)
+- `CONFIG_EPROBE_EVENTS=y` (for eprobe tests)
+- `CONFIG_FPROBE_EVENTS=y` (for fprobe and tracepoint probe tests)
+- `CONFIG_FUNCTION_TRACER=y` (for tracer options)
+- `CONFIG_RESERVE_MEM=y` (for persistent ring buffer tests)
+
+Usage
+-----
+To run all test cases with a compiled kernel image:
+
+ $ ./run_boottime_test.sh -k /path/to/bzImage
+
+To run a specific test case (e.g. `01-kprobe`):
+
+ $ ./run_boottime_test.sh -k /path/to/bzImage -t 01-kprobe
+
+Customizing Tool Paths:
+
+ $ ./run_boottime_test.sh -b /path/to/bootconfig \
+ -k /path/to/bzImage \
+ -q /path/to/qemu-system-x86_64 \
+ -B /path/to/busybox
+
+Environment Variables
+---------------------
+Tool locations and kernel paths can also be set via environment variables:
+- `BOOTCONFIG`: Path to the `bootconfig` tool
+- `KERNEL`: Path to the `bzImage` kernel binary
+- `QEMU`: Path to the QEMU binary
+- `BUSYBOX`: Path to the `busybox` binary
+- `LOGDIR`: Directory path to preserve QEMU execution log files
diff --git a/tools/testing/selftests/ftrace/boottime/bootconfigs/01-kprobe.bconf b/tools/testing/selftests/ftrace/boottime/bootconfigs/01-kprobe.bconf
new file mode 100644
index 000000000000..13d30d1e51a6
--- /dev/null
+++ b/tools/testing/selftests/ftrace/boottime/bootconfigs/01-kprobe.bconf
@@ -0,0 +1,4 @@
+ftrace.event.kprobes.vfs_read {
+ probes = "vfs_read $arg1"
+ enable
+}
diff --git a/tools/testing/selftests/ftrace/boottime/bootconfigs/02-synth.bconf b/tools/testing/selftests/ftrace/boottime/bootconfigs/02-synth.bconf
new file mode 100644
index 000000000000..b1f37713f33c
--- /dev/null
+++ b/tools/testing/selftests/ftrace/boottime/bootconfigs/02-synth.bconf
@@ -0,0 +1,4 @@
+ftrace.event.synthetic.boot_lat {
+ fields = "unsigned long id", "u64 delta"
+ enable
+}
diff --git a/tools/testing/selftests/ftrace/boottime/bootconfigs/03-eprobe.bconf b/tools/testing/selftests/ftrace/boottime/bootconfigs/03-eprobe.bconf
new file mode 100644
index 000000000000..c1a8138e605d
--- /dev/null
+++ b/tools/testing/selftests/ftrace/boottime/bootconfigs/03-eprobe.bconf
@@ -0,0 +1,4 @@
+ftrace.event.eprobes.ep_read {
+ probes = "sched/sched_switch"
+ enable
+}
diff --git a/tools/testing/selftests/ftrace/boottime/bootconfigs/04-fprobe.bconf b/tools/testing/selftests/ftrace/boottime/bootconfigs/04-fprobe.bconf
new file mode 100644
index 000000000000..82ae2ac0629f
--- /dev/null
+++ b/tools/testing/selftests/ftrace/boottime/bootconfigs/04-fprobe.bconf
@@ -0,0 +1,4 @@
+ftrace.event.fprobes.fp_read {
+ probes = "vfs_read"
+ enable
+}
diff --git a/tools/testing/selftests/ftrace/boottime/bootconfigs/05-tprobe.bconf b/tools/testing/selftests/ftrace/boottime/bootconfigs/05-tprobe.bconf
new file mode 100644
index 000000000000..02a2803c770a
--- /dev/null
+++ b/tools/testing/selftests/ftrace/boottime/bootconfigs/05-tprobe.bconf
@@ -0,0 +1,4 @@
+ftrace.event.tracepoints.tp_sched {
+ probes = "sched_switch"
+ enable
+}
diff --git a/tools/testing/selftests/ftrace/boottime/bootconfigs/06-instance.bconf b/tools/testing/selftests/ftrace/boottime/bootconfigs/06-instance.bconf
new file mode 100644
index 000000000000..3fdb6717afe2
--- /dev/null
+++ b/tools/testing/selftests/ftrace/boottime/bootconfigs/06-instance.bconf
@@ -0,0 +1,5 @@
+ftrace.instance.foo {
+ event.sched.sched_switch {
+ enable
+ }
+}
diff --git a/tools/testing/selftests/ftrace/boottime/cmdlines/cmdline-01-ftrace.cmdline b/tools/testing/selftests/ftrace/boottime/cmdlines/cmdline-01-ftrace.cmdline
new file mode 100644
index 000000000000..4f6fc54240c8
--- /dev/null
+++ b/tools/testing/selftests/ftrace/boottime/cmdlines/cmdline-01-ftrace.cmdline
@@ -0,0 +1 @@
+ftrace=function
diff --git a/tools/testing/selftests/ftrace/boottime/cmdlines/cmdline-02-trace-event.cmdline b/tools/testing/selftests/ftrace/boottime/cmdlines/cmdline-02-trace-event.cmdline
new file mode 100644
index 000000000000..66bcd29b14cb
--- /dev/null
+++ b/tools/testing/selftests/ftrace/boottime/cmdlines/cmdline-02-trace-event.cmdline
@@ -0,0 +1 @@
+trace_event=sched:sched_switch,kmem:kmalloc
diff --git a/tools/testing/selftests/ftrace/boottime/cmdlines/cmdline-03-trace-buf-size.cmdline b/tools/testing/selftests/ftrace/boottime/cmdlines/cmdline-03-trace-buf-size.cmdline
new file mode 100644
index 000000000000..e5d1c4a8ca89
--- /dev/null
+++ b/tools/testing/selftests/ftrace/boottime/cmdlines/cmdline-03-trace-buf-size.cmdline
@@ -0,0 +1 @@
+trace_buf_size=2048K
diff --git a/tools/testing/selftests/ftrace/boottime/cmdlines/cmdline-04-trace-options.cmdline b/tools/testing/selftests/ftrace/boottime/cmdlines/cmdline-04-trace-options.cmdline
new file mode 100644
index 000000000000..51d82f8e948d
--- /dev/null
+++ b/tools/testing/selftests/ftrace/boottime/cmdlines/cmdline-04-trace-options.cmdline
@@ -0,0 +1 @@
+trace_options=sym-addr,verbose
diff --git a/tools/testing/selftests/ftrace/boottime/cmdlines/cmdline-05-trace-clock.cmdline b/tools/testing/selftests/ftrace/boottime/cmdlines/cmdline-05-trace-clock.cmdline
new file mode 100644
index 000000000000..f72a4ac808a3
--- /dev/null
+++ b/tools/testing/selftests/ftrace/boottime/cmdlines/cmdline-05-trace-clock.cmdline
@@ -0,0 +1 @@
+trace_clock=global
diff --git a/tools/testing/selftests/ftrace/boottime/cmdlines/cmdline-06-trace-instance.cmdline b/tools/testing/selftests/ftrace/boottime/cmdlines/cmdline-06-trace-instance.cmdline
new file mode 100644
index 000000000000..9618e3e2b88b
--- /dev/null
+++ b/tools/testing/selftests/ftrace/boottime/cmdlines/cmdline-06-trace-instance.cmdline
@@ -0,0 +1 @@
+trace_instance=bar,sched:sched_switch
diff --git a/tools/testing/selftests/ftrace/boottime/cmdlines/persistent-01-reserve-mem.cmdline b/tools/testing/selftests/ftrace/boottime/cmdlines/persistent-01-reserve-mem.cmdline
new file mode 100644
index 000000000000..c69eeb27deb6
--- /dev/null
+++ b/tools/testing/selftests/ftrace/boottime/cmdlines/persistent-01-reserve-mem.cmdline
@@ -0,0 +1 @@
+reserve_mem=12M:32M:trace trace_instance=boot_map@trace
diff --git a/tools/testing/selftests/ftrace/boottime/cmdlines/persistent-02-backup-instance.cmdline b/tools/testing/selftests/ftrace/boottime/cmdlines/persistent-02-backup-instance.cmdline
new file mode 100644
index 000000000000..43217c986ca1
--- /dev/null
+++ b/tools/testing/selftests/ftrace/boottime/cmdlines/persistent-02-backup-instance.cmdline
@@ -0,0 +1 @@
+reserve_mem=12M:32M:trace trace_instance=boot_map@trace trace_instance=backup=boot_map
diff --git a/tools/testing/selftests/ftrace/boottime/run_boottime_test.sh b/tools/testing/selftests/ftrace/boottime/run_boottime_test.sh
new file mode 100755
index 000000000000..6e8593d5a2c5
--- /dev/null
+++ b/tools/testing/selftests/ftrace/boottime/run_boottime_test.sh
@@ -0,0 +1,403 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (C) 2026, Google LLC.
+#
+# Generic Boot Tracing Test Harness
+# Builds a lightweight initramfs, applies bootconfigs and kernel cmdline parameters,
+# boots QEMU, and verifies tracefs configuration using test scripts.
+
+set -e
+
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+if [ -d "$SCRIPT_DIR/boottime" ]; then
+ SCRIPT_DIR="$SCRIPT_DIR/boottime"
+fi
+KERNEL_SRC="$(cd "$SCRIPT_DIR/../../../../.." && pwd)"
+
+# Source KTAP helpers
+if [ -f "$SCRIPT_DIR/../../kselftest/ktap_helpers.sh" ]; then
+ KSELFTEST_DIR="$(cd "$SCRIPT_DIR/../../kselftest" && pwd)"
+elif [ -f "$SCRIPT_DIR/../kselftest/ktap_helpers.sh" ]; then
+ KSELFTEST_DIR="$(cd "$SCRIPT_DIR/../kselftest" && pwd)"
+else
+ echo "Error: ktap_helpers.sh not found relative to $SCRIPT_DIR" >&2
+ exit 1
+fi
+. "$KSELFTEST_DIR/ktap_helpers.sh"
+
+BOOTCONFIG="${BOOTCONFIG:-"$KERNEL_SRC/tools/bootconfig/bootconfig"}"
+KERNEL="${KERNEL:-"$KERNEL_SRC/arch/x86/boot/bzImage"}"
+QEMU="${QEMU:-"qemu-system-x86_64"}"
+BUSYBOX="${BUSYBOX:-"$(which busybox 2>/dev/null || echo "/usr/bin/busybox")"}"
+TIMEOUT="${TIMEOUT:-60s}"
+LOGDIR="${LOGDIR:-""}"
+TARGET_TEST="all"
+
+BUSYBOX_APPLETS=(
+ sh
+ cat
+ mount
+ umount
+ echo
+ sync
+ reboot
+ poweroff
+ cpio
+ grep
+ sleep
+ sed
+)
+
+usage() {
+ echo "Usage: $0 [options]"
+ echo "Options:"
+ echo " -b, --bootconfig PATH Path to bootconfig tool (default: $BOOTCONFIG)"
+ echo " -k, --kernel PATH Path to kernel image (default: $KERNEL)"
+ echo " -q, --qemu PATH Path to QEMU executable (default: $QEMU)"
+ echo " -B, --busybox PATH Path to busybox binary (default: $BUSYBOX)"
+ echo " -T, --timeout DURATION QEMU execution timeout (default: $TIMEOUT)"
+ echo " -l, --logdir DIR Directory to save QEMU log files"
+ echo " -t, --test NAME Run specific test (e.g. 01-kprobe) or 'all'"
+ echo " -h, --help Show this help message"
+ exit 1
+}
+
+while [ $# -gt 0 ]; do
+ case "$1" in
+ -b|--bootconfig)
+ BOOTCONFIG="$2"
+ shift 2
+ ;;
+ -k|--kernel)
+ KERNEL="$2"
+ shift 2
+ ;;
+ -q|--qemu)
+ QEMU="$2"
+ shift 2
+ ;;
+ -B|--busybox)
+ BUSYBOX="$2"
+ shift 2
+ ;;
+ -T|--timeout)
+ TIMEOUT="$2"
+ shift 2
+ ;;
+ -l|--logdir)
+ LOGDIR="$2"
+ shift 2
+ ;;
+ -t|--test)
+ TARGET_TEST="$2"
+ shift 2
+ ;;
+ -h|--help)
+ usage
+ ;;
+ *)
+ echo "Unknown option: $1"
+ usage
+ ;;
+ esac
+done
+
+# Check host architecture
+HOST_ARCH="$(uname -m)"
+case "$HOST_ARCH" in
+ x86_64|i686|i386|x86)
+ ;;
+ *)
+ if [ "$KERNEL" = "$KERNEL_SRC/arch/x86/boot/bzImage" ]; then
+ ktap_print_header
+ ktap_skip_all \
+ "Default boot tracing test is x86 only ($HOST_ARCH)"
+ exit 0
+ fi
+ ;;
+esac
+
+# Ensure bootconfig tool is built if Makefile exists
+if [ ! -x "$BOOTCONFIG" ]; then
+ if [ -f "$KERNEL_SRC/tools/bootconfig/Makefile" ]; then
+ make -C "$KERNEL_SRC/tools/bootconfig" > /dev/null 2>&1 || true
+ fi
+ if [ ! -x "$BOOTCONFIG" ]; then
+ BOOTCONFIG="$(which bootconfig 2>/dev/null || true)"
+ fi
+fi
+
+if [ ! -x "$BOOTCONFIG" ]; then
+ ktap_print_header
+ ktap_skip_all "bootconfig tool not found at $BOOTCONFIG"
+ exit 0
+fi
+
+if [ ! -f "$KERNEL" ]; then
+ ktap_print_header
+ ktap_skip_all "Kernel image not found at $KERNEL"
+ exit 0
+fi
+
+if ! command -v "$QEMU" >/dev/null 2>&1; then
+ ktap_print_header
+ ktap_skip_all "QEMU binary not found: $QEMU"
+ exit 0
+fi
+
+if [ ! -x "$BUSYBOX" ]; then
+ ktap_print_header
+ ktap_skip_all "busybox binary not found at $BUSYBOX"
+ exit 0
+fi
+
+# Verify file command existence and architecture compatibility
+if ! command -v file >/dev/null 2>&1; then
+ ktap_print_header
+ ktap_skip_all "file tool not found"
+ exit 0
+fi
+
+KERNEL_INFO="$(file -bL "$KERNEL" 2>/dev/null || true)"
+case "$KERNEL_INFO" in
+ *x86*)
+ ;;
+ *)
+ ktap_print_header
+ ktap_skip_all "Kernel architecture is not x86 ($KERNEL_INFO)"
+ exit 0
+ ;;
+esac
+
+BUSYBOX_INFO="$(file -bL "$BUSYBOX" 2>/dev/null || true)"
+case "$BUSYBOX_INFO" in
+ *x86-64*|*x86_64*|*80386*|*i386*)
+ ;;
+ *)
+ ktap_print_header
+ ktap_skip_all \
+ "busybox binary architecture is incompatible with x86 kernel ($BUSYBOX_INFO)"
+ exit 0
+ ;;
+esac
+
+if ! command -v cpio >/dev/null 2>&1; then
+ ktap_print_header
+ ktap_skip_all "cpio tool not found"
+ exit 0
+fi
+
+if ! command -v timeout >/dev/null 2>&1; then
+ ktap_print_header
+ ktap_skip_all "timeout tool not found"
+ exit 0
+fi
+
+double_timeout() {
+ local val="$1"
+ if [[ "$val" =~ ^([0-9]+)([a-z]*)$ ]]; then
+ local num="${BASH_REMATCH[1]}"
+ local unit="${BASH_REMATCH[2]}"
+ echo "$((num * 2))$unit"
+ else
+ echo "120s"
+ fi
+}
+
+run_single_test() {
+ local test_script="$1"
+ local name
+ local dir
+ name="$(basename "$test_script" .sh)"
+ dir="$(dirname "$test_script")"
+
+ local bconf_file=""
+ if [ -f "$SCRIPT_DIR/bootconfigs/$name.bconf" ]; then
+ bconf_file="$SCRIPT_DIR/bootconfigs/$name.bconf"
+ elif [ -f "$dir/$name.bconf" ]; then
+ bconf_file="$dir/$name.bconf"
+ fi
+
+ local test_cmdline=""
+ if [ -f "$SCRIPT_DIR/cmdlines/$name.cmdline" ]; then
+ test_cmdline="$(cat "$SCRIPT_DIR/cmdlines/$name.cmdline")"
+ elif [ -f "$dir/$name.cmdline" ]; then
+ test_cmdline="$(cat "$dir/$name.cmdline")"
+ fi
+
+ local inline_cmdline
+ inline_cmdline="$(grep -E '^# *CMDLINE:' "$test_script" | sed 's/^# *CMDLINE://' | xargs || true)"
+ if [ -n "$inline_cmdline" ]; then
+ test_cmdline="$test_cmdline $inline_cmdline"
+ fi
+
+ local test_qemuopts=""
+ if [ -f "$SCRIPT_DIR/qemuopts/$name.qemuopts" ]; then
+ test_qemuopts="$(cat "$SCRIPT_DIR/qemuopts/$name.qemuopts")"
+ elif [ -f "$dir/$name.qemuopts" ]; then
+ test_qemuopts="$(cat "$dir/$name.qemuopts")"
+ fi
+
+ local inline_qemuopts
+ inline_qemuopts="$(grep -E '^# *QEMUOPTS:' "$test_script" | sed 's/^# *QEMUOPTS://' | xargs || true)"
+ if [ -n "$inline_qemuopts" ]; then
+ test_qemuopts="$test_qemuopts $inline_qemuopts"
+ fi
+
+ local test_timeout="$TIMEOUT"
+ local inline_timeout
+ inline_timeout="$(grep -E '^# *TIMEOUT:' "$test_script" | sed 's/^# *TIMEOUT://' | xargs || true)"
+ if [ -n "$inline_timeout" ]; then
+ test_timeout="$inline_timeout"
+ fi
+
+ local is_reboot=0
+ if grep -q -E '^# *REBOOT: *1' "$test_script" || [ -f "$dir/$name.reboot" ]; then
+ is_reboot=1
+ fi
+
+ local effective_timeout="$test_timeout"
+ if [ "$is_reboot" -eq 1 ]; then
+ effective_timeout="$(double_timeout "$test_timeout")"
+ fi
+
+ local workdir
+ workdir="$(mktemp -d)"
+ if [ -z "$workdir" ] || [ ! -d "$workdir" ]; then
+ ktap_test_fail "$name (failed to create temporary directory)"
+ return 0
+ fi
+ trap 'rm -rf "$workdir"' EXIT
+
+ local test_applets=("${BUSYBOX_APPLETS[@]}")
+ local inline_applets
+ inline_applets="$(grep -E '^# *APPLETS:' "$test_script" | sed 's/^# *APPLETS://' | xargs || true)"
+ if [ -n "$inline_applets" ]; then
+ read -r -a extra_applets <<< "$inline_applets" || true
+ test_applets+=("${extra_applets[@]}")
+ fi
+
+ local rootfs="$workdir/rootfs"
+ mkdir -p "$rootfs"/{bin,sbin,etc,proc,sys,dev,tmp}
+
+ cp -L "$BUSYBOX" "$rootfs/bin/busybox"
+ chmod +x "$rootfs/bin/busybox"
+ (cd "$rootfs/bin" && for applet in "${test_applets[@]}"; do ln -sf busybox "$applet"; done)
+
+ if command -v ldd >/dev/null 2>&1; then
+ for lib in $(ldd "$BUSYBOX" 2>/dev/null | grep -o '/[^ ]*' || true); do
+ if [ -f "$lib" ]; then
+ mkdir -p "$rootfs$(dirname "$lib")"
+ cp -L "$lib" "$rootfs$lib" 2>/dev/null || true
+ fi
+ done
+ fi
+
+ cat << 'EOF' > "$rootfs/init"
+#!/bin/sh
+mount -t proc proc /proc 2>/dev/null
+mount -t sysfs sys /sys 2>/dev/null
+mount -t tracefs nodev /sys/kernel/tracing 2>/dev/null
+
+/bin/check_test.sh
+RET=$?
+
+if [ $RET -eq 0 ]; then
+ echo "TEST RESULT: PASS"
+else
+ echo "TEST RESULT: FAIL"
+fi
+sync
+echo o > /proc/sysrq-trigger 2>/dev/null || true
+poweroff -f 2>/dev/null || reboot -f 2>/dev/null || true
+while true; do sleep 100 2>/dev/null || break; done
+EOF
+ chmod +x "$rootfs/init"
+
+ cp "$test_script" "$rootfs/bin/check_test.sh"
+ chmod +x "$rootfs/bin/check_test.sh"
+
+ local initramfs="$workdir/initramfs.cpio"
+ (cd "$rootfs" && find . | cpio -o -H newc --quiet) > "$initramfs"
+
+ if [ -n "$bconf_file" ] && [ -f "$bconf_file" ]; then
+ "$BOOTCONFIG" -a "$bconf_file" "$initramfs" > /dev/null
+ fi
+
+
+ local logfile
+ local base_cmdline="bootconfig console=tty0 console=ttyS0 panic=-1"
+ if [ -n "$test_cmdline" ]; then
+ base_cmdline="$base_cmdline $test_cmdline"
+ fi
+
+ if [ -n "$LOGDIR" ]; then
+ mkdir -p "$LOGDIR"
+ logfile="$LOGDIR/$name.log"
+ base_cmdline="$base_cmdline dump_bconf"
+ else
+ logfile="$workdir/qemu.log"
+ base_cmdline="quiet $base_cmdline"
+ fi
+
+ local qemu_args=()
+ if [ -n "$test_qemuopts" ]; then
+ read -r -d '' -a qemu_args <<< "$test_qemuopts" || true
+ fi
+
+ if [ "$is_reboot" -ne 1 ]; then
+ qemu_args+=("-no-reboot")
+ fi
+
+ timeout "$effective_timeout" "$QEMU" -kernel "$KERNEL" \
+ -initrd "$initramfs" \
+ -append "$base_cmdline" \
+ -display none \
+ -serial stdio \
+ "${qemu_args[@]}" < /dev/null > "$logfile" 2>&1 || true
+
+ if grep -q "TEST RESULT: PASS" "$logfile"; then
+ ktap_test_pass "$name"
+ else
+ ktap_test_fail "$name"
+ ktap_print_msg "QEMU Console Output for $name:"
+ while IFS= read -r line; do
+ ktap_print_msg "$line"
+ done < "$logfile"
+ fi
+
+ rm -rf "$workdir"
+ trap - EXIT
+ return 0
+}
+
+TEST_SCRIPTS=()
+if [ -d "$SCRIPT_DIR/tests" ]; then
+ while IFS= read -r f; do
+ [ -n "$f" ] && TEST_SCRIPTS+=("$f")
+ done < <(find "$SCRIPT_DIR/tests" -type f -name "*.sh" | sort)
+fi
+
+TOTAL_TESTS=0
+for test_script in "${TEST_SCRIPTS[@]}"; do
+ name="$(basename "$test_script" .sh)"
+ if [ "$TARGET_TEST" != "all" ] && [ "$TARGET_TEST" != "$name" ]; then
+ continue
+ fi
+ TOTAL_TESTS=$((TOTAL_TESTS + 1))
+done
+
+ktap_print_header
+ktap_set_plan "$TOTAL_TESTS"
+
+for test_script in "${TEST_SCRIPTS[@]}"; do
+ name="$(basename "$test_script" .sh)"
+
+ if [ "$TARGET_TEST" != "all" ] && [ "$TARGET_TEST" != "$name" ]; then
+ continue
+ fi
+
+ run_single_test "$test_script"
+done
+
+ktap_finished
diff --git a/tools/testing/selftests/ftrace/boottime/tests/01-kprobe.sh b/tools/testing/selftests/ftrace/boottime/tests/01-kprobe.sh
new file mode 100644
index 000000000000..6ed7caca4c5f
--- /dev/null
+++ b/tools/testing/selftests/ftrace/boottime/tests/01-kprobe.sh
@@ -0,0 +1,25 @@
+#!/bin/sh
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (C) 2026, Google LLC.
+# Check kprobe bootconfig settings on tracefs
+TRACEDIR="/sys/kernel/tracing"
+
+if [ -f /proc/bootconfig ] && grep -q "dump_bconf" /proc/cmdline 2>/dev/null; then
+ echo "=== /proc/bootconfig ==="
+ cat /proc/bootconfig
+ echo "========================"
+fi
+
+if [ ! -d "$TRACEDIR/events/kprobes/vfs_read" ]; then
+ echo "FAIL: kprobe event kprobes/vfs_read does not exist"
+ exit 1
+fi
+
+ENABLE=$(cat "$TRACEDIR/events/kprobes/vfs_read/enable")
+if [ "$ENABLE" != "1" ]; then
+ echo "FAIL: kprobe event kprobes/vfs_read is not enabled ($ENABLE)"
+ exit 1
+fi
+
+echo "PASS: 01-kprobe"
+exit 0
diff --git a/tools/testing/selftests/ftrace/boottime/tests/02-synth.sh b/tools/testing/selftests/ftrace/boottime/tests/02-synth.sh
new file mode 100644
index 000000000000..745c328dde15
--- /dev/null
+++ b/tools/testing/selftests/ftrace/boottime/tests/02-synth.sh
@@ -0,0 +1,25 @@
+#!/bin/sh
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (C) 2026, Google LLC.
+# Check synthetic event bootconfig settings on tracefs
+TRACEDIR="/sys/kernel/tracing"
+
+if [ -f /proc/bootconfig ] && grep -q "dump_bconf" /proc/cmdline 2>/dev/null; then
+ echo "=== /proc/bootconfig ==="
+ cat /proc/bootconfig
+ echo "========================"
+fi
+
+if [ ! -d "$TRACEDIR/events/synthetic/boot_lat" ]; then
+ echo "FAIL: synthetic event synthetic/boot_lat does not exist"
+ exit 1
+fi
+
+ENABLE=$(cat "$TRACEDIR/events/synthetic/boot_lat/enable")
+if [ "$ENABLE" != "1" ]; then
+ echo "FAIL: synthetic event synthetic/boot_lat is not enabled ($ENABLE)"
+ exit 1
+fi
+
+echo "PASS: 02-synth"
+exit 0
diff --git a/tools/testing/selftests/ftrace/boottime/tests/03-eprobe.sh b/tools/testing/selftests/ftrace/boottime/tests/03-eprobe.sh
new file mode 100644
index 000000000000..9b3b7ab23c69
--- /dev/null
+++ b/tools/testing/selftests/ftrace/boottime/tests/03-eprobe.sh
@@ -0,0 +1,25 @@
+#!/bin/sh
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (C) 2026, Google LLC.
+# Check eprobe event bootconfig settings on tracefs
+TRACEDIR="/sys/kernel/tracing"
+
+if [ -f /proc/bootconfig ] && grep -q "dump_bconf" /proc/cmdline 2>/dev/null; then
+ echo "=== /proc/bootconfig ==="
+ cat /proc/bootconfig
+ echo "========================"
+fi
+
+if [ ! -d "$TRACEDIR/events/eprobes/ep_read" ]; then
+ echo "FAIL: eprobe event eprobes/ep_read does not exist"
+ exit 1
+fi
+
+ENABLE=$(cat "$TRACEDIR/events/eprobes/ep_read/enable")
+if [ "$ENABLE" != "1" ]; then
+ echo "FAIL: eprobe event eprobes/ep_read is not enabled ($ENABLE)"
+ exit 1
+fi
+
+echo "PASS: 03-eprobe"
+exit 0
diff --git a/tools/testing/selftests/ftrace/boottime/tests/04-fprobe.sh b/tools/testing/selftests/ftrace/boottime/tests/04-fprobe.sh
new file mode 100644
index 000000000000..27e090c28a4b
--- /dev/null
+++ b/tools/testing/selftests/ftrace/boottime/tests/04-fprobe.sh
@@ -0,0 +1,25 @@
+#!/bin/sh
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (C) 2026, Google LLC.
+# Check fprobe event bootconfig settings on tracefs
+TRACEDIR="/sys/kernel/tracing"
+
+if [ -f /proc/bootconfig ] && grep -q "dump_bconf" /proc/cmdline 2>/dev/null; then
+ echo "=== /proc/bootconfig ==="
+ cat /proc/bootconfig
+ echo "========================"
+fi
+
+if [ ! -d "$TRACEDIR/events/fprobes/fp_read" ]; then
+ echo "FAIL: fprobe event fprobes/fp_read does not exist"
+ exit 1
+fi
+
+ENABLE=$(cat "$TRACEDIR/events/fprobes/fp_read/enable")
+if [ "$ENABLE" != "1" ]; then
+ echo "FAIL: fprobe event fprobes/fp_read is not enabled ($ENABLE)"
+ exit 1
+fi
+
+echo "PASS: 04-fprobe"
+exit 0
diff --git a/tools/testing/selftests/ftrace/boottime/tests/05-tprobe.sh b/tools/testing/selftests/ftrace/boottime/tests/05-tprobe.sh
new file mode 100644
index 000000000000..810d34055f94
--- /dev/null
+++ b/tools/testing/selftests/ftrace/boottime/tests/05-tprobe.sh
@@ -0,0 +1,25 @@
+#!/bin/sh
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (C) 2026, Google LLC.
+# Check tracepoint probe event bootconfig settings on tracefs
+TRACEDIR="/sys/kernel/tracing"
+
+if [ -f /proc/bootconfig ] && grep -q "dump_bconf" /proc/cmdline 2>/dev/null; then
+ echo "=== /proc/bootconfig ==="
+ cat /proc/bootconfig
+ echo "========================"
+fi
+
+if [ ! -d "$TRACEDIR/events/tracepoints/tp_sched" ]; then
+ echo "FAIL: tracepoint probe event tracepoints/tp_sched does not exist"
+ exit 1
+fi
+
+ENABLE=$(cat "$TRACEDIR/events/tracepoints/tp_sched/enable")
+if [ "$ENABLE" != "1" ]; then
+ echo "FAIL: tracepoint probe event tracepoints/tp_sched is not enabled ($ENABLE)"
+ exit 1
+fi
+
+echo "PASS: 05-tprobe"
+exit 0
diff --git a/tools/testing/selftests/ftrace/boottime/tests/06-instance.sh b/tools/testing/selftests/ftrace/boottime/tests/06-instance.sh
new file mode 100644
index 000000000000..f6e8519912e4
--- /dev/null
+++ b/tools/testing/selftests/ftrace/boottime/tests/06-instance.sh
@@ -0,0 +1,30 @@
+#!/bin/sh
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (C) 2026, Google LLC.
+# Check instance bootconfig settings on tracefs
+TRACEDIR="/sys/kernel/tracing"
+
+if [ -f /proc/bootconfig ] && grep -q "dump_bconf" /proc/cmdline 2>/dev/null; then
+ echo "=== /proc/bootconfig ==="
+ cat /proc/bootconfig
+ echo "========================"
+fi
+
+if [ ! -d "$TRACEDIR/instances/foo" ]; then
+ echo "FAIL: trace instance foo does not exist"
+ exit 1
+fi
+
+if [ ! -d "$TRACEDIR/instances/foo/events/sched/sched_switch" ]; then
+ echo "FAIL: event sched_switch does not exist in instance foo"
+ exit 1
+fi
+
+ENABLE=$(cat "$TRACEDIR/instances/foo/events/sched/sched_switch/enable")
+if [ "$ENABLE" != "1" ]; then
+ echo "FAIL: event sched_switch is not enabled in instance foo ($ENABLE)"
+ exit 1
+fi
+
+echo "PASS: 06-instance"
+exit 0
diff --git a/tools/testing/selftests/ftrace/boottime/tests/cmdline-01-ftrace.sh b/tools/testing/selftests/ftrace/boottime/tests/cmdline-01-ftrace.sh
new file mode 100644
index 000000000000..23a0b15f2dd3
--- /dev/null
+++ b/tools/testing/selftests/ftrace/boottime/tests/cmdline-01-ftrace.sh
@@ -0,0 +1,19 @@
+#!/bin/sh
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (C) 2026, Google LLC.
+# Check ftrace= kernel command-line tracer setting
+TRACEDIR="/sys/kernel/tracing"
+
+if [ ! -f "$TRACEDIR/current_tracer" ]; then
+ echo "FAIL: current_tracer does not exist"
+ exit 1
+fi
+
+read -r TRACER _ < "$TRACEDIR/current_tracer"
+if [ "$TRACER" != "function" ]; then
+ echo "FAIL: current_tracer is '$TRACER', expected 'function'"
+ exit 1
+fi
+
+echo "PASS: cmdline-01-ftrace"
+exit 0
diff --git a/tools/testing/selftests/ftrace/boottime/tests/cmdline-02-trace-event.sh b/tools/testing/selftests/ftrace/boottime/tests/cmdline-02-trace-event.sh
new file mode 100644
index 000000000000..b793907c48cd
--- /dev/null
+++ b/tools/testing/selftests/ftrace/boottime/tests/cmdline-02-trace-event.sh
@@ -0,0 +1,26 @@
+#!/bin/sh
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (C) 2026, Google LLC.
+# Check trace_event= kernel command-line setting
+TRACEDIR="/sys/kernel/tracing"
+
+if [ ! -d "$TRACEDIR/events/sched/sched_switch" ]; then
+ echo "FAIL: event sched:sched_switch does not exist"
+ exit 1
+fi
+
+if [ ! -d "$TRACEDIR/events/kmem/kmalloc" ]; then
+ echo "FAIL: event kmem:kmalloc does not exist"
+ exit 1
+fi
+
+ENABLE1=$(cat "$TRACEDIR/events/sched/sched_switch/enable")
+ENABLE2=$(cat "$TRACEDIR/events/kmem/kmalloc/enable")
+
+if [ "$ENABLE1" != "1" ] || [ "$ENABLE2" != "1" ]; then
+ echo "FAIL: events not enabled (sched_switch=$ENABLE1, kmalloc=$ENABLE2)"
+ exit 1
+fi
+
+echo "PASS: cmdline-02-trace-event"
+exit 0
diff --git a/tools/testing/selftests/ftrace/boottime/tests/cmdline-03-trace-buf-size.sh b/tools/testing/selftests/ftrace/boottime/tests/cmdline-03-trace-buf-size.sh
new file mode 100644
index 000000000000..4b936f84d4d7
--- /dev/null
+++ b/tools/testing/selftests/ftrace/boottime/tests/cmdline-03-trace-buf-size.sh
@@ -0,0 +1,29 @@
+#!/bin/sh
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (C) 2026, Google LLC.
+# APPLETS: awk
+# Check trace_buf_size= kernel command-line setting
+TRACEDIR="/sys/kernel/tracing"
+
+if [ ! -f "$TRACEDIR/buffer_size_kb" ]; then
+ echo "FAIL: buffer_size_kb does not exist"
+ exit 1
+fi
+
+BUF_RAW=$(cat "$TRACEDIR/buffer_size_kb")
+case "$BUF_RAW" in
+ *"expanded:"*)
+ BUFSIZE=$(echo "$BUF_RAW" | sed -n 's/.*expanded: *\([0-9]*\).*/\1/p')
+ ;;
+ *)
+ BUFSIZE=$(echo "$BUF_RAW" | awk '{print $1}')
+ ;;
+esac
+
+if [ -z "$BUFSIZE" ] || [ "$BUFSIZE" -lt 2048 ]; then
+ echo "FAIL: buffer_size_kb is '$BUF_RAW', expected >= 2048"
+ exit 1
+fi
+
+echo "PASS: cmdline-03-trace-buf-size"
+exit 0
diff --git a/tools/testing/selftests/ftrace/boottime/tests/cmdline-04-trace-options.sh b/tools/testing/selftests/ftrace/boottime/tests/cmdline-04-trace-options.sh
new file mode 100644
index 000000000000..53a65b55f7a7
--- /dev/null
+++ b/tools/testing/selftests/ftrace/boottime/tests/cmdline-04-trace-options.sh
@@ -0,0 +1,23 @@
+#!/bin/sh
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (C) 2026, Google LLC.
+# Check trace_options= kernel command-line setting
+TRACEDIR="/sys/kernel/tracing"
+
+if [ ! -f "$TRACEDIR/trace_options" ]; then
+ echo "FAIL: trace_options file does not exist"
+ exit 1
+fi
+
+if ! grep -qw "sym-addr" "$TRACEDIR/trace_options"; then
+ echo "FAIL: sym-addr option is not set in trace_options"
+ exit 1
+fi
+
+if ! grep -qw "verbose" "$TRACEDIR/trace_options"; then
+ echo "FAIL: verbose option is not set in trace_options"
+ exit 1
+fi
+
+echo "PASS: cmdline-04-trace-options"
+exit 0
diff --git a/tools/testing/selftests/ftrace/boottime/tests/cmdline-05-trace-clock.sh b/tools/testing/selftests/ftrace/boottime/tests/cmdline-05-trace-clock.sh
new file mode 100644
index 000000000000..d6de9f6e2090
--- /dev/null
+++ b/tools/testing/selftests/ftrace/boottime/tests/cmdline-05-trace-clock.sh
@@ -0,0 +1,19 @@
+#!/bin/sh
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (C) 2026, Google LLC.
+# Check trace_clock= kernel command-line setting
+TRACEDIR="/sys/kernel/tracing"
+
+if [ ! -f "$TRACEDIR/trace_clock" ]; then
+ echo "FAIL: trace_clock file does not exist"
+ exit 1
+fi
+
+if ! grep -q '\[global\]' "$TRACEDIR/trace_clock"; then
+ CLOCK=$(cat "$TRACEDIR/trace_clock")
+ echo "FAIL: trace_clock is not set to global ($CLOCK)"
+ exit 1
+fi
+
+echo "PASS: cmdline-05-trace-clock"
+exit 0
diff --git a/tools/testing/selftests/ftrace/boottime/tests/cmdline-06-trace-instance.sh b/tools/testing/selftests/ftrace/boottime/tests/cmdline-06-trace-instance.sh
new file mode 100644
index 000000000000..aae7f0a86d81
--- /dev/null
+++ b/tools/testing/selftests/ftrace/boottime/tests/cmdline-06-trace-instance.sh
@@ -0,0 +1,24 @@
+#!/bin/sh
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (C) 2026, Google LLC.
+# Check trace_instance= kernel command-line setting
+TRACEDIR="/sys/kernel/tracing"
+
+if [ ! -d "$TRACEDIR/instances/bar" ]; then
+ echo "FAIL: trace instance bar does not exist"
+ exit 1
+fi
+
+if [ ! -d "$TRACEDIR/instances/bar/events/sched/sched_switch" ]; then
+ echo "FAIL: event sched_switch does not exist in instance bar"
+ exit 1
+fi
+
+ENABLE=$(cat "$TRACEDIR/instances/bar/events/sched/sched_switch/enable")
+if [ "$ENABLE" != "1" ]; then
+ echo "FAIL: event sched_switch is not enabled in instance bar ($ENABLE)"
+ exit 1
+fi
+
+echo "PASS: cmdline-06-trace-instance"
+exit 0
diff --git a/tools/testing/selftests/ftrace/boottime/tests/persistent-01-reserve-mem.sh b/tools/testing/selftests/ftrace/boottime/tests/persistent-01-reserve-mem.sh
new file mode 100644
index 000000000000..49abaf518481
--- /dev/null
+++ b/tools/testing/selftests/ftrace/boottime/tests/persistent-01-reserve-mem.sh
@@ -0,0 +1,28 @@
+#!/bin/sh
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (C) 2026, Google LLC.
+# Check persistent ring buffer across guest crash/reboot
+# REBOOT: 1
+TRACEDIR="/sys/kernel/tracing"
+
+if [ ! -f "$TRACEDIR/instances/boot_map/trace" ]; then
+ echo "FAIL: persistent trace instance boot_map/trace file missing"
+ exit 1
+fi
+
+# Check if Boot 1 marker was already written
+if grep -q "BOOT1_MARKER" "$TRACEDIR/instances/boot_map/trace" 2>/dev/null; then
+ # Second boot: verify persistent ring buffer content from first boot
+ echo "PASS: persistent-01-reserve-mem"
+ exit 0
+fi
+
+# First boot: write marker to persistent buffer and trigger kernel crash/reboot
+echo "BOOT1_MARKER" > "$TRACEDIR/instances/boot_map/trace_marker"
+sync
+
+# Trigger reboot to restart into second boot
+echo b > /proc/sysrq-trigger 2>/dev/null || echo c > /proc/sysrq-trigger 2>/dev/null || true
+sleep 5
+echo "FAIL: reboot trigger failed on first boot"
+exit 1
diff --git a/tools/testing/selftests/ftrace/boottime/tests/persistent-02-backup-instance.sh b/tools/testing/selftests/ftrace/boottime/tests/persistent-02-backup-instance.sh
new file mode 100644
index 000000000000..0b2a2f03ce4f
--- /dev/null
+++ b/tools/testing/selftests/ftrace/boottime/tests/persistent-02-backup-instance.sh
@@ -0,0 +1,40 @@
+#!/bin/sh
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (C) 2026, Google LLC.
+# Check persistent backup instance across guest crash/reboot
+# REBOOT: 1
+TRACEDIR="/sys/kernel/tracing"
+
+# Ensure both boot_map/trace and backup/trace files exist
+if [ ! -f "$TRACEDIR/instances/boot_map/trace" ]; then
+ echo "FAIL: boot_map/trace file missing"
+ exit 1
+fi
+
+if [ ! -f "$TRACEDIR/instances/backup/trace" ]; then
+ echo "FAIL: backup/trace file missing"
+ exit 1
+fi
+
+# Check if BOOT1_MARKER is in boot_map/trace (indicates second boot)
+if grep -q "BOOT1_MARKER" "$TRACEDIR/instances/boot_map/trace" 2>/dev/null; then
+ # Second boot: verify BOOT1_MARKER was copied into backup/trace from Boot 1
+ if grep -q "BOOT1_MARKER" "$TRACEDIR/instances/backup/trace" 2>/dev/null; then
+ echo "PASS: persistent-02-backup-instance"
+ exit 0
+ else
+ echo "FAIL: BOOT1_MARKER found in boot_map/trace" \
+ "but missing from backup/trace on second boot"
+ exit 1
+ fi
+fi
+
+# First boot: write BOOT1_MARKER to boot_map and reboot via sysrq-trigger
+echo "BOOT1_MARKER" > "$TRACEDIR/instances/boot_map/trace_marker"
+sync
+
+# Trigger reboot to restart into second boot
+echo b > /proc/sysrq-trigger 2>/dev/null || echo c > /proc/sysrq-trigger 2>/dev/null || true
+sleep 5
+echo "FAIL: reboot trigger failed on first boot"
+exit 1
diff --git a/tools/testing/selftests/ftrace/config b/tools/testing/selftests/ftrace/config
index 544de0db5f58..29f05f7c6e2c 100644
--- a/tools/testing/selftests/ftrace/config
+++ b/tools/testing/selftests/ftrace/config
@@ -1,3 +1,5 @@
+CONFIG_BOOT_CONFIG=y
+CONFIG_BOOTTIME_TRACING=y
CONFIG_BPF_SYSCALL=y
CONFIG_DEBUG_INFO_BTF=y
CONFIG_DEBUG_INFO_DWARF4=y
@@ -8,22 +10,26 @@ CONFIG_FTRACE=y
CONFIG_FTRACE_SYSCALLS=y
CONFIG_FUNCTION_GRAPH_RETVAL=y
CONFIG_FUNCTION_PROFILER=y
+CONFIG_FUNCTION_TRACER=y
CONFIG_HIST_TRIGGERS=y
CONFIG_IRQSOFF_TRACER=y
CONFIG_KALLSYMS_ALL=y
CONFIG_KPROBES=y
CONFIG_KPROBE_EVENTS=y
+CONFIG_MAGIC_SYSRQ=y
CONFIG_MODULES=y
CONFIG_MODULE_UNLOAD=y
CONFIG_PREEMPTIRQ_DELAY_TEST=m
CONFIG_PREEMPT_TRACER=y
CONFIG_PROBE_EVENTS_BTF_ARGS=y
+CONFIG_RESERVE_MEM=y
CONFIG_SAMPLES=y
CONFIG_SAMPLE_FTRACE_DIRECT=m
CONFIG_SAMPLE_TRACE_EVENTS=m
CONFIG_SAMPLE_TRACE_PRINTK=m
CONFIG_SCHED_TRACER=y
CONFIG_STACK_TRACER=y
+CONFIG_SYNTH_EVENTS=y
CONFIG_TRACER_SNAPSHOT=y
CONFIG_UPROBES=y
CONFIG_UPROBE_EVENTS=y
diff --git a/tools/testing/selftests/kselftest/runner.sh b/tools/testing/selftests/kselftest/runner.sh
index 311811dc55a0..ee2c1f0403d9 100644
--- a/tools/testing/selftests/kselftest/runner.sh
+++ b/tools/testing/selftests/kselftest/runner.sh
@@ -38,8 +38,12 @@ tap_prefix()
tap_timeout()
{
+ # nommu doesn't support timeout command (missing fork(2))
+ if [ "$NOMMU" = "1" ] ; then
+ echo "timeout isn't supported for NOMMU"
+ $1
# Make sure tests will time out if utility is available.
- if [ -x /usr/bin/timeout ] ; then
+ elif [ -x /usr/bin/timeout ] ; then
/usr/bin/timeout --foreground "$kselftest_timeout" \
/usr/bin/timeout "$kselftest_timeout" $1
else
diff --git a/tools/testing/selftests/kvm/arm64/vgic_init.c b/tools/testing/selftests/kvm/arm64/vgic_init.c
index 47e34b43afb2..5a30f3cb039b 100644
--- a/tools/testing/selftests/kvm/arm64/vgic_init.c
+++ b/tools/testing/selftests/kvm/arm64/vgic_init.c
@@ -5,6 +5,7 @@
* Copyright (C) 2020, Red Hat, Inc.
*/
#include <linux/kernel.h>
+#include <linux/sizes.h>
#include <sys/syscall.h>
#include <asm/kvm.h>
#include <asm/kvm_para.h>
@@ -13,12 +14,21 @@
#include "test_util.h"
#include "kvm_util.h"
+#include "gic.h"
#include "processor.h"
#include "vgic.h"
#include "gic_v3.h"
#define NR_VCPUS 4
+#define REDIST_RETRY_REGION0_BASE GICR_BASE_GPA
+#define REDIST_RETRY_REGION1_BASE \
+ (REDIST_RETRY_REGION0_BASE + 2 * KVM_VGIC_V3_REDIST_SIZE)
+#define REDIST_RETRY_DIST_BASE \
+ (REDIST_RETRY_REGION1_BASE + KVM_VGIC_V3_REDIST_SIZE)
+#define REDIST_RETRY_REGION2_BASE \
+ (REDIST_RETRY_DIST_BASE + KVM_VGIC_V3_DIST_SIZE)
+
#define REG_OFFSET(vcpu, offset) (((u64)vcpu << 32) | offset)
#define VGIC_DEV_IS_V2(_d) ((_d) == KVM_DEV_TYPE_ARM_VGIC_V2)
@@ -65,6 +75,23 @@ static void guest_code(void)
GUEST_DONE();
}
+static void guest_check_redist_retry(void)
+{
+ unsigned int i;
+
+ /* The first three redistributors span adjacent regions 0 and 1. */
+ for (i = 0; i < NR_VCPUS; i++) {
+ u64 base = i < 3 ? REDIST_RETRY_REGION0_BASE +
+ i * KVM_VGIC_V3_REDIST_SIZE :
+ REDIST_RETRY_REGION2_BASE;
+ u64 typer = readq((void *)(unsigned long)(base + GICR_TYPER));
+
+ GUEST_ASSERT_EQ(GICR_TYPER_CPU_NUMBER(typer), i);
+ }
+
+ GUEST_DONE();
+}
+
/* we don't want to assert on run execution, hence that helper */
static int run_vcpu(struct kvm_vcpu *vcpu)
{
@@ -73,6 +100,7 @@ static int run_vcpu(struct kvm_vcpu *vcpu)
static struct vm_gic vm_gic_create_with_vcpus(u32 gic_dev_type,
u32 nr_vcpus,
+ void *guest_code,
struct kvm_vcpu *vcpus[])
{
struct vm_gic v;
@@ -338,7 +366,7 @@ static void test_vgic_then_vcpus(u32 gic_dev_type)
struct vm_gic v;
int ret, i;
- v = vm_gic_create_with_vcpus(gic_dev_type, 1, vcpus);
+ v = vm_gic_create_with_vcpus(gic_dev_type, 1, guest_code, vcpus);
subtest_dist_rdist(&v);
@@ -359,7 +387,8 @@ static void test_vcpus_then_vgic(u32 gic_dev_type)
struct vm_gic v;
int ret;
- v = vm_gic_create_with_vcpus(gic_dev_type, NR_VCPUS, vcpus);
+ v = vm_gic_create_with_vcpus(gic_dev_type, NR_VCPUS, guest_code,
+ vcpus);
subtest_dist_rdist(&v);
@@ -411,7 +440,8 @@ static void test_v3_new_redist_regions(void)
u64 addr;
int ret;
- v = vm_gic_create_with_vcpus(KVM_DEV_TYPE_ARM_VGIC_V3, NR_VCPUS, vcpus);
+ v = vm_gic_create_with_vcpus(KVM_DEV_TYPE_ARM_VGIC_V3, NR_VCPUS,
+ guest_code, vcpus);
subtest_v3_redist_regions(&v);
kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_CTRL,
KVM_DEV_ARM_VGIC_CTRL_INIT, NULL);
@@ -422,7 +452,8 @@ static void test_v3_new_redist_regions(void)
/* step2 */
- v = vm_gic_create_with_vcpus(KVM_DEV_TYPE_ARM_VGIC_V3, NR_VCPUS, vcpus);
+ v = vm_gic_create_with_vcpus(KVM_DEV_TYPE_ARM_VGIC_V3, NR_VCPUS,
+ guest_code, vcpus);
subtest_v3_redist_regions(&v);
addr = REDIST_REGION_ATTR_ADDR(1, 0x280000, 0, 2);
@@ -436,7 +467,8 @@ static void test_v3_new_redist_regions(void)
/* step 3 */
- v = vm_gic_create_with_vcpus(KVM_DEV_TYPE_ARM_VGIC_V3, NR_VCPUS, vcpus);
+ v = vm_gic_create_with_vcpus(KVM_DEV_TYPE_ARM_VGIC_V3, NR_VCPUS,
+ guest_code, vcpus);
subtest_v3_redist_regions(&v);
ret = __kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_ADDR,
@@ -457,6 +489,70 @@ static void test_v3_new_redist_regions(void)
vm_gic_destroy(&v);
}
+static void test_v3_redist_region_retry(void)
+{
+ struct kvm_vcpu *vcpus[NR_VCPUS];
+ struct vm_gic v;
+ struct ucall uc;
+ u64 addr;
+ int ret;
+
+ v = vm_gic_create_with_vcpus(KVM_DEV_TYPE_ARM_VGIC_V3, NR_VCPUS,
+ guest_check_redist_retry, vcpus);
+
+ addr = REDIST_REGION_ATTR_ADDR(2, REDIST_RETRY_REGION0_BASE, 0, 0);
+ kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_ADDR,
+ KVM_VGIC_V3_ADDR_TYPE_REDIST_REGION, &addr);
+
+ addr = REDIST_REGION_ATTR_ADDR(1, REDIST_RETRY_REGION1_BASE, 0, 1);
+ kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_ADDR,
+ KVM_VGIC_V3_ADDR_TYPE_REDIST_REGION, &addr);
+
+ addr = REDIST_RETRY_DIST_BASE;
+ kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_ADDR,
+ KVM_VGIC_V3_ADDR_TYPE_DIST, &addr);
+
+ addr = REDIST_REGION_ATTR_ADDR(1, REDIST_RETRY_DIST_BASE, 0, 2);
+ ret = __kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_ADDR,
+ KVM_VGIC_V3_ADDR_TYPE_REDIST_REGION,
+ &addr);
+ TEST_ASSERT(ret && errno == EINVAL,
+ "register redist region colliding with dist");
+
+ addr = REDIST_REGION_ATTR_ADDR(1, REDIST_RETRY_REGION2_BASE, 0, 2);
+ kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_ADDR,
+ KVM_VGIC_V3_ADDR_TYPE_REDIST_REGION, &addr);
+
+ virt_map(v.vm, REDIST_RETRY_REGION0_BASE, REDIST_RETRY_REGION0_BASE,
+ vm_calc_num_guest_pages(v.vm->mode,
+ 3 * KVM_VGIC_V3_REDIST_SIZE));
+ virt_map(v.vm, REDIST_RETRY_REGION2_BASE, REDIST_RETRY_REGION2_BASE,
+ vm_calc_num_guest_pages(v.vm->mode,
+ KVM_VGIC_V3_REDIST_SIZE));
+
+ kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_CTRL,
+ KVM_DEV_ARM_VGIC_CTRL_INIT, NULL);
+
+ vcpu_run(vcpus[0]);
+ switch (get_ucall(vcpus[0], &uc)) {
+ case UCALL_DONE:
+ break;
+ case UCALL_ABORT:
+ REPORT_GUEST_ASSERT(uc);
+ break;
+ case UCALL_NONE:
+ if (vcpus[0]->run->exit_reason == KVM_EXIT_MMIO)
+ TEST_FAIL("Unexpected MMIO exit at 0x%llx",
+ vcpus[0]->run->mmio.phys_addr);
+ fallthrough;
+ default:
+ TEST_FAIL("Unexpected ucall %lu, exit_reason %u",
+ uc.cmd, vcpus[0]->run->exit_reason);
+ }
+
+ vm_gic_destroy(&v);
+}
+
static void test_v3_typer_accesses(void)
{
struct vm_gic v;
@@ -608,7 +704,8 @@ static void test_v3_redist_ipa_range_check_at_vcpu_run(void)
int ret, i;
u64 addr;
- v = vm_gic_create_with_vcpus(KVM_DEV_TYPE_ARM_VGIC_V3, 1, vcpus);
+ v = vm_gic_create_with_vcpus(KVM_DEV_TYPE_ARM_VGIC_V3, 1, guest_code,
+ vcpus);
/* Set space for 3 redists, we have 1 vcpu, so this succeeds. */
addr = max_phys_size - (3 * 2 * 0x10000);
@@ -641,7 +738,8 @@ static void test_v3_its_region(void)
u64 addr;
int its_fd, ret;
- v = vm_gic_create_with_vcpus(KVM_DEV_TYPE_ARM_VGIC_V3, NR_VCPUS, vcpus);
+ v = vm_gic_create_with_vcpus(KVM_DEV_TYPE_ARM_VGIC_V3, NR_VCPUS,
+ guest_code, vcpus);
its_fd = kvm_create_device(v.vm, KVM_DEV_TYPE_ARM_VGIC_ITS);
addr = 0x401000;
@@ -684,7 +782,8 @@ static void test_v3_nassgicap(void)
u32 typer2;
int ret;
- vm = vm_gic_create_with_vcpus(KVM_DEV_TYPE_ARM_VGIC_V3, NR_VCPUS, vcpus);
+ vm = vm_gic_create_with_vcpus(KVM_DEV_TYPE_ARM_VGIC_V3, NR_VCPUS,
+ guest_code, vcpus);
kvm_device_attr_get(vm.gic_fd, KVM_DEV_ARM_VGIC_GRP_DIST_REGS,
GICD_TYPER2, &typer2);
has_nassgicap = typer2 & GICD_TYPER2_nASSGIcap;
@@ -978,6 +1077,7 @@ void run_tests(u32 gic_dev_type)
if (VGIC_DEV_IS_V3(gic_dev_type)) {
test_v3_new_redist_regions();
+ test_v3_redist_region_retry();
test_v3_typer_accesses();
test_v3_last_bit_redist_regions();
test_v3_last_bit_single_rdist();
diff --git a/tools/testing/selftests/mm/split_huge_page_test.c b/tools/testing/selftests/mm/split_huge_page_test.c
index ef4058662b91..36a6719ff09a 100644
--- a/tools/testing/selftests/mm/split_huge_page_test.c
+++ b/tools/testing/selftests/mm/split_huge_page_test.c
@@ -110,14 +110,11 @@ static char *allocate_zero_filled_hugepage(size_t len)
static void disable_khugepaged(void *addr, size_t len)
{
- /*
- * Disables khugepaged from collapsing THPs in range, existing THP
- * pages remain.
- */
+ /* Disables khugepaged from collapsing pages in range into THPs */
if (!madvise(addr, len, MADV_NOHUGEPAGE))
return;
- ksft_exit_fail_msg("MADV_NOHUGEPAGE failed, err=%d\n", errno);
+ ksft_exit_fail_perror("MADV_NOHUGEPAGE failed");
}
static void verify_rss_anon_split_huge_page_all_zeroes(char *one_page, int nr_hpages, size_t len)
diff --git a/tools/testing/selftests/mm/vm_util.c b/tools/testing/selftests/mm/vm_util.c
index 65bc4761d1c9..515031b8283d 100644
--- a/tools/testing/selftests/mm/vm_util.c
+++ b/tools/testing/selftests/mm/vm_util.c
@@ -351,13 +351,13 @@ err_out:
return entry;
}
-static bool check_large_folios(int pagemap_fd, int kpageflags_fd,
- void *addr, size_t len, int nr_hpages,
+static bool check_large_folios(void *addr, size_t len, int nr_hpages,
uint64_t hpage_size)
{
int order = 0, pagesize = getpagesize();
unsigned int nr_pages = hpage_size / pagesize;
int orders[MAX_NR_ORDERS], status;
+ int pagemap_fd, kpageflags_fd;
bool ret = false;
if (!nr_pages)
@@ -368,6 +368,15 @@ static bool check_large_folios(int pagemap_fd, int kpageflags_fd,
ksft_exit_fail_msg("invalid order\n");
memset(orders, 0, sizeof(int) * MAX_NR_ORDERS);
+ pagemap_fd = open(PAGEMAP_PATH, O_RDONLY);
+ if (pagemap_fd == -1)
+ ksft_exit_fail_msg("read pagemap fail\n");
+
+ kpageflags_fd = open(KPAGEFLAGS_PATH, O_RDONLY);
+ if (kpageflags_fd == -1) {
+ close(pagemap_fd);
+ ksft_exit_fail_msg("read kpageflags fail\n");
+ }
status = gather_folio_orders(addr, len, pagemap_fd,
kpageflags_fd, orders, MAX_NR_ORDERS);
@@ -378,38 +387,53 @@ static bool check_large_folios(int pagemap_fd, int kpageflags_fd,
ret = true;
out:
+ close(pagemap_fd);
+ close(kpageflags_fd);
return ret;
}
-enum check_huge_type {
- CHECK_HUGE_ANON,
- CHECK_HUGE_FILE,
+enum check_type {
+ CHECK_TYPE_ANON,
+ CHECK_TYPE_FILE,
};
-static bool check_huge_type(uint64_t categories, enum check_huge_type type)
+static bool __check_type(void *addr, size_t len, uint64_t page_size,
+ enum check_type type)
{
- const bool file = categories & PAGE_IS_FILE;
+ bool ret = false;
+ int pagemap_fd;
+ char *start = addr;
+ char *end = start + len;
+ uint64_t categories;
- switch (type) {
- case CHECK_HUGE_ANON:
- return !file;
- case CHECK_HUGE_FILE:
- return file;
+ pagemap_fd = open(PAGEMAP_PATH, O_RDONLY);
+ if (pagemap_fd < 0)
+ ksft_exit_fail_perror("open pagemap");
+
+ for (; start < end; start += page_size) {
+ categories = pagemap_scan_get_categories(pagemap_fd, start);
+ if ((categories & PAGE_IS_PRESENT) != PAGE_IS_PRESENT)
+ continue;
+
+ if ((type == CHECK_TYPE_FILE) != !!(categories & PAGE_IS_FILE))
+ goto out;
}
- return false;
+ ret = true;
+
+out:
+ close(pagemap_fd);
+ return ret;
}
static bool __check_huge(void *addr, size_t len, int nr_hpages,
- uint64_t hpage_size, enum check_huge_type type)
+ uint64_t hpage_size)
{
bool ret = false;
- int pagemap_fd, kpageflags_fd;
+ int pagemap_fd;
int nr_pmd_mappings = 0;
- uint64_t pmd_pagesize, scan_mapping_size;
+ uint64_t pmd_pagesize;
uint64_t categories;
- unsigned long pfn;
- bool check_pmd_mapping, allow_nonpresent;
char *start = addr;
char *end = start + len;
@@ -417,57 +441,49 @@ static bool __check_huge(void *addr, size_t len, int nr_hpages,
if (!pmd_pagesize)
ksft_exit_fail_msg("reading PMD pagesize failed\n");
- check_pmd_mapping = hpage_size == pmd_pagesize;
- scan_mapping_size = (nr_hpages > 0) ? hpage_size : psize();
- /* Some mTHP tests check a partially populated PMD-sized range. */
- allow_nonpresent = (uint64_t)nr_hpages * hpage_size < len;
-
pagemap_fd = open(PAGEMAP_PATH, O_RDONLY);
if (pagemap_fd < 0)
- ksft_exit_fail_msg("open pagemap fail\n");
+ ksft_exit_fail_perror("open pagemap");
- kpageflags_fd = open(KPAGEFLAGS_PATH, O_RDONLY);
- if (kpageflags_fd < 0)
- ksft_exit_fail_msg("open kpageflags fail\n");
-
- if (!check_pmd_mapping &&
- !check_large_folios(pagemap_fd, kpageflags_fd,
- addr, len, nr_hpages, hpage_size))
+ if (hpage_size != pmd_pagesize) {
+ ret = check_large_folios(addr, len, nr_hpages, hpage_size);
goto out;
+ }
- for (; start < end; start += scan_mapping_size) {
+ for (; start < end; start += hpage_size) {
categories = pagemap_scan_get_categories(pagemap_fd, start);
- pfn = pagemap_get_pfn(pagemap_fd, start);
- if (pfn == -1UL) {
- if (!allow_nonpresent)
- goto out;
- else
- continue;
- }
- if (check_pmd_mapping && (categories & PAGE_IS_HUGE))
+ if (categories & PAGE_IS_HUGE)
nr_pmd_mappings++;
- if (!check_huge_type(categories, type))
- goto out;
}
- if (check_pmd_mapping && (nr_pmd_mappings != nr_hpages))
+ if (nr_pmd_mappings != nr_hpages)
goto out;
+
ret = true;
out:
close(pagemap_fd);
- close(kpageflags_fd);
return ret;
}
bool check_huge_anon(void *addr, size_t len, int nr_hpages, uint64_t hpage_size)
{
- return __check_huge(addr, len, nr_hpages, hpage_size, CHECK_HUGE_ANON);
+ const uint64_t scan_mapping_size = (nr_hpages > 0) ? hpage_size : psize();
+
+ if (!__check_huge(addr, len, nr_hpages, hpage_size))
+ return false;
+
+ return __check_type(addr, len, scan_mapping_size, CHECK_TYPE_ANON);
}
bool check_huge_file(void *addr, size_t len, int nr_hpages, uint64_t hpage_size)
{
- return __check_huge(addr, len, nr_hpages, hpage_size, CHECK_HUGE_FILE);
+ const uint64_t scan_mapping_size = (nr_hpages > 0) ? hpage_size : psize();
+
+ if (!__check_huge(addr, len, nr_hpages, hpage_size))
+ return false;
+
+ return __check_type(addr, len, scan_mapping_size, CHECK_TYPE_FILE);
}
int64_t allocate_transhuge(void *ptr, int pagemap_fd)
diff --git a/tools/testing/selftests/net/amt.sh b/tools/testing/selftests/net/amt.sh
index 663744305e52..d13b20cccba9 100755
--- a/tools/testing/selftests/net/amt.sh
+++ b/tools/testing/selftests/net/amt.sh
@@ -150,6 +150,13 @@ setup_interface()
ip netns exec "${RELAY}" ip a a 10.0.0.2/24 dev relay_gw
ip netns exec "${RELAY}" ip link add amtr type amt mode relay \
local 10.0.0.2 dev relay_gw relay_port 2268 max_tunnels 4
+ # Count the IGMP and MLD queries that leave the relay through its own
+ # amt device; test_query_egress expects none.
+ ip netns exec "${RELAY}" tc qdisc add dev amtr clsact
+ ip netns exec "${RELAY}" tc filter add dev amtr egress pref 1 \
+ protocol ip flower ip_proto 0x2 action pass
+ ip netns exec "${RELAY}" tc filter add dev amtr egress pref 2 \
+ protocol ipv6 flower ip_proto icmpv6 type 130 action pass
ip netns exec "${RELAY}" ip a a 172.17.0.1/24 dev relay_src
ip netns exec "${RELAY}" ip a a 2001:db8:3::1/64 dev relay_src
ip netns exec "${SOURCE}" ip a a 172.17.0.2/24 dev src_relay
@@ -246,6 +253,27 @@ test_ipv6_forward()
fi
}
+# The relay sends its General Queries straight from the receive path, in
+# the same context that found the tunnel. A query queued on the amt device
+# instead could outlive the tunnel it was built for. The forwarding tests
+# above show that the gateway got its queries.
+test_query_egress()
+{
+ local n4 n6
+
+ n4=$(ip netns exec "${RELAY}" tc -s -j filter show dev amtr egress \
+ pref 1 | jq '[.[].options.actions[0].stats.packets // empty] | add // 0')
+ n6=$(ip netns exec "${RELAY}" tc -s -j filter show dev amtr egress \
+ pref 2 | jq '[.[].options.actions[0].stats.packets // empty] | add // 0')
+ if [ "$n4" -eq 0 ] && [ "$n6" -eq 0 ]; then
+ printf "TEST: %-60s [ OK ]\n" "amt relay queries bypass the amt device"
+ else
+ printf "TEST: %-60s [FAIL]\n" "amt relay queries bypass the amt device"
+ echo "IGMP queries on amtr egress: $n4, MLD queries: $n6" >&2
+ ERR=1
+ fi
+}
+
send_mcast4()
{
sleep 5
@@ -287,6 +315,7 @@ wait $pid || err=$?
if [ $err -eq 1 ]; then
ERR=1
fi
+test_query_egress
printf "TEST: %-50s" "IPv4 amt traffic forwarding torture"
send_mcast_torture4
printf " [ OK ]\n"
diff --git a/tools/testing/selftests/net/drop_monitor_tests.sh b/tools/testing/selftests/net/drop_monitor_tests.sh
index 507d0a82f5f0..7da85608561b 100755
--- a/tools/testing/selftests/net/drop_monitor_tests.sh
+++ b/tools/testing/selftests/net/drop_monitor_tests.sh
@@ -18,18 +18,7 @@ DEVLINK_DEV=netdevsim/${DEV}
log_test()
{
- local rc=$1
- local expected=$2
- local msg="$3"
-
- if [ ${rc} -eq ${expected} ]; then
- printf " TEST: %-60s [ OK ]\n" "${msg}"
- nsuccess=$((nsuccess+1))
- else
- ret=1
- nfail=$((nfail+1))
- printf " TEST: %-60s [FAIL]\n" "${msg}"
- fi
+ log_test_expected "$1" "$2" "$3"
}
setup()
diff --git a/tools/testing/selftests/net/fcnal-test.sh b/tools/testing/selftests/net/fcnal-test.sh
index 890c3f8e51bb..a50609535fed 100755
--- a/tools/testing/selftests/net/fcnal-test.sh
+++ b/tools/testing/selftests/net/fcnal-test.sh
@@ -97,34 +97,7 @@ fi
log_test()
{
- local rc=$1
- local expected=$2
- local msg="$3"
- local ans
-
- [ "${VERBOSE}" = "1" ] && echo
-
- if [ ${rc} -eq ${expected} ]; then
- nsuccess=$((nsuccess+1))
- printf "TEST: %-70s [ OK ]\n" "${msg}"
- else
- nfail=$((nfail+1))
- printf "TEST: %-70s [FAIL]\n" "${msg}"
- echo " expected rc $expected; actual rc $rc"
- if [ "${PAUSE_ON_FAIL}" = "yes" ]; then
- echo
- echo "hit enter to continue, 'q' to quit"
- read ans
- [ "$ans" = "q" ] && exit 1
- fi
- fi
-
- if [ "${PAUSE}" = "yes" ]; then
- echo
- echo "hit enter to continue, 'q' to quit"
- read ans
- [ "$ans" = "q" ] && exit 1
- fi
+ log_test_expected "$1" "$2" "$3"
kill_procs
}
diff --git a/tools/testing/selftests/net/fdb_flush.sh b/tools/testing/selftests/net/fdb_flush.sh
index 9931a1e36e3d..4965e52d5ef5 100755
--- a/tools/testing/selftests/net/fdb_flush.sh
+++ b/tools/testing/selftests/net/fdb_flush.sh
@@ -67,40 +67,7 @@ run_cmd()
log_test()
{
- local rc=$1
- local expected=$2
- local msg="$3"
- local nsuccess
- local nfail
- local ret
-
- if [ ${rc} -eq ${expected} ]; then
- printf "TEST: %-60s [ OK ]\n" "${msg}"
- nsuccess=$((nsuccess+1))
- else
- ret=1
- nfail=$((nfail+1))
- printf "TEST: %-60s [FAIL]\n" "${msg}"
- if [ "$VERBOSE" = "1" ]; then
- echo " rc=$rc, expected $expected"
- fi
-
- if [ "${PAUSE_ON_FAIL}" = "yes" ]; then
- echo
- echo "hit enter to continue, 'q' to quit"
- read a
- [ "$a" = "q" ] && exit 1
- fi
- fi
-
- if [ "${PAUSE}" = "yes" ]; then
- echo
- echo "hit enter to continue, 'q' to quit"
- read a
- [ "$a" = "q" ] && exit 1
- fi
-
- [ "$VERBOSE" = "1" ] && echo
+ log_test_expected "$1" "$2" "$3"
}
MAC_POOL_1="
diff --git a/tools/testing/selftests/net/fib-onlink-tests.sh b/tools/testing/selftests/net/fib-onlink-tests.sh
index e0d45292a298..a26075abf7d2 100755
--- a/tools/testing/selftests/net/fib-onlink-tests.sh
+++ b/tools/testing/selftests/net/fib-onlink-tests.sh
@@ -85,23 +85,7 @@ PBR_TABLE=101
log_test()
{
- local rc=$1
- local expected=$2
- local msg="$3"
-
- if [ ${rc} -eq ${expected} ]; then
- nsuccess=$((nsuccess+1))
- printf " TEST: %-50s [ OK ]\n" "${msg}"
- else
- nfail=$((nfail+1))
- printf " TEST: %-50s [FAIL]\n" "${msg}"
- if [ "${PAUSE_ON_FAIL}" = "yes" ]; then
- echo
- echo "hit enter to continue, 'q' to quit"
- read a
- [ "$a" = "q" ] && exit 1
- fi
- fi
+ log_test_expected "$1" "$2" "$3"
}
log_section()
diff --git a/tools/testing/selftests/net/fib_nexthop_multiprefix.sh b/tools/testing/selftests/net/fib_nexthop_multiprefix.sh
index e85248609af4..bd0b8a053ea1 100755
--- a/tools/testing/selftests/net/fib_nexthop_multiprefix.sh
+++ b/tools/testing/selftests/net/fib_nexthop_multiprefix.sh
@@ -23,26 +23,7 @@ which ping6 > /dev/null 2>&1 && ping6=$(which ping6) || ping6=$(which ping)
log_test()
{
- local rc=$1
- local expected=$2
- local msg="$3"
-
- if [ ${rc} -eq ${expected} ]; then
- printf "TEST: %-60s [ OK ]\n" "${msg}"
- nsuccess=$((nsuccess+1))
- else
- ret=1
- nfail=$((nfail+1))
- printf "TEST: %-60s [FAIL]\n" "${msg}"
- if [ "${PAUSE_ON_FAIL}" = "yes" ]; then
- echo
- echo "hit enter to continue, 'q' to quit"
- read a
- [ "$a" = "q" ] && exit 1
- fi
- fi
-
- [ "$VERBOSE" = "1" ] && echo
+ log_test_expected "$1" "$2" "$3"
}
run_cmd()
diff --git a/tools/testing/selftests/net/fib_nexthop_nongw.sh b/tools/testing/selftests/net/fib_nexthop_nongw.sh
index 1ccf56f10171..4d483cb83593 100755
--- a/tools/testing/selftests/net/fib_nexthop_nongw.sh
+++ b/tools/testing/selftests/net/fib_nexthop_nongw.sh
@@ -18,26 +18,7 @@ ret=0
log_test()
{
- local rc=$1
- local expected=$2
- local msg="$3"
-
- if [ ${rc} -eq ${expected} ]; then
- printf "TEST: %-60s [ OK ]\n" "${msg}"
- nsuccess=$((nsuccess+1))
- else
- ret=1
- nfail=$((nfail+1))
- printf "TEST: %-60s [FAIL]\n" "${msg}"
- if [ "${PAUSE_ON_FAIL}" = "yes" ]; then
- echo
- echo "hit enter to continue, 'q' to quit"
- read a
- [ "$a" = "q" ] && exit 1
- fi
- fi
-
- [ "$VERBOSE" = "1" ] && echo
+ log_test_expected "$1" "$2" "$3"
}
run_cmd()
diff --git a/tools/testing/selftests/net/fib_rule_tests.sh b/tools/testing/selftests/net/fib_rule_tests.sh
index 5fbdd2a0b537..5d66b00e61b2 100755
--- a/tools/testing/selftests/net/fib_rule_tests.sh
+++ b/tools/testing/selftests/net/fib_rule_tests.sh
@@ -31,24 +31,7 @@ SELFTEST_PATH=""
log_test()
{
- local rc=$1
- local expected=$2
- local msg="$3"
-
- if [ ${rc} -eq ${expected} ]; then
- nsuccess=$((nsuccess+1))
- printf " TEST: %-60s [ OK ]\n" "${msg}"
- else
- ret=1
- nfail=$((nfail+1))
- printf " TEST: %-60s [FAIL]\n" "${msg}"
- if [ "${PAUSE_ON_FAIL}" = "yes" ]; then
- echo
- echo "hit enter to continue, 'q' to quit"
- read a
- [ "$a" = "q" ] && exit 1
- fi
- fi
+ log_test_expected "$1" "$2" "$3"
}
setup()
diff --git a/tools/testing/selftests/net/fib_tests.sh b/tools/testing/selftests/net/fib_tests.sh
index b338bfb196a2..7c47d7fe250d 100755
--- a/tools/testing/selftests/net/fib_tests.sh
+++ b/tools/testing/selftests/net/fib_tests.sh
@@ -24,31 +24,7 @@ which ping6 > /dev/null 2>&1 && ping6=$(which ping6) || ping6=$(which ping)
log_test()
{
- local rc=$1
- local expected=$2
- local msg="$3"
-
- if [ ${rc} -eq ${expected} ]; then
- printf " TEST: %-60s [ OK ]\n" "${msg}"
- nsuccess=$((nsuccess+1))
- else
- ret=1
- nfail=$((nfail+1))
- printf " TEST: %-60s [FAIL]\n" "${msg}"
- if [ "${PAUSE_ON_FAIL}" = "yes" ]; then
- echo
- echo "hit enter to continue, 'q' to quit"
- read a
- [ "$a" = "q" ] && exit 1
- fi
- fi
-
- if [ "${PAUSE}" = "yes" ]; then
- echo
- echo "hit enter to continue, 'q' to quit"
- read a
- [ "$a" = "q" ] && exit 1
- fi
+ log_test_expected "$1" "$2" "$3"
}
setup()
@@ -369,8 +345,6 @@ fib_carrier_local_test()
fib_carrier_unicast_test()
{
- ret=0
-
echo
echo "Single path route carrier test"
@@ -685,12 +659,12 @@ fib6_notify_test()
err=`cat errors.txt |grep "Message too long"`
if [ -z "$err" ];then
- ret=0
+ RET=0
else
- ret=1
+ RET=1
fi
- log_test $ret 0 "ipv6 route add notify"
+ log_test "$RET" 0 "ipv6 route add notify"
kill_process %%
@@ -732,12 +706,12 @@ fib_notify_test()
err=`cat errors.txt |grep "Message too long"`
if [ -z "$err" ];then
- ret=0
+ RET=0
else
- ret=1
+ RET=1
fi
- log_test $ret 0 "ipv4 route add notify"
+ log_test "$RET" 0 "ipv4 route add notify"
kill_process %%
@@ -763,9 +737,9 @@ check_rt_num()
if [ $num -ne $expected ]; then
echo "FAIL: Expected $expected routes, got $num"
- ret=1
+ RET=1
else
- ret=0
+ RET=0
fi
}
@@ -812,7 +786,7 @@ fib6_gc_test()
sleep $GC_WAIT_TIME
$NS_EXEC sysctl -wq net.ipv6.route.flush=1
check_rt_num 0 $($IP -6 route list |grep expires|wc -l)
- log_test $ret 0 "ipv6 route garbage collection"
+ log_test "$RET" 0 "ipv6 route garbage collection"
reset_dummy_10
@@ -830,7 +804,7 @@ fib6_gc_test()
# Wait for GC
sleep $GC_WAIT_TIME
check_rt_num 0 $($IP -6 route list |grep expires|wc -l)
- log_test $ret 0 "ipv6 route garbage collection (with permanent routes)"
+ log_test "$RET" 0 "ipv6 route garbage collection (with permanent routes)"
reset_dummy_10
@@ -848,7 +822,7 @@ fib6_gc_test()
# Wait for GC
sleep $GC_WAIT_TIME
check_rt_num 0 $($IP -6 route list |grep expires|wc -l)
- log_test $ret 0 "ipv6 route garbage collection (replace with expires)"
+ log_test "$RET" 0 "ipv6 route garbage collection (replace with expires)"
reset_dummy_10
@@ -868,7 +842,7 @@ fib6_gc_test()
# Wait for GC
sleep $GC_WAIT_TIME
check_rt_num 5 $($IP -6 route list |grep -v expires|grep 2001:20::|wc -l)
- log_test $ret 0 "ipv6 route garbage collection (replace with permanent)"
+ log_test "$RET" 0 "ipv6 route garbage collection (replace with permanent)"
# Delete dummy_10 and remove all routes
$IP link del dev dummy_10
@@ -923,7 +897,7 @@ fib6_gc_test()
# rt6_nh_dump_exceptions() just skips expired exceptions.
$NS_EXEC sysctl -wq net.ipv6.route.flush=1
check_rt_num 0 $($IP -6 route list cache | grep 2001:10:: | wc -l)
- log_test $ret 0 "ipv6 route garbage collection (promote to permanent routes)"
+ log_test "$RET" 0 "ipv6 route garbage collection (promote to permanent routes)"
$IP neigh del fe80:dead::3 lladdr 00:11:22:33:44:55 dev veth1 router
$IP link del veth1
@@ -960,7 +934,7 @@ fib6_gc_test()
# Wait for GC
sleep $GC_WAIT_TIME
check_rt_num 0 $($IP -6 route list |grep expires|wc -l)
- log_test $ret 0 "ipv6 route garbage collection (RA message)"
+ log_test "$RET" 0 "ipv6 route garbage collection (RA message)"
set +e
@@ -1589,7 +1563,7 @@ fib6_ra_to_static()
# Expire is back, on-link route is now owned by RA again
check_rt_num 2 $($IP -6 route list |grep expires|wc -l)
- log_test $ret 0 "ipv6 promote RA route to static"
+ log_test "$RET" 0 "ipv6 promote RA route to static"
# Prepare for RA route with gateway
$NS_EXEC sysctl -wq net.ipv6.conf.veth1.accept_ra_rt_info_max_plen=64
@@ -1606,7 +1580,7 @@ fib6_ra_to_static()
check_rt_num 2 "$($IP -6 route list | grep -c "nexthop via")"
- log_test "$ret" 0 "ipv6 RA route with nexthop do not merge into ECMP with static"
+ log_test "$RET" 0 "ipv6 RA route with nexthop do not merge into ECMP with static"
set +e
@@ -1651,18 +1625,18 @@ fib6_temp_addr_renewal() {
# Restore it
$NS_EXEC ra6 -i veth2 -s fe80::1 -d ff02::1 -P 2001:12::/64\#LA\#3600\#3600 -e
- ret=1
+ RET=1
for i in $(seq 1 25); do
sleep 1
num_dep="$($IP -6 addr | grep -c "temporary deprecated" || true)"
num_tot="$($IP -6 addr | grep -c "temporary" || true)"
if [ "$num_dep" -eq 1 ] && [ "$num_tot" -ge 2 ]; then
- ret=0
+ RET=0
break
fi
done
- log_test "$ret" 0 "IPv6 temporary address cleanly deprecated and regenerated"
+ log_test "$RET" 0 "IPv6 temporary address cleanly deprecated and regenerated"
set +e
diff --git a/tools/testing/selftests/net/gre_gso.sh b/tools/testing/selftests/net/gre_gso.sh
index 5100d90f92d2..4ebe1ed6e9c9 100755
--- a/tools/testing/selftests/net/gre_gso.sh
+++ b/tools/testing/selftests/net/gre_gso.sh
@@ -16,31 +16,7 @@ PID=
log_test()
{
- local rc=$1
- local expected=$2
- local msg="$3"
-
- if [ ${rc} -eq ${expected} ]; then
- printf " TEST: %-60s [ OK ]\n" "${msg}"
- nsuccess=$((nsuccess+1))
- else
- ret=1
- nfail=$((nfail+1))
- printf " TEST: %-60s [FAIL]\n" "${msg}"
- if [ "${PAUSE_ON_FAIL}" = "yes" ]; then
- echo
- echo "hit enter to continue, 'q' to quit"
- read a
- [ "$a" = "q" ] && exit 1
- fi
- fi
-
- if [ "${PAUSE}" = "yes" ]; then
- echo
- echo "hit enter to continue, 'q' to quit"
- read a
- [ "$a" = "q" ] && exit 1
- fi
+ log_test_expected "$1" "$2" "$3"
}
setup()
diff --git a/tools/testing/selftests/net/icmp_redirect.sh b/tools/testing/selftests/net/icmp_redirect.sh
index 35357d02e823..e724f895ae2c 100755
--- a/tools/testing/selftests/net/icmp_redirect.sh
+++ b/tools/testing/selftests/net/icmp_redirect.sh
@@ -61,24 +61,7 @@ log_section()
log_test()
{
- local rc=$1
- local expected=$2
- local msg="$3"
-
- if [ ${rc} -eq ${expected} ]; then
- printf "TEST: %-60s [ OK ]\n" "${msg}"
- nsuccess=$((nsuccess+1))
- else
- ret=1
- nfail=$((nfail+1))
- printf "TEST: %-60s [FAIL]\n" "${msg}"
- if [ "${PAUSE_ON_FAIL}" = "yes" ]; then
- echo
- echo "hit enter to continue, 'q' to quit"
- read a
- [ "$a" = "q" ] && exit 1
- fi
- fi
+ log_test_expected "$1" "$2" "$3"
}
log_debug()
diff --git a/tools/testing/selftests/net/l2tp.sh b/tools/testing/selftests/net/l2tp.sh
index 88de7166c8ae..41e8b19d1bd3 100755
--- a/tools/testing/selftests/net/l2tp.sh
+++ b/tools/testing/selftests/net/l2tp.sh
@@ -23,24 +23,7 @@ which ping6 > /dev/null 2>&1 && ping6=$(which ping6) || ping6=$(which ping)
#
log_test()
{
- local rc=$1
- local expected=$2
- local msg="$3"
-
- if [ ${rc} -eq ${expected} ]; then
- printf "TEST: %-60s [ OK ]\n" "${msg}"
- nsuccess=$((nsuccess+1))
- else
- ret=1
- nfail=$((nfail+1))
- printf "TEST: %-60s [FAIL]\n" "${msg}"
- if [ "${PAUSE_ON_FAIL}" = "yes" ]; then
- echo
- echo "hit enter to continue, 'q' to quit"
- read a
- [ "$a" = "q" ] && exit 1
- fi
- fi
+ log_test_expected "$1" "$2" "$3"
}
run_cmd()
diff --git a/tools/testing/selftests/net/lib.sh b/tools/testing/selftests/net/lib.sh
index d46d2cec89e4..72a0b870f81f 100644
--- a/tools/testing/selftests/net/lib.sh
+++ b/tools/testing/selftests/net/lib.sh
@@ -454,6 +454,40 @@ log_test_xfail()
RET=$ksft_xfail retmsg= log_test "$@"
}
+# Log test result with expected return value
+log_test_expected()
+{
+ local rc=$1
+ local expected=$2
+ local msg="$3"
+ local a
+
+ if [ "${rc}" -eq "${expected}" ]; then
+ nsuccess=$((nsuccess+1))
+ printf " TEST: %-60s [ OK ]\n" "${msg}"
+ else
+ ret="$ksft_fail"
+ nfail=$((nfail+1))
+ printf " TEST: %-60s [FAIL]\n" "${msg}"
+ if [ "$VERBOSE" = "1" ]; then
+ echo " rc=$rc, expected $expected"
+ fi
+
+ pause_on_fail || true
+ fi
+
+ if [ "${PAUSE}" = "yes" ]; then
+ echo
+ echo "hit enter to continue, 'q' to quit"
+ read -r a
+ [ "$a" = "q" ] && exit 1
+ fi
+
+ [ "$VERBOSE" = "1" ] && echo
+
+ return 0
+}
+
log_info()
{
local msg=$1
diff --git a/tools/testing/selftests/net/ndisc_unsolicited_na_test.sh b/tools/testing/selftests/net/ndisc_unsolicited_na_test.sh
index ba9e670b5149..6e63a5eb1ebb 100755
--- a/tools/testing/selftests/net/ndisc_unsolicited_na_test.sh
+++ b/tools/testing/selftests/net/ndisc_unsolicited_na_test.sh
@@ -40,31 +40,7 @@ tcpdump_pid=
log_test()
{
- local rc=$1
- local expected=$2
- local msg="$3"
-
- if [ ${rc} -eq ${expected} ]; then
- printf " TEST: %-60s [ OK ]\n" "${msg}"
- nsuccess=$((nsuccess+1))
- else
- ret=1
- nfail=$((nfail+1))
- printf " TEST: %-60s [FAIL]\n" "${msg}"
- if [ "${PAUSE_ON_FAIL}" = "yes" ]; then
- echo
- echo "hit enter to continue, 'q' to quit"
- read a
- [ "$a" = "q" ] && exit 1
- fi
- fi
-
- if [ "${PAUSE}" = "yes" ]; then
- echo
- echo "hit enter to continue, 'q' to quit"
- read a
- [ "$a" = "q" ] && exit 1
- fi
+ log_test_expected "$1" "$2" "$3"
}
setup()
diff --git a/tools/testing/selftests/net/srv6_encap_lookup_l3vpn_test.sh b/tools/testing/selftests/net/srv6_encap_lookup_l3vpn_test.sh
index d6249303b7ea..8241de6827de 100755
--- a/tools/testing/selftests/net/srv6_encap_lookup_l3vpn_test.sh
+++ b/tools/testing/selftests/net/srv6_encap_lookup_l3vpn_test.sh
@@ -202,24 +202,7 @@ PAUSE_ON_FAIL=${PAUSE_ON_FAIL:=no}
log_test()
{
- local rc="$1"
- local expected="$2"
- local msg="$3"
-
- if [ "${rc}" -eq "${expected}" ]; then
- nsuccess=$((nsuccess+1))
- printf "\n TEST: %-60s [ OK ]\n" "${msg}"
- else
- ret=1
- nfail=$((nfail+1))
- printf "\n TEST: %-60s [FAIL]\n" "${msg}"
- if [ "${PAUSE_ON_FAIL}" = "yes" ]; then
- echo
- echo "hit enter to continue, 'q' to quit"
- read -r a
- [ "$a" = "q" ] && exit 1
- fi
- fi
+ log_test_expected "$1" "$2" "$3"
}
print_log_test_results()
diff --git a/tools/testing/selftests/net/srv6_end_dt46_l3vpn_test.sh b/tools/testing/selftests/net/srv6_end_dt46_l3vpn_test.sh
index 50e37d3217ea..900a2ae42335 100755
--- a/tools/testing/selftests/net/srv6_end_dt46_l3vpn_test.sh
+++ b/tools/testing/selftests/net/srv6_end_dt46_l3vpn_test.sh
@@ -208,24 +208,7 @@ PAUSE_ON_FAIL=${PAUSE_ON_FAIL:=no}
log_test()
{
- local rc=$1
- local expected=$2
- local msg="$3"
-
- if [ ${rc} -eq ${expected} ]; then
- nsuccess=$((nsuccess+1))
- printf "\n TEST: %-60s [ OK ]\n" "${msg}"
- else
- ret=1
- nfail=$((nfail+1))
- printf "\n TEST: %-60s [FAIL]\n" "${msg}"
- if [ "${PAUSE_ON_FAIL}" = "yes" ]; then
- echo
- echo "hit enter to continue, 'q' to quit"
- read a
- [ "$a" = "q" ] && exit 1
- fi
- fi
+ log_test_expected "$1" "$2" "$3"
}
print_log_test_results()
diff --git a/tools/testing/selftests/net/srv6_end_dt4_l3vpn_test.sh b/tools/testing/selftests/net/srv6_end_dt4_l3vpn_test.sh
index 037e5fe1da2a..260170dc8443 100755
--- a/tools/testing/selftests/net/srv6_end_dt4_l3vpn_test.sh
+++ b/tools/testing/selftests/net/srv6_end_dt4_l3vpn_test.sh
@@ -177,24 +177,7 @@ PAUSE_ON_FAIL=${PAUSE_ON_FAIL:=no}
log_test()
{
- local rc=$1
- local expected=$2
- local msg="$3"
-
- if [ ${rc} -eq ${expected} ]; then
- nsuccess=$((nsuccess+1))
- printf "\n TEST: %-60s [ OK ]\n" "${msg}"
- else
- ret=1
- nfail=$((nfail+1))
- printf "\n TEST: %-60s [FAIL]\n" "${msg}"
- if [ "${PAUSE_ON_FAIL}" = "yes" ]; then
- echo
- echo "hit enter to continue, 'q' to quit"
- read a
- [ "$a" = "q" ] && exit 1
- fi
- fi
+ log_test_expected "$1" "$2" "$3"
}
print_log_test_results()
diff --git a/tools/testing/selftests/net/srv6_end_dt6_l3vpn_test.sh b/tools/testing/selftests/net/srv6_end_dt6_l3vpn_test.sh
index 9a29e0d6c912..6d4d6a23ecc4 100755
--- a/tools/testing/selftests/net/srv6_end_dt6_l3vpn_test.sh
+++ b/tools/testing/selftests/net/srv6_end_dt6_l3vpn_test.sh
@@ -178,24 +178,7 @@ PAUSE_ON_FAIL=${PAUSE_ON_FAIL:=no}
log_test()
{
- local rc=$1
- local expected=$2
- local msg="$3"
-
- if [ ${rc} -eq ${expected} ]; then
- nsuccess=$((nsuccess+1))
- printf "\n TEST: %-60s [ OK ]\n" "${msg}"
- else
- ret=1
- nfail=$((nfail+1))
- printf "\n TEST: %-60s [FAIL]\n" "${msg}"
- if [ "${PAUSE_ON_FAIL}" = "yes" ]; then
- echo
- echo "hit enter to continue, 'q' to quit"
- read a
- [ "$a" = "q" ] && exit 1
- fi
- fi
+ log_test_expected "$1" "$2" "$3"
}
print_log_test_results()
diff --git a/tools/testing/selftests/net/srv6_end_dx4_netfilter_test.sh b/tools/testing/selftests/net/srv6_end_dx4_netfilter_test.sh
index e23210aa547f..aeddbef536d6 100755
--- a/tools/testing/selftests/net/srv6_end_dx4_netfilter_test.sh
+++ b/tools/testing/selftests/net/srv6_end_dx4_netfilter_test.sh
@@ -111,8 +111,8 @@
# +---------------------------------------------------+
#
-# Kselftest framework requirement - SKIP code is 4.
-ksft_skip=4
+# shellcheck source=lib.sh
+source lib.sh
readonly IPv6_RT_NETWORK=2001:11
readonly IPv4_HS_NETWORK=10.0.0
@@ -126,24 +126,7 @@ PAUSE_ON_FAIL=${PAUSE_ON_FAIL:=no}
log_test()
{
- local rc=$1
- local expected=$2
- local msg="$3"
-
- if [ ${rc} -eq ${expected} ]; then
- nsuccess=$((nsuccess+1))
- printf "\n TEST: %-60s [ OK ]\n" "${msg}"
- else
- ret=1
- nfail=$((nfail+1))
- printf "\n TEST: %-60s [FAIL]\n" "${msg}"
- if [ "${PAUSE_ON_FAIL}" = "yes" ]; then
- echo
- echo "hit enter to continue, 'q' to quit"
- read a
- [ "$a" = "q" ] && exit 1
- fi
- fi
+ log_test_expected "$1" "$2" "$3"
}
print_log_test_results()
diff --git a/tools/testing/selftests/net/srv6_end_dx6_netfilter_test.sh b/tools/testing/selftests/net/srv6_end_dx6_netfilter_test.sh
index 9e69a2ed5bc3..8fbeda1372e1 100755
--- a/tools/testing/selftests/net/srv6_end_dx6_netfilter_test.sh
+++ b/tools/testing/selftests/net/srv6_end_dx6_netfilter_test.sh
@@ -111,8 +111,8 @@
# +---------------------------------------------------+
#
-# Kselftest framework requirement - SKIP code is 4.
-ksft_skip=4
+# shellcheck source=lib.sh
+source lib.sh
readonly IPv6_RT_NETWORK=2001:11
readonly IPv6_HS_NETWORK=cafe
@@ -126,24 +126,7 @@ PAUSE_ON_FAIL=${PAUSE_ON_FAIL:=no}
log_test()
{
- local rc=$1
- local expected=$2
- local msg="$3"
-
- if [ ${rc} -eq ${expected} ]; then
- nsuccess=$((nsuccess+1))
- printf "\n TEST: %-60s [ OK ]\n" "${msg}"
- else
- ret=1
- nfail=$((nfail+1))
- printf "\n TEST: %-60s [FAIL]\n" "${msg}"
- if [ "${PAUSE_ON_FAIL}" = "yes" ]; then
- echo
- echo "hit enter to continue, 'q' to quit"
- read a
- [ "$a" = "q" ] && exit 1
- fi
- fi
+ log_test_expected "$1" "$2" "$3"
}
print_log_test_results()
diff --git a/tools/testing/selftests/net/srv6_end_flavors_test.sh b/tools/testing/selftests/net/srv6_end_flavors_test.sh
index 318487eda671..30a939392eea 100755
--- a/tools/testing/selftests/net/srv6_end_flavors_test.sh
+++ b/tools/testing/selftests/net/srv6_end_flavors_test.sh
@@ -194,8 +194,8 @@
# after the IPv6 header. At this point, the packet with IPv6 DA=cafe::1 is sent
# to the destination, i.e. hs-1.
-# Kselftest framework requirement - SKIP code is 4.
-readonly ksft_skip=4
+# shellcheck source=lib.sh
+source lib.sh
readonly RDMSUFF="$(mktemp -u XXXXXXXX)"
readonly DUMMY_DEVNAME="dum0"
@@ -224,24 +224,7 @@ nfail=0
log_test()
{
- local rc="$1"
- local expected="$2"
- local msg="$3"
-
- if [ "${rc}" -eq "${expected}" ]; then
- nsuccess=$((nsuccess+1))
- printf "\n TEST: %-60s [ OK ]\n" "${msg}"
- else
- ret=1
- nfail=$((nfail+1))
- printf "\n TEST: %-60s [FAIL]\n" "${msg}"
- if [ "${PAUSE_ON_FAIL}" = "yes" ]; then
- echo
- echo "hit enter to continue, 'q' to quit"
- read a
- [ "$a" = "q" ] && exit 1
- fi
- fi
+ log_test_expected "$1" "$2" "$3"
}
print_log_test_results()
diff --git a/tools/testing/selftests/net/srv6_end_next_csid_l3vpn_test.sh b/tools/testing/selftests/net/srv6_end_next_csid_l3vpn_test.sh
index 4bc135e5c22c..2e2ae21974ae 100755
--- a/tools/testing/selftests/net/srv6_end_next_csid_l3vpn_test.sh
+++ b/tools/testing/selftests/net/srv6_end_next_csid_l3vpn_test.sh
@@ -323,24 +323,7 @@ nfail=0
log_test()
{
- local rc="$1"
- local expected="$2"
- local msg="$3"
-
- if [ "${rc}" -eq "${expected}" ]; then
- nsuccess=$((nsuccess+1))
- printf "\n TEST: %-60s [ OK ]\n" "${msg}"
- else
- ret=1
- nfail=$((nfail+1))
- printf "\n TEST: %-60s [FAIL]\n" "${msg}"
- if [ "${PAUSE_ON_FAIL}" = "yes" ]; then
- echo
- echo "hit enter to continue, 'q' to quit"
- read a
- [ "$a" = "q" ] && exit 1
- fi
- fi
+ log_test_expected "$1" "$2" "$3"
}
print_log_test_results()
diff --git a/tools/testing/selftests/net/srv6_end_x_next_csid_l3vpn_test.sh b/tools/testing/selftests/net/srv6_end_x_next_csid_l3vpn_test.sh
index 34b781a2ae74..b492a7f0297f 100755
--- a/tools/testing/selftests/net/srv6_end_x_next_csid_l3vpn_test.sh
+++ b/tools/testing/selftests/net/srv6_end_x_next_csid_l3vpn_test.sh
@@ -368,24 +368,7 @@ nfail=0
log_test()
{
- local rc="$1"
- local expected="$2"
- local msg="$3"
-
- if [ "${rc}" -eq "${expected}" ]; then
- nsuccess=$((nsuccess+1))
- printf "\n TEST: %-60s [ OK ]\n" "${msg}"
- else
- ret=1
- nfail=$((nfail+1))
- printf "\n TEST: %-60s [FAIL]\n" "${msg}"
- if [ "${PAUSE_ON_FAIL}" = "yes" ]; then
- echo
- echo "hit enter to continue, 'q' to quit"
- read a
- [ "$a" = "q" ] && exit 1
- fi
- fi
+ log_test_expected "$1" "$2" "$3"
}
print_log_test_results()
diff --git a/tools/testing/selftests/net/srv6_hencap_red_l3vpn_test.sh b/tools/testing/selftests/net/srv6_hencap_red_l3vpn_test.sh
index cd7d061e21f8..64ea4e2308b6 100755
--- a/tools/testing/selftests/net/srv6_hencap_red_l3vpn_test.sh
+++ b/tools/testing/selftests/net/srv6_hencap_red_l3vpn_test.sh
@@ -197,24 +197,7 @@ HAS_TUNSRC=false
log_test()
{
- local rc="$1"
- local expected="$2"
- local msg="$3"
-
- if [ "${rc}" -eq "${expected}" ]; then
- nsuccess=$((nsuccess+1))
- printf "\n TEST: %-60s [ OK ]\n" "${msg}"
- else
- ret=1
- nfail=$((nfail+1))
- printf "\n TEST: %-60s [FAIL]\n" "${msg}"
- if [ "${PAUSE_ON_FAIL}" = "yes" ]; then
- echo
- echo "hit enter to continue, 'q' to quit"
- read a
- [ "$a" = "q" ] && exit 1
- fi
- fi
+ log_test_expected "$1" "$2" "$3"
}
print_log_test_results()
diff --git a/tools/testing/selftests/net/srv6_hl2encap_red_l2vpn_test.sh b/tools/testing/selftests/net/srv6_hl2encap_red_l2vpn_test.sh
index 0979b5316fdf..31e41cc4922e 100755
--- a/tools/testing/selftests/net/srv6_hl2encap_red_l2vpn_test.sh
+++ b/tools/testing/selftests/net/srv6_hl2encap_red_l2vpn_test.sh
@@ -146,24 +146,7 @@ nfail=0
log_test()
{
- local rc="$1"
- local expected="$2"
- local msg="$3"
-
- if [ "${rc}" -eq "${expected}" ]; then
- nsuccess=$((nsuccess+1))
- printf "\n TEST: %-60s [ OK ]\n" "${msg}"
- else
- ret=1
- nfail=$((nfail+1))
- printf "\n TEST: %-60s [FAIL]\n" "${msg}"
- if [ "${PAUSE_ON_FAIL}" = "yes" ]; then
- echo
- echo "hit enter to continue, 'q' to quit"
- read a
- [ "$a" = "q" ] && exit 1
- fi
- fi
+ log_test_expected "$1" "$2" "$3"
}
print_log_test_results()
diff --git a/tools/testing/selftests/net/test_bridge_backup_port.sh b/tools/testing/selftests/net/test_bridge_backup_port.sh
index 2a7224fe74f2..8bc76be2b2d3 100755
--- a/tools/testing/selftests/net/test_bridge_backup_port.sh
+++ b/tools/testing/selftests/net/test_bridge_backup_port.sh
@@ -56,37 +56,7 @@ PING_TIMEOUT=5
log_test()
{
- local rc=$1
- local expected=$2
- local msg="$3"
-
- if [ ${rc} -eq ${expected} ]; then
- printf "TEST: %-60s [ OK ]\n" "${msg}"
- nsuccess=$((nsuccess+1))
- else
- ret=1
- nfail=$((nfail+1))
- printf "TEST: %-60s [FAIL]\n" "${msg}"
- if [ "$VERBOSE" = "1" ]; then
- echo " rc=$rc, expected $expected"
- fi
-
- if [ "${PAUSE_ON_FAIL}" = "yes" ]; then
- echo
- echo "hit enter to continue, 'q' to quit"
- read a
- [ "$a" = "q" ] && exit 1
- fi
- fi
-
- if [ "${PAUSE}" = "yes" ]; then
- echo
- echo "hit enter to continue, 'q' to quit"
- read a
- [ "$a" = "q" ] && exit 1
- fi
-
- [ "$VERBOSE" = "1" ] && echo
+ log_test_expected "$1" "$2" "$3"
}
run_cmd()
diff --git a/tools/testing/selftests/net/test_bridge_neigh_suppress.sh b/tools/testing/selftests/net/test_bridge_neigh_suppress.sh
index e9ed0d750996..9d2dc0faf741 100755
--- a/tools/testing/selftests/net/test_bridge_neigh_suppress.sh
+++ b/tools/testing/selftests/net/test_bridge_neigh_suppress.sh
@@ -72,39 +72,7 @@ PAUSE=no
log_test()
{
- local rc=$1
- local expected=$2
- local msg="$3"
-
- if [ ${rc} -eq ${expected} ]; then
- printf "TEST: %-60s [ OK ]\n" "${msg}"
- nsuccess=$((nsuccess+1))
- else
- # shellcheck disable=SC2154
- ret=$(ksft_exit_status_merge "$ret" "$ksft_fail")
- nfail=$((nfail+1))
- printf "TEST: %-60s [FAIL]\n" "${msg}"
- if [ "$VERBOSE" = "1" ]; then
- echo " rc=$rc, expected $expected"
- fi
-
- if [ "${PAUSE_ON_FAIL}" = "yes" ]; then
- echo
- echo "hit enter to continue, 'q' to quit"
- read a
- [ "$a" = "q" ] && exit 1
- fi
- fi
-
- if [ "${PAUSE}" = "yes" ]; then
- echo
- echo "hit enter to continue, 'q' to quit"
- read a
- [ "$a" = "q" ] && exit 1
- fi
-
- [ "$VERBOSE" = "1" ] && echo
- return 0
+ log_test_expected "$1" "$2" "$3"
}
run_cmd()
diff --git a/tools/testing/selftests/net/test_vxlan_mdb.sh b/tools/testing/selftests/net/test_vxlan_mdb.sh
index f9600aabd4a2..2ebb4d8a3026 100755
--- a/tools/testing/selftests/net/test_vxlan_mdb.sh
+++ b/tools/testing/selftests/net/test_vxlan_mdb.sh
@@ -133,37 +133,7 @@ PAUSE=no
log_test()
{
- local rc=$1
- local expected=$2
- local msg="$3"
-
- if [ ${rc} -eq ${expected} ]; then
- printf "TEST: %-60s [ OK ]\n" "${msg}"
- nsuccess=$((nsuccess+1))
- else
- ret=1
- nfail=$((nfail+1))
- printf "TEST: %-60s [FAIL]\n" "${msg}"
- if [ "$VERBOSE" = "1" ]; then
- echo " rc=$rc, expected $expected"
- fi
-
- if [ "${PAUSE_ON_FAIL}" = "yes" ]; then
- echo
- echo "hit enter to continue, 'q' to quit"
- read a
- [ "$a" = "q" ] && exit 1
- fi
- fi
-
- if [ "${PAUSE}" = "yes" ]; then
- echo
- echo "hit enter to continue, 'q' to quit"
- read a
- [ "$a" = "q" ] && exit 1
- fi
-
- [ "$VERBOSE" = "1" ] && echo
+ log_test_expected "$1" "$2" "$3"
}
run_cmd()
diff --git a/tools/testing/selftests/net/test_vxlan_nolocalbypass.sh b/tools/testing/selftests/net/test_vxlan_nolocalbypass.sh
index b8805983b728..c51ca0b532dd 100755
--- a/tools/testing/selftests/net/test_vxlan_nolocalbypass.sh
+++ b/tools/testing/selftests/net/test_vxlan_nolocalbypass.sh
@@ -24,37 +24,7 @@ PAUSE=no
log_test()
{
- local rc=$1
- local expected=$2
- local msg="$3"
-
- if [ ${rc} -eq ${expected} ]; then
- printf "TEST: %-60s [ OK ]\n" "${msg}"
- nsuccess=$((nsuccess+1))
- else
- ret=1
- nfail=$((nfail+1))
- printf "TEST: %-60s [FAIL]\n" "${msg}"
- if [ "$VERBOSE" = "1" ]; then
- echo " rc=$rc, expected $expected"
- fi
-
- if [ "${PAUSE_ON_FAIL}" = "yes" ]; then
- echo
- echo "hit enter to continue, 'q' to quit"
- read a
- [ "$a" = "q" ] && exit 1
- fi
- fi
-
- if [ "${PAUSE}" = "yes" ]; then
- echo
- echo "hit enter to continue, 'q' to quit"
- read a
- [ "$a" = "q" ] && exit 1
- fi
-
- [ "$VERBOSE" = "1" ] && echo
+ log_test_expected "$1" "$2" "$3"
}
run_cmd()
diff --git a/tools/testing/selftests/net/test_vxlan_vnifiltering.sh b/tools/testing/selftests/net/test_vxlan_vnifiltering.sh
index 8deacc565afa..6fb5af013dac 100755
--- a/tools/testing/selftests/net/test_vxlan_vnifiltering.sh
+++ b/tools/testing/selftests/net/test_vxlan_vnifiltering.sh
@@ -98,31 +98,7 @@ which ping6 > /dev/null 2>&1 && ping6=$(which ping6) || ping6=$(which ping)
log_test()
{
- local rc=$1
- local expected=$2
- local msg="$3"
-
- if [ ${rc} -eq ${expected} ]; then
- printf " TEST: %-60s [ OK ]\n" "${msg}"
- nsuccess=$((nsuccess+1))
- else
- ret=1
- nfail=$((nfail+1))
- printf " TEST: %-60s [FAIL]\n" "${msg}"
- if [ "${PAUSE_ON_FAIL}" = "yes" ]; then
- echo
- echo "hit enter to continue, 'q' to quit"
- read a
- [ "$a" = "q" ] && exit 1
- fi
- fi
-
- if [ "${PAUSE}" = "yes" ]; then
- echo
- echo "hit enter to continue, 'q' to quit"
- read a
- [ "$a" = "q" ] && exit 1
- fi
+ log_test_expected "$1" "$2" "$3"
}
run_cmd()
diff --git a/tools/testing/selftests/net/vrf-xfrm-tests.sh b/tools/testing/selftests/net/vrf-xfrm-tests.sh
index b64dd891699d..a4617e5dace7 100755
--- a/tools/testing/selftests/net/vrf-xfrm-tests.sh
+++ b/tools/testing/selftests/net/vrf-xfrm-tests.sh
@@ -35,24 +35,7 @@ which ping6 > /dev/null 2>&1 && ping6=$(which ping6) || ping6=$(which ping)
#
log_test()
{
- local rc=$1
- local expected=$2
- local msg="$3"
-
- if [ ${rc} -eq ${expected} ]; then
- printf "TEST: %-60s [ OK ]\n" "${msg}"
- nsuccess=$((nsuccess+1))
- else
- ret=1
- nfail=$((nfail+1))
- printf "TEST: %-60s [FAIL]\n" "${msg}"
- if [ "${PAUSE_ON_FAIL}" = "yes" ]; then
- echo
- echo "hit enter to continue, 'q' to quit"
- read a
- [ "$a" = "q" ] && exit 1
- fi
- fi
+ log_test_expected "$1" "$2" "$3"
}
run_cmd_host1()
diff --git a/tools/testing/selftests/net/vrf_route_leaking.sh b/tools/testing/selftests/net/vrf_route_leaking.sh
index ce34cb2e6e0b..abf106e0f0c0 100755
--- a/tools/testing/selftests/net/vrf_route_leaking.sh
+++ b/tools/testing/selftests/net/vrf_route_leaking.sh
@@ -99,24 +99,7 @@ log_section()
log_test()
{
- local rc=$1
- local expected=$2
- local msg="$3"
-
- if [ "${rc}" -eq "${expected}" ]; then
- printf "TEST: %-60s [ OK ]\n" "${msg}"
- nsuccess=$((nsuccess+1))
- else
- ret=1
- nfail=$((nfail+1))
- printf "TEST: %-60s [FAIL]\n" "${msg}"
- if [ "${PAUSE_ON_FAIL}" = "yes" ]; then
- echo
- echo "hit enter to continue, 'q' to quit"
- read -r a
- [ "$a" = "q" ] && exit 1
- fi
- fi
+ log_test_expected "$1" "$2" "$3"
}
run_cmd()
diff --git a/tools/testing/selftests/net/vrf_strict_mode_test.sh b/tools/testing/selftests/net/vrf_strict_mode_test.sh
index 01552b542544..7de1873362ba 100755
--- a/tools/testing/selftests/net/vrf_strict_mode_test.sh
+++ b/tools/testing/selftests/net/vrf_strict_mode_test.sh
@@ -16,24 +16,7 @@ TESTS="init testns mix"
log_test()
{
- local rc=$1
- local expected=$2
- local msg="$3"
-
- if [ ${rc} -eq ${expected} ]; then
- nsuccess=$((nsuccess+1))
- printf "\n TEST: %-60s [ OK ]\n" "${msg}"
- else
- ret=1
- nfail=$((nfail+1))
- printf "\n TEST: %-60s [FAIL]\n" "${msg}"
- if [ "${PAUSE_ON_FAIL}" = "yes" ]; then
- echo
- echo "hit enter to continue, 'q' to quit"
- read a
- [ "$a" = "q" ] && exit 1
- fi
- fi
+ log_test_expected "$1" "$2" "$3"
}
print_log_test_results()
diff --git a/tools/testing/selftests/nfsd/.gitignore b/tools/testing/selftests/nfsd/.gitignore
new file mode 100644
index 000000000000..19e6dec04d8e
--- /dev/null
+++ b/tools/testing/selftests/nfsd/.gitignore
@@ -0,0 +1 @@
+nfsd_netlink_listener
diff --git a/tools/testing/selftests/nfsd/Makefile b/tools/testing/selftests/nfsd/Makefile
new file mode 100644
index 000000000000..15ac65549d25
--- /dev/null
+++ b/tools/testing/selftests/nfsd/Makefile
@@ -0,0 +1,6 @@
+# SPDX-License-Identifier: GPL-2.0
+CFLAGS += $(KHDR_INCLUDES) -Wall
+
+TEST_GEN_PROGS := nfsd_netlink_listener
+
+include ../lib.mk
diff --git a/tools/testing/selftests/nfsd/config b/tools/testing/selftests/nfsd/config
new file mode 100644
index 000000000000..0eef03af3503
--- /dev/null
+++ b/tools/testing/selftests/nfsd/config
@@ -0,0 +1,14 @@
+CONFIG_NAMESPACES=y
+CONFIG_NET_NS=y
+CONFIG_SHMEM=y
+CONFIG_TMPFS=y
+CONFIG_UNIX=y
+CONFIG_INET=y
+CONFIG_IPV6=y
+CONFIG_MULTIUSER=y
+CONFIG_PROC_FS=y
+CONFIG_FILE_LOCKING=y
+CONFIG_INOTIFY_USER=y
+CONFIG_SUNRPC=y
+CONFIG_NFSD=y
+CONFIG_NFSD_V4=y
diff --git a/tools/testing/selftests/nfsd/nfsd_netlink_listener.c b/tools/testing/selftests/nfsd/nfsd_netlink_listener.c
new file mode 100644
index 000000000000..106360f87b99
--- /dev/null
+++ b/tools/testing/selftests/nfsd/nfsd_netlink_listener.c
@@ -0,0 +1,1323 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Regression tests for the NFSD generic-netlink listener interface
+ * (NFSD_CMD_LISTENER_SET / NFSD_CMD_LISTENER_GET).
+ *
+ * Three groups:
+ * validation - malformed/abusive LISTENER_SET requests are rejected by
+ * nfsd_nl_validate_listeners(), before nfsd_mutex is taken.
+ * functional - create/add/remove listeners and verify LISTENER_GET
+ * reflects the set (round-trip of transport + addr:port).
+ * semantics - once threads are running (THREADS_SET) a listener change
+ * is refused with -EBUSY.
+ *
+ * Each test runs in its own private net + mount namespace (unshare in
+ * FIXTURE_SETUP). /run is masked there: a pathname AF_LOCAL connect is not
+ * scoped by the network namespace, since unix_find_bsd() resolves by inode
+ * and takes no struct net, so the kernel's rpcbind client would otherwise be
+ * able to reach the rpcbind running on the host. Anything that creates a
+ * serv is served by the per-netns rpcbind stub below instead.
+ */
+#define _GNU_SOURCE
+#include <errno.h>
+#include <poll.h>
+#include <sched.h>
+#include <signal.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/mman.h>
+#include <sys/mount.h>
+#include <sys/prctl.h>
+#include <sys/socket.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/un.h>
+#include <sys/wait.h>
+#include <net/if.h>
+#include <netinet/in.h>
+#include <linux/netlink.h>
+#include <linux/genetlink.h>
+#include <linux/nfsd_netlink.h>
+
+#include "../kselftest_harness.h"
+
+#define NLA_ALIGN4(len) (((len) + 3) & ~3)
+#define TEST_PORT 20049
+#define MAX_LISTENERS 8
+#define RECV_TIMEO_SEC 30
+
+static int nfsd_family = -1; /* set per-test in FIXTURE_SETUP */
+
+/* Extack message from the last genl_request(); empty if there was none. */
+static char last_extack[128];
+
+static void die(const char *msg)
+{
+ perror(msg);
+ exit(1);
+}
+
+/* ------------------- minimal generic-netlink plumbing ------------------- */
+
+static int genl_open(void)
+{
+ struct sockaddr_nl sa = { .nl_family = AF_NETLINK };
+ struct timeval tv = { .tv_sec = RECV_TIMEO_SEC };
+ int fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_GENERIC);
+ int on = 1;
+
+ if (fd < 0)
+ die("socket(NETLINK_GENERIC)");
+ if (bind(fd, (void *)&sa, sizeof(sa)) < 0)
+ die("bind(netlink)");
+ setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
+ /*
+ * Ask for extack, and cap the ack so the request is not echoed back:
+ * the TLVs then always follow the fixed part of the error message.
+ */
+ setsockopt(fd, SOL_NETLINK, NETLINK_EXT_ACK, &on, sizeof(on));
+ setsockopt(fd, SOL_NETLINK, NETLINK_CAP_ACK, &on, sizeof(on));
+ return fd;
+}
+
+/* Stash the extack message of an ack, if it carries one. */
+static void parse_extack(const char *rbuf)
+{
+ const struct nlmsghdr *nlh = (const void *)rbuf;
+ const struct nlattr *na;
+ int off, left;
+
+ last_extack[0] = '\0';
+ if (nlh->nlmsg_type != NLMSG_ERROR ||
+ !(nlh->nlmsg_flags & NLM_F_ACK_TLVS))
+ return;
+
+ off = NLMSG_HDRLEN + NLMSG_ALIGN(sizeof(struct nlmsgerr));
+ left = nlh->nlmsg_len - off;
+ na = (const void *)(rbuf + off);
+
+ while (left >= (int)NLA_HDRLEN) {
+ if ((na->nla_type & NLA_TYPE_MASK) == NLMSGERR_ATTR_MSG) {
+ strncpy(last_extack, (const char *)na + NLA_HDRLEN,
+ sizeof(last_extack) - 1);
+ last_extack[sizeof(last_extack) - 1] = '\0';
+ return;
+ }
+ left -= NLA_ALIGN4(na->nla_len);
+ na = (const void *)((const char *)na + NLA_ALIGN4(na->nla_len));
+ }
+}
+
+/* Append an attribute at @off; return the new (aligned) offset. */
+static int put_attr(char *buf, int off, uint16_t type,
+ const void *data, int len)
+{
+ struct nlattr *na = (void *)(buf + off);
+
+ na->nla_type = type;
+ na->nla_len = NLA_HDRLEN + len;
+ if (len)
+ memcpy(buf + off + NLA_HDRLEN, data, len);
+ return off + NLA_ALIGN4(NLA_HDRLEN + len);
+}
+
+/* Build a genl message header into @buf; return the offset past it. */
+static int genl_hdr(char *buf, uint16_t type, uint16_t flags, uint8_t cmd)
+{
+ struct nlmsghdr *nlh = (void *)buf;
+ struct genlmsghdr *gnl = (void *)(buf + NLMSG_HDRLEN);
+
+ memset(buf, 0, NLMSG_HDRLEN + GENL_HDRLEN);
+ nlh->nlmsg_type = type;
+ nlh->nlmsg_flags = flags;
+ nlh->nlmsg_seq = 1;
+ gnl->cmd = cmd;
+ gnl->version = 1;
+ return NLMSG_HDRLEN + GENL_HDRLEN;
+}
+
+/* Send an nfsd command with an ACK; return the ACK errno (<= 0). */
+static int genl_request(uint8_t cmd, const char *attrs, int attrs_len)
+{
+ char buf[1 << 20], rbuf[4096];
+ struct nlmsghdr *nlh = (void *)buf;
+ int fd = genl_open();
+ int off, n, ret;
+
+ off = genl_hdr(buf, nfsd_family, NLM_F_REQUEST | NLM_F_ACK, cmd);
+ if (attrs_len) {
+ memcpy(buf + off, attrs, attrs_len);
+ off += attrs_len;
+ }
+ nlh->nlmsg_len = off;
+
+ if (send(fd, buf, off, 0) < 0)
+ die("send(genl)");
+
+ last_extack[0] = '\0';
+ n = recv(fd, rbuf, sizeof(rbuf), 0);
+ if (n < 0) {
+ ret = (errno == EAGAIN || errno == EWOULDBLOCK) ? -ETIMEDOUT : -errno;
+ } else if (((struct nlmsghdr *)rbuf)->nlmsg_type == NLMSG_ERROR) {
+ ret = ((struct nlmsgerr *)NLMSG_DATA(rbuf))->error;
+ parse_extack(rbuf);
+ } else {
+ ret = 0;
+ }
+ close(fd);
+ return ret;
+}
+
+/* Send a command and return the full reply message; -errno on failure. */
+static int genl_request_reply(uint8_t cmd, char *rbuf, size_t rlen)
+{
+ char buf[256];
+ struct nlmsghdr *nlh = (void *)buf;
+ int fd = genl_open();
+ int off, n, ret;
+
+ off = genl_hdr(buf, nfsd_family, NLM_F_REQUEST, cmd);
+ nlh->nlmsg_len = off;
+
+ if (send(fd, buf, off, 0) < 0)
+ die("send(genl reply)");
+
+ n = recv(fd, rbuf, rlen, 0);
+ if (n < 0)
+ ret = (errno == EAGAIN || errno == EWOULDBLOCK) ? -ETIMEDOUT : -errno;
+ else if (((struct nlmsghdr *)rbuf)->nlmsg_type == NLMSG_ERROR)
+ ret = ((struct nlmsgerr *)NLMSG_DATA(rbuf))->error;
+ else
+ ret = n;
+ close(fd);
+ return ret;
+}
+
+/* Resolve the "nfsd" genl family id; -1 if not registered. */
+static int genl_resolve_nfsd(void)
+{
+ char buf[1024], rbuf[4096];
+ struct nlmsghdr *nlh = (void *)buf;
+ struct nlmsghdr *rh = (void *)rbuf;
+ struct nlattr *na;
+ int fd, off, left, id = -1;
+
+ fd = genl_open();
+ off = genl_hdr(buf, GENL_ID_CTRL, NLM_F_REQUEST, CTRL_CMD_GETFAMILY);
+ off = put_attr(buf, off, CTRL_ATTR_FAMILY_NAME,
+ NFSD_FAMILY_NAME, sizeof(NFSD_FAMILY_NAME));
+ nlh->nlmsg_len = off;
+
+ if (send(fd, buf, off, 0) < 0)
+ die("send(GETFAMILY)");
+ if (recv(fd, rbuf, sizeof(rbuf), 0) < 0)
+ die("recv(GETFAMILY)");
+ close(fd);
+
+ if (rh->nlmsg_type == NLMSG_ERROR)
+ return -1;
+
+ na = (void *)((char *)NLMSG_DATA(rh) + GENL_HDRLEN);
+ left = rh->nlmsg_len - NLMSG_HDRLEN - GENL_HDRLEN;
+ while (left >= (int)NLA_HDRLEN) {
+ if (na->nla_type == CTRL_ATTR_FAMILY_ID) {
+ id = *(uint16_t *)((char *)na + NLA_HDRLEN);
+ break;
+ }
+ left -= NLA_ALIGN4(na->nla_len);
+ na = (void *)((char *)na + NLA_ALIGN4(na->nla_len));
+ }
+ return id;
+}
+
+/* ------------------- listener request builders ------------------- */
+
+/* Fine-grained control for negative tests: any field can be omitted/malformed. */
+struct raw_listener {
+ const char *xprt; /* NULL -> omit NFSD_A_SOCK_TRANSPORT_NAME */
+ int emit_addr; /* 0 -> omit NFSD_A_SOCK_ADDR */
+ const void *addr;
+ int addr_len; /* bytes to emit for NFSD_A_SOCK_ADDR */
+};
+
+static int put_raw_listener(char *buf, int off, const struct raw_listener *r)
+{
+ struct nlattr *nest = (void *)(buf + off);
+ int inner = off + NLA_HDRLEN;
+
+ if (r->emit_addr)
+ inner = put_attr(buf, inner, NFSD_A_SOCK_ADDR, r->addr, r->addr_len);
+ if (r->xprt)
+ inner = put_attr(buf, inner, NFSD_A_SOCK_TRANSPORT_NAME,
+ r->xprt, strlen(r->xprt) + 1);
+ nest->nla_type = NFSD_A_SERVER_SOCK_ADDR | NLA_F_NESTED;
+ nest->nla_len = inner - off;
+ return off + NLA_ALIGN4(nest->nla_len);
+}
+
+/* Well-formed loopback listener for @family (AF_INET or AF_INET6). */
+static int put_listener_af(char *buf, int off, const char *xprt, int family,
+ uint16_t port)
+{
+ struct sockaddr_storage ss = {0};
+ struct raw_listener r = { .xprt = xprt, .emit_addr = 1, .addr = &ss };
+
+ if (family == AF_INET6) {
+ struct sockaddr_in6 *s6 = (void *)&ss;
+
+ s6->sin6_family = AF_INET6;
+ s6->sin6_port = htons(port);
+ s6->sin6_addr = in6addr_loopback;
+ r.addr_len = sizeof(*s6);
+ } else {
+ struct sockaddr_in *s4 = (void *)&ss;
+
+ s4->sin_family = AF_INET;
+ s4->sin_port = htons(port);
+ s4->sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+ r.addr_len = sizeof(*s4);
+ }
+ return put_raw_listener(buf, off, &r);
+}
+
+static int put_listener(char *buf, int off, const char *xprt, uint16_t port)
+{
+ return put_listener_af(buf, off, xprt, AF_INET, port);
+}
+
+/* ------------------- LISTENER_GET parsing ------------------- */
+
+struct listener_ent {
+ char xprt[16];
+ int family;
+ uint16_t port;
+ struct in_addr a4;
+ struct in6_addr a6;
+};
+
+static int parse_listener_get(const char *rbuf, int len,
+ struct listener_ent *out, int max)
+{
+ const struct nlmsghdr *nlh = (const void *)rbuf;
+ const struct nlattr *na;
+ int left, count = 0;
+
+ (void)len;
+ na = (const void *)(rbuf + NLMSG_HDRLEN + GENL_HDRLEN);
+ left = nlh->nlmsg_len - NLMSG_HDRLEN - GENL_HDRLEN;
+
+ while (left >= (int)NLA_HDRLEN) {
+ int alen = na->nla_len;
+
+ if ((na->nla_type & NLA_TYPE_MASK) == NFSD_A_SERVER_SOCK_ADDR &&
+ count < max) {
+ const struct nlattr *in = (const void *)((char *)na + NLA_HDRLEN);
+ int ileft = alen - NLA_HDRLEN;
+ struct listener_ent *e = &out[count];
+
+ memset(e, 0, sizeof(*e));
+ while (ileft >= (int)NLA_HDRLEN) {
+ const void *d = (const char *)in + NLA_HDRLEN;
+ int t = in->nla_type & NLA_TYPE_MASK;
+
+ if (t == NFSD_A_SOCK_TRANSPORT_NAME) {
+ strncpy(e->xprt, d, sizeof(e->xprt) - 1);
+ } else if (t == NFSD_A_SOCK_ADDR) {
+ const struct sockaddr_storage *ss = d;
+
+ e->family = ss->ss_family;
+ if (ss->ss_family == AF_INET) {
+ const struct sockaddr_in *s = d;
+
+ e->a4 = s->sin_addr;
+ e->port = ntohs(s->sin_port);
+ } else if (ss->ss_family == AF_INET6) {
+ const struct sockaddr_in6 *s = d;
+
+ e->a6 = s->sin6_addr;
+ e->port = ntohs(s->sin6_port);
+ }
+ }
+ ileft -= NLA_ALIGN4(in->nla_len);
+ in = (const void *)((char *)in + NLA_ALIGN4(in->nla_len));
+ }
+ count++;
+ }
+ left -= NLA_ALIGN4(alen);
+ na = (const void *)((char *)na + NLA_ALIGN4(alen));
+ }
+ return count;
+}
+
+/* ------------------- convenience wrappers ------------------- */
+
+static int listener_set(const char *attrs, int len)
+{
+ return genl_request(NFSD_CMD_LISTENER_SET, attrs, len);
+}
+
+/*
+ * Enable exactly one NFS version in this netns. NFSD_CMD_VERSION_SET clears
+ * every version first, so one nest is enough to leave the server v4-only.
+ * It refuses once a serv exists, so call it before any listener.
+ */
+static int version_set_only(uint32_t major, uint32_t minor)
+{
+ char attrs[64];
+ struct nlattr *nest = (void *)attrs;
+ int inner = NLA_HDRLEN;
+
+ inner = put_attr(attrs, inner, NFSD_A_VERSION_MAJOR,
+ &major, sizeof(major));
+ inner = put_attr(attrs, inner, NFSD_A_VERSION_MINOR,
+ &minor, sizeof(minor));
+ inner = put_attr(attrs, inner, NFSD_A_VERSION_ENABLED, NULL, 0);
+ nest->nla_type = NFSD_A_SERVER_PROTO_VERSION | NLA_F_NESTED;
+ nest->nla_len = inner;
+
+ return genl_request(NFSD_CMD_VERSION_SET, attrs, NLA_ALIGN4(inner));
+}
+
+/* Fetch the current listeners; returns count (>=0) or -errno. */
+static int listener_get(struct listener_ent *out, int max)
+{
+ char rbuf[8192];
+ int n = genl_request_reply(NFSD_CMD_LISTENER_GET, rbuf, sizeof(rbuf));
+
+ if (n < 0)
+ return n;
+ return parse_listener_get(rbuf, n, out, max);
+}
+
+/*
+ * Every listener these tests create comes from put_listener_af(), so the
+ * address is always loopback. Match on it too: without that, a reply that
+ * gave the right transport and port on the wrong address (0.0.0.0, say)
+ * would pass.
+ */
+static struct listener_ent *find_listener(struct listener_ent *e, int n,
+ const char *xprt, int family,
+ uint16_t port)
+{
+ int i;
+
+ for (i = 0; i < n; i++) {
+ if (e[i].family != family || e[i].port != port ||
+ strcmp(e[i].xprt, xprt))
+ continue;
+ if (family == AF_INET6) {
+ if (memcmp(&e[i].a6, &in6addr_loopback, sizeof(e[i].a6)))
+ continue;
+ } else if (e[i].a4.s_addr != htonl(INADDR_LOOPBACK)) {
+ continue;
+ }
+ return &e[i];
+ }
+ return NULL;
+}
+
+/* Start (@n > 0) or stop (@n == 0) nfsd threads in this netns. */
+static int threads_set(int n)
+{
+ char attrs[64];
+ uint32_t v = n;
+ int off = put_attr(attrs, 0, NFSD_A_SERVER_THREADS, &v, sizeof(v));
+
+ return genl_request(NFSD_CMD_THREADS_SET, attrs, off);
+}
+
+/* ------------------- per-netns local rpcbind stub ------------------- */
+
+/*
+ * Creating a listener registers with rpcbind: nfsd_nl_listener_set_doit()
+ * passes no SVC_SOCK_ANONYMOUS for the first entry of a request, so
+ * pmap_register is true in svc_setup_socket(). The fixture's server has v3
+ * enabled, and nfsd_version3 does not set vs_rpcb_optnl, so a failure there
+ * comes back out of svc_register() and takes the listener down with it.
+ * With nothing listening, every attempt first waits out the local rpcbind
+ * timeout. The abstract AF_LOCAL name the kernel tries first is per-netns
+ * (unix_find_abstract() takes a struct net), so answer it here and stay out
+ * of the host's rpcbind.
+ *
+ * Arguments are never decoded. The NULL procedure gets an empty success and
+ * SET/UNSET get TRUE, for both RPCBVERS_2 and RPCBVERS_4. v4 has to be
+ * answered because __svc_rpcb_register6() turns a v4 refusal into
+ * -EAFNOSUPPORT, which would leave every IPv6 listener unregistered.
+ *
+ * In RPCB_STUB_REFUSE mode SET is answered FALSE instead, which
+ * rpcb_register_call() reports as -EACCES. UNSET is left alone: only
+ * svc_unregister() issues it, and it discards the result.
+ *
+ * In RPCB_STUB_SILENT mode a SET or an UNSET is read and nothing is written
+ * back, so the kernel waits out its own timeout. That is the only mode that
+ * makes rpcb_register_call() report a call that got no answer, which is what
+ * the per-net failure count records. The NULL procedure is still answered:
+ * rpcb_create_af_local() builds its client without RPC_CLNT_CREATE_NOPING, so
+ * rpc_create() pings, and a ping that goes unanswered drops the kernel onto
+ * the loopback rpcb_create_local_net() client, which never reaches this stub.
+ *
+ * The stub also keeps counters and the mode in a page shared with the test, so
+ * a test can assert that the kernel never talked to rpcbind at all, or that it
+ * dropped the local rpcbind client and had to reconnect.
+ *
+ * The mode lives there rather than in the child so that a test can change it
+ * with a serv already up. Killing and restarting the stub would close the
+ * connection the kernel holds, and rpcb_register_call() issues UNSET over
+ * AF_LOCAL with RPC_TASK_NOCONNECT, so the next call would fail at once with
+ * -ENOTCONN instead of waiting out a timeout.
+ */
+#define RPCB_PROGRAM 100000
+#define RPCB_PROC_NULL 0
+#define RPCB_PROC_SET 1
+#define RPCB_PROC_UNSET 2
+#define RPCB_ABSTRACT_NAME "/run/rpcbind.sock"
+#define RPCB_STUB_MAXCONN 4
+
+enum { RPCB_STUB_ACCEPT, RPCB_STUB_REFUSE, RPCB_STUB_SILENT };
+
+struct rpcb_stub_stats {
+ unsigned int conns; /* connections accepted */
+ unsigned int calls; /* calls received */
+ unsigned int mode; /* RPCB_STUB_*, read on every call */
+};
+
+static volatile struct rpcb_stub_stats *rpcb_stats; /* MAP_SHARED */
+
+static int rpcb_stats_alloc(void)
+{
+ void *p = mmap(NULL, sizeof(*rpcb_stats), PROT_READ | PROT_WRITE,
+ MAP_SHARED | MAP_ANONYMOUS, -1, 0);
+
+ if (p == MAP_FAILED)
+ return -1;
+ rpcb_stats = p;
+ return 0;
+}
+
+/*
+ * The stub bumps these before it replies and the kernel waits for that reply,
+ * so whatever a netlink request provoked is visible once it returns.
+ */
+static int rpcb_calls(void)
+{
+ return rpcb_stats ? (int)rpcb_stats->calls : 0;
+}
+
+static int rpcb_conns(void)
+{
+ return rpcb_stats ? (int)rpcb_stats->conns : 0;
+}
+
+/* Takes effect on the stub's next call; the caller has not sent one yet. */
+static void rpcb_stub_set_mode(int mode)
+{
+ rpcb_stats->mode = mode;
+}
+
+static int rpcb_stub_listen(void)
+{
+ struct sockaddr_un sun = { .sun_family = AF_UNIX };
+ size_t nlen = strlen(RPCB_ABSTRACT_NAME);
+ socklen_t alen;
+ int fd;
+
+ /* Abstract names are length-delimited, so the length must match. */
+ memcpy(sun.sun_path + 1, RPCB_ABSTRACT_NAME, nlen);
+ alen = offsetof(struct sockaddr_un, sun_path) + 1 + nlen;
+
+ fd = socket(AF_UNIX, SOCK_STREAM, 0);
+ if (fd < 0)
+ return -1;
+ if (bind(fd, (struct sockaddr *)&sun, alen) < 0 ||
+ listen(fd, RPCB_STUB_MAXCONN) < 0) {
+ close(fd);
+ return -1;
+ }
+ return fd;
+}
+
+static int rpcb_stub_read(int fd, void *buf, size_t len)
+{
+ size_t done = 0;
+
+ while (done < len) {
+ ssize_t n = read(fd, (char *)buf + done, len - done);
+
+ if (n <= 0)
+ return -1;
+ done += n;
+ }
+ return 0;
+}
+
+/* Handle one record-marked RPC call. Returns -1 when the peer is done. */
+static int rpcb_stub_call(int fd)
+{
+ unsigned int len, nrep = 6, mode = rpcb_stats->mode;
+ uint32_t mark, call[6], rep[7];
+ size_t replen;
+
+ if (rpcb_stub_read(fd, &mark, sizeof(mark)))
+ return -1;
+ len = ntohl(mark) & 0x7fffffff;
+ if (len < sizeof(call) || len > 4096)
+ return -1;
+ if (rpcb_stub_read(fd, call, sizeof(call)))
+ return -1;
+
+ /* xid, msg_type, rpcvers, prog, vers, proc; the rest is discarded */
+ for (len -= sizeof(call); len; ) {
+ char sink[256];
+ unsigned int n = len > sizeof(sink) ? sizeof(sink) : len;
+
+ if (rpcb_stub_read(fd, sink, n))
+ return -1;
+ len -= n;
+ }
+
+ if (rpcb_stats)
+ rpcb_stats->calls++;
+
+ rep[0] = call[0]; /* xid */
+ rep[1] = htonl(1); /* REPLY */
+ rep[2] = htonl(0); /* MSG_ACCEPTED */
+ rep[3] = htonl(0); /* verifier flavor AUTH_NULL */
+ rep[4] = htonl(0); /* verifier length */
+ rep[5] = htonl(0); /* SUCCESS */
+
+ if (ntohl(call[3]) != RPCB_PROGRAM) {
+ rep[5] = htonl(1); /* PROG_UNAVAIL */
+ } else {
+ unsigned int proc = ntohl(call[5]);
+
+ switch (proc) {
+ case RPCB_PROC_NULL:
+ break;
+ case RPCB_PROC_SET:
+ rep[6] = htonl(mode == RPCB_STUB_REFUSE ? 0 : 1);
+ nrep = 7;
+ break;
+ case RPCB_PROC_UNSET:
+ rep[6] = htonl(1); /* TRUE */
+ nrep = 7;
+ break;
+ default:
+ rep[5] = htonl(3); /* PROC_UNAVAIL */
+ }
+
+ /*
+ * Answer nothing, so the caller waits out its timeout. The
+ * NULL procedure is answered even here: the kernel pings at
+ * client creation, and a ping with no answer takes it off
+ * this socket entirely.
+ */
+ if (mode == RPCB_STUB_SILENT && proc != RPCB_PROC_NULL)
+ return 0;
+ }
+
+ replen = nrep * sizeof(rep[0]);
+ mark = htonl(0x80000000 | replen);
+ if (write(fd, &mark, sizeof(mark)) != (ssize_t)sizeof(mark) ||
+ write(fd, rep, replen) != (ssize_t)replen)
+ return -1;
+ return 0;
+}
+
+static void rpcb_stub_serve(int lfd)
+{
+ struct pollfd pfd[1 + RPCB_STUB_MAXCONN];
+ nfds_t n = 1, i;
+
+ pfd[0].fd = lfd;
+
+ for (;;) {
+ /* stop polling the listener when full, or poll() spins */
+ pfd[0].events = n < 1 + RPCB_STUB_MAXCONN ? POLLIN : 0;
+
+ if (poll(pfd, n, -1) < 0)
+ return;
+
+ if (pfd[0].revents & POLLIN) {
+ int c = accept(lfd, NULL, NULL);
+
+ if (c >= 0) {
+ pfd[n].fd = c;
+ pfd[n].events = POLLIN;
+ /*
+ * poll() ran with the old n, so it did not
+ * write this revents. The loop below reads it.
+ */
+ pfd[n].revents = 0;
+ n++;
+ if (rpcb_stats)
+ rpcb_stats->conns++;
+ }
+ }
+
+ for (i = 1; i < n; i++) {
+ if (!(pfd[i].revents & (POLLIN | POLLHUP | POLLERR)))
+ continue;
+ if (rpcb_stub_call(pfd[i].fd)) {
+ close(pfd[i].fd);
+ pfd[i] = pfd[--n];
+ }
+ }
+ }
+}
+
+/* Returns the stub's pid, or -1. The socket is listening before we fork. */
+static pid_t rpcb_stub_start(int mode)
+{
+ int lfd = rpcb_stub_listen();
+ pid_t pid;
+
+ if (lfd < 0)
+ return -1;
+
+ rpcb_stats->mode = mode;
+
+ pid = fork();
+ if (pid < 0) {
+ close(lfd);
+ return -1;
+ }
+ if (pid == 0) {
+ signal(SIGPIPE, SIG_IGN);
+ prctl(PR_SET_PDEATHSIG, SIGKILL);
+ if (getppid() == 1) /* raced with parent exit */
+ _exit(0);
+ rpcb_stub_serve(lfd);
+ _exit(0);
+ }
+
+ close(lfd);
+ return pid;
+}
+
+/* --------------------------- fixture --------------------------- */
+
+FIXTURE(nfsd_listener) {
+ pid_t rpcbd;
+};
+
+FIXTURE_SETUP(nfsd_listener)
+{
+ struct ifreq ifr = {0};
+ struct stat st;
+ int s;
+
+ if (geteuid() != 0)
+ SKIP(return, "must be run as root");
+ if (unshare(CLONE_NEWNET | CLONE_NEWNS) < 0)
+ SKIP(return, "unshare(NEWNET|NEWNS): %s", strerror(errno));
+ if (mount("", "/", NULL, MS_REC | MS_PRIVATE, NULL) < 0)
+ SKIP(return, "mount(/ private): %s", strerror(errno));
+
+ /*
+ * Keep the kernel's rpcbind client inside this namespace. The
+ * abstract socket it tries first is per-netns, but the
+ * "/var/run/rpcbind.sock" fallback is not, so hide the path.
+ */
+ if (mount("tmpfs", "/run", "tmpfs", 0, NULL) < 0)
+ SKIP(return, "mount(tmpfs on /run): %s", strerror(errno));
+ if (lstat("/var/run", &st) == 0 && S_ISDIR(st.st_mode) &&
+ mount("tmpfs", "/var/run", "tmpfs", 0, NULL) < 0)
+ SKIP(return, "mount(tmpfs on /var/run): %s", strerror(errno));
+
+ /*
+ * Bring loopback up so listener binds (127.0.0.1 / ::1) work. Root
+ * without CAP_NET_ADMIN in this netns gets -EPERM here, so skip.
+ */
+ s = socket(AF_INET, SOCK_DGRAM, 0);
+ ASSERT_GE(s, 0);
+ strcpy(ifr.ifr_name, "lo");
+ if (ioctl(s, SIOCGIFFLAGS, &ifr) < 0) {
+ close(s);
+ SKIP(return, "SIOCGIFFLAGS(lo): %s", strerror(errno));
+ }
+ ifr.ifr_flags |= IFF_UP | IFF_RUNNING;
+ if (ioctl(s, SIOCSIFFLAGS, &ifr) < 0) {
+ close(s);
+ SKIP(return, "SIOCSIFFLAGS(lo): %s", strerror(errno));
+ }
+ close(s);
+
+ nfsd_family = genl_resolve_nfsd();
+ if (nfsd_family < 0)
+ SKIP(return, "nfsd genl family not found (modprobe nfsd?)");
+
+ if (rpcb_stats_alloc() < 0)
+ SKIP(return, "mmap(rpcbind stub counters): %s", strerror(errno));
+
+ self->rpcbd = rpcb_stub_start(RPCB_STUB_ACCEPT);
+ if (self->rpcbd < 0)
+ SKIP(return, "cannot start the rpcbind stub: %s",
+ strerror(errno));
+}
+
+FIXTURE_TEARDOWN(nfsd_listener)
+{
+ /*
+ * A listener holds a reference to this netns, which outlives the test
+ * process, so anything still up leaks it. Threads pin the listeners in
+ * turn; dropping them destroys the serv and everything under it.
+ */
+ if (nfsd_family >= 0 && listener_set(NULL, 0) == -EBUSY)
+ threads_set(0);
+
+ if (self->rpcbd > 0) {
+ kill(self->rpcbd, SIGKILL);
+ waitpid(self->rpcbd, NULL, 0);
+ }
+ if (rpcb_stats) {
+ munmap((void *)rpcb_stats, sizeof(*rpcb_stats));
+ rpcb_stats = NULL;
+ }
+}
+
+/* ===================== validation / negative ===================== */
+
+TEST_F(nfsd_listener, val_empty_list_ok)
+{
+ EXPECT_EQ(0, listener_set(NULL, 0));
+}
+
+TEST_F(nfsd_listener, val_too_many)
+{
+ static char attrs[1 << 20];
+ int i, off = 0;
+
+ for (i = 0; i < 1025; i++) /* > NFSD_NL_LISTENER_MAX (1024) */
+ off = put_listener(attrs, off, "udp", TEST_PORT);
+ EXPECT_EQ(-E2BIG, listener_set(attrs, off));
+}
+
+TEST_F(nfsd_listener, val_missing_addr)
+{
+ char attrs[64];
+ struct raw_listener r = { .xprt = "tcp", .emit_addr = 0 };
+ int off = put_raw_listener(attrs, 0, &r);
+
+ EXPECT_EQ(-EINVAL, listener_set(attrs, off));
+}
+
+TEST_F(nfsd_listener, val_missing_transport)
+{
+ struct sockaddr_in s4 = { .sin_family = AF_INET, .sin_port = htons(TEST_PORT) };
+ struct raw_listener r = { .xprt = NULL, .emit_addr = 1,
+ .addr = &s4, .addr_len = sizeof(s4) };
+ char attrs[64];
+ int off = put_raw_listener(attrs, 0, &r);
+
+ EXPECT_EQ(-EINVAL, listener_set(attrs, off));
+}
+
+/*
+ * A name matching no transport class must be refused before nfsd_mutex is
+ * taken, so it never reaches svc_xprt_create_from_sa() and its
+ * request_module("svc%s", name) upcall.
+ *
+ * The errno cannot show that -- svc_xprt_create_from_sa() returns
+ * -EPROTONOSUPPORT for an unknown name too. The rpcbind traffic can:
+ * getting that far means nfsd_create_serv() ran, and svc_bind() pings
+ * rpcbind at client creation and then sweeps stale entries with
+ * svc_unregister(). A silent stub is the proof nothing was created.
+ */
+TEST_F(nfsd_listener, val_bad_transport)
+{
+ char attrs[64];
+ int off = put_listener(attrs, 0, "bogus_xprt", TEST_PORT);
+
+ ASSERT_EQ(0, rpcb_calls());
+ EXPECT_EQ(-EPROTONOSUPPORT, listener_set(attrs, off));
+ EXPECT_EQ(0, rpcb_calls());
+}
+
+TEST_F(nfsd_listener, val_addr_too_short)
+{
+ unsigned char tiny = 0;
+ struct raw_listener r = { .xprt = "tcp", .emit_addr = 1,
+ .addr = &tiny, .addr_len = 1 };
+ char attrs[64];
+ int off = put_raw_listener(attrs, 0, &r);
+
+ EXPECT_EQ(-EINVAL, listener_set(attrs, off));
+}
+
+TEST_F(nfsd_listener, val_inet_short)
+{
+ struct sockaddr_in s4 = { .sin_family = AF_INET, .sin_port = htons(TEST_PORT) };
+ struct raw_listener r = { .xprt = "tcp", .emit_addr = 1, .addr = &s4,
+ .addr_len = sizeof(sa_family_t) + 2 };
+ char attrs[64];
+ int off = put_raw_listener(attrs, 0, &r);
+
+ EXPECT_EQ(-EINVAL, listener_set(attrs, off));
+}
+
+TEST_F(nfsd_listener, val_inet6_short)
+{
+ struct sockaddr_in6 s6 = { .sin6_family = AF_INET6, .sin6_port = htons(TEST_PORT) };
+ struct raw_listener r = { .xprt = "tcp", .emit_addr = 1, .addr = &s6,
+ .addr_len = sizeof(struct sockaddr_in) };
+ char attrs[64];
+ int off = put_raw_listener(attrs, 0, &r);
+
+ EXPECT_EQ(-EINVAL, listener_set(attrs, off));
+}
+
+TEST_F(nfsd_listener, val_bad_family)
+{
+ struct sockaddr_storage ss = { .ss_family = AF_UNIX };
+ struct raw_listener r = { .xprt = "tcp", .emit_addr = 1, .addr = &ss,
+ .addr_len = sizeof(struct sockaddr_in) };
+ char attrs[64];
+ int off = put_raw_listener(attrs, 0, &r);
+
+ EXPECT_EQ(-EAFNOSUPPORT, listener_set(attrs, off));
+}
+
+TEST_F(nfsd_listener, val_second_entry_bad)
+{
+ struct sockaddr_storage ss = { .ss_family = AF_UNIX };
+ struct raw_listener bad = { .xprt = "tcp", .emit_addr = 1, .addr = &ss,
+ .addr_len = sizeof(struct sockaddr_in) };
+ struct listener_ent got[MAX_LISTENERS];
+ char attrs[128];
+ int off = put_listener(attrs, 0, "tcp", TEST_PORT);
+
+ off = put_raw_listener(attrs, off, &bad);
+ /* The whole request is rejected during validation; nothing applied. */
+ EXPECT_EQ(-EAFNOSUPPORT, listener_set(attrs, off));
+ /*
+ * Again the errno alone does not say so: svc_xprt_create_from_sa()
+ * also returns -EAFNOSUPPORT, and the doit keeps the listeners it did
+ * manage to create, so the well-formed tcp entry ahead of the bad one
+ * would still be up.
+ */
+ EXPECT_EQ(0, listener_get(got, MAX_LISTENERS));
+}
+
+/*
+ * A rejected request must leave the listeners that are already up alone.
+ * The errno alone does not show that: svc_xprt_create_from_sa() returns
+ * -EPROTONOSUPPORT for an unknown name too. What differs is how far the
+ * request gets -- without the check in nfsd_nl_validate_listeners(),
+ * nfsd_nl_listener_set_doit() has already moved the unmatched tcp listener
+ * off sv_permsocks and run svc_xprt_destroy_all() on it by the time the
+ * name fails.
+ */
+TEST_F(nfsd_listener, val_reject_keeps_listeners)
+{
+ struct listener_ent got[MAX_LISTENERS];
+ char good[64], bad[64];
+ int og = put_listener(good, 0, "tcp", TEST_PORT);
+ int ob = put_listener(bad, 0, "bogus_xprt", TEST_PORT);
+
+ ASSERT_EQ(0, listener_set(good, og));
+ ASSERT_EQ(1, listener_get(got, MAX_LISTENERS));
+
+ EXPECT_EQ(-EPROTONOSUPPORT, listener_set(bad, ob));
+
+ ASSERT_EQ(1, listener_get(got, MAX_LISTENERS));
+ EXPECT_NE(NULL, find_listener(got, 1, "tcp", AF_INET, TEST_PORT));
+}
+
+/* ===================== functional / round-trip ===================== */
+
+/* LISTENER_GET with no serv in this netns returns an empty list. */
+TEST_F(nfsd_listener, func_get_empty)
+{
+ struct listener_ent got[MAX_LISTENERS];
+
+ EXPECT_EQ(0, listener_get(got, MAX_LISTENERS));
+}
+
+TEST_F(nfsd_listener, func_create_tcp)
+{
+ struct listener_ent got[MAX_LISTENERS];
+ char attrs[64];
+ int off = put_listener(attrs, 0, "tcp", TEST_PORT);
+
+ ASSERT_EQ(0, listener_set(attrs, off));
+ EXPECT_STREQ("", last_extack); /* nothing to warn about */
+ ASSERT_EQ(1, listener_get(got, MAX_LISTENERS));
+ EXPECT_NE(NULL, find_listener(got, 1, "tcp", AF_INET, TEST_PORT));
+}
+
+TEST_F(nfsd_listener, func_create_udp)
+{
+ struct listener_ent got[MAX_LISTENERS];
+ char attrs[64];
+ int off = put_listener(attrs, 0, "udp", TEST_PORT);
+
+ ASSERT_EQ(0, listener_set(attrs, off));
+ ASSERT_EQ(1, listener_get(got, MAX_LISTENERS));
+ EXPECT_NE(NULL, find_listener(got, 1, "udp", AF_INET, TEST_PORT));
+}
+
+TEST_F(nfsd_listener, func_create_multi)
+{
+ struct listener_ent got[MAX_LISTENERS];
+ char attrs[128];
+ int off = put_listener(attrs, 0, "tcp", TEST_PORT);
+
+ off = put_listener(attrs, off, "udp", TEST_PORT);
+ ASSERT_EQ(0, listener_set(attrs, off));
+ ASSERT_EQ(2, listener_get(got, MAX_LISTENERS));
+ EXPECT_NE(NULL, find_listener(got, 2, "tcp", AF_INET, TEST_PORT));
+ EXPECT_NE(NULL, find_listener(got, 2, "udp", AF_INET, TEST_PORT));
+}
+
+TEST_F(nfsd_listener, func_idempotent)
+{
+ struct listener_ent got[MAX_LISTENERS];
+ char attrs[64];
+ int off = put_listener(attrs, 0, "tcp", TEST_PORT);
+
+ ASSERT_EQ(0, listener_set(attrs, off));
+ EXPECT_EQ(0, listener_set(attrs, off)); /* re-set same list */
+ ASSERT_EQ(1, listener_get(got, MAX_LISTENERS));
+ EXPECT_NE(NULL, find_listener(got, 1, "tcp", AF_INET, TEST_PORT));
+}
+
+TEST_F(nfsd_listener, func_add)
+{
+ struct listener_ent got[MAX_LISTENERS];
+ char one[64], two[128];
+ int o1 = put_listener(one, 0, "tcp", TEST_PORT);
+ int o2 = put_listener(two, 0, "tcp", TEST_PORT);
+
+ o2 = put_listener(two, o2, "udp", TEST_PORT);
+ ASSERT_EQ(0, listener_set(one, o1));
+ ASSERT_EQ(0, listener_set(two, o2)); /* add udp, keep tcp */
+ ASSERT_EQ(2, listener_get(got, MAX_LISTENERS));
+ EXPECT_NE(NULL, find_listener(got, 2, "tcp", AF_INET, TEST_PORT));
+ EXPECT_NE(NULL, find_listener(got, 2, "udp", AF_INET, TEST_PORT));
+}
+
+TEST_F(nfsd_listener, func_remove_subset)
+{
+ struct listener_ent got[MAX_LISTENERS];
+ char both[128], one[64];
+ int ob = put_listener(both, 0, "tcp", TEST_PORT);
+ int oo = put_listener(one, 0, "tcp", TEST_PORT);
+
+ ob = put_listener(both, ob, "udp", TEST_PORT);
+ ASSERT_EQ(0, listener_set(both, ob));
+ ASSERT_EQ(0, listener_set(one, oo)); /* drop udp */
+ ASSERT_EQ(1, listener_get(got, MAX_LISTENERS));
+ EXPECT_NE(NULL, find_listener(got, 1, "tcp", AF_INET, TEST_PORT));
+}
+
+/*
+ * LISTENER_GET cannot tell a destroyed serv from a live one with no
+ * permsocks: nfsd_nl_listener_get_doit() replies empty either way. The
+ * rpcbind client can. nfsd_destroy_serv() is the only path that reaches
+ * svc_xprt_destroy_all(..., unregister=true) -> svc_rpcb_cleanup() ->
+ * rpcb_put_local(), which drops the last user and shuts the local client
+ * down; the next serv then has to connect again. Leaving the serv in place
+ * would keep the first connection and the stub would see just the one.
+ */
+TEST_F(nfsd_listener, func_empty_destroys)
+{
+ struct listener_ent got[MAX_LISTENERS];
+ char attrs[64];
+ int off = put_listener(attrs, 0, "tcp", TEST_PORT);
+ int conns;
+
+ ASSERT_EQ(0, listener_set(attrs, off));
+ conns = rpcb_conns();
+ ASSERT_GT(conns, 0);
+
+ EXPECT_EQ(0, listener_set(NULL, 0)); /* empty -> destroy serv */
+ EXPECT_EQ(0, listener_get(got, MAX_LISTENERS));
+
+ ASSERT_EQ(0, listener_set(attrs, off));
+ EXPECT_GT(rpcb_conns(), conns);
+}
+
+TEST_F(nfsd_listener, func_ipv6)
+{
+ struct listener_ent got[MAX_LISTENERS];
+ char attrs[64];
+ int off, s;
+
+ s = socket(AF_INET6, SOCK_STREAM, 0);
+ if (s < 0)
+ SKIP(return, "IPv6 unavailable: %s", strerror(errno));
+ close(s);
+
+ off = put_listener_af(attrs, 0, "tcp", AF_INET6, TEST_PORT);
+ ASSERT_EQ(0, listener_set(attrs, off));
+ ASSERT_EQ(1, listener_get(got, MAX_LISTENERS));
+ EXPECT_NE(NULL, find_listener(got, 1, "tcp", AF_INET6, TEST_PORT));
+}
+
+/* ===================== rpcbind registration ===================== */
+
+/*
+ * A rpcbind that refuses the registration takes the listener down with it.
+ * svc_register() fails, so svc_setup_socket() fails, so no listener is
+ * created. -EACCES alone does not show that, since a bind can return it
+ * too, so read the listener set back as well.
+ */
+TEST_F(nfsd_listener, sem_register_refused)
+{
+ struct listener_ent got[MAX_LISTENERS];
+ char attrs[64];
+ int off = put_listener(attrs, 0, "tcp", TEST_PORT);
+
+ rpcb_stub_set_mode(RPCB_STUB_REFUSE);
+
+ EXPECT_EQ(-EACCES, listener_set(attrs, off));
+ EXPECT_STRNE("", last_extack);
+ EXPECT_EQ(0, listener_get(got, MAX_LISTENERS));
+}
+
+/*
+ * A listener that cannot be created reports which one it was: the errno
+ * alone does not name the entry in a multi-listener request.
+ */
+TEST_F(nfsd_listener, sem_create_failure_extack)
+{
+ struct sockaddr_in s4 = { .sin_family = AF_INET,
+ .sin_port = htons(TEST_PORT),
+ .sin_addr.s_addr = htonl(INADDR_LOOPBACK) };
+ struct listener_ent got[MAX_LISTENERS];
+ char attrs[64];
+ int off = put_listener(attrs, 0, "tcp", TEST_PORT);
+ int s;
+
+ /* squat on the port so the listener cannot bind */
+ s = socket(AF_INET, SOCK_STREAM, 0);
+ ASSERT_GE(s, 0);
+ ASSERT_EQ(0, bind(s, (struct sockaddr *)&s4, sizeof(s4)));
+
+ EXPECT_EQ(-EADDRINUSE, listener_set(attrs, off));
+ EXPECT_STRNE("", last_extack);
+ EXPECT_EQ(0, listener_get(got, MAX_LISTENERS));
+ close(s);
+}
+
+/* ============ one rpcbind attempt for each request ============ */
+
+/*
+ * Every listener used to register on its own, so a rpcbind that never
+ * answers cost one timeout for each entry. Ask for one listener, then for
+ * three, and compare what the stub saw. Three entries must not cost three
+ * times as much.
+ *
+ * The stub has to stay silent rather than refuse. A refusal is an answer,
+ * and rpcbind refuses one entry at a time, so the count ignores it.
+ */
+TEST_F(nfsd_listener, rpcb_stop_after_failure)
+{
+ int before, one, three, off;
+ char attrs[192];
+
+ rpcb_stub_set_mode(RPCB_STUB_SILENT);
+
+ before = rpcb_calls();
+ off = put_listener(attrs, 0, "tcp", TEST_PORT);
+ listener_set(attrs, off);
+ one = rpcb_calls() - before;
+ ASSERT_GT(one, 0);
+
+ ASSERT_EQ(0, listener_set(attrs, 0));
+
+ before = rpcb_calls();
+ off = put_listener(attrs, 0, "tcp", TEST_PORT);
+ off = put_listener(attrs, off, "tcp", TEST_PORT + 1);
+ off = put_listener(attrs, off, "tcp", TEST_PORT + 2);
+ listener_set(attrs, off);
+ three = rpcb_calls() - before;
+
+ /* the second and third entries must not reach rpcbind at all */
+ EXPECT_LE(three, one);
+}
+
+/*
+ * The entry that finds rpcbind silent is the one that pays for the
+ * discovery, and v3 has no vs_rpcb_optnl to discard the error, so it is the
+ * only entry whose listener would be lost. Nothing distinguishes it from the
+ * rest of the request, and a retry of the same request would fail the same
+ * entry again, so the set would stay short for as long as rpcbind was quiet.
+ *
+ * Ask for three listeners against a silent stub and require the whole set,
+ * a success, and a warning that says why.
+ */
+TEST_F(nfsd_listener, rpcb_silent_set_complete)
+{
+ struct listener_ent got[MAX_LISTENERS];
+ char attrs[192];
+ int off;
+
+ rpcb_stub_set_mode(RPCB_STUB_SILENT);
+
+ off = put_listener(attrs, 0, "tcp", TEST_PORT);
+ off = put_listener(attrs, off, "tcp", TEST_PORT + 1);
+ off = put_listener(attrs, off, "tcp", TEST_PORT + 2);
+ EXPECT_EQ(0, listener_set(attrs, off));
+
+ /* the first entry is not the odd one out */
+ EXPECT_EQ(3, listener_get(got, MAX_LISTENERS));
+ /* no errno reports this, so the ack has to */
+ EXPECT_STRNE("", last_extack);
+}
+
+/*
+ * The case that needs the count rather than a failed listener. NFSv4 sets
+ * vs_rpcb_optnl, so svc_generic_rpcbind_set() discards the error, every
+ * listener comes up, and nothing reports a failure. Without the fix each
+ * entry still waits for rpcbind on its own.
+ *
+ * Make the server v4-only, answer no SET, and require three things: the
+ * listeners come up, the ack warns that they are not registered, and the
+ * stub does not see one round trip for each entry.
+ */
+TEST_F(nfsd_listener, rpcb_v4_only_bounded)
+{
+ struct listener_ent got[MAX_LISTENERS];
+ int before, one, three, off;
+ char attrs[192];
+
+ /* refuses once a serv exists, so this has to come first */
+ ASSERT_EQ(0, version_set_only(4, 1));
+ rpcb_stub_set_mode(RPCB_STUB_SILENT);
+
+ before = rpcb_calls();
+ off = put_listener(attrs, 0, "tcp", TEST_PORT);
+ ASSERT_EQ(0, listener_set(attrs, off));
+ one = rpcb_calls() - before;
+ ASSERT_GT(one, 0);
+
+ /* start over, so the second measurement also builds a serv */
+ ASSERT_EQ(0, listener_set(attrs, 0));
+
+ before = rpcb_calls();
+ off = put_listener(attrs, 0, "tcp", TEST_PORT);
+ off = put_listener(attrs, off, "tcp", TEST_PORT + 1);
+ off = put_listener(attrs, off, "tcp", TEST_PORT + 2);
+ ASSERT_EQ(0, listener_set(attrs, off));
+ three = rpcb_calls() - before;
+
+ /* the listeners are up even though rpcbind never answered */
+ EXPECT_EQ(3, listener_get(got, MAX_LISTENERS));
+ /* and the ack says they are unregistered, since no errno can */
+ EXPECT_STRNE("", last_extack);
+ EXPECT_LE(three, one);
+}
+
+/*
+ * The stop applies to one request only. After rpcbind starts answering,
+ * the next request must register without any other step.
+ */
+TEST_F(nfsd_listener, rpcb_retry_next_request)
+{
+ int before, after, off;
+ char attrs[192];
+
+ rpcb_stub_set_mode(RPCB_STUB_SILENT);
+
+ off = put_listener(attrs, 0, "tcp", TEST_PORT);
+ off = put_listener(attrs, off, "tcp", TEST_PORT + 1);
+ listener_set(attrs, off);
+ ASSERT_EQ(0, listener_set(attrs, 0));
+
+ /* rpcbind recovers */
+ rpcb_stub_set_mode(RPCB_STUB_ACCEPT);
+
+ before = rpcb_calls();
+ off = put_listener(attrs, 0, "tcp", TEST_PORT);
+ EXPECT_EQ(0, listener_set(attrs, off));
+ after = rpcb_calls();
+
+ /* a fresh request starts from a fresh reading and tries again */
+ EXPECT_GT(after, before);
+ EXPECT_STREQ("", last_extack);
+}
+
+/*
+ * The same rule on the way out. Removing a listener unregisters it, so a
+ * rpcbind that stops answering used to cost one timeout for each listener
+ * removed. Register one listener while the stub answers, silence the stub,
+ * remove it and count; then do the same with three.
+ *
+ * Both measurements also pay the svc_unregister() sweep that
+ * nfsd_destroy_serv() runs once the last listener is gone, so that cancels
+ * out of the comparison.
+ */
+TEST_F(nfsd_listener, rpcb_unreg_stop_after_failure)
+{
+ int before, one, three, off;
+ char attrs[192];
+
+ off = put_listener(attrs, 0, "tcp", TEST_PORT);
+ ASSERT_EQ(0, listener_set(attrs, off));
+
+ rpcb_stub_set_mode(RPCB_STUB_SILENT);
+ before = rpcb_calls();
+ ASSERT_EQ(0, listener_set(NULL, 0));
+ one = rpcb_calls() - before;
+ ASSERT_GT(one, 0);
+
+ rpcb_stub_set_mode(RPCB_STUB_ACCEPT);
+ off = put_listener(attrs, 0, "tcp", TEST_PORT);
+ off = put_listener(attrs, off, "tcp", TEST_PORT + 1);
+ off = put_listener(attrs, off, "tcp", TEST_PORT + 2);
+ ASSERT_EQ(0, listener_set(attrs, off));
+
+ rpcb_stub_set_mode(RPCB_STUB_SILENT);
+ before = rpcb_calls();
+ ASSERT_EQ(0, listener_set(NULL, 0));
+ three = rpcb_calls() - before;
+
+ /* the second and third removals must not reach rpcbind at all */
+ EXPECT_LE(three, one);
+}
+
+/* ===================== threads / -EBUSY semantics ===================== */
+
+TEST_F(nfsd_listener, sem_busy_on_change)
+{
+ struct listener_ent got[MAX_LISTENERS];
+ char one[64], two[128];
+ int o1 = put_listener(one, 0, "tcp", TEST_PORT);
+ int o2 = put_listener(two, 0, "tcp", TEST_PORT);
+
+ o2 = put_listener(two, o2, "udp", TEST_PORT);
+ ASSERT_EQ(0, listener_set(one, o1));
+ ASSERT_EQ(0, threads_set(1)); /* threads now running */
+ EXPECT_EQ(-EBUSY, listener_set(two, o2)); /* add refused */
+
+ /* refused means refused: the udp listener must not have been added */
+ EXPECT_EQ(1, listener_get(got, MAX_LISTENERS));
+ EXPECT_NE(NULL, find_listener(got, 1, "tcp", AF_INET, TEST_PORT));
+
+ threads_set(0); /* stop before netns exit */
+}
+
+TEST_F(nfsd_listener, sem_busy_on_remove)
+{
+ struct listener_ent got[MAX_LISTENERS];
+ char one[64];
+ int o1 = put_listener(one, 0, "tcp", TEST_PORT);
+
+ ASSERT_EQ(0, listener_set(one, o1));
+ ASSERT_EQ(0, threads_set(1));
+ EXPECT_EQ(-EBUSY, listener_set(NULL, 0)); /* remove refused */
+
+ /* the doit moves the permsocks to a temp list before it can fail */
+ EXPECT_EQ(1, listener_get(got, MAX_LISTENERS));
+ EXPECT_NE(NULL, find_listener(got, 1, "tcp", AF_INET, TEST_PORT));
+
+ threads_set(0);
+}
+
+TEST_HARNESS_MAIN
diff --git a/tools/testing/selftests/nfsd/settings b/tools/testing/selftests/nfsd/settings
new file mode 100644
index 000000000000..6091b45d226b
--- /dev/null
+++ b/tools/testing/selftests/nfsd/settings
@@ -0,0 +1 @@
+timeout=120
diff --git a/tools/testing/selftests/nommu/Makefile b/tools/testing/selftests/nommu/Makefile
new file mode 100644
index 000000000000..8e7cd7315c53
--- /dev/null
+++ b/tools/testing/selftests/nommu/Makefile
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: GPL-2.0
+# Makefile for nommu selftests
+
+TEST_GEN_PROGS += nommu_mmap_test
+TEST_GEN_PROGS += nommu_mremap_test
+
+include ../lib.mk
+include local.mk
diff --git a/tools/testing/selftests/nommu/local.mk b/tools/testing/selftests/nommu/local.mk
new file mode 100644
index 000000000000..0bd1300f00f4
--- /dev/null
+++ b/tools/testing/selftests/nommu/local.mk
@@ -0,0 +1,7 @@
+# detect if users request NOMMU build or not
+# User can set NOMMU to 1 to build/test for NOMMU platforms
+NOMMU ?= 0
+ifeq ($(NOMMU),1)
+CFLAGS += -DNOMMU
+export NOMMU
+endif
diff --git a/tools/testing/selftests/nommu/nommu_mmap_test.c b/tools/testing/selftests/nommu/nommu_mmap_test.c
new file mode 100644
index 000000000000..a1f5fdda554a
--- /dev/null
+++ b/tools/testing/selftests/nommu/nommu_mmap_test.c
@@ -0,0 +1,261 @@
+// SPDX-License-Identifier: GPL-2.0
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/mman.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <string.h>
+#include <limits.h>
+#include "kselftest.h"
+
+#include <sys/vfs.h>
+#ifndef RAMFS_MAGIC
+#define RAMFS_MAGIC 0x858458f6
+#endif
+
+static size_t ps;
+
+struct test_case_t {
+ const char *name;
+ const char *pathname;
+ int open_flags;
+ int mmap_prot;
+ int mmap_flags;
+ int exp_err;
+ int (*resolve_exp_err)(const char *path);
+};
+
+static int get_shm_expected_error(const char *path)
+{
+ struct statfs fs;
+
+ if (statfs(path, &fs) == 0) {
+ if (fs.f_type == RAMFS_MAGIC)
+ return 0; /* ramfs succeed with contiguous memory */
+ }
+ /* hostfs, etc returns ENODEV due to lack of contiguous allocation */
+ return ENODEV;
+}
+
+static struct test_case_t test_cases[] = {
+ {
+ .name = "anonymous private allocation",
+ .pathname = NULL,
+ .open_flags = O_CREAT | O_RDWR | O_EXCL,
+ .mmap_prot = PROT_READ | PROT_WRITE,
+ .mmap_flags = MAP_ANONYMOUS | MAP_PRIVATE,
+ .exp_err = 0,
+ .resolve_exp_err = NULL,
+ },
+ {
+ .name = "non-anonymous private file mapping (rw-)",
+ .pathname = "/tmp/ksft.nommu-reg-XXXXXX",
+ .open_flags = O_CREAT | O_RDWR | O_EXCL,
+ .mmap_prot = PROT_READ | PROT_WRITE,
+ .mmap_flags = MAP_PRIVATE,
+ .exp_err = 0,
+ .resolve_exp_err = NULL,
+ },
+ {
+ .name = "non-anonymous private file mapping (r--)",
+ .pathname = "/tmp/ksft.nommu-reg-XXXXXX",
+ .open_flags = O_CREAT | O_RDWR | O_EXCL,
+ .mmap_prot = PROT_READ,
+ .mmap_flags = MAP_PRIVATE,
+ .exp_err = 0,
+ .resolve_exp_err = NULL,
+ },
+ {
+ .name = "non-anonymous shared file mapping (rw-)",
+ .pathname = "/tmp/ksft.nommu-shm-XXXXXX",
+ .open_flags = O_CREAT | O_RDWR | O_EXCL,
+ .mmap_prot = PROT_READ | PROT_WRITE,
+ .mmap_flags = MAP_SHARED,
+ .exp_err = 0,
+#ifdef NOMMU
+ .resolve_exp_err = get_shm_expected_error,
+#else
+ .resolve_exp_err = NULL,
+#endif
+ },
+ {
+ .name = "non-anonymous shared file mapping (r--)",
+ .pathname = "/tmp/ksft.nommu-shm-XXXXXX",
+ .open_flags = O_CREAT | O_RDWR | O_EXCL,
+ .mmap_prot = PROT_READ,
+ .mmap_flags = MAP_SHARED,
+ .exp_err = 0,
+#ifdef NOMMU
+ .resolve_exp_err = get_shm_expected_error,
+#else
+ .resolve_exp_err = 0,
+#endif
+ },
+};
+
+static int run_mapping_matrix_test(struct test_case_t *tcase)
+{
+ int fd;
+ void *ptr;
+ char path_buf[PATH_MAX];
+ const char *path = tcase->pathname;
+ int rc = KSFT_PASS;
+ int expected_error;
+
+ ksft_print_msg("[RUN] %s\n", tcase->name);
+
+ if (tcase->pathname == NULL) {
+ fd = -1;
+ } else if (strstr(tcase->pathname, "XXXXXX")) {
+ strncpy(path_buf, tcase->pathname, sizeof(path_buf) - 1);
+ path_buf[sizeof(path_buf) - 1] = '\0';
+ fd = mkstemp(path_buf);
+ if (fd < 0) {
+ ksft_print_msg("Failed to setup temp node: %s\n",
+ tcase->pathname);
+ ksft_test_result_skip("%s\n", tcase->name);
+ return KSFT_SKIP;
+ }
+ if (ftruncate(fd, ps) != 0) {
+ ksft_print_msg("ftruncate failed for: %s\n", tcase->pathname);
+ ksft_test_result_fail("%s\n", tcase->name);
+ close(fd);
+ unlink(path_buf);
+ return KSFT_FAIL;
+ }
+ path = path_buf;
+ } else {
+ fd = open(tcase->pathname, tcase->open_flags, 0600);
+ if (fd < 0) {
+ ksft_print_msg("Device node not accessible: %s\n",
+ tcase->pathname);
+ ksft_test_result_skip("%s\n", tcase->name);
+ return KSFT_SKIP;
+ }
+ }
+
+ expected_error = tcase->exp_err;
+ if (tcase->resolve_exp_err && fd >= 0)
+ expected_error = tcase->resolve_exp_err(path);
+
+ ptr = mmap(NULL, ps, tcase->mmap_prot, tcase->mmap_flags, fd, 0);
+
+ if (expected_error != 0) {
+ if (ptr != MAP_FAILED) {
+ ksft_print_msg("mmap unexpectedly succeeded (exp error %d)\n",
+ expected_error);
+ ksft_test_result_fail("%s\n", tcase->name);
+ munmap(ptr, ps);
+ rc = KSFT_FAIL;
+ goto cleanup;
+ }
+ if (errno != expected_error) {
+ ksft_print_msg("mmap failed with %d (%s), but expected %d\n",
+ errno, strerror(errno), expected_error);
+ ksft_test_result_fail("%s\n", tcase->name);
+ rc = KSFT_FAIL;
+ goto cleanup;
+ }
+ ksft_print_msg("Correctly rejected with expected error %s(%d)\n",
+ strerror(expected_error), expected_error);
+ ksft_test_result_pass("%s\n", tcase->name);
+ rc = KSFT_PASS;
+ goto cleanup;
+ }
+
+ if (ptr == MAP_FAILED) {
+ ksft_print_msg("mmap failed unexpectedly: %s\n", strerror(errno));
+ ksft_test_result_fail("%s\n", tcase->name);
+ rc = KSFT_FAIL;
+ goto cleanup;
+ }
+
+ ksft_test_result_pass("%s\n", tcase->name);
+ munmap(ptr, ps);
+
+cleanup:
+ if (fd >= 0) {
+ close(fd);
+ if (tcase->pathname && strstr(tcase->pathname, "XXXXXX"))
+ unlink(path_buf);
+ }
+ return rc;
+}
+
+static int test_map_fixed(void)
+{
+ void *fixed_addr;
+ void *ptr;
+
+ ksft_print_msg("[RUN] %s\n", __func__);
+
+ fixed_addr = mmap(NULL, ps, PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+ if (fixed_addr == MAP_FAILED) {
+ ksft_print_msg("Unable to reserve test address: %s\n",
+ strerror(errno));
+ ksft_test_result_skip("MAP_FIXED behavior\n");
+ return KSFT_SKIP;
+ }
+
+ if (munmap(fixed_addr, ps)) {
+ ksft_print_msg("Unable to release test address: %s\n",
+ strerror(errno));
+ ksft_test_result_fail("MAP_FIXED behavior\n");
+ return KSFT_FAIL;
+ }
+
+ ptr = mmap(fixed_addr, ps, PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0);
+
+#ifdef NOMMU
+ if (ptr == MAP_FAILED && (errno == ENODEV || errno == EINVAL)) {
+ ksft_print_msg("MAP_FIXED correctly rejected under nommu\n");
+ ksft_test_result_pass("MAP_FIXED behavior\n");
+ return KSFT_PASS;
+ }
+ if (ptr != MAP_FAILED) {
+ ksft_print_msg("MAP_FIXED unexpectedly allowed under nommu\n");
+ ksft_test_result_fail("MAP_FIXED behavior\n");
+ munmap(ptr, ps);
+ return KSFT_FAIL;
+ }
+ ksft_print_msg("MAP_FIXED failed under NOMMU: %s\n",
+ strerror(errno));
+ ksft_test_result_fail("MAP_FIXED behavior\n");
+ return KSFT_FAIL;
+#else
+ if (ptr != MAP_FAILED) {
+ ksft_print_msg("MAP_FIXED successfully allocated under MMU\n");
+ ksft_test_result_pass("MAP_FIXED behavior\n");
+ munmap(ptr, ps);
+ return KSFT_PASS;
+ }
+ ksft_print_msg("MAP_FIXED failed allocation under MMU\n");
+ ksft_test_result_fail("MAP_FIXED behavior\n");
+ return KSFT_FAIL;
+#endif
+}
+
+int main(int argc, char **argv)
+{
+ int i;
+
+ ps = sysconf(_SC_PAGESIZE);
+ ksft_print_header();
+ ksft_set_plan(ARRAY_SIZE(test_cases) + 1);
+
+#ifdef NOMMU
+ ksft_print_msg("Running strict MMAP test criteria under nommu architecture\n");
+#else
+ ksft_print_msg("Running MMAP test criteria under MMU architecture\n");
+#endif
+
+ test_map_fixed();
+ for (i = 0; i < (int)ARRAY_SIZE(test_cases); i++)
+ run_mapping_matrix_test(&test_cases[i]);
+
+ ksft_finished();
+}
diff --git a/tools/testing/selftests/nommu/nommu_mremap_test.c b/tools/testing/selftests/nommu/nommu_mremap_test.c
new file mode 100644
index 000000000000..7ccdf65b675f
--- /dev/null
+++ b/tools/testing/selftests/nommu/nommu_mremap_test.c
@@ -0,0 +1,366 @@
+// SPDX-License-Identifier: GPL-2.0
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/mman.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <string.h>
+#include <limits.h>
+#include "kselftest.h"
+
+#include <sys/vfs.h>
+#ifndef RAMFS_MAGIC
+#define RAMFS_MAGIC 0x858458f6
+#endif
+
+static size_t ps;
+
+static long get_fs_type(const char *path)
+{
+ struct statfs fs;
+
+ if (statfs(path, &fs) == 0)
+ return fs.f_type;
+
+ return 0;
+}
+
+static void munmap_shrink_test(void)
+{
+ void *addr;
+ int ret;
+
+ /* munmap shrink test */
+ for (int i = 0; i < 4; i++) {
+ addr = mmap(NULL, ps * 4, PROT_READ | PROT_WRITE,
+ MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
+ if (addr == MAP_FAILED) {
+ ksft_print_msg("mmap failed: %s(%d)\n", strerror(errno), errno);
+ ksft_test_result_fail("munmap shrink\n");
+ return;
+ }
+ ret = munmap((char *)addr + ps * i, ps);
+ if (ret != 0) {
+ ksft_print_msg("memory %p isn't unmapped at %p\n",
+ addr, (char *)addr + ps * i);
+ ksft_test_result_fail("munmap shrink\n");
+ return;
+ }
+
+ if (i == 0) {
+ if (munmap(addr + ps, ps * 3))
+ goto error;
+ } else if (i == 1) {
+ if (munmap(addr, ps) || munmap(addr + (ps * 2), ps * 2))
+ goto error;
+ } else if (i == 2) {
+ if (munmap(addr, ps * 2) || munmap(addr + (ps * 3), ps))
+ goto error;
+ } else if (i == 3) {
+ if (munmap(addr, ps * 3))
+ goto error;
+ }
+ }
+
+ ksft_test_result_pass("munmap shrink\n");
+ return;
+error:
+ for (int j = 0; j < 4; j++)
+ munmap((char *)addr + j * ps, ps);
+ ksft_print_msg("clean up failures\n");
+ ksft_test_result_fail("munmap shrink\n");
+}
+
+static size_t page_align(size_t len)
+{
+ return (len + ps - 1) / ps * ps;
+}
+
+static void mremap_shrink_test(void)
+{
+ void *addr, *addr2;
+ size_t current_len;
+ size_t old_len, new_len;
+ struct param {
+ size_t old;
+ size_t new;
+ } params[] = {
+ /* should not happen any shrink */
+ { .old = ps * 4 - 1, .new = ps * 4 - 2 },
+ /* should not happen any shrink */
+ { .old = ps * 4 - 1, .new = ps * 4 },
+ { .old = ps * 4, .new = ps * 2 },
+ /* should not happen any shrink */
+ { .old = ps * 2, .new = ps * 2 - 2 },
+ { .old = ps * 2 - 2, .new = ps * 1 },
+ };
+
+ /* mremap shrink test */
+ current_len = page_align(ps * 4 - 1);
+ addr = mmap(NULL, ps * 4 - 1, PROT_READ | PROT_WRITE,
+ MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
+ if (addr == MAP_FAILED) {
+ ksft_print_msg("mmap failed: %s(%d)\n", strerror(errno), errno);
+ ksft_test_result_fail("mremap shrink\n");
+ return;
+ }
+
+ for (int i = 0; i < ARRAY_SIZE(params); i++) {
+ old_len = params[i].old;
+ new_len = params[i].new;
+ current_len = page_align(new_len);
+ addr2 = mremap(addr, old_len, new_len, MREMAP_MAYMOVE);
+ if (addr2 == MAP_FAILED) {
+ ksft_print_msg("memory %p isn't remapped at %p\n", addr, addr2);
+ ksft_test_result_fail("mremap shrink\n");
+ munmap(addr, page_align(old_len));
+ return;
+ }
+
+ addr = addr2;
+ }
+
+ if (munmap(addr, current_len)) {
+ ksft_print_msg("cleanup failed: %s\n", strerror(errno));
+ ksft_test_result_fail("mremap shrink\n");
+ return;
+ }
+ ksft_test_result_pass("mremap shrink\n");
+}
+
+static int get_shared_writable_file_expected_error(const char *path)
+{
+ if (get_fs_type(path) == RAMFS_MAGIC)
+ return EPERM; /* ramfs failed */
+
+ return 0;
+}
+
+struct mremap_case_t {
+ const char *name;
+ const char *pathname;
+ int open_flags;
+ int mmap_prot;
+ int mmap_flags;
+ int exp_err;
+ int (*resolve_exp_err)(const char *path);
+ unsigned int old_pages;
+ unsigned int new_pages;
+};
+
+static struct mremap_case_t mremap_cases[] = {
+ {
+ .name = "anonymous shrink (r--)",
+ .pathname = NULL,
+ .open_flags = O_CREAT | O_RDWR | O_EXCL,
+ .mmap_prot = PROT_READ,
+ .mmap_flags = MAP_ANONYMOUS | MAP_PRIVATE,
+ .exp_err = 0,
+ .resolve_exp_err = 0,
+ },
+ {
+ .name = "shared file shrink (r--)",
+ .pathname = "/tmp/ksft.nommu-remap-XXXXXX",
+ .open_flags = O_CREAT | O_RDWR | O_EXCL,
+ .mmap_prot = PROT_READ,
+ .mmap_flags = MAP_SHARED,
+ .exp_err = 0,
+#ifdef NOMMU
+ .resolve_exp_err = get_shared_writable_file_expected_error,
+#else
+ .resolve_exp_err = 0,
+#endif
+ },
+ {
+ .name = "private file unchanged length (r-)",
+ .pathname = "/tmp/ksft.nommu-remap-XXXXXX",
+ .open_flags = O_CREAT | O_RDWR | O_EXCL,
+ .mmap_prot = PROT_READ,
+ .mmap_flags = MAP_PRIVATE,
+#ifdef NOMMU
+ .exp_err = EPERM,
+#else
+ .exp_err = 0,
+#endif
+ .resolve_exp_err = 0,
+ .old_pages = 4,
+ .new_pages = 4,
+ },
+ {
+ .name = "private file unchanged length (rw-)",
+ .pathname = "/tmp/ksft.nommu-remap-XXXXXX",
+ .open_flags = O_CREAT | O_RDWR | O_EXCL,
+ .mmap_prot = PROT_READ | PROT_WRITE,
+ .mmap_flags = MAP_PRIVATE,
+ .exp_err = 0,
+ .resolve_exp_err = 0,
+ .old_pages = 4,
+ .new_pages = 4,
+ },
+ {
+ .name = "private file growth (r-)",
+ .pathname = "/tmp/ksft.nommu-remap-XXXXXX",
+ .open_flags = O_CREAT | O_RDWR | O_EXCL,
+ .mmap_prot = PROT_READ,
+ .mmap_flags = MAP_PRIVATE,
+#ifdef NOMMU
+ .exp_err = EPERM,
+#else
+ .exp_err = 0,
+#endif
+ .resolve_exp_err = 0,
+ .old_pages = 4,
+ .new_pages = 8,
+ },
+ {
+ .name = "private file growth (rw-)",
+ .pathname = "/tmp/ksft.nommu-remap-XXXXXX",
+ .open_flags = O_CREAT | O_RDWR | O_EXCL,
+ .mmap_prot = PROT_READ | PROT_WRITE,
+ .mmap_flags = MAP_PRIVATE,
+#ifdef NOMMU
+ .exp_err = ENOMEM,
+#else
+ .exp_err = 0,
+#endif
+ .resolve_exp_err = 0,
+ .old_pages = 4,
+ .new_pages = 8,
+ },
+};
+
+static int run_mremap_test(struct mremap_case_t *tcase)
+{
+ int fd = -1;
+ void *addr, *addr2;
+ char pb[PATH_MAX];
+ const char *path = tcase->pathname;
+ int rc = KSFT_PASS;
+ int expected_error;
+ unsigned int old_pages = tcase->old_pages ?: 4;
+ unsigned int new_pages = tcase->new_pages ?: 2;
+ unsigned int file_pages = old_pages > new_pages ?
+ old_pages : new_pages;
+
+ ksft_print_msg("[RUN] Testing mremap: %s\n", tcase->name);
+
+ if (tcase->pathname && strstr(tcase->pathname, "XXXXXX")) {
+ strncpy(pb, tcase->pathname, sizeof(pb) - 1);
+ pb[sizeof(pb) - 1] = '\0';
+ fd = mkstemp(pb);
+ if (fd < 0) {
+ ksft_print_msg("Failed to setup file backing\n");
+ ksft_test_result_skip("%s\n", tcase->name);
+ return KSFT_SKIP;
+ }
+ if (ftruncate(fd, ps * file_pages) != 0) {
+ ksft_print_msg("Failed to setup file backing\n");
+ ksft_test_result_fail("%s\n", tcase->name);
+ close(fd);
+ unlink(pb);
+ return KSFT_FAIL;
+ }
+
+#ifdef NOMMU
+ if ((tcase->mmap_flags & MAP_SHARED) && get_fs_type(pb) != RAMFS_MAGIC) {
+ ksft_print_msg("Skip the test under non-ramfs filesystem (%s)\n",
+ pb);
+ ksft_test_result_skip("%s\n", tcase->name);
+ close(fd);
+ unlink(pb);
+ return KSFT_SKIP;
+ }
+#endif
+ path = pb;
+ } else if (tcase->pathname) {
+ fd = open(tcase->pathname, tcase->open_flags, 0600);
+ if (fd < 0) {
+ ksft_print_msg("Backing node not accessible\n");
+ ksft_test_result_skip("%s\n", tcase->name);
+ return KSFT_SKIP;
+ }
+
+#ifdef NOMMU
+ if ((tcase->mmap_flags & MAP_SHARED) &&
+ get_fs_type(tcase->pathname) != RAMFS_MAGIC) {
+ ksft_print_msg("Skip the test under non-ramfs filesystem (%s)\n",
+ tcase->pathname);
+ ksft_test_result_skip("%s\n", tcase->name);
+ close(fd);
+ return KSFT_SKIP;
+ }
+#endif
+ }
+
+ addr = mmap(NULL, ps * old_pages, tcase->mmap_prot,
+ tcase->mmap_flags, fd, 0);
+ if (addr == MAP_FAILED) {
+ ksft_print_msg("mmap mapping failed %s(%d)\n", strerror(errno), errno);
+ rc = KSFT_FAIL;
+ goto out;
+ }
+
+ expected_error = tcase->exp_err;
+ if (tcase->resolve_exp_err && fd >= 0)
+ expected_error = tcase->resolve_exp_err(path);
+
+ addr2 = mremap(addr, ps * old_pages, ps * new_pages,
+ MREMAP_MAYMOVE);
+
+ if (expected_error != 0) {
+ if (addr2 != MAP_FAILED) {
+ ksft_print_msg("Expected error %d, but mremap unexpectedly succeeded\n",
+ expected_error);
+ rc = KSFT_FAIL;
+ } else if (errno != expected_error) {
+ ksft_print_msg("Expected error %d, got %s(%d)\n",
+ expected_error, strerror(errno), errno);
+ rc = KSFT_FAIL;
+ } else {
+ ksft_print_msg("%s: Handled expected error path (errno=%d)\n",
+ tcase->name, expected_error);
+ }
+ } else if (addr2 == MAP_FAILED) {
+ ksft_print_msg("mremap shrink failed unexpectedly: %s\n",
+ strerror(errno));
+ rc = KSFT_FAIL;
+ } else {
+ ksft_print_msg("%s step successful\n", tcase->name);
+ }
+
+ /* clean up */
+ if (munmap(addr2 == MAP_FAILED ? addr : addr2,
+ addr2 == MAP_FAILED ? ps * old_pages : ps * new_pages)) {
+ ksft_print_msg("munmap failed: %s\n", strerror(errno));
+ rc = KSFT_FAIL;
+ }
+
+out:
+ if (fd >= 0) {
+ close(fd);
+ if (tcase->pathname && strstr(tcase->pathname, "XXXXXX"))
+ unlink(pb);
+ }
+
+ ksft_test_result_report(rc, "%s\n", tcase->name);
+ return rc;
+}
+
+int main(int argc, char **argv)
+{
+ int i;
+
+ ps = sysconf(_SC_PAGESIZE);
+ ksft_print_header();
+ ksft_set_plan(ARRAY_SIZE(mremap_cases) + 2);
+
+ munmap_shrink_test();
+ mremap_shrink_test();
+
+ for (i = 0; i < (int)ARRAY_SIZE(mremap_cases); i++)
+ run_mremap_test(&mremap_cases[i]);
+
+ ksft_finished();
+}