diff options
Diffstat (limited to 'tools/testing')
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(); +} |
