summaryrefslogtreecommitdiffstats
path: root/tools/testing/selftests/fchmodat2
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2026-10-02 12:17:24 -0700
committerLinus Torvalds <torvalds@linux-foundation.org>2026-10-02 12:17:24 -0700
commit3f1fe48a36b0b6722dc3fd421d93512bac138e9a (patch)
treeb767d7f6e26bc64334d3f14f8422d187239dc45d /tools/testing/selftests/fchmodat2
downloadlinux-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/.gitignore2
-rw-r--r--tools/testing/selftests/fchmodat2/Makefile15
-rw-r--r--tools/testing/selftests/fchmodat2/fchmodat2_test.c200
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();
+}