diff options
| author | Linus Torvalds <torvalds@linux-foundation.org> | 2026-10-02 12:17:24 -0700 |
|---|---|---|
| committer | Linus Torvalds <torvalds@linux-foundation.org> | 2026-10-02 12:17:24 -0700 |
| commit | 3f1fe48a36b0b6722dc3fd421d93512bac138e9a (patch) | |
| tree | b767d7f6e26bc64334d3f14f8422d187239dc45d /tools/testing/selftests/fchmodat2 | |
| download | linux-stable-3f1fe48a36b0b6722dc3fd421d93512bac138e9a.tar.gz linux-stable-3f1fe48a36b0b6722dc3fd421d93512bac138e9a.zip | |
Merge tag 'io_uring-7.3-20261002' of git://git.kernel.org/pub/scm/linux/kernel/git/axboe/linuxgrafted
Pull io_uring fixes from Jens Axboe:
- Fix a task_work add use-after-free with SQPOLL.
The sqpoll thread could pop and complete the last request while
io_req_normal_work_add() was still looking at them after the mpscq
push.
Use the same approach as DEFER_TASKRUN to protect from that, holding
an RCU read lock across the add, and have exit wait for an RCU grace
period for SQPOLL rings as well.
- CQE32 ring fixes: correct the free entry check for 32b CQEs, zero the
big_cqe for aux CQEs, and only post the dummy skip CQE on CQE_MIXED
rings
- Mark the source filter table as COW when cloning bpf filters, so
registering another filter on the source doesn't modify the shared
table in place
- Initialize the task context before running the BPF loop
- Requeue zcrx multishot receives stopped by a local resource
- End a TX_TIMESTAMP multishot cmd when the CQ is full (lollipopkit)
* tag 'io_uring-7.3-20261002' of git://git.kernel.org/pub/scm/linux/kernel/git/axboe/linux:
io_uring: fix task_work add use-after-free with SQPOLL
io_uring/cmd_net: end TX_TIMESTAMP multishot when the CQ is full
io_uring/zcrx: requeue multishot receives stopped by a local resource
io_uring: initialize task context before running the BPF loop
io_uring: zero big_cqe for aux CQEs on CQE32 rings
io_uring: fix free entry check for 32b CQEs on CQE32 rings
io_uring: only post the dummy skip CQE on CQE_MIXED rings
io_uring/bpf_filter: mark source as COW when cloning filters
Diffstat (limited to 'tools/testing/selftests/fchmodat2')
| -rw-r--r-- | tools/testing/selftests/fchmodat2/.gitignore | 2 | ||||
| -rw-r--r-- | tools/testing/selftests/fchmodat2/Makefile | 15 | ||||
| -rw-r--r-- | tools/testing/selftests/fchmodat2/fchmodat2_test.c | 200 |
3 files changed, 217 insertions, 0 deletions
diff --git a/tools/testing/selftests/fchmodat2/.gitignore b/tools/testing/selftests/fchmodat2/.gitignore new file mode 100644 index 000000000..82a4846cb --- /dev/null +++ b/tools/testing/selftests/fchmodat2/.gitignore @@ -0,0 +1,2 @@ +# SPDX-License-Identifier: GPL-2.0-only +/*_test diff --git a/tools/testing/selftests/fchmodat2/Makefile b/tools/testing/selftests/fchmodat2/Makefile new file mode 100644 index 000000000..4373cea79 --- /dev/null +++ b/tools/testing/selftests/fchmodat2/Makefile @@ -0,0 +1,15 @@ +# SPDX-License-Identifier: GPL-2.0-or-later + +CFLAGS += -Wall -O2 -g -fsanitize=address -fsanitize=undefined $(KHDR_INCLUDES) + +# gcc requires -static-libasan in order to ensure that Address Sanitizer's +# library is the first one loaded. However, clang already statically links the +# Address Sanitizer if -fsanitize is specified. Therefore, simply omit +# -static-libasan for clang builds. +ifeq ($(LLVM),) + CFLAGS += -static-libasan +endif + +TEST_GEN_PROGS := fchmodat2_test + +include ../lib.mk diff --git a/tools/testing/selftests/fchmodat2/fchmodat2_test.c b/tools/testing/selftests/fchmodat2/fchmodat2_test.c new file mode 100644 index 000000000..8e1b6384b --- /dev/null +++ b/tools/testing/selftests/fchmodat2/fchmodat2_test.c @@ -0,0 +1,200 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#define _GNU_SOURCE +#include <fcntl.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <syscall.h> +#include <unistd.h> + +#include "kselftest.h" + +struct testdir { + char *dirname; + int dfd; +}; + +int sys_fchmodat2(int dfd, const char *filename, mode_t mode, int flags) +{ + int ret = syscall(__NR_fchmodat2, dfd, filename, mode, flags); + + return ret >= 0 ? ret : -errno; +} + +static void setup_testdir(struct testdir *testdir) +{ + int ret, dfd; + char dirname[] = "/tmp/ksft-fchmodat2.XXXXXX"; + + /* Make the top-level directory. */ + if (!mkdtemp(dirname)) + ksft_exit_fail_msg("%s: failed to create tmpdir\n", __func__); + + dfd = open(dirname, O_PATH | O_DIRECTORY); + if (dfd < 0) { + ksft_perror("failed to open tmpdir"); + goto err; + } + + ret = openat(dfd, "regfile", O_CREAT | O_WRONLY | O_TRUNC, 0644); + if (ret < 0) { + ksft_perror("failed to create file in tmpdir"); + goto err; + } + close(ret); + + ret = symlinkat("regfile", dfd, "symlink"); + if (ret < 0) { + ksft_perror("symlinkat() failed"); + goto err_regfile; + } + + testdir->dirname = strdup(dirname); + if (!testdir->dirname) { + ksft_perror("Out of memory"); + goto err_symlink; + } + testdir->dfd = dfd; + + return; + +err_symlink: + unlinkat(testdir->dfd, "symlink", 0); +err_regfile: + unlinkat(testdir->dfd, "regfile", 0); +err: + unlink(dirname); + ksft_exit_fail(); +} + +static void cleanup_testdir(struct testdir *testdir) +{ + unlinkat(testdir->dfd, "regfile", 0); + unlinkat(testdir->dfd, "symlink", 0); + rmdir(testdir->dirname); + free(testdir->dirname); +} + +int expect_mode(int dfd, const char *filename, mode_t expect_mode) +{ + struct stat st; + int ret = fstatat(dfd, filename, &st, AT_SYMLINK_NOFOLLOW); + + if (ret) { + ksft_perror("fstatat() failed\n"); + return 0; + } + + return (st.st_mode == expect_mode); +} + +void test_regfile(void) +{ + struct testdir testdir; + int ret; + + setup_testdir(&testdir); + + ret = sys_fchmodat2(testdir.dfd, "regfile", 0640, 0); + + if (ret < 0) { + ksft_perror("fchmodat2(noflag) failed"); + goto out; + } + + if (!expect_mode(testdir.dfd, "regfile", 0100640)) { + ksft_print_msg("%s: wrong file mode bits after fchmodat2\n", + __func__); + ret = 1; + goto out; + } + + ret = sys_fchmodat2(testdir.dfd, "regfile", 0600, AT_SYMLINK_NOFOLLOW); + + if (ret < 0) { + ksft_perror("fchmodat2(AT_SYMLINK_NOFOLLOW) failed"); + goto out; + } + + if (!expect_mode(testdir.dfd, "regfile", 0100600)) { + ksft_print_msg("%s: wrong file mode bits after fchmodat2 with nofollow\n", + __func__); + ret = 1; + } + +out: + ksft_test_result(ret == 0, "fchmodat2(regfile)\n"); + cleanup_testdir(&testdir); +} + +void test_symlink(void) +{ + struct testdir testdir; + int ret; + + setup_testdir(&testdir); + + ret = sys_fchmodat2(testdir.dfd, "symlink", 0640, 0); + + if (ret < 0) { + ksft_perror("fchmodat2(noflag) failed"); + goto err; + } + + if (!expect_mode(testdir.dfd, "regfile", 0100640)) { + ksft_print_msg("%s: wrong file mode bits after fchmodat2\n", + __func__); + goto err; + } + + if (!expect_mode(testdir.dfd, "symlink", 0120777)) { + ksft_print_msg("%s: wrong symlink mode bits after fchmodat2\n", + __func__); + goto err; + } + + ret = sys_fchmodat2(testdir.dfd, "symlink", 0600, AT_SYMLINK_NOFOLLOW); + + /* + * On certain filesystems (xfs or btrfs), chmod operation fails. So we + * first check the symlink target but if the operation fails we mark the + * test as skipped. + * + * https://sourceware.org/legacy-ml/libc-alpha/2020-02/msg00467.html + */ + if (ret == 0 && !expect_mode(testdir.dfd, "symlink", 0120600)) { + ksft_print_msg("%s: wrong symlink mode bits after fchmodat2 with nofollow\n", + __func__); + ret = 1; + goto err; + } + + if (!expect_mode(testdir.dfd, "regfile", 0100640)) { + ksft_print_msg("%s: wrong file mode bits after fchmodat2 with nofollow\n", + __func__); + } + + if (ret != 0) + ksft_test_result_skip("fchmodat2(symlink)\n"); + else + ksft_test_result_pass("fchmodat2(symlink)\n"); + cleanup_testdir(&testdir); + return; + +err: + ksft_test_result_fail("fchmodat2(symlink)\n"); + cleanup_testdir(&testdir); +} + +#define NUM_TESTS 2 + +int main(int argc, char **argv) +{ + ksft_print_header(); + ksft_set_plan(NUM_TESTS); + + test_regfile(); + test_symlink(); + + ksft_finished(); +} |
