From 4911de3145a797389577abfdf9a5185d36cc18d7 Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Tue, 7 Apr 2026 18:03:13 +0200 Subject: [PATCH 01/73] uaccess: fix ignored_trailing logic in copy_struct_to_user() Currently all callers pass ignored_trailing=NULL, but I have code that will make use of. Now it actually behaves like documented: * If @usize < @ksize, then the kernel is trying to pass userspace a newer struct than it supports. Thus we only copy the interoperable portions (@usize) and ignore the rest (but @ignored_trailing is set to %true if any of the trailing (@ksize - @usize) bytes are non-zero). Fixes: 424a55a4a908 ("uaccess: add copy_struct_to_user helper") Cc: Dmitry Safonov <0x7f454c46@gmail.com> Cc: Dmitry Safonov Cc: Francesco Ruggeri Cc: Salam Noureddine Cc: David Ahern Cc: David S. Miller Cc: Michal Luczaj Cc: David Wei Cc: Luiz Augusto von Dentz Cc: Luiz Augusto von Dentz Cc: Marcel Holtmann Cc: Xin Long Cc: Eric Dumazet Cc: Kuniyuki Iwashima Cc: Paolo Abeni Cc: Willem de Bruijn Cc: Neal Cardwell Cc: Jakub Kicinski Cc: Simon Horman Cc: Aleksa Sarai Cc: Christian Brauner CC: Kees Cook Cc: netdev@vger.kernel.org Cc: linux-bluetooth@vger.kernel.org Cc: linux-kernel@vger.kernel.org Signed-off-by: Stefan Metzmacher Link: https://patch.msgid.link/71f69442410c1186ed8ce6d5b4b9d4a5a70edbad.1775576651.git.metze@samba.org Reviewed-by: Aleksa Sarai Signed-off-by: Christian Brauner --- include/linux/uaccess.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/linux/uaccess.h b/include/linux/uaccess.h index 56328601218c..09a09cc4aac2 100644 --- a/include/linux/uaccess.h +++ b/include/linux/uaccess.h @@ -510,7 +510,7 @@ copy_struct_to_user(void __user *dst, size_t usize, const void *src, return -EFAULT; } if (ignored_trailing) - *ignored_trailing = ksize < usize && + *ignored_trailing = usize < ksize && memchr_inv(src + size, 0, rest) != NULL; /* Copy the interoperable parts of the struct. */ if (copy_to_user(dst, src, size)) From db0493512931fe1e5a71612e6a358df1aa22d80c Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Tue, 7 Apr 2026 18:03:14 +0200 Subject: [PATCH 02/73] sockptr: fix usize check in copy_struct_from_sockptr() for user pointers copy_struct_from_user will never hit the check_zeroed_user() call and will never return -E2BIG if new userspace passed new bits in a larger structure than the current kernel structure. As far as I can there are no critical/related uapi changes in - include/net/bluetooth/bluetooth.h and net/bluetooth/sco.c after the use of copy_struct_from_sockptr in v6.13-rc3 - include/uapi/linux/tcp.h and net/ipv4/tcp_ao.c after the use of copy_struct_from_sockptr in v6.6-rc1 So that new callers will get the correct behavior from the start. Fixes: 4954f17ddefc ("net/tcp: Introduce TCP_AO setsockopt()s") Fixes: ef84703a911f ("net/tcp: Add TCP-AO getsockopt()s") Fixes: faadfaba5e01 ("net/tcp: Add TCP_AO_REPAIR") Fixes: 3e643e4efa1e ("Bluetooth: Improve setsockopt() handling of malformed user input") Cc: Dmitry Safonov <0x7f454c46@gmail.com> Cc: Dmitry Safonov Cc: Francesco Ruggeri Cc: Salam Noureddine Cc: David Ahern Cc: David S. Miller Cc: Michal Luczaj Cc: David Wei Cc: Luiz Augusto von Dentz Cc: Luiz Augusto von Dentz Cc: Marcel Holtmann Cc: Xin Long Cc: Eric Dumazet Cc: Kuniyuki Iwashima Cc: Paolo Abeni Cc: Willem de Bruijn Cc: Neal Cardwell Cc: Jakub Kicinski Cc: Simon Horman Cc: Aleksa Sarai Cc: Christian Brauner CC: Kees Cook Cc: netdev@vger.kernel.org Cc: linux-bluetooth@vger.kernel.org Cc: linux-kernel@vger.kernel.org Signed-off-by: Stefan Metzmacher Link: https://patch.msgid.link/cfaedbc33ae9d36adaabf04fa79424f30ff1efdd.1775576651.git.metze@samba.org Reviewed-by: Aleksa Sarai Signed-off-by: Christian Brauner --- include/linux/sockptr.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/linux/sockptr.h b/include/linux/sockptr.h index 3e6c8e9d67ae..ba88f4d78c1b 100644 --- a/include/linux/sockptr.h +++ b/include/linux/sockptr.h @@ -91,7 +91,7 @@ static inline int copy_struct_from_sockptr(void *dst, size_t ksize, size_t rest = max(ksize, usize) - size; if (!sockptr_is_kernel(src)) - return copy_struct_from_user(dst, ksize, src.user, size); + return copy_struct_from_user(dst, ksize, src.user, usize); if (usize < ksize) { memset(dst + size, 0, rest); From 2eef8b32e4c84caa927495f1d9bc9529d2bc5ac6 Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Tue, 7 Apr 2026 18:03:15 +0200 Subject: [PATCH 03/73] uaccess: add copy_struct_{from,to}_bounce_buffer() helpers These are similar to copy_struct_{from,to}_user() but operate on kernel buffers instead of user buffers. They can be used when there is a temporary bounce buffer used, e.g. in msg_control or similar places. It allows us to have the same logic to handle old vs. current and current vs. new structures in the same compatible way. copy_struct_from_sockptr() will also be able to use copy_struct_from_bounce_buffer() for the kernel case as follow us patch. I'll use this in my IPPROTO_SMBDIRECT work, but maybe it will also be useful for others... IPPROTO_QUIC will likely also use it. Cc: Dmitry Safonov <0x7f454c46@gmail.com> Cc: Dmitry Safonov Cc: Francesco Ruggeri Cc: Salam Noureddine Cc: David Ahern Cc: David S. Miller Cc: Michal Luczaj Cc: David Wei Cc: Luiz Augusto von Dentz Cc: Luiz Augusto von Dentz Cc: Marcel Holtmann Cc: Xin Long Cc: Eric Dumazet Cc: Kuniyuki Iwashima Cc: Paolo Abeni Cc: Willem de Bruijn Cc: Neal Cardwell Cc: Jakub Kicinski Cc: Simon Horman Cc: Aleksa Sarai Cc: Christian Brauner CC: Kees Cook Cc: netdev@vger.kernel.org Cc: linux-bluetooth@vger.kernel.org Cc: linux-kernel@vger.kernel.org Signed-off-by: Stefan Metzmacher Link: https://patch.msgid.link/f29570914590c50b9b6f451eb3a38d0fe1d954df.1775576651.git.metze@samba.org Signed-off-by: Christian Brauner --- include/linux/uaccess.h | 63 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/include/linux/uaccess.h b/include/linux/uaccess.h index 09a09cc4aac2..e4a64976f1c5 100644 --- a/include/linux/uaccess.h +++ b/include/linux/uaccess.h @@ -518,6 +518,69 @@ copy_struct_to_user(void __user *dst, size_t usize, const void *src, return 0; } +static __always_inline void +__copy_struct_generic_bounce_buffer(void *dst, size_t dstsize, + const void *src, size_t srcsize, + bool *ignored_trailing) +{ + size_t size = min(dstsize, srcsize); + size_t rest = max(dstsize, srcsize) - size; + + /* Deal with trailing bytes. */ + if (dstsize > srcsize) + memset(dst + size, 0, rest); + if (ignored_trailing) + *ignored_trailing = dstsize < srcsize && + memchr_inv(src + size, 0, rest) != NULL; + /* Copy the interoperable parts of the struct. */ + memcpy(dst, src, size); +} + +/** + * This is like copy_struct_from_user(), but the + * src buffer was already copied into a kernel + * bounce buffer, so it will never return -EFAULT. + */ +static __always_inline __must_check int +copy_struct_from_bounce_buffer(void *dst, size_t dstsize, + const void *src, size_t srcsize) +{ + bool ignored_trailing; + + /* Double check if ksize is larger than a known object size. */ + if (WARN_ON_ONCE(dstsize > __builtin_object_size(dst, 1))) + return -E2BIG; + + __copy_struct_generic_bounce_buffer(dst, dstsize, + src, srcsize, + &ignored_trailing); + if (unlikely(ignored_trailing)) + return -E2BIG; + + return 0; +} + +/** + * This is like copy_struct_to_user(), but the + * dst buffer is a kernel bounce buffer instead + * of a direct userspace buffer, so it will never return -EFAULT. + */ +static __always_inline __must_check int +copy_struct_to_bounce_buffer(void *dst, size_t dstsize, + const void *src, + size_t srcsize, + bool *ignored_trailing) +{ + /* Double check if srcsize is larger than a known object size. */ + if (WARN_ON_ONCE(srcsize > __builtin_object_size(src, 1))) + return -E2BIG; + + __copy_struct_generic_bounce_buffer(dst, dstsize, + src, srcsize, + ignored_trailing); + return 0; +} + bool copy_from_kernel_nofault_allowed(const void *unsafe_src, size_t size); long copy_from_kernel_nofault(void *dst, const void *src, size_t size); From d2c344740bf9e54c91d8d4a99bfe5fc1709a3ecc Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Tue, 7 Apr 2026 18:03:16 +0200 Subject: [PATCH 04/73] sockptr: let copy_struct_from_sockptr() use copy_struct_from_bounce_buffer() The world would be better without sockptr_t, but this at least simplifies copy_struct_from_sockptr() to be just a dispatcher for copy_struct_from_user() or copy_struct_from_bounce_buffer() without any special logic on its own. Cc: Dmitry Safonov <0x7f454c46@gmail.com> Cc: Dmitry Safonov Cc: Francesco Ruggeri Cc: Salam Noureddine Cc: David Ahern Cc: David S. Miller Cc: Michal Luczaj Cc: David Wei Cc: Luiz Augusto von Dentz Cc: Luiz Augusto von Dentz Cc: Marcel Holtmann Cc: Xin Long Cc: Eric Dumazet Cc: Kuniyuki Iwashima Cc: Paolo Abeni Cc: Willem de Bruijn Cc: Neal Cardwell Cc: Jakub Kicinski Cc: Simon Horman Cc: Aleksa Sarai Cc: Christian Brauner CC: Kees Cook Cc: netdev@vger.kernel.org Cc: linux-bluetooth@vger.kernel.org Cc: linux-kernel@vger.kernel.org Signed-off-by: Stefan Metzmacher Link: https://patch.msgid.link/b9b7e22664a53251d7ad099b12aead8b599c1257.1775576651.git.metze@samba.org Signed-off-by: Christian Brauner --- include/linux/sockptr.h | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/include/linux/sockptr.h b/include/linux/sockptr.h index ba88f4d78c1b..706a8526cf3c 100644 --- a/include/linux/sockptr.h +++ b/include/linux/sockptr.h @@ -87,24 +87,10 @@ static inline int copy_safe_from_sockptr(void *dst, size_t ksize, static inline int copy_struct_from_sockptr(void *dst, size_t ksize, sockptr_t src, size_t usize) { - size_t size = min(ksize, usize); - size_t rest = max(ksize, usize) - size; - if (!sockptr_is_kernel(src)) return copy_struct_from_user(dst, ksize, src.user, usize); - if (usize < ksize) { - memset(dst + size, 0, rest); - } else if (usize > ksize) { - char *p = src.kernel; - - while (rest--) { - if (*p++) - return -E2BIG; - } - } - memcpy(dst, src.kernel, size); - return 0; + return copy_struct_from_bounce_buffer(dst, ksize, src.kernel, usize); } static inline int copy_to_sockptr_offset(sockptr_t dst, size_t offset, From c5ca9f85d7fe27a8c9c88195bb819e8b569fa930 Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Tue, 7 Apr 2026 18:03:17 +0200 Subject: [PATCH 05/73] sockptr: introduce copy_struct_to_sockptr() We already have copy_struct_from_sockptr() as wrapper to copy_struct_from_user() or copy_struct_from_bounce_buffer(), so it's good to have copy_struct_to_sockptr() as well matching the behavior of copy_struct_to_user() or copy_struct_to_bounce_buffer(). The world would be better without sockptr_t, but having copy_struct_to_sockptr() is better than open code it in various places. I'll use this in my IPPROTO_SMBDIRECT work, but maybe it will also be useful for others... IPPROTO_QUIC will likely also use it. Cc: Dmitry Safonov <0x7f454c46@gmail.com> Cc: Dmitry Safonov Cc: Francesco Ruggeri Cc: Salam Noureddine Cc: David Ahern Cc: David S. Miller Cc: Michal Luczaj Cc: David Wei Cc: Luiz Augusto von Dentz Cc: Luiz Augusto von Dentz Cc: Marcel Holtmann Cc: Xin Long Cc: Eric Dumazet Cc: Kuniyuki Iwashima Cc: Paolo Abeni Cc: Willem de Bruijn Cc: Neal Cardwell Cc: Jakub Kicinski Cc: Simon Horman Cc: Aleksa Sarai Cc: Christian Brauner CC: Kees Cook Cc: netdev@vger.kernel.org Cc: linux-bluetooth@vger.kernel.org Cc: linux-kernel@vger.kernel.org Signed-off-by: Stefan Metzmacher Link: https://patch.msgid.link/c950ee1578cb93b4411c3731010def9c1cd82f0d.1775576651.git.metze@samba.org Signed-off-by: Christian Brauner --- include/linux/sockptr.h | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/include/linux/sockptr.h b/include/linux/sockptr.h index 706a8526cf3c..9c2429c1a570 100644 --- a/include/linux/sockptr.h +++ b/include/linux/sockptr.h @@ -107,6 +107,16 @@ static inline int copy_to_sockptr(sockptr_t dst, const void *src, size_t size) return copy_to_sockptr_offset(dst, 0, src, size); } +static inline int +copy_struct_to_sockptr(sockptr_t dst, size_t usize, const void *src, + size_t ksize, bool *ignored_trailing) +{ + if (!sockptr_is_kernel(dst)) + return copy_struct_to_user(dst.user, usize, src, ksize, ignored_trailing); + + return copy_struct_to_bounce_buffer(dst.kernel, usize, src, ksize, ignored_trailing); +} + static inline void *memdup_sockptr_noprof(sockptr_t src, size_t len) { void *p = kmalloc_track_caller_noprof(len, GFP_USER | __GFP_NOWARN); From fdb48976b6379c2b91e1ad4aafef07ee8b1ddb0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ricardo=20B=2E=20Marli=C3=A8re?= Date: Tue, 7 Apr 2026 11:35:45 -0300 Subject: [PATCH 06/73] selftests/namespaces: Kill grandchild in nsid fixture teardown MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The timens_separate and pidns_separate test cases fork a grandchild that calls pause(). FIXTURE_TEARDOWN only kills the direct child, which is the init process of the grandchild's namespace. Once the child (init) exits, the grandchild is reparented to the host init but remains alive and continues to hold the inherited write end of the test runner's TAP pipe open. tap_prefix never receives EOF and blocks indefinitely, hanging the entire test collection. Record the grandchild PID in the fixture struct so that teardown can send SIGKILL and reap it before dealing with the child. The grandchild must be reaped first because the child acts as its PID namespace init; killing the child first would kill the grandchild without giving us a chance to waitpid() it. Signed-off-by: Ricardo B. Marlière Link: https://patch.msgid.link/20260407-selftests-namespaces_fixes-v1-1-59109909d88b@suse.com Signed-off-by: Christian Brauner --- tools/testing/selftests/namespaces/nsid_test.c | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/tools/testing/selftests/namespaces/nsid_test.c b/tools/testing/selftests/namespaces/nsid_test.c index b4a14c6693a5..46dc838cba82 100644 --- a/tools/testing/selftests/namespaces/nsid_test.c +++ b/tools/testing/selftests/namespaces/nsid_test.c @@ -25,14 +25,24 @@ /* Fixture for tests that create child processes */ FIXTURE(nsid) { pid_t child_pid; + pid_t grandchild_pid; }; FIXTURE_SETUP(nsid) { self->child_pid = 0; + self->grandchild_pid = 0; } FIXTURE_TEARDOWN(nsid) { - /* Clean up any child process that may still be running */ + /* + * Kill grandchild first: timens_separate and pidns_separate fork a + * grandchild that calls pause(). It is reparented to init on child + * exit and keeps the test runner's tap pipe open, hanging the runner. + */ + if (self->grandchild_pid > 0) { + kill(self->grandchild_pid, SIGKILL); + waitpid(self->grandchild_pid, NULL, 0); + } if (self->child_pid > 0) { kill(self->child_pid, SIGKILL); waitpid(self->child_pid, NULL, 0); @@ -676,6 +686,7 @@ TEST_F(nsid, timens_separate) pid_t grandchild_pid; ASSERT_EQ(read(pipefd[0], &grandchild_pid, sizeof(grandchild_pid)), sizeof(grandchild_pid)); + self->grandchild_pid = grandchild_pid; close(pipefd[0]); /* Open grandchild's time namespace */ @@ -797,6 +808,7 @@ TEST_F(nsid, pidns_separate) pid_t grandchild_pid; ASSERT_EQ(read(pipefd[0], &grandchild_pid, sizeof(grandchild_pid)), sizeof(grandchild_pid)); + self->grandchild_pid = grandchild_pid; close(pipefd[0]); /* Open grandchild's PID namespace */ From f36ddf9317dc99019a498af3853d547de1f62e5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ricardo=20B=2E=20Marli=C3=A8re?= Date: Tue, 7 Apr 2026 11:35:46 -0300 Subject: [PATCH 07/73] selftests/namespaces: Fix waitpid race in listns_efault_test cleanup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The efault tests spawn two categories of child processes: namespace children (each in its own mount namespace, for concurrent destruction) and an iterator child that calls listns() in a tight loop. The cleanup loop used waitpid(-1), which reaps any child in any order. If the iterator child exits early (e.g. because listns() returned ENOSYS) before all namespace children have been reaped, waitpid(-1) may consume it instead. The subsequent targeted waitpid(iter_pid) would then block indefinitely. Track the PIDs of the namespace children explicitly and use targeted waitpid() calls in the cleanup loop so the iterator child cannot be inadvertently reaped during namespace cleanup. Signed-off-by: Ricardo B. Marlière Link: https://patch.msgid.link/20260407-selftests-namespaces_fixes-v1-2-59109909d88b@suse.com Signed-off-by: Christian Brauner --- .../selftests/namespaces/listns_efault_test.c | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/tools/testing/selftests/namespaces/listns_efault_test.c b/tools/testing/selftests/namespaces/listns_efault_test.c index b570746e917c..8df5397adbb0 100644 --- a/tools/testing/selftests/namespaces/listns_efault_test.c +++ b/tools/testing/selftests/namespaces/listns_efault_test.c @@ -38,7 +38,7 @@ TEST(listns_partial_fault_with_ns_cleanup) __u64 *ns_ids; ssize_t ret; long page_size; - pid_t pid, iter_pid; + pid_t pid, iter_pid, ns_pids[5]; int pidfds[5]; int sv[5][2]; int iter_pidfd; @@ -114,6 +114,7 @@ TEST(listns_partial_fault_with_ns_cleanup) pid = create_child(&pidfds[i], CLONE_NEWNS); ASSERT_NE(pid, -1); + ns_pids[i] = pid; if (pid == 0) { close(sv[i][0]); /* Close parent end */ @@ -164,7 +165,7 @@ TEST(listns_partial_fault_with_ns_cleanup) /* Wait for all mount namespace children to exit and cleanup */ for (i = 0; i < 5; i++) { - waitpid(-1, NULL, 0); + waitpid(ns_pids[i], NULL, 0); close(sv[i][0]); close(pidfds[i]); } @@ -250,7 +251,7 @@ TEST(listns_late_fault_with_ns_cleanup) __u64 *ns_ids; ssize_t ret; long page_size; - pid_t pid, iter_pid; + pid_t pid, iter_pid, ns_pids[10]; int pidfds[10]; int sv[10][2]; int iter_pidfd; @@ -320,6 +321,7 @@ TEST(listns_late_fault_with_ns_cleanup) pid = create_child(&pidfds[i], CLONE_NEWNS); ASSERT_NE(pid, -1); + ns_pids[i] = pid; if (pid == 0) { close(sv[i][0]); /* Close parent end */ @@ -373,7 +375,7 @@ TEST(listns_late_fault_with_ns_cleanup) /* Wait for all children and cleanup */ for (i = 0; i < 10; i++) { - waitpid(-1, NULL, 0); + waitpid(ns_pids[i], NULL, 0); close(sv[i][0]); close(pidfds[i]); } @@ -402,7 +404,7 @@ TEST(listns_mnt_ns_cleanup_on_fault) __u64 *ns_ids; ssize_t ret; long page_size; - pid_t pid, iter_pid; + pid_t pid, iter_pid, ns_pids[8]; int pidfds[8]; int sv[8][2]; int iter_pidfd; @@ -462,6 +464,7 @@ TEST(listns_mnt_ns_cleanup_on_fault) pid = create_child(&pidfds[i], CLONE_NEWNS); ASSERT_NE(pid, -1); + ns_pids[i] = pid; if (pid == 0) { close(sv[i][0]); /* Close parent end */ @@ -508,7 +511,7 @@ TEST(listns_mnt_ns_cleanup_on_fault) /* Wait for children and cleanup */ for (i = 0; i < 8; i++) { - waitpid(-1, NULL, 0); + waitpid(ns_pids[i], NULL, 0); close(sv[i][0]); close(pidfds[i]); } From 2509bdc8a47c2f13471ac43ec989c778ed304d77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ricardo=20B=2E=20Marli=C3=A8re?= Date: Tue, 7 Apr 2026 11:35:47 -0300 Subject: [PATCH 08/73] selftests/namespaces: Skip efault tests when listns() is not available MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When listns() is not implemented the iterator child detects ENOSYS and exits cleanly with status PIDFD_SKIP before the parent has a chance to signal it. The parent sends SIGKILL (which is a harmless no-op at that point) and then calls waitpid(), obtaining a normal-exit status. The subsequent ASSERT_TRUE(WIFSIGNALED(status)) therefore fails, causing the three EFAULT-focused tests to report FAIL rather than SKIP on kernels that do not yet carry listns() support. After collecting the iterator's exit status, check whether it exited with PIDFD_SKIP and issue a SKIP verdict in that case, consistent with the behaviour of every other listns test that already handles ENOSYS correctly. Signed-off-by: Ricardo B. Marlière Link: https://patch.msgid.link/20260407-selftests-namespaces_fixes-v1-3-59109909d88b@suse.com Signed-off-by: Christian Brauner --- .../selftests/namespaces/listns_efault_test.c | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/tools/testing/selftests/namespaces/listns_efault_test.c b/tools/testing/selftests/namespaces/listns_efault_test.c index 8df5397adbb0..26b452c98c66 100644 --- a/tools/testing/selftests/namespaces/listns_efault_test.c +++ b/tools/testing/selftests/namespaces/listns_efault_test.c @@ -176,6 +176,12 @@ TEST(listns_partial_fault_with_ns_cleanup) ASSERT_EQ(ret, iter_pid); close(iter_pidfd); + /* If listns() is not supported the iterator exits cleanly via ENOSYS */ + if (WIFEXITED(status) && WEXITSTATUS(status) == PIDFD_SKIP) { + munmap(map, page_size); + SKIP(return, "listns() not supported"); + } + /* Should have been killed */ ASSERT_TRUE(WIFSIGNALED(status)); ASSERT_EQ(WTERMSIG(status), SIGKILL); @@ -386,6 +392,12 @@ TEST(listns_late_fault_with_ns_cleanup) ASSERT_EQ(ret, iter_pid); close(iter_pidfd); + /* If listns() is not supported the iterator exits cleanly via ENOSYS */ + if (WIFEXITED(status) && WEXITSTATUS(status) == PIDFD_SKIP) { + munmap(map, page_size); + SKIP(return, "listns() not supported"); + } + /* Should have been killed */ ASSERT_TRUE(WIFSIGNALED(status)); ASSERT_EQ(WTERMSIG(status), SIGKILL); @@ -522,6 +534,12 @@ TEST(listns_mnt_ns_cleanup_on_fault) ASSERT_EQ(ret, iter_pid); close(iter_pidfd); + /* If listns() is not supported the iterator exits cleanly via ENOSYS */ + if (WIFEXITED(status) && WEXITSTATUS(status) == PIDFD_SKIP) { + munmap(map, page_size); + SKIP(return, "listns() not supported"); + } + /* Should have been killed */ ASSERT_TRUE(WIFSIGNALED(status)); ASSERT_EQ(WTERMSIG(status), SIGKILL); From 8d14fe78cb5ac5b7ac85f5fbf0be9afb3a3dba0e Mon Sep 17 00:00:00 2001 From: David Disseldorp Date: Tue, 31 Mar 2026 17:57:31 +1100 Subject: [PATCH 09/73] initramfs_test: add fill_cpio() inject_ox parameter fill_cpio() uses sprintf() to write out the in-memory cpio archive from an array of struct initramfs_test_cpio. This change allows callers to modify the cpio sprintf() format string so that future tests can intentionally corrupt the header with "0x" and "0X" prefixed fields. Signed-off-by: David Disseldorp Link: https://patch.msgid.link/20260331070519.5974-2-ddiss@suse.de Reviewed-by: Andy Shevchenko Reviewed-by: Petr Mladek Signed-off-by: Christian Brauner --- init/initramfs_test.c | 36 +++++++++++++++++++++++------------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/init/initramfs_test.c b/init/initramfs_test.c index 2ce38d9a8fd0..6fe1c44a74a5 100644 --- a/init/initramfs_test.c +++ b/init/initramfs_test.c @@ -27,7 +27,18 @@ struct initramfs_test_cpio { char *data; }; -static size_t fill_cpio(struct initramfs_test_cpio *cs, size_t csz, char *out) +/* regular newc header format */ +#define CPIO_HDR_FMT "%s%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%s" +/* + * Bogus newc header with "0x" prefixes on the uid, gid, and namesize values. + * parse_header()/simple_str[n]toul() accept this, contrary to the initramfs + * specification. + */ +#define CPIO_HDR_OX_INJECT \ + "%s%08x%08x0x%06x0X%06x%08x%08x%08x%08x%08x%08x%08x0x%06x%08x%s" + +static size_t fill_cpio(struct initramfs_test_cpio *cs, size_t csz, + bool inject_ox, char *out) { int i; size_t off = 0; @@ -38,9 +49,8 @@ static size_t fill_cpio(struct initramfs_test_cpio *cs, size_t csz, char *out) size_t thislen; /* +1 to account for nulterm */ - thislen = sprintf(pos, "%s" - "%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x" - "%s", + thislen = sprintf(pos, + inject_ox ? CPIO_HDR_OX_INJECT : CPIO_HDR_FMT, c->magic, c->ino, c->mode, c->uid, c->gid, c->nlink, c->mtime, c->filesize, c->devmajor, c->devminor, c->rdevmajor, c->rdevminor, c->namesize, c->csum, @@ -102,7 +112,7 @@ static void __init initramfs_test_extract(struct kunit *test) /* +3 to cater for any 4-byte end-alignment */ cpio_srcbuf = kzalloc(ARRAY_SIZE(c) * (CPIO_HDRLEN + PATH_MAX + 3), GFP_KERNEL); - len = fill_cpio(c, ARRAY_SIZE(c), cpio_srcbuf); + len = fill_cpio(c, ARRAY_SIZE(c), false, cpio_srcbuf); ktime_get_real_ts64(&ts_before); err = unpack_to_rootfs(cpio_srcbuf, len); @@ -177,7 +187,7 @@ static void __init initramfs_test_fname_overrun(struct kunit *test) /* limit overrun to avoid crashes / filp_open() ENAMETOOLONG */ cpio_srcbuf[CPIO_HDRLEN + strlen(c[0].fname) + 20] = '\0'; - len = fill_cpio(c, ARRAY_SIZE(c), cpio_srcbuf); + len = fill_cpio(c, ARRAY_SIZE(c), false, cpio_srcbuf); /* overwrite trailing fname terminator and padding */ suffix_off = len - 1; while (cpio_srcbuf[suffix_off] == '\0') { @@ -219,7 +229,7 @@ static void __init initramfs_test_data(struct kunit *test) cpio_srcbuf = kmalloc(CPIO_HDRLEN + c[0].namesize + c[0].filesize + 6, GFP_KERNEL); - len = fill_cpio(c, ARRAY_SIZE(c), cpio_srcbuf); + len = fill_cpio(c, ARRAY_SIZE(c), false, cpio_srcbuf); err = unpack_to_rootfs(cpio_srcbuf, len); KUNIT_EXPECT_NULL(test, err); @@ -274,7 +284,7 @@ static void __init initramfs_test_csum(struct kunit *test) cpio_srcbuf = kmalloc(8192, GFP_KERNEL); - len = fill_cpio(c, ARRAY_SIZE(c), cpio_srcbuf); + len = fill_cpio(c, ARRAY_SIZE(c), false, cpio_srcbuf); err = unpack_to_rootfs(cpio_srcbuf, len); KUNIT_EXPECT_NULL(test, err); @@ -284,7 +294,7 @@ static void __init initramfs_test_csum(struct kunit *test) /* mess up the csum and confirm that unpack fails */ c[0].csum--; - len = fill_cpio(c, ARRAY_SIZE(c), cpio_srcbuf); + len = fill_cpio(c, ARRAY_SIZE(c), false, cpio_srcbuf); err = unpack_to_rootfs(cpio_srcbuf, len); KUNIT_EXPECT_NOT_NULL(test, err); @@ -330,7 +340,7 @@ static void __init initramfs_test_hardlink(struct kunit *test) cpio_srcbuf = kmalloc(8192, GFP_KERNEL); - len = fill_cpio(c, ARRAY_SIZE(c), cpio_srcbuf); + len = fill_cpio(c, ARRAY_SIZE(c), false, cpio_srcbuf); err = unpack_to_rootfs(cpio_srcbuf, len); KUNIT_EXPECT_NULL(test, err); @@ -371,7 +381,7 @@ static void __init initramfs_test_many(struct kunit *test) }; c.namesize = 1 + sprintf(thispath, "initramfs_test_many-%d", i); - p += fill_cpio(&c, 1, p); + p += fill_cpio(&c, 1, false, p); } len = p - cpio_srcbuf; @@ -425,7 +435,7 @@ static void __init initramfs_test_fname_pad(struct kunit *test) } }; memcpy(tbufs->padded_fname, "padded_fname", sizeof("padded_fname")); - len = fill_cpio(c, ARRAY_SIZE(c), tbufs->cpio_srcbuf); + len = fill_cpio(c, ARRAY_SIZE(c), false, tbufs->cpio_srcbuf); err = unpack_to_rootfs(tbufs->cpio_srcbuf, len); KUNIT_EXPECT_NULL(test, err); @@ -481,7 +491,7 @@ static void __init initramfs_test_fname_path_max(struct kunit *test) memcpy(tbufs->fname_oversize, "fname_oversize", sizeof("fname_oversize") - 1); memcpy(tbufs->fname_ok, "fname_ok", sizeof("fname_ok") - 1); - len = fill_cpio(c, ARRAY_SIZE(c), tbufs->cpio_src); + len = fill_cpio(c, ARRAY_SIZE(c), false, tbufs->cpio_src); /* unpack skips over fname_oversize instead of returning an error */ err = unpack_to_rootfs(tbufs->cpio_src, len); From 19868f7034a1c5a548d70f20a35ad3562ac69e2c Mon Sep 17 00:00:00 2001 From: David Disseldorp Date: Tue, 31 Mar 2026 17:57:32 +1100 Subject: [PATCH 10/73] initramfs_test: test header fields with 0x hex prefix cpio header fields are 8-byte hex strings, but one "interesting" side-effect of our historic simple_str[n]toul() use means that a "0x" (or "0X") prefixed header field will be successfully processed when coupled alongside a 6-byte hex remainder string. "0x" prefix support is contrary to the initramfs specification at Documentation/driver-api/early-userspace/buffer-format.rst which states: The structure of the cpio_header is as follows (all fields contain hexadecimal ASCII numbers fully padded with '0' on the left to the full width of the field, for example, the integer 4780 is represented by the ASCII string "000012ac"): Test for this corner case by injecting "0x" prefixes into the uid, gid and namesize cpio header fields. Confirm that init_stat() returns matching uid and gid values. This test can be modified in future to expect unpack_to_rootfs() failure when header validation is changed to properly follow the specification. Add some missing struct kstat initializations to account for possible init_stat() failures. Signed-off-by: David Disseldorp Link: https://patch.msgid.link/20260331070519.5974-3-ddiss@suse.de Reviewed-by: Petr Mladek Reviewed-by: Andy Shevchenko Signed-off-by: Christian Brauner --- init/initramfs_test.c | 60 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 58 insertions(+), 2 deletions(-) diff --git a/init/initramfs_test.c b/init/initramfs_test.c index 6fe1c44a74a5..b9f83dc194aa 100644 --- a/init/initramfs_test.c +++ b/init/initramfs_test.c @@ -316,7 +316,7 @@ static void __init initramfs_test_hardlink(struct kunit *test) { char *err, *cpio_srcbuf; size_t len; - struct kstat st0, st1; + struct kstat st0 = {}, st1 = {}; struct initramfs_test_cpio c[] = { { .magic = "070701", .ino = 1, @@ -461,7 +461,7 @@ static void __init initramfs_test_fname_path_max(struct kunit *test) { char *err; size_t len; - struct kstat st0, st1; + struct kstat st0 = {}, st1 = {}; char fdata[] = "this file data will not be unpacked"; struct test_fname_path_max { char fname_oversize[PATH_MAX + 1]; @@ -504,6 +504,61 @@ static void __init initramfs_test_fname_path_max(struct kunit *test) kfree(tbufs); } +static void __init initramfs_test_hdr_hex(struct kunit *test) +{ + char *err; + size_t len; + struct kstat st0 = {}, st1 = {}; + char fdata[] = "this file data will be unpacked"; + struct initramfs_test_bufs { + char cpio_src[(CPIO_HDRLEN + PATH_MAX + 3 + sizeof(fdata)) * 2]; + } *tbufs = kzalloc(sizeof(struct initramfs_test_bufs), GFP_KERNEL); + struct initramfs_test_cpio c[] = { { + .magic = "070701", + .ino = 1, + .mode = S_IFREG | 0777, + .uid = 0x123456, + .gid = 0x123457, + .nlink = 1, + .namesize = sizeof("initramfs_test_hdr_hex_0"), + .fname = "initramfs_test_hdr_hex_0", + .filesize = sizeof(fdata), + .data = fdata, + }, { + .magic = "070701", + .ino = 2, + .mode = S_IFDIR | 0777, + .uid = 0x000056, + .gid = 0x000057, + .nlink = 1, + .namesize = sizeof("initramfs_test_hdr_hex_1"), + .fname = "initramfs_test_hdr_hex_1", + } }; + + /* inject_ox=true to add "0x" cpio field prefixes */ + len = fill_cpio(c, ARRAY_SIZE(c), true, tbufs->cpio_src); + + err = unpack_to_rootfs(tbufs->cpio_src, len); + KUNIT_EXPECT_NULL(test, err); + + KUNIT_EXPECT_EQ(test, init_stat(c[0].fname, &st0, 0), 0); + KUNIT_EXPECT_EQ(test, init_stat(c[1].fname, &st1, 0), 0); + + KUNIT_EXPECT_TRUE(test, + uid_eq(st0.uid, make_kuid(current_user_ns(), (uid_t)0x123456))); + KUNIT_EXPECT_TRUE(test, + gid_eq(st0.gid, make_kgid(current_user_ns(), (gid_t)0x123457))); + KUNIT_EXPECT_TRUE(test, + uid_eq(st1.uid, make_kuid(current_user_ns(), (uid_t)0x56))); + KUNIT_EXPECT_TRUE(test, + gid_eq(st1.gid, make_kgid(current_user_ns(), (gid_t)0x57))); + + KUNIT_EXPECT_EQ(test, init_unlink(c[0].fname), 0); + KUNIT_EXPECT_EQ(test, init_rmdir(c[1].fname), 0); + + kfree(tbufs); +} + /* * The kunit_case/_suite struct cannot be marked as __initdata as this will be * used in debugfs to retrieve results after test has run. @@ -517,6 +572,7 @@ static struct kunit_case __refdata initramfs_test_cases[] = { KUNIT_CASE(initramfs_test_many), KUNIT_CASE(initramfs_test_fname_pad), KUNIT_CASE(initramfs_test_fname_path_max), + KUNIT_CASE(initramfs_test_hdr_hex), {}, }; From a4d6170e86c72fad45257e189bbd901f64dfb40a Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Tue, 31 Mar 2026 17:57:33 +1100 Subject: [PATCH 11/73] initramfs: Sort headers alphabetically Sorting headers alphabetically helps locating duplicates, and makes it easier to figure out where to insert new headers. Signed-off-by: Andy Shevchenko Link: https://patch.msgid.link/20260331070519.5974-4-ddiss@suse.de Reviewed-by: David Disseldorp Reviewed-by: Petr Mladek Signed-off-by: Christian Brauner --- init/initramfs.c | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/init/initramfs.c b/init/initramfs.c index 58db15fb18fd..bf9664fdd8fe 100644 --- a/init/initramfs.c +++ b/init/initramfs.c @@ -1,25 +1,25 @@ // SPDX-License-Identifier: GPL-2.0 -#include #include -#include -#include -#include -#include -#include #include -#include #include -#include -#include +#include +#include #include +#include +#include +#include #include #include #include #include -#include -#include -#include #include +#include +#include +#include +#include +#include +#include +#include #include "do_mounts.h" #include "initramfs_internal.h" From ec03d259f67bab506dc176a661210a3ee15e31b6 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Tue, 31 Mar 2026 17:57:34 +1100 Subject: [PATCH 12/73] initramfs: Refactor to use hex2bin() instead of custom approach There is a simple_strntoul() function used solely as a shortcut for hex2bin() with proper endianess conversions. Replace that and drop the unneeded function in the next changes. This implementation will abort if we fail to parse the cpio header, instead of using potentially bogus header values. Co-developed-by: David Disseldorp Signed-off-by: David Disseldorp Signed-off-by: Andy Shevchenko Link: https://patch.msgid.link/20260331070519.5974-5-ddiss@suse.de Reviewed-by: Petr Mladek Signed-off-by: Christian Brauner --- init/initramfs.c | 44 +++++++++++++++++++++++++------------------ init/initramfs_test.c | 24 ++++------------------- 2 files changed, 30 insertions(+), 38 deletions(-) diff --git a/init/initramfs.c b/init/initramfs.c index bf9664fdd8fe..20a18fcda48e 100644 --- a/init/initramfs.c +++ b/init/initramfs.c @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -21,6 +22,8 @@ #include #include +#include + #include "do_mounts.h" #include "initramfs_internal.h" @@ -190,26 +193,30 @@ static __initdata gid_t gid; static __initdata unsigned rdev; static __initdata u32 hdr_csum; -static void __init parse_header(char *s) +static int __init parse_header(char *s) { - unsigned long parsed[13]; - int i; + __be32 header[13]; + int ret; - for (i = 0, s += 6; i < 13; i++, s += 8) - parsed[i] = simple_strntoul(s, NULL, 16, 8); + ret = hex2bin((u8 *)header, s + 6, sizeof(header)); + if (ret) { + error("damaged header"); + return ret; + } - ino = parsed[0]; - mode = parsed[1]; - uid = parsed[2]; - gid = parsed[3]; - nlink = parsed[4]; - mtime = parsed[5]; /* breaks in y2106 */ - body_len = parsed[6]; - major = parsed[7]; - minor = parsed[8]; - rdev = new_encode_dev(MKDEV(parsed[9], parsed[10])); - name_len = parsed[11]; - hdr_csum = parsed[12]; + ino = be32_to_cpu(header[0]); + mode = be32_to_cpu(header[1]); + uid = be32_to_cpu(header[2]); + gid = be32_to_cpu(header[3]); + nlink = be32_to_cpu(header[4]); + mtime = be32_to_cpu(header[5]); /* breaks in y2106 */ + body_len = be32_to_cpu(header[6]); + major = be32_to_cpu(header[7]); + minor = be32_to_cpu(header[8]); + rdev = new_encode_dev(MKDEV(be32_to_cpu(header[9]), be32_to_cpu(header[10]))); + name_len = be32_to_cpu(header[11]); + hdr_csum = be32_to_cpu(header[12]); + return 0; } /* Finite-state machine */ @@ -289,7 +296,8 @@ static int __init do_header(void) error("no cpio magic"); return 1; } - parse_header(collected); + if (parse_header(collected)) + return 1; next_header = this_header + N_ALIGN(name_len) + body_len; next_header = (next_header + 3) & ~3; state = SkipIt; diff --git a/init/initramfs_test.c b/init/initramfs_test.c index b9f83dc194aa..8a0ddc2db2c0 100644 --- a/init/initramfs_test.c +++ b/init/initramfs_test.c @@ -31,8 +31,8 @@ struct initramfs_test_cpio { #define CPIO_HDR_FMT "%s%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%s" /* * Bogus newc header with "0x" prefixes on the uid, gid, and namesize values. - * parse_header()/simple_str[n]toul() accept this, contrary to the initramfs - * specification. + * parse_header()/simple_str[n]toul() accepted this, contrary to the initramfs + * specification. hex2bin() now fails. */ #define CPIO_HDR_OX_INJECT \ "%s%08x%08x0x%06x0X%06x%08x%08x%08x%08x%08x%08x%08x0x%06x%08x%s" @@ -508,8 +508,7 @@ static void __init initramfs_test_hdr_hex(struct kunit *test) { char *err; size_t len; - struct kstat st0 = {}, st1 = {}; - char fdata[] = "this file data will be unpacked"; + char fdata[] = "this file data will not be unpacked"; struct initramfs_test_bufs { char cpio_src[(CPIO_HDRLEN + PATH_MAX + 3 + sizeof(fdata)) * 2]; } *tbufs = kzalloc(sizeof(struct initramfs_test_bufs), GFP_KERNEL); @@ -539,22 +538,7 @@ static void __init initramfs_test_hdr_hex(struct kunit *test) len = fill_cpio(c, ARRAY_SIZE(c), true, tbufs->cpio_src); err = unpack_to_rootfs(tbufs->cpio_src, len); - KUNIT_EXPECT_NULL(test, err); - - KUNIT_EXPECT_EQ(test, init_stat(c[0].fname, &st0, 0), 0); - KUNIT_EXPECT_EQ(test, init_stat(c[1].fname, &st1, 0), 0); - - KUNIT_EXPECT_TRUE(test, - uid_eq(st0.uid, make_kuid(current_user_ns(), (uid_t)0x123456))); - KUNIT_EXPECT_TRUE(test, - gid_eq(st0.gid, make_kgid(current_user_ns(), (gid_t)0x123457))); - KUNIT_EXPECT_TRUE(test, - uid_eq(st1.uid, make_kuid(current_user_ns(), (uid_t)0x56))); - KUNIT_EXPECT_TRUE(test, - gid_eq(st1.gid, make_kgid(current_user_ns(), (gid_t)0x57))); - - KUNIT_EXPECT_EQ(test, init_unlink(c[0].fname), 0); - KUNIT_EXPECT_EQ(test, init_rmdir(c[1].fname), 0); + KUNIT_EXPECT_NOT_NULL(test, err); kfree(tbufs); } From 54d30551e460b74b171a6d4a1b49157999d68247 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Tue, 31 Mar 2026 17:57:35 +1100 Subject: [PATCH 13/73] vsprintf: Revert "add simple_strntoul" No users anymore and none should be in the first place. This reverts commit fcc155008a20fa31b01569e105250490750f0687. Signed-off-by: Andy Shevchenko Link: https://patch.msgid.link/20260331070519.5974-6-ddiss@suse.de Acked-by: Petr Mladek Reviewed-by: David Disseldorp Signed-off-by: Christian Brauner --- include/linux/kstrtox.h | 1 - lib/vsprintf.c | 7 ------- 2 files changed, 8 deletions(-) diff --git a/include/linux/kstrtox.h b/include/linux/kstrtox.h index 6ea897222af1..7fcf29a4e0de 100644 --- a/include/linux/kstrtox.h +++ b/include/linux/kstrtox.h @@ -143,7 +143,6 @@ static inline int __must_check kstrtos32_from_user(const char __user *s, size_t */ extern unsigned long simple_strtoul(const char *,char **,unsigned int); -extern unsigned long simple_strntoul(const char *,char **,unsigned int,size_t); extern long simple_strtol(const char *,char **,unsigned int); extern unsigned long long simple_strtoull(const char *,char **,unsigned int); extern long long simple_strtoll(const char *,char **,unsigned int); diff --git a/lib/vsprintf.c b/lib/vsprintf.c index 9f359b31c8d1..a6169e9bcdc9 100644 --- a/lib/vsprintf.c +++ b/lib/vsprintf.c @@ -129,13 +129,6 @@ unsigned long simple_strtoul(const char *cp, char **endp, unsigned int base) } EXPORT_SYMBOL(simple_strtoul); -unsigned long simple_strntoul(const char *cp, char **endp, unsigned int base, - size_t max_chars) -{ - return simple_strntoull(cp, endp, base, max_chars); -} -EXPORT_SYMBOL(simple_strntoul); - /** * simple_strtol - convert a string to a signed long * @cp: The start of the string From a2faa0e062db577a0b2e76bea632e924c63c132f Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Tue, 31 Mar 2026 17:57:36 +1100 Subject: [PATCH 14/73] kstrtox: Drop extern keyword in the simple_strtox() declarations There is legacy 'extern' keyword for the exported simple_strtox() function which are the artefact that can be removed. So drop it. While at it, tweak the declaration to provide parameter names. Signed-off-by: Andy Shevchenko Link: https://patch.msgid.link/20260331070519.5974-7-ddiss@suse.de Reviewed-by: David Disseldorp Reviewed-by: Petr Mladek Signed-off-by: Christian Brauner --- include/linux/kstrtox.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/include/linux/kstrtox.h b/include/linux/kstrtox.h index 7fcf29a4e0de..6c9282866770 100644 --- a/include/linux/kstrtox.h +++ b/include/linux/kstrtox.h @@ -142,9 +142,9 @@ static inline int __must_check kstrtos32_from_user(const char __user *s, size_t * Keep in mind above caveat. */ -extern unsigned long simple_strtoul(const char *,char **,unsigned int); -extern long simple_strtol(const char *,char **,unsigned int); -extern unsigned long long simple_strtoull(const char *,char **,unsigned int); -extern long long simple_strtoll(const char *,char **,unsigned int); +unsigned long simple_strtoul(const char *cp, char **endp, unsigned int base); +long simple_strtol(const char *cp, char **endp, unsigned int base); +unsigned long long simple_strtoull(const char *cp, char **endp, unsigned int base); +long long simple_strtoll(const char *cp, char **endp, unsigned int base); #endif /* _LINUX_KSTRTOX_H */ From 30beced6ec4931db201b77493d41d0df7d7eb5aa Mon Sep 17 00:00:00 2001 From: Wang Haoran Date: Mon, 13 Apr 2026 14:06:55 +0800 Subject: [PATCH 15/73] iov_iter: use kmemdup_array for dup_iter to harden against overflow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit While auditing the Linux 7.0-rc2 kernel, I identified a potential security vulnerability in the iov_iter framework's memory allocation logic. The dup_iter() function, which is exported via EXPORT_SYMBOL, currently uses kmemdup() with a raw multiplication to allocate the duplicate iovec array: new->iov = kmemdup(from->iov, nr_segs * sizeof(struct iovec), gfp); The hazard here is that dup_iter() relies on a primitive multiplication without any integrated overflow check. Since nr_segs is often derived from user-space input, this line is vulnerable to integer overflow (on 32-bit systems or via type narrowing), potentially leading to a small allocation followed by a large out-of-bounds memory copy. Furthermore, it allows for unbounded memory allocations, as the function lacks intrinsic knowledge of safe limits. On the 7.0-rc2 branch, several high-impact callchains still rely on this exported function: drivers/usb/gadget/function/f_fs.c: The ffs_epfile_read_iter() path demonstrates why relying on dup_iter() is dangerous: it performs allocation based on user input before verifying driver state. This confirms that dup_iter() must be hardened internally as it cannot assume pre-validated input. drivers/usb/gadget/legacy/inode.c: The ep_read_iter() path illustrates how dup_iter()’s lack of boundary awareness compounds resource risks. When combined with other allocations, it creates a multiplier effect for kernel memory pressure. This patch replaces kmemdup() with kmemdup_array(), which utilizes check_mul_overflow() to ensure the allocation size is calculated safely, hardening dup_iter() against malicious or malformed inputs from its callers Signed-off-by: Wang Haoran Link: https://patch.msgid.link/20260413060655.1139141-1-haoranwangsec@gmail.com Reviewed-by: Christoph Hellwig Signed-off-by: Christian Brauner --- lib/iov_iter.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/iov_iter.c b/lib/iov_iter.c index 243662af1af7..273919b16161 100644 --- a/lib/iov_iter.c +++ b/lib/iov_iter.c @@ -1224,13 +1224,13 @@ const void *dup_iter(struct iov_iter *new, struct iov_iter *old, gfp_t flags) { *new = *old; if (iov_iter_is_bvec(new)) - return new->bvec = kmemdup(new->bvec, - new->nr_segs * sizeof(struct bio_vec), + return new->bvec = kmemdup_array(new->bvec, + new->nr_segs, sizeof(struct bio_vec), flags); else if (iov_iter_is_kvec(new) || iter_is_iovec(new)) /* iovec and kvec have identical layout */ - return new->__iov = kmemdup(new->__iov, - new->nr_segs * sizeof(struct iovec), + return new->__iov = kmemdup_array(new->__iov, + new->nr_segs, sizeof(struct iovec), flags); return NULL; } From 80008c75f4523076a0ada0ac883ad8890b830ec9 Mon Sep 17 00:00:00 2001 From: Jori Koolstra Date: Mon, 20 Apr 2026 12:18:01 +0200 Subject: [PATCH 16/73] vfs: remove always taken if-branch in find_next_fd() find_next_fd() finds the next free fd slot in the passed fdtable's bitmap. It does so in two steps: first it checks whether the bitmap has a free entry in the word containing start. If not, it looks at second level bitmap that registers which words in the first level bitmap are full and then looks at the first level bitmap at the first non-full word. In the current code the second level lookup is done by: bitbit = find_next_zero_bit(fdt->full_fds_bits, maxbit, bitbit) * BITS_PER_LONG; where bitbit = start / BITS_PER_LONG. However, in the fast path (first step) we already checked the word at bitbit, so we can skip that word bit and start at bitbit+1. This also means that we can get rid of the branch if (bitbit > start) start = bitbit; since if we set bitbit = find_next_zero_bit(fdt->full_fds_bits, maxbit, bitbit+1) * BITS_PER_LONG; the reassigned bitbit can never be less than ((start/BITS_PER_LONG)+1) * BITS_PER_LONG > start So the branch is always taken. Obviously the reuse of the variable name bitbit (and the name itself) is quite confusing, so change that as well. Signed-off-by: Jori Koolstra Link: https://patch.msgid.link/20260420101801.806785-1-jkoolstra@xs4all.nl Reviewed-by: Jan Kara Signed-off-by: Christian Brauner --- fs/file.c | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/fs/file.c b/fs/file.c index 2c81c0b162d0..e5c75b22e0c7 100644 --- a/fs/file.c +++ b/fs/file.c @@ -544,24 +544,23 @@ struct files_struct init_files = { static unsigned int find_next_fd(struct fdtable *fdt, unsigned int start) { unsigned int maxfd = fdt->max_fds; /* always multiple of BITS_PER_LONG */ - unsigned int maxbit = maxfd / BITS_PER_LONG; - unsigned int bitbit = start / BITS_PER_LONG; + unsigned int max_fds_words = maxfd / BITS_PER_LONG; + unsigned int fds_word_idx = start / BITS_PER_LONG; unsigned int bit; /* * Try to avoid looking at the second level bitmap */ - bit = find_next_zero_bit(&fdt->open_fds[bitbit], BITS_PER_LONG, + bit = find_next_zero_bit(&fdt->open_fds[fds_word_idx], BITS_PER_LONG, start & (BITS_PER_LONG - 1)); if (bit < BITS_PER_LONG) - return bit + bitbit * BITS_PER_LONG; + return bit + (fds_word_idx * BITS_PER_LONG); - bitbit = find_next_zero_bit(fdt->full_fds_bits, maxbit, bitbit) * BITS_PER_LONG; - if (bitbit >= maxfd) + bit = BITS_PER_LONG * + find_next_zero_bit(fdt->full_fds_bits, max_fds_words, fds_word_idx + 1); + if (bit >= maxfd) return maxfd; - if (bitbit > start) - start = bitbit; - return find_next_zero_bit(fdt->open_fds, maxfd, start); + return find_next_zero_bit(fdt->open_fds, maxfd, bit); } /* From 0447533faeeefc02c2979abf46c4dcbbbe6d9871 Mon Sep 17 00:00:00 2001 From: Thorsten Blum Date: Fri, 17 Apr 2026 11:42:40 +0200 Subject: [PATCH 17/73] dcache: use kmalloc_flex() in __d_alloc Use kmalloc_flex() when allocating a new 'struct external_name' in __d_alloc() to replace offsetof() and the open-coded size arithmetic, and to keep the size type-safe. Signed-off-by: Thorsten Blum Link: https://patch.msgid.link/20260417094238.551114-3-thorsten.blum@linux.dev Reviewed-by: Jan Kara Reviewed-by: Jori Koolstra Signed-off-by: Christian Brauner --- fs/dcache.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/fs/dcache.c b/fs/dcache.c index 2c61aeea41f4..8ffc4ef79bba 100644 --- a/fs/dcache.c +++ b/fs/dcache.c @@ -1820,10 +1820,10 @@ static struct dentry *__d_alloc(struct super_block *sb, const struct qstr *name) name = &slash_name; dname = dentry->d_shortname.string; } else if (name->len > DNAME_INLINE_LEN-1) { - size_t size = offsetof(struct external_name, name[1]); - struct external_name *p = kmalloc(size + name->len, - GFP_KERNEL_ACCOUNT | - __GFP_RECLAIMABLE); + struct external_name *p; + + p = kmalloc_flex(*p, name, name->len + 1, + GFP_KERNEL_ACCOUNT | __GFP_RECLAIMABLE); if (!p) { kmem_cache_free(dentry_cache, dentry); return NULL; From 9f93417af489f74739c9434c4c43557ccc07d222 Mon Sep 17 00:00:00 2001 From: Li RongQing Date: Fri, 10 Apr 2026 04:09:18 -0400 Subject: [PATCH 18/73] fs/coredump: reduce redundant log noise in validate_coredump_safety Currently, writing to 'core_pattern' or 'suid_dumpable' sysctl nodes always triggers validate_coredump_safety(), even if the values have not changed. This results in redundant warning messages in dmesg: "Unsafe core_pattern used with fs.suid_dumpable=2..." This patch optimizes the procfs handlers to only invoke the safety validation when an actual change in the configuration is detected: 1. In proc_dostring_coredump(), compare the new core_pattern string with the existing one using strncmp(). 2. In proc_dointvec_minmax_coredump(), check if the new suid_dumpable value differs from the previous one. This keeps the kernel log clean from repetitive warnings when re-applying the same sysctl settings. Signed-off-by: Li RongQing Link: https://patch.msgid.link/20260410080918.2319-1-lirongqing@baidu.com Signed-off-by: Christian Brauner (Amutable) --- fs/coredump.c | 3 ++- fs/exec.c | 6 ++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/fs/coredump.c b/fs/coredump.c index bb6fdb1f458e..c0c919621a7a 100644 --- a/fs/coredump.c +++ b/fs/coredump.c @@ -1488,7 +1488,8 @@ static int proc_dostring_coredump(const struct ctl_table *table, int write, return -EINVAL; } - validate_coredump_safety(); + if (strncmp(old_core_pattern, core_pattern, CORENAME_MAX_SIZE)) + validate_coredump_safety(); return error; } diff --git a/fs/exec.c b/fs/exec.c index ba12b4c466f6..2889b7cf808d 100644 --- a/fs/exec.c +++ b/fs/exec.c @@ -1975,9 +1975,11 @@ COMPAT_SYSCALL_DEFINE5(execveat, int, fd, static int proc_dointvec_minmax_coredump(const struct ctl_table *table, int write, void *buffer, size_t *lenp, loff_t *ppos) { - int error = proc_dointvec_minmax(table, write, buffer, lenp, ppos); + int error, old = READ_ONCE(suid_dumpable); - if (!error && write) + error = proc_dointvec_minmax(table, write, buffer, lenp, ppos); + + if (!error && write && (old != READ_ONCE(suid_dumpable))) validate_coredump_safety(); return error; } From c06d4e760c9842b013fce9614fbfd6db966c061d Mon Sep 17 00:00:00 2001 From: Jeff Layton Date: Wed, 22 Apr 2026 07:29:48 -0400 Subject: [PATCH 19/73] dcache: add extra sanity checks of the dentry in dentry_free() If d_flags isn't what we expect, then it's good to display it. Add a new DENTRY_WARN_ONCE() macro that also displays d_flags for the dentry. Change D_FLAG_VERIFY() to call that instead of a generic WARN_ON_ONCE(). Change the existing hlist_unhashed() check in dentry_free() to use the new macro, and add checks for other invariants of a dead dentry. Notably: 1) Ensure that DCACHE_LRU_LIST and DCACHE_SHRINK_LIST are not set. 2) Ensure that d_lockref is negative Signed-off-by: Jeff Layton Link: https://patch.msgid.link/20260422-dcache-warn-v1-1-50155e1b40b6@kernel.org Signed-off-by: Christian Brauner (Amutable) --- fs/dcache.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/fs/dcache.c b/fs/dcache.c index 8ffc4ef79bba..131a3cf1c360 100644 --- a/fs/dcache.c +++ b/fs/dcache.c @@ -426,9 +426,16 @@ static inline void __d_clear_type_and_inode(struct dentry *dentry) this_cpu_inc(nr_dentry_negative); } +#define DENTRY_WARN_ONCE(condition, dentry) \ + WARN_ONCE((condition), "dentry=%p d_flags=0x%x\n", (dentry), (dentry)->d_flags) +#define D_FLAG_VERIFY(dentry, x) \ + DENTRY_WARN_ONCE(((dentry)->d_flags & (DCACHE_LRU_LIST | DCACHE_SHRINK_LIST)) != (x), (dentry)) + static void dentry_free(struct dentry *dentry) { - WARN_ON(d_really_is_positive(dentry)); + DENTRY_WARN_ONCE(d_really_is_positive(dentry), dentry); + DENTRY_WARN_ONCE(dentry->d_lockref.count >= 0, dentry); + D_FLAG_VERIFY(dentry, 0); if (unlikely(dname_external(dentry))) { struct external_name *p = external_name(dentry); if (likely(atomic_dec_and_test(&p->count))) { @@ -495,7 +502,6 @@ static void dentry_unlink_inode(struct dentry * dentry) * These helper functions make sure we always follow the * rules. d_lock must be held by the caller. */ -#define D_FLAG_VERIFY(dentry,x) WARN_ON_ONCE(((dentry)->d_flags & (DCACHE_LRU_LIST | DCACHE_SHRINK_LIST)) != (x)) static void d_lru_add(struct dentry *dentry) { D_FLAG_VERIFY(dentry, 0); From 4d636e5aabb887dc72867110710737398effaa70 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Mon, 11 May 2026 09:22:28 +0200 Subject: [PATCH 20/73] fs: unexport drop_super_exclusive drop_super_exclusive is only used by the built-in quota code. Signed-off-by: Christoph Hellwig Link: https://patch.msgid.link/20260511072239.2456725-2-hch@lst.de Signed-off-by: Christian Brauner (Amutable) --- fs/super.c | 1 - 1 file changed, 1 deletion(-) diff --git a/fs/super.c b/fs/super.c index 378e81efe643..5d46a0d5b616 100644 --- a/fs/super.c +++ b/fs/super.c @@ -882,7 +882,6 @@ void drop_super_exclusive(struct super_block *sb) super_unlock_excl(sb); put_super(sb); } -EXPORT_SYMBOL(drop_super_exclusive); enum super_iter_flags_t { SUPER_ITER_EXCL = (1U << 0), From e6666aef11053d492ff6e715c89b139fc1655b1c Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Mon, 11 May 2026 09:22:29 +0200 Subject: [PATCH 21/73] fs: remove start_removing_user_path_at This function is entirely unused, remove it. Signed-off-by: Christoph Hellwig Link: https://patch.msgid.link/20260511072239.2456725-3-hch@lst.de Signed-off-by: Christian Brauner (Amutable) --- Documentation/filesystems/porting.rst | 1 - fs/namei.c | 9 --------- include/linux/namei.h | 1 - 3 files changed, 11 deletions(-) diff --git a/Documentation/filesystems/porting.rst b/Documentation/filesystems/porting.rst index fdf074429cd3..f546b1d3897f 100644 --- a/Documentation/filesystems/porting.rst +++ b/Documentation/filesystems/porting.rst @@ -1297,7 +1297,6 @@ Several functions are renamed: - kern_path_locked -> start_removing_path - kern_path_create -> start_creating_path - user_path_create -> start_creating_user_path -- user_path_locked_at -> start_removing_user_path_at - done_path_create -> end_creating_path --- diff --git a/fs/namei.c b/fs/namei.c index c7fac83c9a85..bc641838530f 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -3029,15 +3029,6 @@ struct dentry *start_removing_path(const char *name, struct path *path) return __start_removing_path(AT_FDCWD, filename, path); } -struct dentry *start_removing_user_path_at(int dfd, - const char __user *name, - struct path *path) -{ - CLASS(filename, filename)(name); - return __start_removing_path(dfd, filename, path); -} -EXPORT_SYMBOL(start_removing_user_path_at); - int kern_path(const char *name, unsigned int flags, struct path *path) { CLASS(filename_kernel, filename)(name); diff --git a/include/linux/namei.h b/include/linux/namei.h index 2ad6dd9987b9..80488b3de0c9 100644 --- a/include/linux/namei.h +++ b/include/linux/namei.h @@ -61,7 +61,6 @@ extern struct dentry *start_creating_path(int, const char *, struct path *, unsi extern struct dentry *start_creating_user_path(int, const char __user *, struct path *, unsigned int); extern void end_creating_path(const struct path *, struct dentry *); extern struct dentry *start_removing_path(const char *, struct path *); -extern struct dentry *start_removing_user_path_at(int , const char __user *, struct path *); static inline void end_removing_path(const struct path *path , struct dentry *dentry) { end_creating_path(path, dentry); From 5d833d8ba5b058d391d476fd877a7850965c104f Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Mon, 11 May 2026 09:22:30 +0200 Subject: [PATCH 22/73] fs: fold __start_removing_path into start_removing_path Only one caller left, and simplified this way. Signed-off-by: Christoph Hellwig Link: https://patch.msgid.link/20260511072239.2456725-4-hch@lst.de Signed-off-by: Christian Brauner (Amutable) --- fs/namei.c | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/fs/namei.c b/fs/namei.c index bc641838530f..4852ca208bd4 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -2955,15 +2955,16 @@ void end_dirop(struct dentry *de) EXPORT_SYMBOL(end_dirop); /* does lookup, returns the object with parent locked */ -static struct dentry *__start_removing_path(int dfd, struct filename *name, - struct path *path) +struct dentry *start_removing_path(const char *name, struct path *path) { + CLASS(filename_kernel, filename)(name); struct path parent_path __free(path_put) = {}; struct dentry *d; struct qstr last; int type, error; - error = filename_parentat(dfd, name, 0, &parent_path, &last, &type); + error = filename_parentat(AT_FDCWD, filename, 0, &parent_path, &last, + &type); if (error) return ERR_PTR(error); if (unlikely(type != LAST_NORM)) @@ -3023,12 +3024,6 @@ struct dentry *kern_path_parent(const char *name, struct path *path) return d; } -struct dentry *start_removing_path(const char *name, struct path *path) -{ - CLASS(filename_kernel, filename)(name); - return __start_removing_path(AT_FDCWD, filename, path); -} - int kern_path(const char *name, unsigned int flags, struct path *path) { CLASS(filename_kernel, filename)(name); From e7d43a48a8e990b43ef8634248ee5b03f19ed3ea Mon Sep 17 00:00:00 2001 From: Amir Goldstein Date: Wed, 22 Apr 2026 14:52:12 +0200 Subject: [PATCH 23/73] docs: add guidelines for submitting new filesystems This document is motivated by the ongoing maintenance burden that abandoned and untestable filesystems impose on VFS developers, blocking infrastructure changes such as folio conversions and iomap migration. This week alone, two new filesystems were proposed on linux-fsdevel (VMUFAT and FTRFS), highlighting the need for documented guidelines that new filesystem authors can refer to before submission. Multiple recent discussions on linux-fsdevel have touched on the criteria for merging new filesystems and for deprecating old ones, covering topics such as modern VFS interface adoption, testability, userspace utilities, maintainer commitment, and user base viability. Add Documentation/filesystems/adding-new-filesystems.rst describing the technical requirements and community expectations for merging a new filesystem into the kernel. The guidelines cover: - Alternatives to consider before proposing a new in-kernel filesystem - Technical requirements: modern VFS interfaces (iomap, folios, fs_context mount API), testability, and userspace utilities - Community expectations: identified maintainers, demonstrated commitment, sustained backing, and a clear user base - Ongoing obligations after merging, including the risk of deprecation for unmaintained filesystems Link: https://lore.kernel.org/linux-fsdevel/20260411151155.321214-1-adrianmcmenamin@gmail.com/ Link: https://lore.kernel.org/linux-fsdevel/20260413142357.515792-1-aurelien@hackers.camp/ Link: https://lore.kernel.org/linux-fsdevel/yndtg2jbj55fzd2kkhsmel4pp5ll5xfvfiaqh24tdct3jiqosd@jzbfzf3rrxrd/ Link: https://lore.kernel.org/linux-fsdevel/20260124091742.GA43313@macsyma.local/ Link: https://lore.kernel.org/lkml/20260111140345.3866-1-linkinjeon@kernel.org/ Cc: Christian Brauner Cc: Alexander Viro Cc: Jan Kara Cc: Theodore Tso Cc: Christoph Hellwig Cc: Darrick J. Wong Cc: Matthew Wilcox Assisted-by: Cursor:claude-4-opus Signed-off-by: Amir Goldstein Link: https://patch.msgid.link/20260422125212.1743006-1-amir73il@gmail.com Reviewed-by: Jan Kara Signed-off-by: Christian Brauner (Amutable) --- .../filesystems/adding-new-filesystems.rst | 195 ++++++++++++++++++ Documentation/filesystems/index.rst | 1 + 2 files changed, 196 insertions(+) create mode 100644 Documentation/filesystems/adding-new-filesystems.rst diff --git a/Documentation/filesystems/adding-new-filesystems.rst b/Documentation/filesystems/adding-new-filesystems.rst new file mode 100644 index 000000000000..a3d0bf16f73a --- /dev/null +++ b/Documentation/filesystems/adding-new-filesystems.rst @@ -0,0 +1,195 @@ +.. SPDX-License-Identifier: GPL-2.0 + +.. _adding_new_filesystems: + +Adding New Filesystems +====================== + +This document describes what is involved in adding a new filesystem to the +Linux kernel. + +Every filesystem merged into the kernel becomes the collective responsibility +of the VFS maintainers and the wider filesystem development community. +Experience has shown that filesystems which become unmaintained impose a +significant and ongoing burden: they are hard or impossible to test, they +block infrastructure changes because someone must update or preserve old APIs +for code that nobody is actively looking after, and they accumulate unfixed +bugs. The requirements and expectations described here are informed by this +experience and are intended to ensure that new filesystems enter the kernel +on a sustainable footing. + + +Do You Need a New In-Kernel Filesystem? +--------------------------------------- + +Before proposing a new in-kernel filesystem, consider whether one of the +alternatives might be more appropriate. + + - If an existing in-kernel filesystem covers the same use case, improving it + is generally preferred over adding a new implementation. The kernel + community favors incremental improvement over parallel implementations. + + - If the filesystem serves a niche audience or has a small user base, a FUSE + (Filesystem in Userspace) implementation may be a better fit. FUSE + filesystems avoid the long-term kernel maintenance commitment and can be + developed and released on their own schedule. + + - If kernel-level performance, reliability, or integration is genuinely + required, make the case explicitly. Explain who the users are, what the + use case is, and why a FUSE implementation would not be sufficient. + + +Technical Requirements +---------------------- + +New filesystems must use current kernel interfaces and practices. +Submitting a filesystem built on outdated APIs creates an unacceptable +maintenance debt and is likely to face pushback during review. + +Use modern VFS interfaces + Do not use interfaces listed in + :ref:`Documentation/process/deprecated.rst `. + + Use folios rather than raw page operations for page cache management and + iomap rather than buffer heads for block mapping and I/O. See + ``Documentation/filesystems/iomap/index.rst`` for iomap documentation. + + Block-based filesystems that need functionality not currently provided by + iomap should be prepared to explain why adding that functionality to iomap + is infeasible, rather than reimplementing their own block mapping layer. + + Network filesystems should consider using the netfs library + (``Documentation/filesystems/netfs_library.rst``), or be prepared to explain + why it is not a good fit. + +Provide userspace utilities + A ``mkfs`` tool is expected so that the filesystem can be created and used + by testers and users. A ``fsck`` tool is strongly recommended; while not + strictly required for every filesystem type, the ability to verify + consistency and repair corruption is an important part of a mature + filesystem. + +Be testable + The filesystem must be testable in a meaningful way. The + `fstests `_ + framework (also known as xfstests) is the standard testing infrastructure + for Linux filesystems and its use is highly recommended. At a minimum, + there must be a credible and documented way to test the filesystem and + detect regressions. When submitting, include a summary of test results + indicating which tests pass, fail, or are not applicable. + +Provide documentation + A documentation file under ``Documentation/filesystems/`` describing the + filesystem, its on-disk format, mount options, and any notable design + decisions is recommended. + + +Community and Maintainership Expectations +----------------------------------------- + +Merging a filesystem is a long-term commitment. The kernel community +needs confidence that the filesystem will be actively maintained after it +is merged. + +Identified maintainers + The submission must include a ``MAINTAINERS`` entry with at least one + maintainer (``M:``), a mailing list (``L:``), and a git tree (``T:``). + Having two or more maintainers is strongly preferred so that coverage + does not depend on a single person. The maintainers are expected to be + the primary points of contact for the filesystem going forward. + +Demonstrated commitment + A track record of maintaining kernel code -- for example, in other + subsystems -- significantly strengthens the case for a new filesystem. + Maintainers who are already known and trusted within the community face + less friction during review. + +Sustained backing + Major filesystems in Linux have organizational or corporate support behind + their development. Filesystems that depend entirely on volunteer effort + face higher scrutiny about their long-term viability. + +Responsiveness + The maintainer is expected to respond to bug reports, address review + feedback, and adapt the filesystem to VFS infrastructure changes such as + folio conversions, iomap migration, and mount API updates. Unresponsive + maintainership is one of the primary reasons filesystems end up on the + path to deprecation. + +User base + Clearly describe who the users of this filesystem are and the scale of the + user base. Filesystems with a very small or unclear user base face a + harder path to acceptance and a higher risk of future deprecation. + +Building your track record + A practical way to demonstrate many of the qualities above is to maintain + the filesystem out-of-tree for a period before requesting a merge. This + shows sustained commitment, builds a visible user base, and gives reviewers + confidence that the code and its maintainer will persist after merging. + That said, it is recognized that for some filesystems the user base grows + significantly only after upstreaming, so a compelling case for expected + adoption can substitute for a large existing user base. + + +Submission Process +------------------ + +This section covers what is specific to filesystem submissions, over and +above the normal submission advice in +:ref:`Documentation/process/submitting-patches.rst ` and +:ref:`Documentation/process/submit-checklist.rst `. + + - Send patches to the linux-fsdevel mailing list + (``linux-fsdevel@vger.kernel.org``). CC the relevant VFS maintainers as + listed in the ``MAINTAINERS`` file under + ``FILESYSTEMS (VFS and infrastructure)``. + + - Structure the submission logically. It is neither acceptable to send one + large patch containing the entire filesystem, nor is a replay of the full + development history helpful to reviewers. Instead, split the series by + topic -- for example: superblock and mount handling, inode operations, + directory operations, address space operations, and so on -- so that each + patch is reviewable in isolation. + + - Separate any filesystem-specific ioctls into their own patches with + dedicated justification. Interfaces beyond those already common across + other filesystems will receive additional scrutiny because they are hard + to maintain and may conflict with future generic interfaces. + + - Expect thorough review. Filesystem code interacts deeply with the VFS, + memory management, and block layers, so reviewers will examine the code + carefully. Address all review feedback and be prepared for multiple + revision cycles. + + - It may be appropriate to mark the filesystem as experimental in its Kconfig + help text for the first few releases to set expectations while the code + stabilizes in-tree. + + +Ongoing Obligations +------------------- + +Merging is not the finish line. Maintaining a filesystem in the kernel is an +ongoing commitment. + + - Adapt to VFS infrastructure changes. The VFS layer evolves continuously; + maintainers are expected to keep up with conversions such as folio + migration, iomap adoption, and mount API updates. + + - Maintain test coverage. As test suites evolve, the filesystem's test + results should be kept current. + + - Handle security issues and regression promptly. Both those reported + by ordinary users and those reported by test bots and fuzzing tools. + The filesystem must handle corrupted input gracefully without corrupting + memory, hanging, or crashing the kernel. + + - Engage with the wider filesystem community. Participate on linux-fsdevel, + share approaches to common problems, and look for opportunities to reuse + shared infrastructure. It is inappropriate to develop in isolation on a + private list and surface patches only at merge time. + + - Filesystems that become unmaintained -- where the maintainer stops + responding, infrastructure changes go unadapted, and testing becomes + impossible -- are candidates for deprecation and eventual removal from + the kernel. diff --git a/Documentation/filesystems/index.rst b/Documentation/filesystems/index.rst index fc7254d01a2b..1f71cf159547 100644 --- a/Documentation/filesystems/index.rst +++ b/Documentation/filesystems/index.rst @@ -43,6 +43,7 @@ algorithms work. caching/index porting + adding-new-filesystems Filesystem support layers ========================= From 2430e3380936df0b648af720cae624eef035a2d1 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Mon, 11 May 2026 09:16:46 +0200 Subject: [PATCH 24/73] bfs: handle set_blocksize failures bfs uses buffer_heads, which don't handle block size > PAGE_SIZE well. Without this, mounting will hit the BUG_ON(offset >= folio_size(folio)); in folio_set_bh on the first __bread_gfp call. Signed-off-by: Christoph Hellwig Link: https://patch.msgid.link/20260511071701.2456211-2-hch@lst.de Signed-off-by: Christian Brauner (Amutable) --- fs/bfs/inode.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/fs/bfs/inode.c b/fs/bfs/inode.c index 19e49c8cf750..9c3e90390824 100644 --- a/fs/bfs/inode.c +++ b/fs/bfs/inode.c @@ -346,7 +346,8 @@ static int bfs_fill_super(struct super_block *s, struct fs_context *fc) s->s_time_min = 0; s->s_time_max = U32_MAX; - sb_set_blocksize(s, BFS_BSIZE); + if (!sb_set_blocksize(s, BFS_BSIZE)) + goto out; sbh = sb_bread(s, 0); if (!sbh) From a405996f23e04942aad064ab8d50c55827482872 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Mon, 11 May 2026 09:16:47 +0200 Subject: [PATCH 25/73] hpfs: handle set_blocksize failures hpfs uses buffer_heads, which don't handle block size > PAGE_SIZE well. Without this, mounting will hit the BUG_ON(offset >= folio_size(folio)); in folio_set_bh on the first __bread_gfp call. Signed-off-by: Christoph Hellwig Link: https://patch.msgid.link/20260511071701.2456211-3-hch@lst.de Signed-off-by: Christian Brauner (Amutable) --- fs/hpfs/super.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/fs/hpfs/super.c b/fs/hpfs/super.c index c16d5d4caead..8fbdbf080627 100644 --- a/fs/hpfs/super.c +++ b/fs/hpfs/super.c @@ -523,7 +523,8 @@ static int hpfs_fill_super(struct super_block *s, struct fs_context *fc) hpfs_lock(s); /*sbi->sb_mounting = 1;*/ - sb_set_blocksize(s, 512); + if (!sb_set_blocksize(s, 512)) + goto bail0; sbi->sb_fs_size = -1; if (!(bootblock = hpfs_map_sector(s, 0, &bh0, 0))) goto bail1; if (!(superblock = hpfs_map_sector(s, 16, &bh1, 1))) goto bail2; From c7d911ea1cc9a63b07e52f5e75b263be0615b289 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Mon, 11 May 2026 09:16:48 +0200 Subject: [PATCH 26/73] qnx4: handle set_blocksize failures qnx4 uses buffer_heads, which don't handle block size > PAGE_SIZE well. Without this, mounting will hit the BUG_ON(offset >= folio_size(folio)); in folio_set_bh on the first __bread_gfp call. Signed-off-by: Christoph Hellwig Link: https://patch.msgid.link/20260511071701.2456211-4-hch@lst.de Acked-by: Anders Larsen Signed-off-by: Christian Brauner (Amutable) --- fs/qnx4/inode.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/fs/qnx4/inode.c b/fs/qnx4/inode.c index 4deb0eeadbde..42fcd500fad2 100644 --- a/fs/qnx4/inode.c +++ b/fs/qnx4/inode.c @@ -202,7 +202,8 @@ static int qnx4_fill_super(struct super_block *s, struct fs_context *fc) return -ENOMEM; s->s_fs_info = qs; - sb_set_blocksize(s, QNX4_BLOCK_SIZE); + if (!sb_set_blocksize(s, QNX4_BLOCK_SIZE)) + return -EINVAL; s->s_op = &qnx4_sops; s->s_magic = QNX4_SUPER_MAGIC; From 05107f5602751fcfd3d108c1f579eb45aabead52 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Mon, 11 May 2026 09:16:49 +0200 Subject: [PATCH 27/73] jfs: handle set_blocksize failures jfs uses buffer_heads, which don't handle block size > PAGE_SIZE well. Without this, mounting we will hit the BUG_ON(offset >= folio_size(folio)); in folio_set_bh on the first __bread_gfp call. Signed-off-by: Christoph Hellwig Link: https://patch.msgid.link/20260511071701.2456211-5-hch@lst.de Signed-off-by: Christian Brauner (Amutable) --- fs/jfs/super.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/fs/jfs/super.c b/fs/jfs/super.c index 61575f7397ae..8180d83d33fe 100644 --- a/fs/jfs/super.c +++ b/fs/jfs/super.c @@ -491,7 +491,8 @@ static int jfs_fill_super(struct super_block *sb, struct fs_context *fc) /* * Initialize blocksize to 4K. */ - sb_set_blocksize(sb, PSIZE); + if (!sb_set_blocksize(sb, PSIZE)) + goto out_unload; /* * Set method vectors. From 7597d42a25332617a3dfe596758d780ec6c028d7 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Mon, 11 May 2026 09:16:50 +0200 Subject: [PATCH 28/73] befs: handle set_blocksize failures befs uses buffer_heads, which don't handle block size > PAGE_SIZE well. Without this, mounting we will hit the BUG_ON(offset >= folio_size(folio)); in folio_set_bh on the first __bread_gfp call. Signed-off-by: Christoph Hellwig Link: https://patch.msgid.link/20260511071701.2456211-6-hch@lst.de Signed-off-by: Christian Brauner (Amutable) --- fs/befs/linuxvfs.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/fs/befs/linuxvfs.c b/fs/befs/linuxvfs.c index c12caae9a967..ee0cbae521b9 100644 --- a/fs/befs/linuxvfs.c +++ b/fs/befs/linuxvfs.c @@ -860,7 +860,8 @@ befs_fill_super(struct super_block *sb, struct fs_context *fc) */ sb->s_magic = BEFS_SUPER_MAGIC; /* Set real blocksize of fs */ - sb_set_blocksize(sb, (ulong) befs_sb->block_size); + if (!sb_set_blocksize(sb, (ulong) befs_sb->block_size)) + goto unacquire_priv_sbp; sb->s_op = &befs_sops; sb->s_export_op = &befs_export_operations; sb->s_time_min = 0; From 0861182af5983a39bd2a891966436c5679b74a45 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Mon, 11 May 2026 09:16:51 +0200 Subject: [PATCH 29/73] affs: handle set_blocksize failures affs uses buffer_heads, which don't handle block size > PAGE_SIZE well. Without this, mounting we will hit the BUG_ON(offset >= folio_size(folio)); in folio_set_bh on the first __bread_gfp call. Signed-off-by: Christoph Hellwig Link: https://patch.msgid.link/20260511071701.2456211-7-hch@lst.de Signed-off-by: Christian Brauner (Amutable) --- fs/affs/affs.h | 5 ----- fs/affs/super.c | 6 ++++-- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/fs/affs/affs.h b/fs/affs/affs.h index a0caf6ace860..44a3f69d275f 100644 --- a/fs/affs/affs.h +++ b/fs/affs/affs.h @@ -227,11 +227,6 @@ static inline bool affs_validblock(struct super_block *sb, int block) block < AFFS_SB(sb)->s_partition_size); } -static inline void -affs_set_blocksize(struct super_block *sb, int size) -{ - sb_set_blocksize(sb, size); -} static inline struct buffer_head * affs_bread(struct super_block *sb, int block) { diff --git a/fs/affs/super.c b/fs/affs/super.c index 079f36e1ddec..b232251aa7bb 100644 --- a/fs/affs/super.c +++ b/fs/affs/super.c @@ -358,7 +358,8 @@ static int affs_fill_super(struct super_block *sb, struct fs_context *fc) size = bdev_nr_sectors(sb->s_bdev); pr_debug("initial blocksize=%d, #blocks=%d\n", 512, size); - affs_set_blocksize(sb, PAGE_SIZE); + if (!sb_set_blocksize(sb, PAGE_SIZE)) + return -EINVAL; /* Try to find root block. Its location depends on the block size. */ i = bdev_logical_block_size(sb->s_bdev); @@ -374,7 +375,8 @@ static int affs_fill_super(struct super_block *sb, struct fs_context *fc) if (ctx->root_block < 0) sbi->s_root_block = (ctx->reserved + size - 1) / 2; pr_debug("setting blocksize to %d\n", blocksize); - affs_set_blocksize(sb, blocksize); + if (!sb_set_blocksize(sb, blocksize)) + return -EINVAL; sbi->s_partition_size = size; /* The root block location that was calculated above is not From 25ef4c4d9f0e96fb89c0ae0d7127c3f12a31bc32 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Mon, 11 May 2026 09:16:52 +0200 Subject: [PATCH 30/73] isofs: handle set_blocksize failures isofs uses buffer_heads, which don't handle block size > PAGE_SIZE well. Without this, mounting we will hit the BUG_ON(offset >= folio_size(folio)); in folio_set_bh on the first __bread_gfp call. Signed-off-by: Christoph Hellwig Link: https://patch.msgid.link/20260511071701.2456211-8-hch@lst.de Signed-off-by: Christian Brauner (Amutable) --- fs/isofs/inode.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/fs/isofs/inode.c b/fs/isofs/inode.c index efee53717f1c..337836a0a170 100644 --- a/fs/isofs/inode.c +++ b/fs/isofs/inode.c @@ -818,7 +818,8 @@ static int isofs_fill_super(struct super_block *s, struct fs_context *fc) * entries. By forcing the blocksize in this way, we ensure * that we will never be required to do this. */ - sb_set_blocksize(s, orig_zonesize); + if (!sb_set_blocksize(s, orig_zonesize)) + goto out_freesbi; sbi->s_nls_iocharset = NULL; From 38a03dc2bc71e7e0746cdb9ef5e9947f72470c67 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Mon, 11 May 2026 09:16:53 +0200 Subject: [PATCH 31/73] minix: handle set_blocksize failures minix uses buffer_heads, which don't handle block size > PAGE_SIZE well. Without this, mounting we will hit the BUG_ON(offset >= folio_size(folio)); in folio_set_bh on the first __bread_gfp call. Signed-off-by: Christoph Hellwig Link: https://patch.msgid.link/20260511071701.2456211-9-hch@lst.de Signed-off-by: Christian Brauner (Amutable) --- fs/minix/inode.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/fs/minix/inode.c b/fs/minix/inode.c index 9c6bac248907..03a69b13950d 100644 --- a/fs/minix/inode.c +++ b/fs/minix/inode.c @@ -292,7 +292,8 @@ static int minix_fill_super(struct super_block *s, struct fs_context *fc) sbi->s_namelen = 60; sbi->s_version = MINIX_V3; sbi->s_mount_state = MINIX_VALID_FS; - sb_set_blocksize(s, m3s->s_blocksize); + if (!sb_set_blocksize(s, m3s->s_blocksize)) + goto out; s->s_max_links = MINIX2_LINK_MAX; } else goto out_no_fs; From 24f7d1824b7581ae3daf9d443c5dfeabd89df6d8 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Mon, 11 May 2026 09:16:54 +0200 Subject: [PATCH 32/73] ntfs3: handle set_blocksize failures ntfs3 uses buffer_heads, which don't handle block size > PAGE_SIZE well. Without this, mounting we will hit the BUG_ON(offset >= folio_size(folio)); in folio_set_bh on the first __bread_gfp call. Signed-off-by: Christoph Hellwig Link: https://patch.msgid.link/20260511071701.2456211-10-hch@lst.de Signed-off-by: Christian Brauner (Amutable) --- fs/ntfs3/super.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/fs/ntfs3/super.c b/fs/ntfs3/super.c index 004f59937559..3305fe406cb2 100644 --- a/fs/ntfs3/super.c +++ b/fs/ntfs3/super.c @@ -1174,7 +1174,10 @@ static int ntfs_init_from_boot(struct super_block *sb, u32 sector_size, rec->total = cpu_to_le32(sbi->record_size); ((struct ATTRIB *)Add2Ptr(rec, ao))->type = ATTR_END; - sb_set_blocksize(sb, min_t(u32, sbi->cluster_size, PAGE_SIZE)); + if (!sb_set_blocksize(sb, min_t(u32, sbi->cluster_size, PAGE_SIZE))) { + err = -EINVAL; + goto out; + } sbi->block_mask = sb->s_blocksize - 1; sbi->blocks_per_cluster = sbi->cluster_size >> sb->s_blocksize_bits; @@ -1225,7 +1228,8 @@ static int ntfs_init_from_boot(struct super_block *sb, u32 sector_size, /* * Try alternative boot (last sector) */ - sb_set_blocksize(sb, block_size); + if (!sb_set_blocksize(sb, block_size)) + return -EINVAL; hint = "Alternative boot"; dev_size = dev_size0; /* restore original size. */ goto read_boot; From 18c3d6fcb557f920c9143711497625e70153874c Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Mon, 11 May 2026 09:16:55 +0200 Subject: [PATCH 33/73] omfs: handle set_blocksize failures omfs uses buffer_heads, which don't handle block size > PAGE_SIZE well. Without this, mounting we will hit the BUG_ON(offset >= folio_size(folio)); in folio_set_bh on the first __bread_gfp call. Signed-off-by: Christoph Hellwig Signed-off-by: Christoph Hellwig Link: https://patch.msgid.link/20260511071701.2456211-11-hch@lst.de Signed-off-by: Christian Brauner (Amutable) --- fs/omfs/inode.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/fs/omfs/inode.c b/fs/omfs/inode.c index 834cae1e6223..1d915ef72119 100644 --- a/fs/omfs/inode.c +++ b/fs/omfs/inode.c @@ -478,7 +478,8 @@ static int omfs_fill_super(struct super_block *sb, struct fs_context *fc) sb->s_time_min = 0; sb->s_time_max = U64_MAX / MSEC_PER_SEC; - sb_set_blocksize(sb, 0x200); + if (!sb_set_blocksize(sb, 0x200)) + goto end; bh = sb_bread(sb, 0); if (!bh) @@ -530,7 +531,8 @@ static int omfs_fill_super(struct super_block *sb, struct fs_context *fc) * Use sys_blocksize as the fs block since it is smaller than a * page while the fs blocksize can be larger. */ - sb_set_blocksize(sb, sbi->s_sys_blocksize); + if (!sb_set_blocksize(sb, sbi->s_sys_blocksize)) + goto out_brelse_bh; /* * ...and the difference goes into a shift. sys_blocksize is always From de7680d9438fa145c90e96a783e3e69405fecd33 Mon Sep 17 00:00:00 2001 From: Thorsten Blum Date: Tue, 5 May 2026 13:42:42 +0200 Subject: [PATCH 34/73] namei: use QSTR() instead of QSTR_INIT() in path_pts Drop the hard-coded length argument and use the simpler QSTR(). Inline the code and drop the local variable. Reviewed-by: Jan Kara Signed-off-by: Thorsten Blum Link: https://patch.msgid.link/20260505114242.158883-2-thorsten.blum@linux.dev Signed-off-by: Christian Brauner (Amutable) --- fs/namei.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/fs/namei.c b/fs/namei.c index 4852ca208bd4..96d553caf915 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -3603,7 +3603,6 @@ int path_pts(struct path *path) */ struct dentry *parent = dget_parent(path->dentry); struct dentry *child; - struct qstr this = QSTR_INIT("pts", 3); if (unlikely(!path_connected(path->mnt, parent))) { dput(parent); @@ -3611,7 +3610,7 @@ int path_pts(struct path *path) } dput(path->dentry); path->dentry = parent; - child = d_hash_and_lookup(parent, &this); + child = d_hash_and_lookup(parent, &QSTR("pts")); if (IS_ERR_OR_NULL(child)) return -ENOENT; From 50d377ef12d9680ff8fd0923afc7edaf63995511 Mon Sep 17 00:00:00 2001 From: Alexey Dobriyan Date: Tue, 5 May 2026 21:55:29 +0300 Subject: [PATCH 35/73] sync_file_range: delete dead S_ISLNK code Symlinks can't appear as opened file. Signed-off-by: Alexey Dobriyan Link: https://patch.msgid.link/295235c7-7f68-4554-bb6f-85398beca350@p183 Reviewed-by: Jan Kara Signed-off-by: Christian Brauner (Amutable) --- fs/sync.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/fs/sync.c b/fs/sync.c index 942a60cfedfb..4a84dd837b86 100644 --- a/fs/sync.c +++ b/fs/sync.c @@ -266,8 +266,7 @@ int sync_file_range(struct file *file, loff_t offset, loff_t nbytes, i_mode = file_inode(file)->i_mode; ret = -ESPIPE; - if (!S_ISREG(i_mode) && !S_ISBLK(i_mode) && !S_ISDIR(i_mode) && - !S_ISLNK(i_mode)) + if (!S_ISREG(i_mode) && !S_ISBLK(i_mode) && !S_ISDIR(i_mode)) goto out; mapping = file->f_mapping; From 7dc6acb3d56bc2c5d119c86abd8fe96034084fc8 Mon Sep 17 00:00:00 2001 From: Mateusz Guzik Date: Sat, 16 May 2026 04:18:52 +0200 Subject: [PATCH 36/73] fs/pipe: write to ->poll_usage only once Both GNU and BSD makes share a "token pipe" between their instances, as a result a -j $BIGNUM invocation results in multicore perf problems in the poll handler. Avoiding the store will reduce it a little bit. However, the crux of the problem is the locked queuing up in poll_wait(). Signed-off-by: Mateusz Guzik Link: https://patch.msgid.link/20260516021852.256932-1-mjguzik@gmail.com Signed-off-by: Christian Brauner (Amutable) --- fs/pipe.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/fs/pipe.c b/fs/pipe.c index 9841648c9cf3..e37c79935ecb 100644 --- a/fs/pipe.c +++ b/fs/pipe.c @@ -664,7 +664,8 @@ pipe_poll(struct file *filp, poll_table *wait) union pipe_index idx; /* Epoll has some historical nasty semantics, this enables them */ - WRITE_ONCE(pipe->poll_usage, true); + if (unlikely(!READ_ONCE(pipe->poll_usage))) + WRITE_ONCE(pipe->poll_usage, true); /* * Reading pipe state only -- no need for acquiring the semaphore. From 3fb2d124b64716f16355b9b722b2f062c0702f24 Mon Sep 17 00:00:00 2001 From: "Mike Rapoport (Microsoft)" Date: Wed, 20 May 2026 11:16:51 +0300 Subject: [PATCH 37/73] init: do_mounts: use kmalloc() for allocations of temporary buffers Several places in init/do_mounts.c allocate temporary buffers for filesystem names or options using __get_free_page() or alloc_page(). Usage of alloc_page() APIs is not required there and only creates unnecessary noise with castings or conversion from struct page to void *. kmalloc() is a better API for these uses and it also provides better scalability and more debugging possibilities. Replace use of __get_free_page() and alloc_page() with kmalloc(). While on it, add a check for -ENOMEM condition in mount_root_generic(). Link: https://lore.kernel.org/all/635405e4-9423-4a25-a6e7-e03c8ea0bcbe@redhat.com Signed-off-by: Mike Rapoport (Microsoft) Link: https://patch.msgid.link/20260520-init-v1-1-aaf2ebac5ad9@kernel.org Reviewed-by: David Disseldorp Reviewed-by: SeongJae Park Signed-off-by: Christian Brauner (Amutable) --- init/do_mounts.c | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/init/do_mounts.c b/init/do_mounts.c index 55ed3ac0b70f..95e0b3a0f711 100644 --- a/init/do_mounts.c +++ b/init/do_mounts.c @@ -143,16 +143,14 @@ static int __init do_mount_root(const char *name, const char *fs, const int flags, const void *data) { struct super_block *s; - struct page *p = NULL; char *data_page = NULL; int ret; if (data) { /* init_mount() requires a full page as fifth argument */ - p = alloc_page(GFP_KERNEL); - if (!p) + data_page = kmalloc(PAGE_SIZE, GFP_KERNEL); + if (!data_page) return -ENOMEM; - data_page = page_address(p); strscpy_pad(data_page, data, PAGE_SIZE); } @@ -170,19 +168,20 @@ static int __init do_mount_root(const char *name, const char *fs, MAJOR(ROOT_DEV), MINOR(ROOT_DEV)); out: - if (p) - put_page(p); + kfree(data_page); return ret; } void __init mount_root_generic(char *name, char *pretty_name, int flags) { - struct page *page = alloc_page(GFP_KERNEL); - char *fs_names = page_address(page); + char *fs_names = kmalloc(PAGE_SIZE, GFP_KERNEL); char *p; char b[BDEVNAME_SIZE]; int num_fs, i; + if (!fs_names) + panic("VFS: Unable to mount root fs: not enough memory"); + scnprintf(b, BDEVNAME_SIZE, "unknown-block(%u,%u)", MAJOR(ROOT_DEV), MINOR(ROOT_DEV)); if (root_fs_names) @@ -242,7 +241,7 @@ void __init mount_root_generic(char *name, char *pretty_name, int flags) printk("\n"); panic("VFS: Unable to mount root fs on \"%s\" or %s", pretty_name, b); out: - put_page(page); + kfree(fs_names); } #ifdef CONFIG_ROOT_NFS @@ -343,7 +342,7 @@ static int __init mount_nodev_root(char *root_device_name) int err = -EINVAL; int num_fs, i; - fs_names = (void *)__get_free_page(GFP_KERNEL); + fs_names = kmalloc(PAGE_SIZE, GFP_KERNEL); if (!fs_names) return -EINVAL; num_fs = split_fs_names(fs_names, PAGE_SIZE); @@ -360,7 +359,7 @@ static int __init mount_nodev_root(char *root_device_name) break; } - free_page((unsigned long)fs_names); + kfree(fs_names); return err; } From 988c918b812eef5623d44cf758857f28ee050570 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Mon, 18 May 2026 15:03:30 +0200 Subject: [PATCH 38/73] minix: release the sb buffer_head when setting the v3 block size fails At this point the superblock is already read, so jump to the label that releases the buffer_head for it. Fixes: d893fc670546 ("minix: handle set_blocksize failures") Reviewed-by: Jan Kara Signed-off-by: Christoph Hellwig Link: https://patch.msgid.link/20260518130330.529085-1-hch@lst.de Signed-off-by: Christian Brauner (Amutable) --- fs/minix/inode.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/minix/inode.c b/fs/minix/inode.c index 03a69b13950d..c30cc590698d 100644 --- a/fs/minix/inode.c +++ b/fs/minix/inode.c @@ -293,7 +293,7 @@ static int minix_fill_super(struct super_block *s, struct fs_context *fc) sbi->s_version = MINIX_V3; sbi->s_mount_state = MINIX_VALID_FS; if (!sb_set_blocksize(s, m3s->s_blocksize)) - goto out; + goto out_release; s->s_max_links = MINIX2_LINK_MAX; } else goto out_no_fs; From 0432b89f6158f18618ac46a322c2f2dddd4adfc5 Mon Sep 17 00:00:00 2001 From: Agatha Isabelle Moreira Date: Wed, 20 May 2026 16:58:16 -0300 Subject: [PATCH 39/73] fs: buffer: use clear_and_wake_up_bit() in unlock_buffer() Use `clear_and_wake_up_bit()` in `unlock_buffer()`, since the helper was introduced in 'commit 8236b0ae31c83 ("bdi: wake up concurrent wb_shutdown() callers.")' as a generic way of doing the same sequence of operations: clear_bit_unlock(); smp_mb__after_atomic(); wake_up_bit(); The helper was implemented to avoid bugs caused by forgetting to call `wake_up_bit()` after `clear_bit_unlock()`. Since `unlock_buffer()` predates git and was last modified in 'commit 4e857c58efeb9 ("arch: Mass conversion of smp_mb__*()")', years before `clear_and_wake_up_bit()`, it still uses the open-coded sequence. Replace the open-coded sequence with the helper to avoid duplicate code and reduce code paths to maintain. Suggested-by: shuo chen <1289151713@qq.com> Link: https://lore.kernel.org/kernelnewbies/agzoqV835-co4kAN@guidai/T/#t Signed-off-by: Agatha Isabelle Moreira Link: https://patch.msgid.link/ag4SD-mkmn5IbuN7@guidai Signed-off-by: Christian Brauner (Amutable) --- fs/buffer.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/fs/buffer.c b/fs/buffer.c index b0b3792b1496..4348b240bd97 100644 --- a/fs/buffer.c +++ b/fs/buffer.c @@ -74,9 +74,7 @@ EXPORT_SYMBOL(__lock_buffer); void unlock_buffer(struct buffer_head *bh) { - clear_bit_unlock(BH_Lock, &bh->b_state); - smp_mb__after_atomic(); - wake_up_bit(&bh->b_state, BH_Lock); + clear_and_wake_up_bit(BH_Lock, &bh->b_state); } EXPORT_SYMBOL(unlock_buffer); From 2fddb8479d000aca35ed34243982a9ba141808c4 Mon Sep 17 00:00:00 2001 From: Agatha Isabelle Moreira Date: Wed, 20 May 2026 17:05:46 -0300 Subject: [PATCH 40/73] fs: jbd2: use clear_and_wake_up_bit() in journal_end_buffer_io_sync() Use `clear_and_wake_up_bit()` in `journal_end_buffer_io_sync()`, since the helper was introduced in 'commit 8236b0ae31c83 ("bdi: wake up concurrent wb_shutdown() callers.")' as a generic way of doing the same sequence of operations: clear_bit_unlock(); smp_mb__after_atomic(); wake_up_bit(); The helper was first implemented to avoid bugs caused by forgetting to call `wake_up_bit()` after `clear_bit_unlock()`. Since `journal_end_buffer_io_sync()` was first introduced by 'commit 470decc613ab2 ("jbd2: initial copy of files from jbd")' and last modified in this operation by 'commit 4e857c58efeb9 ("arch: Mass conversion of smp_mb__*()")', years before `clear_and_wake_up_bit()`, it still uses the open-coded sequence. Replace the open-coded sequence with the helper to avoid duplicate code and reduce code paths to maintain. Suggested-by: shuo chen <1289151713@qq.com> Link: https://lore.kernel.org/kernelnewbies/agzoqV835-co4kAN@guidai/T/#t Signed-off-by: Agatha Isabelle Moreira Link: https://patch.msgid.link/ag4SrrOl7R2DcLLi@guidai Signed-off-by: Christian Brauner (Amutable) --- fs/jbd2/commit.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/fs/jbd2/commit.c b/fs/jbd2/commit.c index 8cf61e7185c4..b647fde76e49 100644 --- a/fs/jbd2/commit.c +++ b/fs/jbd2/commit.c @@ -39,9 +39,7 @@ static void journal_end_buffer_io_sync(struct buffer_head *bh, int uptodate) else clear_buffer_uptodate(bh); if (orig_bh) { - clear_bit_unlock(BH_Shadow, &orig_bh->b_state); - smp_mb__after_atomic(); - wake_up_bit(&orig_bh->b_state, BH_Shadow); + clear_and_wake_up_bit(BH_Shadow, &orig_bh->b_state); } unlock_buffer(bh); } From ec3f4e0443a61e68092ac07111f16dd4ca89ddb4 Mon Sep 17 00:00:00 2001 From: Jia He Date: Tue, 19 May 2026 09:39:37 +0000 Subject: [PATCH 41/73] init/initramfs_test: wait_for_initramfs() before running initramfs_test_extract() and friends call unpack_to_rootfs() from a kunit kthread while do_populate_rootfs() may still be running asynchronously from rootfs_initcall. unpack_to_rootfs() keeps its parser state in module-static variables (victim, byte_count, state, this_header, header_buf, name_buf, ...), so the two writers corrupt each other. On arm64 v7.0-rc5+ this oopses early in boot: Unable to handle kernel paging request at virtual address ffff80018f9f0ffc pc : do_reset+0x3c/0x98 Call trace: do_reset initramfs_test_extract kunit_try_run_case Initramfs unpacking failed: junk within compressed archive do_reset() faults because 'victim' was overwritten by the boot-time unpacker; the boot unpacker meanwhile logs the bogus "junk within compressed archive" on the real initrd because the test wrecked its state machine. Add a .suite_init callback that calls wait_for_initramfs() so the async unpack is quiescent before the first case runs. suite_init runs once per suite rather than before every individual test case. Fixes: 83c0b27266ec ("initramfs_test: kunit tests for initramfs unpacking") Signed-off-by: Jia He Link: https://patch.msgid.link/20260519093937.1064628-1-justin.he@arm.com Reviewed-by: David Disseldorp Signed-off-by: Christian Brauner (Amutable) --- init/initramfs_test.c | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/init/initramfs_test.c b/init/initramfs_test.c index 8a0ddc2db2c0..bc55306d226d 100644 --- a/init/initramfs_test.c +++ b/init/initramfs_test.c @@ -3,7 +3,9 @@ #include #include #include +#include #include +#include #include #include #include "initramfs_internal.h" @@ -560,8 +562,21 @@ static struct kunit_case __refdata initramfs_test_cases[] = { {}, }; -static struct kunit_suite initramfs_test_suite = { +static int __init initramfs_test_init(struct kunit_suite *suite) +{ + /* + * unpack_to_rootfs() uses module-static state (victim, byte_count, + * state, ...). The boot-time async do_populate_rootfs() may still be + * running, so wait for it to finish before we call unpack_to_rootfs() + * from the test thread, otherwise the two writers race and crash. + */ + wait_for_initramfs(); + return 0; +} + +static struct kunit_suite __refdata initramfs_test_suite = { .name = "initramfs", + .suite_init = initramfs_test_init, .test_cases = initramfs_test_cases, }; kunit_test_init_section_suites(&initramfs_test_suite); From 6c519166e10ec5c72944ba3560fad12f8b8c78fd Mon Sep 17 00:00:00 2001 From: "Mike Rapoport (Microsoft)" Date: Sat, 23 May 2026 20:54:13 +0300 Subject: [PATCH 42/73] quota: allocate dquot_hash with kmalloc() dquot_init() allocates a single page for dquot_hash with __get_free_pages(). kmalloc() is a better API for such use and it also provides better scalability and more debugging possibilities. Replace use of __get_free_pages() with kmalloc() and get rid of the order variable that remained 0 for more than 20 years. Signed-off-by: Mike Rapoport (Microsoft) Link: https://patch.msgid.link/20260523-b4-fs-v1-1-275e36a83f0e@kernel.org Signed-off-by: Christian Brauner (Amutable) --- fs/quota/dquot.c | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/fs/quota/dquot.c b/fs/quota/dquot.c index 64cf42721496..9850de3955d3 100644 --- a/fs/quota/dquot.c +++ b/fs/quota/dquot.c @@ -3022,7 +3022,7 @@ static const struct ctl_table fs_dqstats_table[] = { static int __init dquot_init(void) { int i, ret; - unsigned long nr_hash, order; + unsigned long nr_hash; struct shrinker *dqcache_shrinker; printk(KERN_NOTICE "VFS: Disk quotas %s\n", __DQUOT_VERSION__); @@ -3035,8 +3035,7 @@ static int __init dquot_init(void) SLAB_PANIC), NULL); - order = 0; - dquot_hash = (struct hlist_head *)__get_free_pages(GFP_KERNEL, order); + dquot_hash = kmalloc(PAGE_SIZE, GFP_KERNEL); if (!dquot_hash) panic("Cannot create dquot hash table"); @@ -3046,7 +3045,7 @@ static int __init dquot_init(void) panic("Cannot create dquot stat counters"); /* Find power-of-two hlist_heads which can fit into allocation */ - nr_hash = (1UL << order) * PAGE_SIZE / sizeof(struct hlist_head); + nr_hash = PAGE_SIZE / sizeof(struct hlist_head); dq_hash_bits = ilog2(nr_hash); nr_hash = 1UL << dq_hash_bits; @@ -3054,8 +3053,8 @@ static int __init dquot_init(void) for (i = 0; i < nr_hash; i++) INIT_HLIST_HEAD(dquot_hash + i); - pr_info("VFS: Dquot-cache hash table entries: %ld (order %ld," - " %ld bytes)\n", nr_hash, order, (PAGE_SIZE << order)); + pr_info("VFS: Dquot-cache hash table entries: %ld (%ld bytes)\n", + nr_hash, PAGE_SIZE); dqcache_shrinker = shrinker_alloc(0, "dquota-cache"); if (!dqcache_shrinker) From cb7907e36c1054987a9709efc422b30e10ce839f Mon Sep 17 00:00:00 2001 From: "Mike Rapoport (Microsoft)" Date: Sat, 23 May 2026 20:54:14 +0300 Subject: [PATCH 43/73] proc: replace __get_free_page() with kmalloc() A few functions in fs/proc/base.c use __get_free_page() to allocate a temporary buffer. kmalloc() is a better API for such use and it also provides better scalability and more debugging possibilities. Replace use of __get_free_page() with kmalloc(). Signed-off-by: Mike Rapoport (Microsoft) Link: https://patch.msgid.link/20260523-b4-fs-v1-2-275e36a83f0e@kernel.org Reviewed-by: Jan Kara Signed-off-by: Christian Brauner (Amutable) --- fs/proc/base.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/fs/proc/base.c b/fs/proc/base.c index d9acfa89c894..e129dc509b79 100644 --- a/fs/proc/base.c +++ b/fs/proc/base.c @@ -261,7 +261,7 @@ static ssize_t get_mm_proctitle(struct mm_struct *mm, char __user *buf, if (pos >= PAGE_SIZE) return 0; - page = (char *)__get_free_page(GFP_KERNEL); + page = kmalloc(PAGE_SIZE, GFP_KERNEL); if (!page) return -ENOMEM; @@ -284,7 +284,7 @@ static ssize_t get_mm_proctitle(struct mm_struct *mm, char __user *buf, ret = len; } } - free_page((unsigned long)page); + kfree(page); return ret; } @@ -347,7 +347,7 @@ static ssize_t get_mm_cmdline(struct mm_struct *mm, char __user *buf, if (count > arg_end - pos) count = arg_end - pos; - page = (char *)__get_free_page(GFP_KERNEL); + page = kmalloc(PAGE_SIZE, GFP_KERNEL); if (!page) return -ENOMEM; @@ -371,7 +371,7 @@ static ssize_t get_mm_cmdline(struct mm_struct *mm, char __user *buf, count -= got; } - free_page((unsigned long)page); + kfree(page); return len; } @@ -908,7 +908,7 @@ static ssize_t mem_rw(struct file *file, char __user *buf, if (!mm) return 0; - page = (char *)__get_free_page(GFP_KERNEL); + page = kmalloc(PAGE_SIZE, GFP_KERNEL); if (!page) return -ENOMEM; @@ -949,7 +949,7 @@ static ssize_t mem_rw(struct file *file, char __user *buf, mmput(mm); free: - free_page((unsigned long) page); + kfree(page); return copied; } @@ -1016,7 +1016,7 @@ static ssize_t environ_read(struct file *file, char __user *buf, if (!mm || !mm->env_end) return 0; - page = (char *)__get_free_page(GFP_KERNEL); + page = kmalloc(PAGE_SIZE, GFP_KERNEL); if (!page) return -ENOMEM; @@ -1062,7 +1062,7 @@ static ssize_t environ_read(struct file *file, char __user *buf, mmput(mm); free: - free_page((unsigned long) page); + kfree(page); return ret; } From bc24c5557912073b4f61c0aa137a6faa7a187934 Mon Sep 17 00:00:00 2001 From: "Mike Rapoport (Microsoft)" Date: Sat, 23 May 2026 20:54:15 +0300 Subject: [PATCH 44/73] ocfs2/dlm: replace __get_free_page() with kmalloc() A few places in ocsfs2 allocate temporary buffers with __get_free_page() or get_zeroed_page(). kmalloc() is a better API for such use and it also provides better scalability and more debugging possibilities. Replace use of __get_free_page() and get_zeroed_page() with kmalloc() and kzalloc() respectively. Signed-off-by: Mike Rapoport (Microsoft) Link: https://patch.msgid.link/20260523-b4-fs-v1-3-275e36a83f0e@kernel.org Reviewed-by: Joseph Qi Reviewed-by: Jan Kara Signed-off-by: Christian Brauner (Amutable) --- fs/ocfs2/dlm/dlmdebug.c | 24 +++++++++--------------- fs/ocfs2/dlm/dlmdomain.c | 8 +++++--- fs/ocfs2/dlm/dlmmaster.c | 5 ++--- fs/ocfs2/dlm/dlmrecovery.c | 4 ++-- 4 files changed, 18 insertions(+), 23 deletions(-) diff --git a/fs/ocfs2/dlm/dlmdebug.c b/fs/ocfs2/dlm/dlmdebug.c index fe4fdd09bae3..6ca8b3b68eef 100644 --- a/fs/ocfs2/dlm/dlmdebug.c +++ b/fs/ocfs2/dlm/dlmdebug.c @@ -260,10 +260,10 @@ void dlm_print_one_mle(struct dlm_master_list_entry *mle) { char *buf; - buf = (char *) get_zeroed_page(GFP_ATOMIC); + buf = kzalloc(PAGE_SIZE, GFP_ATOMIC); if (buf) { dump_mle(mle, buf, PAGE_SIZE - 1); - free_page((unsigned long)buf); + kfree(buf); } } @@ -280,7 +280,7 @@ static struct dentry *dlm_debugfs_root; /* begin - utils funcs */ static int debug_release(struct inode *inode, struct file *file) { - free_page((unsigned long)file->private_data); + kfree(file->private_data); return 0; } @@ -327,17 +327,15 @@ static int debug_purgelist_open(struct inode *inode, struct file *file) struct dlm_ctxt *dlm = inode->i_private; char *buf = NULL; - buf = (char *) get_zeroed_page(GFP_NOFS); + buf = kzalloc(PAGE_SIZE, GFP_NOFS); if (!buf) - goto bail; + return -ENOMEM; i_size_write(inode, debug_purgelist_print(dlm, buf, PAGE_SIZE - 1)); file->private_data = buf; return 0; -bail: - return -ENOMEM; } static const struct file_operations debug_purgelist_fops = { @@ -384,17 +382,15 @@ static int debug_mle_open(struct inode *inode, struct file *file) struct dlm_ctxt *dlm = inode->i_private; char *buf = NULL; - buf = (char *) get_zeroed_page(GFP_NOFS); + buf = kzalloc(PAGE_SIZE, GFP_NOFS); if (!buf) - goto bail; + return -ENOMEM; i_size_write(inode, debug_mle_print(dlm, buf, PAGE_SIZE - 1)); file->private_data = buf; return 0; -bail: - return -ENOMEM; } static const struct file_operations debug_mle_fops = { @@ -775,17 +771,15 @@ static int debug_state_open(struct inode *inode, struct file *file) struct dlm_ctxt *dlm = inode->i_private; char *buf = NULL; - buf = (char *) get_zeroed_page(GFP_NOFS); + buf = kzalloc(PAGE_SIZE, GFP_NOFS); if (!buf) - goto bail; + return -ENOMEM; i_size_write(inode, debug_state_print(dlm, buf, PAGE_SIZE - 1)); file->private_data = buf; return 0; -bail: - return -ENOMEM; } static const struct file_operations debug_state_fops = { diff --git a/fs/ocfs2/dlm/dlmdomain.c b/fs/ocfs2/dlm/dlmdomain.c index dc9da9133c8e..97bb9400e24b 100644 --- a/fs/ocfs2/dlm/dlmdomain.c +++ b/fs/ocfs2/dlm/dlmdomain.c @@ -63,7 +63,7 @@ static inline void byte_copymap(u8 dmap[], unsigned long smap[], static void dlm_free_pagevec(void **vec, int pages) { while (pages--) - free_page((unsigned long)vec[pages]); + kfree(vec[pages]); kfree(vec); } @@ -75,9 +75,11 @@ static void **dlm_alloc_pagevec(int pages) if (!vec) return NULL; - for (i = 0; i < pages; i++) - if (!(vec[i] = (void *)__get_free_page(GFP_KERNEL))) + for (i = 0; i < pages; i++) { + vec[i] = kmalloc(PAGE_SIZE, GFP_KERNEL); + if (!vec[i]) goto out_free; + } mlog(0, "Allocated DLM hash pagevec; %d pages (%lu expected), %lu buckets per page\n", pages, (unsigned long)DLM_HASH_PAGES, diff --git a/fs/ocfs2/dlm/dlmmaster.c b/fs/ocfs2/dlm/dlmmaster.c index 93eff38fdadd..aee3b4c56dcc 100644 --- a/fs/ocfs2/dlm/dlmmaster.c +++ b/fs/ocfs2/dlm/dlmmaster.c @@ -2548,7 +2548,7 @@ static int dlm_migrate_lockres(struct dlm_ctxt *dlm, /* preallocate up front. if this fails, abort */ ret = -ENOMEM; - mres = (struct dlm_migratable_lockres *) __get_free_page(GFP_NOFS); + mres = kmalloc(PAGE_SIZE, GFP_NOFS); if (!mres) { mlog_errno(ret); goto leave; @@ -2725,8 +2725,7 @@ static int dlm_migrate_lockres(struct dlm_ctxt *dlm, if (wake) wake_up(&res->wq); - if (mres) - free_page((unsigned long)mres); + kfree(mres); dlm_put(dlm); diff --git a/fs/ocfs2/dlm/dlmrecovery.c b/fs/ocfs2/dlm/dlmrecovery.c index 128872bd945d..9b97bf73df22 100644 --- a/fs/ocfs2/dlm/dlmrecovery.c +++ b/fs/ocfs2/dlm/dlmrecovery.c @@ -837,7 +837,7 @@ int dlm_request_all_locks_handler(struct o2net_msg *msg, u32 len, void *data, } /* this will get freed by dlm_request_all_locks_worker */ - buf = (char *) __get_free_page(GFP_NOFS); + buf = kmalloc(PAGE_SIZE, GFP_NOFS); if (!buf) { kfree(item); dlm_put(dlm); @@ -933,7 +933,7 @@ static void dlm_request_all_locks_worker(struct dlm_work_item *item, void *data) } } leave: - free_page((unsigned long)data); + kfree(data); } From 4948580c76d970bf364f4e225c24ea827f7b3138 Mon Sep 17 00:00:00 2001 From: "Mike Rapoport (Microsoft)" Date: Sat, 23 May 2026 20:54:16 +0300 Subject: [PATCH 45/73] nilfs2: replace get_zeroed_page() with kzalloc() nilfs_ioctl_wrap_copy() allocates a temporary buffer with get_zeroed_page(). kzalloc() is a better API for such use and it also provides better scalability and more debugging possibilities. Replace use of get_zeroed_page() with kzalloc(). Signed-off-by: Mike Rapoport (Microsoft) Link: https://patch.msgid.link/20260523-b4-fs-v1-4-275e36a83f0e@kernel.org Reviewed-by: Viacheslav Dubeyko Signed-off-by: Christian Brauner (Amutable) --- fs/nilfs2/ioctl.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fs/nilfs2/ioctl.c b/fs/nilfs2/ioctl.c index e0a606643e87..b73f2c5d10f0 100644 --- a/fs/nilfs2/ioctl.c +++ b/fs/nilfs2/ioctl.c @@ -69,7 +69,7 @@ static int nilfs_ioctl_wrap_copy(struct the_nilfs *nilfs, if (argv->v_index > ~(__u64)0 - argv->v_nmembs) return -EINVAL; - buf = (void *)get_zeroed_page(GFP_NOFS); + buf = kzalloc(PAGE_SIZE, GFP_NOFS); if (unlikely(!buf)) return -ENOMEM; maxmembs = PAGE_SIZE / argv->v_size; @@ -107,7 +107,7 @@ static int nilfs_ioctl_wrap_copy(struct the_nilfs *nilfs, } argv->v_nmembs = total; - free_pages((unsigned long)buf, 0); + kfree(buf); return ret; } From eb28dd9d34a842ecc7847ab29c586ef9ba98e53d Mon Sep 17 00:00:00 2001 From: "Mike Rapoport (Microsoft)" Date: Sat, 23 May 2026 20:54:17 +0300 Subject: [PATCH 46/73] NFS: replace __get_free_page() with kmalloc() in nfs_show_devname() nfs_show_devname() allocates a tmemporary buffer __get_free_page(). kmalloc() is a better API for such use and it also provides better scalability and more debugging possibilities. Replace use of __get_free_page() with kmalloc(). Signed-off-by: Mike Rapoport (Microsoft) Link: https://patch.msgid.link/20260523-b4-fs-v1-5-275e36a83f0e@kernel.org Signed-off-by: Christian Brauner (Amutable) --- fs/nfs/super.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fs/nfs/super.c b/fs/nfs/super.c index 4cd420b14ce3..8f8a03a68d3d 100644 --- a/fs/nfs/super.c +++ b/fs/nfs/super.c @@ -623,7 +623,7 @@ static void show_implementation_id(struct seq_file *m, struct nfs_server *nfss) int nfs_show_devname(struct seq_file *m, struct dentry *root) { - char *page = (char *) __get_free_page(GFP_KERNEL); + char *page = kmalloc(PAGE_SIZE, GFP_KERNEL); char *devname, *dummy; int err = 0; if (!page) @@ -633,7 +633,7 @@ int nfs_show_devname(struct seq_file *m, struct dentry *root) err = PTR_ERR(devname); else seq_escape(m, devname, " \t\n\\"); - free_page((unsigned long)page); + kfree(page); return err; } EXPORT_SYMBOL_GPL(nfs_show_devname); From 7c031f1d478fde2fdb0769ebe76bcaf02749242e Mon Sep 17 00:00:00 2001 From: "Mike Rapoport (Microsoft)" Date: Sat, 23 May 2026 20:54:18 +0300 Subject: [PATCH 47/73] NFS: remove unused page and page2 in nfs4_replace_transport() Temporary buffers page and page2 allocated by nfs4_replace_transport() and passed to nfs4_try_replacing_one_location() are never used. Remove them and the code that allocates and frees memory for these buffers. Signed-off-by: Mike Rapoport (Microsoft) Link: https://patch.msgid.link/20260523-b4-fs-v1-6-275e36a83f0e@kernel.org Signed-off-by: Christian Brauner (Amutable) --- fs/nfs/nfs4namespace.c | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/fs/nfs/nfs4namespace.c b/fs/nfs/nfs4namespace.c index 14f72baf3b30..2a03f02bba7c 100644 --- a/fs/nfs/nfs4namespace.c +++ b/fs/nfs/nfs4namespace.c @@ -481,7 +481,6 @@ int nfs4_submount(struct fs_context *fc, struct nfs_server *server) * Returns zero on success, or a negative errno value. */ static int nfs4_try_replacing_one_location(struct nfs_server *server, - char *page, char *page2, const struct nfs4_fs_location *location) { struct net *net = rpc_net_ns(server->client); @@ -541,21 +540,12 @@ static int nfs4_try_replacing_one_location(struct nfs_server *server, int nfs4_replace_transport(struct nfs_server *server, const struct nfs4_fs_locations *locations) { - char *page = NULL, *page2 = NULL; int loc, error; error = -ENOENT; if (locations == NULL || locations->nlocations <= 0) goto out; - error = -ENOMEM; - page = (char *) __get_free_page(GFP_USER); - if (!page) - goto out; - page2 = (char *) __get_free_page(GFP_USER); - if (!page2) - goto out; - for (loc = 0; loc < locations->nlocations; loc++) { const struct nfs4_fs_location *location = &locations->locations[loc]; @@ -564,14 +554,11 @@ int nfs4_replace_transport(struct nfs_server *server, location->rootpath.ncomponents == 0) continue; - error = nfs4_try_replacing_one_location(server, page, - page2, location); + error = nfs4_try_replacing_one_location(server, location); if (error == 0) break; } out: - free_page((unsigned long)page); - free_page((unsigned long)page2); return error; } From 06040e75202d7f4fb6aa8f8daf3ffe5aa3e1c9da Mon Sep 17 00:00:00 2001 From: "Mike Rapoport (Microsoft)" Date: Sat, 23 May 2026 20:54:19 +0300 Subject: [PATCH 48/73] NFSD: replace __get_free_page() with kmalloc() in nfsd_buffered_readdir() nfsd_buffered_readdir() allocates a staging buffer with __get_free_page(). kmalloc() is a better API for such use and it also provides better scalability and more debugging possibilities. Replace use of __get_free_page() with kmalloc(). Signed-off-by: Mike Rapoport (Microsoft) Link: https://patch.msgid.link/20260523-b4-fs-v1-7-275e36a83f0e@kernel.org Acked-by: Jeff Layton Signed-off-by: Christian Brauner (Amutable) --- fs/nfsd/vfs.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fs/nfsd/vfs.c b/fs/nfsd/vfs.c index eafdf7b7890f..c99e54b23cd9 100644 --- a/fs/nfsd/vfs.c +++ b/fs/nfsd/vfs.c @@ -2407,7 +2407,7 @@ static __be32 nfsd_buffered_readdir(struct file *file, struct svc_fh *fhp, loff_t offset; struct readdir_data buf = { .ctx.actor = nfsd_buffered_filldir, - .dirent = (void *)__get_free_page(GFP_KERNEL) + .dirent = kmalloc(PAGE_SIZE, GFP_KERNEL) }; if (!buf.dirent) @@ -2458,7 +2458,7 @@ static __be32 nfsd_buffered_readdir(struct file *file, struct svc_fh *fhp, offset = vfs_llseek(file, 0, SEEK_CUR); } - free_page((unsigned long)(buf.dirent)); + kfree((buf.dirent)); if (host_err) return nfserrno(host_err); From 02d7d892d26ebbcbc542b9b914522f713ce1735b Mon Sep 17 00:00:00 2001 From: "Mike Rapoport (Microsoft)" Date: Sat, 23 May 2026 20:54:20 +0300 Subject: [PATCH 49/73] libfs: simple_transaction_get(): replace get_zeroed_page() with kzalloc() simple_transaction_get() allocates memory with get_zeroed_page(). That memory is used as a file local buffer that is accessed using copy_from_user() and simple_read_from_buffer(). kmalloc() is a better API for such use and it also provides better scalability and more debugging possibilities. Replace use of get_zeroed_page() with kzalloc(). Signed-off-by: Mike Rapoport (Microsoft) Link: https://patch.msgid.link/20260523-b4-fs-v1-8-275e36a83f0e@kernel.org Signed-off-by: Christian Brauner (Amutable) --- fs/libfs.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/fs/libfs.c b/fs/libfs.c index 1bbea5e7bae3..80a330c8296f 100644 --- a/fs/libfs.c +++ b/fs/libfs.c @@ -1258,7 +1258,7 @@ char *simple_transaction_get(struct file *file, const char __user *buf, size_t s if (size > SIMPLE_TRANSACTION_LIMIT - 1) return ERR_PTR(-EFBIG); - ar = (struct simple_transaction_argresp *)get_zeroed_page(GFP_KERNEL); + ar = kzalloc(PAGE_SIZE, GFP_KERNEL); if (!ar) return ERR_PTR(-ENOMEM); @@ -1267,7 +1267,7 @@ char *simple_transaction_get(struct file *file, const char __user *buf, size_t s /* only one write allowed per open */ if (file->private_data) { spin_unlock(&simple_transaction_lock); - free_page((unsigned long)ar); + kfree(ar); return ERR_PTR(-EBUSY); } @@ -1294,7 +1294,7 @@ EXPORT_SYMBOL(simple_transaction_read); int simple_transaction_release(struct inode *inode, struct file *file) { - free_page((unsigned long)file->private_data); + kfree(file->private_data); return 0; } EXPORT_SYMBOL(simple_transaction_release); From de9f4f0b2c0f31c3a9483dedd0e9bb7dcb409a13 Mon Sep 17 00:00:00 2001 From: "Mike Rapoport (Microsoft)" Date: Sat, 23 May 2026 20:54:21 +0300 Subject: [PATCH 50/73] jfs: replace __get_free_page() with kmalloc() jfs_readdir() allocates dirent_buf with __get_free_page(). kmalloc() is a better API for such use and it also provides better scalability and more debugging possibilities. Replace use of __get_free_page() with kmalloc(). Signed-off-by: Mike Rapoport (Microsoft) Link: https://patch.msgid.link/20260523-b4-fs-v1-9-275e36a83f0e@kernel.org Signed-off-by: Christian Brauner (Amutable) --- fs/jfs/jfs_dtree.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/fs/jfs/jfs_dtree.c b/fs/jfs/jfs_dtree.c index ac0f79fafaca..8ce6e4458cc2 100644 --- a/fs/jfs/jfs_dtree.c +++ b/fs/jfs/jfs_dtree.c @@ -2729,7 +2729,7 @@ int jfs_readdir(struct file *file, struct dir_context *ctx) struct ldtentry *d; struct dtslot *t; int d_namleft, len, outlen; - unsigned long dirent_buf; + void *dirent_buf; char *name_ptr; u32 dir_index; int do_index = 0; @@ -2884,7 +2884,7 @@ int jfs_readdir(struct file *file, struct dir_context *ctx) } } - dirent_buf = __get_free_page(GFP_KERNEL); + dirent_buf = kmalloc(PAGE_SIZE, GFP_KERNEL); if (dirent_buf == 0) { DT_PUTPAGE(mp); jfs_warn("jfs_readdir: __get_free_page failed!"); @@ -2893,7 +2893,7 @@ int jfs_readdir(struct file *file, struct dir_context *ctx) } while (1) { - jfs_dirent = (struct jfs_dirent *) dirent_buf; + jfs_dirent = dirent_buf; jfs_dirents = 0; overflow = fix_page = 0; @@ -2903,7 +2903,7 @@ int jfs_readdir(struct file *file, struct dir_context *ctx) if (stbl[i] < 0) { jfs_err("JFS: Invalid stbl[%d] = %d for inode %ld, block = %lld", i, stbl[i], (long)ip->i_ino, (long long)bn); - free_page(dirent_buf); + kfree(dirent_buf); DT_PUTPAGE(mp); return -EIO; } @@ -2911,7 +2911,7 @@ int jfs_readdir(struct file *file, struct dir_context *ctx) d = (struct ldtentry *) & p->slot[stbl[i]]; if (((long) jfs_dirent + d->namlen + 1) > - (dirent_buf + PAGE_SIZE)) { + ((long)dirent_buf + PAGE_SIZE)) { /* DBCS codepages could overrun dirent_buf */ index = i; overflow = 1; @@ -3014,7 +3014,7 @@ int jfs_readdir(struct file *file, struct dir_context *ctx) /* unpin previous leaf page */ DT_PUTPAGE(mp); - jfs_dirent = (struct jfs_dirent *) dirent_buf; + jfs_dirent = dirent_buf; while (jfs_dirents--) { ctx->pos = jfs_dirent->position; if (!dir_emit(ctx, jfs_dirent->name, @@ -3037,13 +3037,13 @@ int jfs_readdir(struct file *file, struct dir_context *ctx) DT_GETPAGE(ip, bn, mp, PSIZE, p, rc); if (rc) { - free_page(dirent_buf); + kfree(dirent_buf); return rc; } } out: - free_page(dirent_buf); + kfree(dirent_buf); return rc; } From 2f6702dc6fdcf0ccc85417140e9ee1ce6a64863c Mon Sep 17 00:00:00 2001 From: "Mike Rapoport (Microsoft)" Date: Sat, 23 May 2026 20:54:22 +0300 Subject: [PATCH 51/73] jbd2: replace __get_free_pages() with kmalloc() jbd2_alloc() falls back from kmem_cache_alloc() to __get_free_pages() for allocations larger than PAGE_SIZE. But kmalloc() can handle such cases with essentially the same fallback. Replace use of __get_free_pages() with kmalloc() and simplify jbd2_free() as both kmem_cache_alloc() and kmalloc() allocations can be freed with kfree(). Signed-off-by: Mike Rapoport (Microsoft) Link: https://patch.msgid.link/20260523-b4-fs-v1-10-275e36a83f0e@kernel.org Reviewed-by: Jan Kara Signed-off-by: Christian Brauner (Amutable) --- fs/jbd2/journal.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/fs/jbd2/journal.c b/fs/jbd2/journal.c index 4f397fcdb13c..1137b471e490 100644 --- a/fs/jbd2/journal.c +++ b/fs/jbd2/journal.c @@ -2784,7 +2784,7 @@ void *jbd2_alloc(size_t size, gfp_t flags) if (size < PAGE_SIZE) ptr = kmem_cache_alloc(get_slab(size), flags); else - ptr = (void *)__get_free_pages(flags, get_order(size)); + ptr = kmalloc(size, flags); /* Check alignment; SLUB has gotten this wrong in the past, * and this can lead to user data corruption! */ @@ -2795,10 +2795,7 @@ void *jbd2_alloc(size_t size, gfp_t flags) void jbd2_free(void *ptr, size_t size) { - if (size < PAGE_SIZE) - kmem_cache_free(get_slab(size), ptr); - else - free_pages((unsigned long)ptr, get_order(size)); + kfree(ptr); }; /* From 263873f40e895ed6df7a1bae001f70009f5fa925 Mon Sep 17 00:00:00 2001 From: "Mike Rapoport (Microsoft)" Date: Sat, 23 May 2026 20:54:23 +0300 Subject: [PATCH 52/73] isofs: replace __get_free_page() with kmalloc() isofs_readdir() allocates a temporary buffer with __get_free_page(). kmalloc() is a better API for such use and it also provides better scalability and more debugging possibilities. Replace use of __get_free_page() with kmalloc(). Signed-off-by: Mike Rapoport (Microsoft) Link: https://patch.msgid.link/20260523-b4-fs-v1-11-275e36a83f0e@kernel.org Signed-off-by: Christian Brauner (Amutable) --- fs/isofs/dir.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/fs/isofs/dir.c b/fs/isofs/dir.c index 2fd9948d606e..6d220eab531e 100644 --- a/fs/isofs/dir.c +++ b/fs/isofs/dir.c @@ -13,6 +13,7 @@ */ #include #include +#include #include "isofs.h" int isofs_name_translate(struct iso_directory_record *de, char *new, struct inode *inode) @@ -255,7 +256,7 @@ static int isofs_readdir(struct file *file, struct dir_context *ctx) struct iso_directory_record *tmpde; struct inode *inode = file_inode(file); - tmpname = (char *)__get_free_page(GFP_KERNEL); + tmpname = kmalloc(PAGE_SIZE, GFP_KERNEL); if (tmpname == NULL) return -ENOMEM; @@ -263,7 +264,7 @@ static int isofs_readdir(struct file *file, struct dir_context *ctx) result = do_isofs_readdir(inode, file, ctx, tmpname, tmpde); - free_page((unsigned long) tmpname); + kfree(tmpname); return result; } From 3db329857c6003bfd0a6cc61d01e0b750fe5d32c Mon Sep 17 00:00:00 2001 From: "Mike Rapoport (Microsoft)" Date: Sat, 23 May 2026 20:54:24 +0300 Subject: [PATCH 53/73] fuse: replace __get_free_page() with kmalloc() fuse_do_ioctl allocates memory for struct iov array using __get_free_page(). kmalloc() is a better API for such use and it also provides better scalability and more debugging possibilities. Replace use of __get_free_page() with kmalloc(). Signed-off-by: Mike Rapoport (Microsoft) Link: https://patch.msgid.link/20260523-b4-fs-v1-12-275e36a83f0e@kernel.org Acked-by: Miklos Szeredi Signed-off-by: Christian Brauner (Amutable) --- fs/fuse/ioctl.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/fs/fuse/ioctl.c b/fs/fuse/ioctl.c index fdc175e93f74..3614ea603913 100644 --- a/fs/fuse/ioctl.c +++ b/fs/fuse/ioctl.c @@ -10,6 +10,7 @@ #include #include +#include #define FUSE_VERITY_ENABLE_ARG_MAX_PAGES 256 static ssize_t fuse_send_ioctl(struct fuse_mount *fm, struct fuse_args *args, @@ -252,7 +253,7 @@ long fuse_do_ioctl(struct file *file, unsigned int cmd, unsigned long arg, err = -ENOMEM; ap.folios = fuse_folios_alloc(fm->fc->max_pages, GFP_KERNEL, &ap.descs); - iov_page = (struct iovec *) __get_free_page(GFP_KERNEL); + iov_page = kmalloc(PAGE_SIZE, GFP_KERNEL); if (!ap.folios || !iov_page) goto out; @@ -400,7 +401,7 @@ long fuse_do_ioctl(struct file *file, unsigned int cmd, unsigned long arg, } err = 0; out: - free_page((unsigned long) iov_page); + kfree(iov_page); while (ap.num_folios) folio_put(ap.folios[--ap.num_folios]); kfree(ap.folios); From cf080236f386a6c275abb052786e7f5e63799af8 Mon Sep 17 00:00:00 2001 From: "Mike Rapoport (Microsoft)" Date: Sat, 23 May 2026 20:54:25 +0300 Subject: [PATCH 54/73] fs/select: replace __get_free_page() with kmalloc() poll_get_entry() allocates new memory for poll_table entries using __get_free_page(). kmalloc() is a better API for such use and it also provides better scalability and more debugging possibilities. Replace use of __get_free_page() with kmalloc(). Signed-off-by: Mike Rapoport (Microsoft) Link: https://patch.msgid.link/20260523-b4-fs-v1-13-275e36a83f0e@kernel.org Reviewed-by: Jan Kara Signed-off-by: Christian Brauner (Amutable) --- fs/select.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fs/select.c b/fs/select.c index 75978b18f48f..6fa63e48cdee 100644 --- a/fs/select.c +++ b/fs/select.c @@ -150,7 +150,7 @@ void poll_freewait(struct poll_wqueues *pwq) } while (entry > p->entries); old = p; p = p->next; - free_page((unsigned long) old); + kfree(old); } } EXPORT_SYMBOL(poll_freewait); @@ -165,7 +165,7 @@ static struct poll_table_entry *poll_get_entry(struct poll_wqueues *p) if (!table || POLL_TABLE_FULL(table)) { struct poll_table_page *new_table; - new_table = (struct poll_table_page *) __get_free_page(GFP_KERNEL); + new_table = kmalloc(PAGE_SIZE, GFP_KERNEL); if (!new_table) { p->error = -ENOMEM; return NULL; From aca4fd395d178b8f811db55797a8b3c773d61538 Mon Sep 17 00:00:00 2001 From: "Mike Rapoport (Microsoft)" Date: Sat, 23 May 2026 20:54:26 +0300 Subject: [PATCH 55/73] fs/namespace: use __getname() to allocate mntpath buffer mnt_warn_timestamp_expiry() allocates memory for a path with __get_free_page() although there is a dedicated helper for allocation of file paths: __getname(). Replace __get_free_page() for allocation of a path buffer with __getname(). Christian Brauner says: Pass PATH_MAX (not PAGE_SIZE) to d_path() to match the size that __getname() actually allocates, and drop the now-unnecessary NULL check around __putname() since __putname() handles NULL. Both per Jan Kara's review feedback, acked by the author. Signed-off-by: Mike Rapoport (Microsoft) Link: https://patch.msgid.link/20260523-b4-fs-v1-14-275e36a83f0e@kernel.org Signed-off-by: Christian Brauner (Amutable) --- fs/namespace.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/fs/namespace.c b/fs/namespace.c index fe919abd2f01..d67c2f61b3df 100644 --- a/fs/namespace.c +++ b/fs/namespace.c @@ -3303,9 +3303,9 @@ static void mnt_warn_timestamp_expiry(const struct path *mountpoint, (ktime_get_real_seconds() + TIME_UPTIME_SEC_MAX > sb->s_time_max)) { char *buf, *mntpath; - buf = (char *)__get_free_page(GFP_KERNEL); + buf = __getname(); if (buf) - mntpath = d_path(mountpoint, buf, PAGE_SIZE); + mntpath = d_path(mountpoint, buf, PATH_MAX); else mntpath = ERR_PTR(-ENOMEM); if (IS_ERR(mntpath)) @@ -3318,8 +3318,7 @@ static void mnt_warn_timestamp_expiry(const struct path *mountpoint, (unsigned long long)sb->s_time_max); sb->s_iflags |= SB_I_TS_EXPIRY_WARNED; - if (buf) - free_page((unsigned long)buf); + __putname(buf); } } From 1b27d11b17b729e12a4abf09031a1aa5cc05d56d Mon Sep 17 00:00:00 2001 From: "Mike Rapoport (Microsoft)" Date: Sat, 23 May 2026 20:54:27 +0300 Subject: [PATCH 56/73] configfs: replace __get_free_pages() with kzalloc() configfs allocates staging buffers __get_free_pages(). kmalloc() is a better API for such use and it also provides better scalability and more debugging possibilities. Replace use of __get_free_pages() with kzalloc(). Signed-off-by: Mike Rapoport (Microsoft) Link: https://patch.msgid.link/20260523-b4-fs-v1-15-275e36a83f0e@kernel.org Reviewed-by: Jan Kara Signed-off-by: Christian Brauner (Amutable) --- fs/configfs/file.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/fs/configfs/file.c b/fs/configfs/file.c index ef8c3cd10cc6..a48cece775a3 100644 --- a/fs/configfs/file.c +++ b/fs/configfs/file.c @@ -59,7 +59,7 @@ static int fill_read_buffer(struct file *file, struct configfs_buffer *buffer) ssize_t count = -ENOENT; if (!buffer->page) - buffer->page = (char *) get_zeroed_page(GFP_KERNEL); + buffer->page = kzalloc(PAGE_SIZE, GFP_KERNEL); if (!buffer->page) return -ENOMEM; @@ -184,7 +184,7 @@ static int fill_write_buffer(struct configfs_buffer *buffer, int copied; if (!buffer->page) - buffer->page = (char *)__get_free_pages(GFP_KERNEL, 0); + buffer->page = kmalloc(PAGE_SIZE, GFP_KERNEL); if (!buffer->page) return -ENOMEM; @@ -381,8 +381,7 @@ static int configfs_release(struct inode *inode, struct file *filp) struct configfs_buffer *buffer = filp->private_data; module_put(buffer->owner); - if (buffer->page) - free_page((unsigned long)buffer->page); + kfree(buffer->page); mutex_destroy(&buffer->mutex); kfree(buffer); return 0; From 6ff17653e94d97735d426a4924df7be51fd63abd Mon Sep 17 00:00:00 2001 From: "Mike Rapoport (Microsoft)" Date: Sat, 23 May 2026 20:54:28 +0300 Subject: [PATCH 57/73] binfmt_misc: replace __get_free_page() with kmalloc() bm_entry_read() allocates temporary buffer using __get_free_page(). kmalloc() is a better API for such use and it also provides better scalability and more debugging possibilities. Replace use of __get_free_page() with kmalloc(). Signed-off-by: Mike Rapoport (Microsoft) Link: https://patch.msgid.link/20260523-b4-fs-v1-16-275e36a83f0e@kernel.org Signed-off-by: Christian Brauner (Amutable) --- fs/binfmt_misc.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fs/binfmt_misc.c b/fs/binfmt_misc.c index b3d8fd70e8b1..84349fcb93f1 100644 --- a/fs/binfmt_misc.c +++ b/fs/binfmt_misc.c @@ -704,7 +704,7 @@ bm_entry_read(struct file *file, char __user *buf, size_t nbytes, loff_t *ppos) ssize_t res; char *page; - page = (char *) __get_free_page(GFP_KERNEL); + page = kmalloc(PAGE_SIZE, GFP_KERNEL); if (!page) return -ENOMEM; @@ -712,7 +712,7 @@ bm_entry_read(struct file *file, char __user *buf, size_t nbytes, loff_t *ppos) res = simple_read_from_buffer(buf, nbytes, ppos, page, strlen(page)); - free_page((unsigned long) page); + kfree(page); return res; } From c1fe8d334f93ebdba07c8a28c07fbd619e673633 Mon Sep 17 00:00:00 2001 From: "Mike Rapoport (Microsoft)" Date: Sat, 23 May 2026 20:54:29 +0300 Subject: [PATCH 58/73] bfs: replace get_zeroed_page() with kzalloc() bfs_dump_imap() allocates temporary buffer with get_zeroed_page(). kmalloc() is a better API for such use and it also provides better scalability and more debugging possibilities. Replace use of get_zeroed_page() with kzalloc(). Signed-off-by: Mike Rapoport (Microsoft) Link: https://patch.msgid.link/20260523-b4-fs-v1-17-275e36a83f0e@kernel.org Signed-off-by: Christian Brauner (Amutable) --- fs/bfs/inode.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fs/bfs/inode.c b/fs/bfs/inode.c index 9c3e90390824..e41efdd35db9 100644 --- a/fs/bfs/inode.c +++ b/fs/bfs/inode.c @@ -311,7 +311,7 @@ void bfs_dump_imap(const char *prefix, struct super_block *s) { #ifdef DEBUG int i; - char *tmpbuf = (char *)get_zeroed_page(GFP_KERNEL); + char *tmpbuf = kzalloc(PAGE_SIZE, GFP_KERNEL); if (!tmpbuf) return; @@ -323,7 +323,7 @@ void bfs_dump_imap(const char *prefix, struct super_block *s) strcat(tmpbuf, "0"); } printf("%s: lasti=%08lx <%s>\n", prefix, BFS_SB(s)->si_lasti, tmpbuf); - free_page((unsigned long)tmpbuf); + kfree(tmpbuf); #endif } From 5afe734e76214a06f66a1e679aaa032d0e532516 Mon Sep 17 00:00:00 2001 From: Qingshuang Fu Date: Wed, 27 May 2026 18:00:24 +0800 Subject: [PATCH 59/73] fs: fix spelling mistakes in comment MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix three spelling errors in the comment for an internal file structure allocation function: - happend → happened - over → exceed (grammatical fix) - int → in Changes since v1: - Fix comma after e.g. - Fix incorrect use of "imbalance" Signed-off-by: Qingshuang Fu Link: https://patch.msgid.link/20260527100025.960339-1-fffsqian@163.com Signed-off-by: Christian Brauner (Amutable) --- fs/file_table.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fs/file_table.c b/fs/file_table.c index 16e52e7fc2ac..3c08832aa387 100644 --- a/fs/file_table.c +++ b/fs/file_table.c @@ -231,13 +231,13 @@ static int init_file(struct file *f, int flags, const struct cred *cred) } /* Find an unused file structure and return a pointer to it. - * Returns an error pointer if some error happend e.g. we over file + * Returns an error pointer if some error happened, e.g., we exceed the file * structures limit, run out of memory or operation is not permitted. * * Be very careful using this. You are responsible for * getting write access to any mount that you might assign * to this filp, if it is opened for write. If this is not - * done, you will imbalance int the mount's writer count + * done, the mount's writer count will be wrong * and a warning at __fput() time. */ struct file *alloc_empty_file(int flags, const struct cred *cred) From d24576fddf645a84e190a8991985458e40fbe424 Mon Sep 17 00:00:00 2001 From: Mateusz Guzik Date: Fri, 22 May 2026 16:21:52 +0200 Subject: [PATCH 60/73] fs: retire stale comment in fget_task_next() The routine originally showed up in e9a53aeb5e0a838f ("file: Implement task_lookup_next_fd_rcu"), afterwards it got renamed and started entering RCU on its own in 8fd3395ec9051a52 ("get rid of ...lookup...fdget_rcu() family"). Signed-off-by: Mateusz Guzik Link: https://patch.msgid.link/20260522142152.1515572-1-mjguzik@gmail.com Reviewed-by: Jan Kara Signed-off-by: Christian Brauner (Amutable) --- fs/file.c | 1 - 1 file changed, 1 deletion(-) diff --git a/fs/file.c b/fs/file.c index e5c75b22e0c7..628ca07dc4b1 100644 --- a/fs/file.c +++ b/fs/file.c @@ -1133,7 +1133,6 @@ struct file *fget_task(struct task_struct *task, unsigned int fd) struct file *fget_task_next(struct task_struct *task, unsigned int *ret_fd) { - /* Must be called with rcu_read_lock held */ struct files_struct *files; unsigned int fd = *ret_fd; struct file *file = NULL; From 212ed884a1aeefda22d87c270d082e4b0a95821f Mon Sep 17 00:00:00 2001 From: Breno Leitao Date: Sun, 24 May 2026 07:44:58 -0700 Subject: [PATCH 61/73] fs/pipe: pre-allocate pages outside pipe->mutex in anon_pipe_write anon_pipe_write() takes pipe->mutex (aka "mutex protecting the whole thing") and then, from the per-iteration anon_pipe_get_page() helper, used to call alloc_page(GFP_HIGHUSER | __GFP_ACCOUNT) once per page while still holding it. That allocation can sleep doing direct reclaim and/or runs memcg charging, which extends the critical section and stalls a concurrent reader on the very same mutex. Just pre-alloc the required pages before the lock in an array and just pop them inside the lock. This can improve the pipe throughput up to 48% and reduce the latency in 33%, easily seen when there is memory pressure and direct reclaim. Reviewed-by: Mateusz Guzik Reviewed-by: Jeff Layton Signed-off-by: Breno Leitao Link: https://patch.msgid.link/20260524-fix_pipe-v3-1-bb4a75d23a90@debian.org Signed-off-by: Christian Brauner (Amutable) --- fs/pipe.c | 103 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 100 insertions(+), 3 deletions(-) diff --git a/fs/pipe.c b/fs/pipe.c index e37c79935ecb..429b0714ec57 100644 --- a/fs/pipe.c +++ b/fs/pipe.c @@ -111,16 +111,76 @@ void pipe_double_lock(struct pipe_inode_info *pipe1, pipe_lock(pipe2); } -static struct page *anon_pipe_get_page(struct pipe_inode_info *pipe) +#define PIPE_PREALLOC_MAX 8 + +struct anon_pipe_prealloc { + struct page *pages[PIPE_PREALLOC_MAX]; + unsigned int count; +}; + +/* + * Pre-allocate pages outside pipe->mutex for multi-page writes. + * alloc_page() with GFP_HIGHUSER can sleep in reclaim and runs memcg + * charging; doing it under the mutex stalls a concurrent reader. + * + * Loop alloc_page() instead of alloc_pages_bulk_*(): the bulk path refuses + * __GFP_ACCOUNT under memcg (see commit 8dcb3060d81d "memcg: page_alloc: + * skip bulk allocator for __GFP_ACCOUNT") and silently degrades to a single + * page. A per-page loop keeps memcg accounting and the task NUMA mempolicy + * honoured for every page; the per-call overhead is small compared to the + * pipe->mutex hold-time being shrunk. Any shortfall is covered by the + * in-lock alloc_page() fallback in anon_pipe_get_page(). + */ +static void anon_pipe_get_page_prealloc(struct anon_pipe_prealloc *prealloc, + size_t total_len) { + unsigned int want, i; + struct page *page; + + prealloc->count = 0; + if (total_len <= PAGE_SIZE) + return; + + want = min_t(unsigned int, DIV_ROUND_UP(total_len, PAGE_SIZE), + PIPE_PREALLOC_MAX); + + for (i = 0; i < want; i++) { + page = alloc_page(GFP_HIGHUSER | __GFP_ACCOUNT); + if (!page) + break; + prealloc->pages[prealloc->count++] = page; + } +} + +static struct page *anon_pipe_prealloc_pop(struct anon_pipe_prealloc *prealloc) +{ + if (!prealloc->count) + return NULL; + + prealloc->count--; + + return prealloc->pages[prealloc->count]; +} + +static struct page *anon_pipe_get_page(struct pipe_inode_info *pipe, + struct anon_pipe_prealloc *prealloc) +{ + struct page *page; + + /* Drain prealloc first to keep tmp_page[] hot for later small writes. */ + page = anon_pipe_prealloc_pop(prealloc); + if (page) + return page; + for (int i = 0; i < ARRAY_SIZE(pipe->tmp_page); i++) { if (pipe->tmp_page[i]) { - struct page *page = pipe->tmp_page[i]; + page = pipe->tmp_page[i]; pipe->tmp_page[i] = NULL; return page; } } + /* FWIW: This is called with pipe->mutex held */ return alloc_page(GFP_HIGHUSER | __GFP_ACCOUNT); } @@ -139,6 +199,38 @@ static void anon_pipe_put_page(struct pipe_inode_info *pipe, put_page(page); } +/* + * Stash leftover prealloc pages in tmp_page[] so the next write to this + * pipe gets a hot page without entering the allocator. + */ +static void anon_pipe_refill_tmp_pages(struct pipe_inode_info *pipe, + struct anon_pipe_prealloc *prealloc) +{ + int i, idx; + + if (!prealloc->count) + return; + + for (i = 0; i < ARRAY_SIZE(pipe->tmp_page); i++) { + if (pipe->tmp_page[i]) + continue; + if (!prealloc->count) + return; + idx = --prealloc->count; + pipe->tmp_page[i] = prealloc->pages[idx]; + prealloc->pages[idx] = NULL; + } +} + +/* Runs after mutex_unlock() to keep put_page() out of the critical section. */ +static void anon_pipe_free_pages(struct anon_pipe_prealloc *prealloc) +{ + while (prealloc->count) { + prealloc->count--; + put_page(prealloc->pages[prealloc->count]); + } +} + static void anon_pipe_buf_release(struct pipe_inode_info *pipe, struct pipe_buffer *buf) { @@ -432,6 +524,7 @@ anon_pipe_write(struct kiocb *iocb, struct iov_iter *from) { struct file *filp = iocb->ki_filp; struct pipe_inode_info *pipe = filp->private_data; + struct anon_pipe_prealloc prealloc; unsigned int head; ssize_t ret = 0; size_t total_len = iov_iter_count(from); @@ -455,6 +548,8 @@ anon_pipe_write(struct kiocb *iocb, struct iov_iter *from) if (unlikely(total_len == 0)) return 0; + anon_pipe_get_page_prealloc(&prealloc, total_len); + mutex_lock(&pipe->mutex); if (!pipe->readers) { @@ -512,7 +607,7 @@ anon_pipe_write(struct kiocb *iocb, struct iov_iter *from) struct page *page; int copied; - page = anon_pipe_get_page(pipe); + page = anon_pipe_get_page(pipe, &prealloc); if (unlikely(!page)) { if (!ret) ret = -ENOMEM; @@ -576,9 +671,11 @@ anon_pipe_write(struct kiocb *iocb, struct iov_iter *from) wake_next_writer = true; } out: + anon_pipe_refill_tmp_pages(pipe, &prealloc); if (pipe_is_full(pipe)) wake_next_writer = false; mutex_unlock(&pipe->mutex); + anon_pipe_free_pages(&prealloc); /* * If we do do a wakeup event, we do a 'sync' wakeup, because we From d29bd8efe16239608b60173a7e8d842bcbfcd9e9 Mon Sep 17 00:00:00 2001 From: Breno Leitao Date: Sun, 24 May 2026 07:44:59 -0700 Subject: [PATCH 62/73] selftests/pipe: add pipe_bench microbenchmark Add a small selftest that stresses pipe->mutex contention by spawning N writer threads that hammer a single pipe with multi-page writes, plus M reader threads that drain. Each writer records its own write() latency samples into a log2-bucketed histogram; main aggregates and prints total writes, throughput, average and percentile (p50/p99) latencies, and the maximum observed latency. Pass --memory-pressure to fork stress-ng (--vm 4 --vm-bytes 80% --vm-method all) for the duration of the run, so alloc_page() in anon_pipe_write() routinely hits direct reclaim. The flag fails fast if stress-ng is not on $PATH. Program print something like the following, for different writes, readers, msgsizes and memory pressure: config: writers=X readers=Y msgsize=Z duration=3 pipe_size=1048576 memory_pressure=[no|yes] writes: total=54451 rate=18150/s throughput_MBps: 1134.40 lat_avg_ns: 275355 lat_p50_ns_upper: 262143 lat_p99_ns_upper: 1048575 lat_max_ns: 2145633 Reviewed-by: Jeff Layton Signed-off-by: Breno Leitao Link: https://patch.msgid.link/20260524-fix_pipe-v3-2-bb4a75d23a90@debian.org Signed-off-by: Christian Brauner (Amutable) --- tools/testing/selftests/Makefile | 1 + tools/testing/selftests/pipe/.gitignore | 1 + tools/testing/selftests/pipe/Makefile | 9 + tools/testing/selftests/pipe/pipe_bench.c | 616 ++++++++++++++++++++++ 4 files changed, 627 insertions(+) create mode 100644 tools/testing/selftests/pipe/.gitignore create mode 100644 tools/testing/selftests/pipe/Makefile create mode 100644 tools/testing/selftests/pipe/pipe_bench.c diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile index 6e59b8f63e41..bcd9db9d292c 100644 --- a/tools/testing/selftests/Makefile +++ b/tools/testing/selftests/Makefile @@ -91,6 +91,7 @@ TARGETS += pcie_bwctrl TARGETS += perf_events TARGETS += pidfd TARGETS += pid_namespace +TARGETS += pipe TARGETS += power_supply TARGETS += powerpc TARGETS += prctl diff --git a/tools/testing/selftests/pipe/.gitignore b/tools/testing/selftests/pipe/.gitignore new file mode 100644 index 000000000000..20b549361a15 --- /dev/null +++ b/tools/testing/selftests/pipe/.gitignore @@ -0,0 +1 @@ +pipe_bench diff --git a/tools/testing/selftests/pipe/Makefile b/tools/testing/selftests/pipe/Makefile new file mode 100644 index 000000000000..1810c680117b --- /dev/null +++ b/tools/testing/selftests/pipe/Makefile @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: GPL-2.0 +# Copyright (c) 2026 Meta Platforms, Inc. and affiliates +# Copyright (c) 2026 Breno Leitao + +CFLAGS += -O2 -Wall -Wextra -pthread + +TEST_GEN_PROGS := pipe_bench + +include ../lib.mk diff --git a/tools/testing/selftests/pipe/pipe_bench.c b/tools/testing/selftests/pipe/pipe_bench.c new file mode 100644 index 000000000000..7e96429b8fb4 --- /dev/null +++ b/tools/testing/selftests/pipe/pipe_bench.c @@ -0,0 +1,616 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * pipe_bench - exercise concurrent pipe operation + * + * N writer threads hammer a single pipe with multi-page writes; M reader + * threads drain it. Each writer records its own write() latency histogram. + * Multi-page writes (msgsize >= PAGE_SIZE) force the loop in + * anon_pipe_write() to call alloc_page(GFP_HIGHUSER | __GFP_ACCOUNT) under + * pipe->mutex, which is the critical section the patch shrinks. + * + * By default the benchmark sweeps writers in {1, 2, 5} x readers in + * {1, 5, 10} and prints one block per configuration so two runs (e.g. + * baseline vs patched) can be diffed directly. Pass -w and -r to run a + * single configuration instead. Pass --memory-pressure to spawn stress-ng + * alongside the sweep so the per-page alloc_page() path under pipe->mutex + * has to dip into reclaim. + * + * Copyright (c) 2026 Meta Platforms, Inc. and affiliates + * Copyright (c) 2026 Breno Leitao + */ + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) +#define HIST_BUCKETS 32 + +static size_t g_msgsize = 16 * 4096; +static int g_duration = 3; +static int g_pipe_size = 1024 * 1024; +static int g_memory_pressure; + +static atomic_int g_stop; +static int g_pipe[2]; + +struct wstats { + uint64_t writes; + uint64_t bytes; + uint64_t lat_sum_ns; + uint64_t lat_max_ns; + uint64_t lat_hist[HIST_BUCKETS]; + char *buf; +}; + +struct rstats { + char *buf; +}; + +struct hist_totals { + uint64_t writes; + uint64_t bytes; + uint64_t lat_sum; + uint64_t lat_max; +}; + +static inline uint64_t now_ns(void) +{ + struct timespec ts; + + clock_gettime(CLOCK_MONOTONIC, &ts); + return (uint64_t)ts.tv_sec * 1000000000ull + (uint64_t)ts.tv_nsec; +} + +static inline int log2_bucket(uint64_t v) +{ + int b = 0; + + if (!v) + return 0; + while (v >>= 1) + b++; + return b < HIST_BUCKETS ? b : HIST_BUCKETS - 1; +} + +static void *writer(void *arg) +{ + struct wstats *s = arg; + + while (!atomic_load_explicit(&g_stop, memory_order_relaxed)) { + uint64_t t0 = now_ns(); + ssize_t n = write(g_pipe[1], s->buf, g_msgsize); + uint64_t dt = now_ns() - t0; + + if (n > 0) { + s->writes++; + s->bytes += (uint64_t)n; + s->lat_sum_ns += dt; + if (dt > s->lat_max_ns) + s->lat_max_ns = dt; + s->lat_hist[log2_bucket(dt)]++; + } else if (n < 0 && (errno == EPIPE || errno == EBADF)) { + break; + } + } + return NULL; +} + +static void *reader(void *arg) +{ + struct rstats *s = arg; + + /* + * Drain until EOF (write end closed by main). g_stop is not checked + * here on purpose: writers may be blocked in write() with the pipe + * full when g_stop is set, so the reader must keep draining until + * main closes the write end. + */ + for (;;) { + ssize_t n = read(g_pipe[0], s->buf, g_msgsize); + + if (n <= 0) + break; + } + return NULL; +} + +/* Sum per-writer stats and per-bucket counts into the caller's aggregates. */ +static void aggregate_wstats(struct wstats *all, int nw, + uint64_t agg[HIST_BUCKETS], + struct hist_totals *t) +{ + memset(t, 0, sizeof(*t)); + for (int i = 0; i < nw; i++) { + t->writes += all[i].writes; + t->bytes += all[i].bytes; + t->lat_sum += all[i].lat_sum_ns; + if (all[i].lat_max_ns > t->lat_max) + t->lat_max = all[i].lat_max_ns; + for (int b = 0; b < HIST_BUCKETS; b++) + agg[b] += all[i].lat_hist[b]; + } +} + +/* + * Walk @agg in order, returning the inclusive upper bound (in ns) of the + * log2 bucket where the running sum first reaches @target. + * + * A percentile is undefined with zero samples, and with very low sample + * counts integer truncation could make @target zero -- then "cum >= 0" + * would latch on the first (possibly empty) bucket. Callers must pass + * @target >= 1. + */ +static uint64_t bucket_at(const uint64_t agg[HIST_BUCKETS], uint64_t target) +{ + uint64_t cum = 0; + + for (int b = 0; b < HIST_BUCKETS; b++) { + /* HIST_BUCKETS <= 63, so (b + 1) is always a safe shift. */ + uint64_t upper = (1ULL << (b + 1)) - 1; + + cum += agg[b]; + if (cum >= target) + return upper; + } + return 0; +} + +static void compute_p50_p99(const uint64_t agg[HIST_BUCKETS], uint64_t writes, + uint64_t *p50, uint64_t *p99) +{ + uint64_t p50_target, p99_target; + + *p50 = *p99 = 0; + if (!writes) + return; + + p50_target = writes * 50 / 100; + p99_target = writes * 99 / 100; + if (!p50_target) + p50_target = 1; + if (!p99_target) + p99_target = 1; + + *p50 = bucket_at(agg, p50_target); + *p99 = bucket_at(agg, p99_target); +} + +static void print_summary(int nw, int nr, const struct hist_totals *t, + uint64_t p50, uint64_t p99) +{ + double sec = g_duration; + uint64_t avg_ns = t->writes ? t->lat_sum / t->writes : 0; + + printf("config: writers=%d readers=%d msgsize=%zu duration=%d pipe_size=%d memory_pressure=%s\n", + nw, nr, g_msgsize, g_duration, g_pipe_size, + g_memory_pressure ? "yes" : "no"); + printf("writes: total=%llu rate=%.0f/s\n", + (unsigned long long)t->writes, (double)t->writes / sec); + printf("throughput_MBps: %.2f\n", + ((double)t->bytes / sec) / (1024.0 * 1024.0)); + printf("lat_avg_ns: %llu\n", (unsigned long long)avg_ns); + printf("lat_p50_ns_upper: %llu\n", (unsigned long long)p50); + printf("lat_p99_ns_upper: %llu\n", (unsigned long long)p99); + printf("lat_max_ns: %llu\n", (unsigned long long)t->lat_max); +} + +static void summarize(struct wstats *all, int nw, int nr) +{ + uint64_t agg[HIST_BUCKETS] = {0}; + struct hist_totals t; + uint64_t p50, p99; + + aggregate_wstats(all, nw, agg, &t); + compute_p50_p99(agg, t.writes, &p50, &p99); + print_summary(nw, nr, &t, p50, p99); +} + +/* + * Child branch of fork(): restore SIGPIPE to default (parent ignores it), + * exec stress-ng, and on failure write the reason into @hs_wr before + * exiting. The parent observes EOF on hs_wr (closed via O_CLOEXEC) when + * exec succeeds. + */ +static void stress_ng_child(int hs_wr) __attribute__((noreturn)); +static void stress_ng_child(int hs_wr) +{ + char errbuf[256]; + + signal(SIGPIPE, SIG_DFL); + execlp("stress-ng", "stress-ng", + "--vm", "4", "--vm-bytes", "80%", + "--vm-method", "all", + (char *)NULL); + snprintf(errbuf, sizeof(errbuf), + "exec stress-ng failed: %s\n", strerror(errno)); + (void)!write(hs_wr, errbuf, strlen(errbuf)); + _exit(127); +} + +/* + * Read from the O_CLOEXEC handshake pipe. Anything readable means the + * child wrote an error before exec; EOF (n == 0) means the write-end + * closed because exec succeeded. Returns 0 on exec success, -1 if the + * child failed and was reaped. + */ +static int stress_ng_wait_handshake(int hs_rd, pid_t pid) +{ + struct pollfd pfd = { .fd = hs_rd, .events = POLLIN }; + char errbuf[256]; + int status; + int ret; + + ret = poll(&pfd, 1, 500); + if (ret <= 0) + return 0; + + ssize_t n = read(hs_rd, errbuf, sizeof(errbuf) - 1); + + if (n > 0) { + errbuf[n] = '\0'; + fputs(errbuf, stderr); + waitpid(pid, &status, 0); + return -1; + } + return 0; +} + +static pid_t spawn_stress_ng(void) +{ + int hs[2]; + pid_t pid; + + /* + * Handshake pipe: child writes one byte and _exit()s on exec + * failure. On exec success the O_CLOEXEC flag closes the write + * end, which the parent observes as EOF. This makes the "is + * stress-ng on $PATH?" check fail fast rather than silently. + */ + if (pipe2(hs, O_CLOEXEC) < 0) { + perror("pipe2"); + return -1; + } + + pid = fork(); + if (pid < 0) { + perror("fork"); + close(hs[0]); + close(hs[1]); + return -1; + } + if (pid == 0) { + close(hs[0]); + stress_ng_child(hs[1]); + } + + close(hs[1]); + if (stress_ng_wait_handshake(hs[0], pid) < 0) { + close(hs[0]); + return -1; + } + close(hs[0]); + + /* Give stress-ng a moment to map its VM regions before measuring. */ + sleep(1); + return pid; +} + +static void kill_stress_ng(pid_t pid) +{ + int status; + + if (pid <= 0) + return; + kill(pid, SIGTERM); + for (int i = 0; i < 20; i++) { + if (waitpid(pid, &status, WNOHANG) > 0) + return; + usleep(100 * 1000); + } + kill(pid, SIGKILL); + waitpid(pid, &status, 0); +} + +/* + * Allocate per-thread page-aligned buffers in main so a failed + * aligned_alloc() aborts the run before any thread starts. Workers used + * to allocate their own buffer and return NULL on failure, which left + * peers blocked in write()/read() with nobody to unblock them. + */ +static int alloc_thread_bufs(struct wstats *ws, int nw, + struct rstats *rs, int nr) +{ + for (int i = 0; i < nw; i++) { + ws[i].buf = aligned_alloc(4096, g_msgsize); + if (!ws[i].buf) { + fprintf(stderr, "writer %d: aligned_alloc(%zu) failed\n", + i, g_msgsize); + return -1; + } + memset(ws[i].buf, 0xAA, g_msgsize); + } + for (int i = 0; i < nr; i++) { + rs[i].buf = aligned_alloc(4096, g_msgsize); + if (!rs[i].buf) { + fprintf(stderr, "reader %d: aligned_alloc(%zu) failed\n", + i, g_msgsize); + return -1; + } + } + return 0; +} + +static void free_thread_bufs(struct wstats *ws, int nw, + struct rstats *rs, int nr) +{ + if (ws) + for (int i = 0; i < nw; i++) + free(ws[i].buf); + if (rs) + for (int i = 0; i < nr; i++) + free(rs[i].buf); +} + +static int start_readers(pthread_t *rt, struct rstats *rs, int nr, + int *created) +{ + for (int i = 0; i < nr; i++) { + int err = pthread_create(&rt[i], NULL, reader, &rs[i]); + + if (err) { + fprintf(stderr, "pthread_create reader %d: %s\n", + i, strerror(err)); + return -1; + } + (*created)++; + } + return 0; +} + +static int start_writers(pthread_t *wt, struct wstats *ws, int nw, + int *created) +{ + for (int i = 0; i < nw; i++) { + int err = pthread_create(&wt[i], NULL, writer, &ws[i]); + + if (err) { + fprintf(stderr, "pthread_create writer %d: %s\n", + i, strerror(err)); + return -1; + } + (*created)++; + } + return 0; +} + +static int open_bench_pipe(void) +{ + if (pipe(g_pipe) < 0) { + perror("pipe"); + return -1; + } + if (fcntl(g_pipe[1], F_SETPIPE_SZ, g_pipe_size) < 0) + perror("F_SETPIPE_SZ (continuing)"); + return 0; +} + +/* + * Normal termination: g_stop tells writers to leave the loop after the + * current write() returns. Closing the shared write-end fd means once + * the in-flight writes drain, readers see EOF and exit. Writers are not + * unblocked by EPIPE here -- g_pipe[0] stays open so readers can keep + * draining. + * + * Error path: some threads may have been created and others skipped, so + * writers could be blocked in write() with no reader making progress. + * Close both ends -- closing the read end is what delivers EPIPE to a + * blocked writer. + */ +static void stop_and_join(pthread_t *wt, int nw_created, + pthread_t *rt, int nr_created, int rc) +{ + atomic_store(&g_stop, 1); + close(g_pipe[1]); + if (rc < 0) + close(g_pipe[0]); + for (int i = 0; i < nw_created; i++) + pthread_join(wt[i], NULL); + for (int i = 0; i < nr_created; i++) + pthread_join(rt[i], NULL); + if (rc == 0) + close(g_pipe[0]); +} + +static int run_one(int nw, int nr) +{ + pthread_t *wt = NULL, *rt = NULL; + struct wstats *ws = NULL; + struct rstats *rs = NULL; + int nw_created = 0, nr_created = 0; + int rc = 0; + + atomic_store(&g_stop, 0); + + if (open_bench_pipe() < 0) + return -1; + + wt = calloc((size_t)nw, sizeof(*wt)); + rt = calloc((size_t)nr, sizeof(*rt)); + ws = calloc((size_t)nw, sizeof(*ws)); + rs = calloc((size_t)nr, sizeof(*rs)); + if (!wt || !rt || !ws || !rs) { + fprintf(stderr, "alloc failed\n"); + rc = -1; + goto teardown; + } + + if (alloc_thread_bufs(ws, nw, rs, nr) < 0) { + rc = -1; + goto teardown; + } + + if (start_readers(rt, rs, nr, &nr_created) < 0 || + start_writers(wt, ws, nw, &nw_created) < 0) { + rc = -1; + goto teardown; + } + + sleep((unsigned int)g_duration); + +teardown: + stop_and_join(wt, nw_created, rt, nr_created, rc); + + if (rc == 0) { + summarize(ws, nw, nr); + fflush(stdout); + } + + free_thread_bufs(ws, nw, rs, nr); + free(wt); + free(rt); + free(ws); + free(rs); + return rc; +} + +static void usage(const char *prog) +{ + fprintf(stderr, + "usage: %s [-w writers] [-r readers] [-s msgsize] [-d secs] [-p pipe_size] [--memory-pressure]\n" + " default: sweep writers={1,2,5} x readers={1,5,10}\n" + " --memory-pressure: spawn stress-ng (--vm 4 --vm-bytes 80%% --vm-method all) for the run\n", + prog); +} + +static int parse_args(int argc, char **argv, + int *writers_override, int *readers_override) +{ + static const struct option long_opts[] = { + {"memory-pressure", no_argument, NULL, 'M'}, + {0, 0, 0, 0}, + }; + int opt; + + while ((opt = getopt_long(argc, argv, "w:r:s:d:p:", + long_opts, NULL)) != -1) { + switch (opt) { + case 'w': + *writers_override = atoi(optarg); + break; + case 'r': + *readers_override = atoi(optarg); + break; + case 's': + g_msgsize = (size_t)atol(optarg); + break; + case 'd': + g_duration = atoi(optarg); + break; + case 'p': + g_pipe_size = atoi(optarg); + break; + case 'M': + g_memory_pressure = 1; + break; + default: + usage(argv[0]); + return -1; + } + } + return 0; +} + +/* + * aligned_alloc(4096, size) requires size to be a multiple of the + * alignment (C11); glibc returns NULL otherwise, which would make + * writer/reader threads silently exit and the run report zero writes. + * Validate up front instead. + */ +static int validate_args(void) +{ + if (g_msgsize == 0 || g_msgsize % 4096 != 0) { + fprintf(stderr, + "msgsize must be a positive multiple of 4096 (got %zu)\n", + g_msgsize); + return -1; + } + if (g_duration <= 0) { + fprintf(stderr, "duration must be > 0 seconds (got %d)\n", + g_duration); + return -1; + } + if (g_pipe_size <= 0) { + fprintf(stderr, "pipe_size must be > 0 bytes (got %d)\n", + g_pipe_size); + return -1; + } + return 0; +} + +static int run_sweep(void) +{ + static const int writers_sweep[] = {1, 2, 5}; + static const int readers_sweep[] = {1, 5, 10}; + + for (size_t i = 0; i < ARRAY_SIZE(writers_sweep); i++) { + for (size_t j = 0; j < ARRAY_SIZE(readers_sweep); j++) { + printf("---\n"); + if (run_one(writers_sweep[i], readers_sweep[j]) < 0) + return -1; + } + } + return 0; +} + +int main(int argc, char **argv) +{ + int writers_override = 0, readers_override = 0; + pid_t stress_pid = -1; + int rc = 0; + + if (parse_args(argc, argv, &writers_override, &readers_override) < 0) + return 1; + if (validate_args() < 0) + return 1; + + signal(SIGPIPE, SIG_IGN); + setvbuf(stdout, NULL, _IOLBF, 0); + setvbuf(stderr, NULL, _IOLBF, 0); + + fprintf(stderr, "pid=%d\n", getpid()); + fflush(stderr); + + if (g_memory_pressure) { + stress_pid = spawn_stress_ng(); + if (stress_pid < 0) { + fprintf(stderr, + "memory_pressure requested but stress-ng could not be spawned\n"); + return 1; + } + } + + if (writers_override > 0 || readers_override > 0) { + int nw = writers_override > 0 ? writers_override : 1; + int nr = readers_override > 0 ? readers_override : 1; + + rc = run_one(nw, nr) < 0 ? 1 : 0; + } else { + rc = run_sweep() < 0 ? 1 : 0; + } + + kill_stress_ng(stress_pid); + return rc; +} From 00633c4683828acd5256fa8d5163f440d74bbe71 Mon Sep 17 00:00:00 2001 From: Mingyu Wang <25181214217@stu.xidian.edu.cn> Date: Sat, 23 May 2026 21:52:10 +0800 Subject: [PATCH 63/73] fs/fcntl: fix SOFTIRQ-unsafe lock order in fasync signaling A SOFTIRQ-safe to SOFTIRQ-unsafe lock order deadlock can occur in send_sigio() and send_sigurg() when a process group receives a signal. When FASYNC is configured for a process group (PIDTYPE_PGID), both functions use read_lock(&tasklist_lock) to traverse the task list. However, they are frequently called from softirq context: - send_sigio() via input_inject_event -> kill_fasync - send_sigurg() via tcp_check_urg -> sk_send_sigurg (NET_RX_SOFTIRQ) The deadlock is caused by the rwlock writer fairness mechanism: 1. CPU 0 (process context) holds read_lock(&tasklist_lock) in do_wait(). 2. CPU 1 (process context) attempts write_lock(&tasklist_lock) in fork() or exit() and spins, which blocks all new readers. 3. CPU 0 is interrupted by a softirq (e.g., TCP URG packet reception). 4. The softirq calls send_sigurg() and attempts to acquire read_lock(&tasklist_lock), deadlocking because CPU 1 is waiting. Since PID hashing and do_each_pid_task() traversals are already RCU-protected, the read_lock on tasklist_lock is no longer strictly required for safe traversal. Fix this by replacing tasklist_lock with rcu_read_lock(), aligning the process group signaling path with the single-PID path. This also mitigates a potential remote denial of service vector via TCP URG packets. Lockdep splat: ===================================================== WARNING: SOFTIRQ-safe -> SOFTIRQ-unsafe lock order detected [...] Chain exists of: &dev->event_lock --> &f_owner->lock --> tasklist_lock Possible interrupt unsafe locking scenario: CPU0 CPU1 ---- ---- lock(tasklist_lock); local_irq_disable(); lock(&dev->event_lock); lock(&f_owner->lock); lock(&dev->event_lock); *** DEADLOCK *** Reviewed-by: Jeff Layton Signed-off-by: Mingyu Wang <25181214217@stu.xidian.edu.cn> Link: https://patch.msgid.link/20260523135210.590928-1-w15303746062@163.com Signed-off-by: Christian Brauner (Amutable) --- fs/fcntl.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/fs/fcntl.c b/fs/fcntl.c index beab8080badf..92d643a14196 100644 --- a/fs/fcntl.c +++ b/fs/fcntl.c @@ -929,11 +929,11 @@ void send_sigio(struct fown_struct *fown, int fd, int band) send_sigio_to_task(p, fown, fd, band, type); rcu_read_unlock(); } else { - read_lock(&tasklist_lock); + rcu_read_lock(); do_each_pid_task(pid, type, p) { send_sigio_to_task(p, fown, fd, band, type); } while_each_pid_task(pid, type, p); - read_unlock(&tasklist_lock); + rcu_read_unlock(); } out_unlock_fown: read_unlock_irqrestore(&fown->lock, flags); @@ -975,11 +975,11 @@ int send_sigurg(struct file *file) send_sigurg_to_task(p, fown, type); rcu_read_unlock(); } else { - read_lock(&tasklist_lock); + rcu_read_lock(); do_each_pid_task(pid, type, p) { send_sigurg_to_task(p, fown, type); } while_each_pid_task(pid, type, p); - read_unlock(&tasklist_lock); + rcu_read_unlock(); } out_unlock_fown: read_unlock_irqrestore(&fown->lock, flags); From 6dd3c6884cd9defb511284b566cef5ac8f657dbf Mon Sep 17 00:00:00 2001 From: Al Viro Date: Tue, 2 Jun 2026 03:04:44 +0100 Subject: [PATCH 64/73] mount: honour SB_NOUSER in the new mount API One should *not* be allowed to mount one of those, new API or not. Reported-by: Denis Arefev Signed-off-by: Al Viro Link: https://patch.msgid.link/20260602020444.GP2636677@ZenIV Signed-off-by: Christian Brauner (Amutable) --- fs/namespace.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/fs/namespace.c b/fs/namespace.c index d67c2f61b3df..71ae1e9a1266 100644 --- a/fs/namespace.c +++ b/fs/namespace.c @@ -4498,6 +4498,10 @@ SYSCALL_DEFINE3(fsmount, int, fs_fd, unsigned int, flags, new_mnt = vfs_create_mount(fc); if (IS_ERR(new_mnt)) return PTR_ERR(new_mnt); + if (new_mnt->mnt_sb->s_flags & SB_NOUSER) { + mntput(new_mnt); + return -EINVAL; + } new_mnt->mnt_flags = mnt_flags; new_path.dentry = dget(fc->root); From 6de2aeffabaafaeda819e60ec8d04f199711e11a Mon Sep 17 00:00:00 2001 From: John Hubbard Date: Wed, 3 Jun 2026 19:53:14 -0700 Subject: [PATCH 65/73] libfs: set SB_I_NOEXEC and SB_I_NODEV by default in init_pseudo() Since commit 1e7ab6f67824 ("anon_inode: rework assertions"), path_noexec() warns when an anonymous-inode file is mmap'd from a superblock that has not set SB_I_NOEXEC. dma-buf backs its files this way and never set the flag, so mmap of any exported buffer trips the warning on a CONFIG_DEBUG_VFS=y kernel: WARNING: CPU: 11 PID: 121813 at fs/exec.c:118 path_noexec+0x47/0x50 do_mmap+0x2b5/0x680 vm_mmap_pgoff+0x129/0x210 ksys_mmap_pgoff+0x177/0x240 __x64_sys_mmap+0x33/0x70 init_pseudo() sets up internal SB_NOUSER mounts that are never path-reachable. Set both flags here so every pseudo filesystem gets them by default instead of each caller setting them. SB_I_NODEV is inert for unreachable mounts. SB_I_NOEXEC has one visible effect: an executable mapping of a pseudo-fs fd, such as a dma-buf, now fails with -EPERM, which is the invariant the assertion enforces. No in-tree caller maps these executable. Reproduce on CONFIG_DEBUG_VFS=y: make -C tools/testing/selftests/dmabuf-heaps sudo ./tools/testing/selftests/dmabuf-heaps/dmabuf-heap -t system Fixes: 1e7ab6f67824 ("anon_inode: rework assertions") Suggested-by: Christoph Hellwig Cc: stable@vger.kernel.org Signed-off-by: John Hubbard Link: https://patch.msgid.link/20260604025315.245910-2-jhubbard@nvidia.com Signed-off-by: Christian Brauner (Amutable) --- fs/libfs.c | 1 + 1 file changed, 1 insertion(+) diff --git a/fs/libfs.c b/fs/libfs.c index 80a330c8296f..124139645f7f 100644 --- a/fs/libfs.c +++ b/fs/libfs.c @@ -736,6 +736,7 @@ struct pseudo_fs_context *init_pseudo(struct fs_context *fc, fc->fs_private = ctx; fc->ops = &pseudo_fs_context_ops; fc->sb_flags |= SB_NOUSER; + fc->s_iflags |= SB_I_NOEXEC | SB_I_NODEV; fc->global = true; } return ctx; From be5748d2ae03907918298cc355bea73aed98ebc0 Mon Sep 17 00:00:00 2001 From: John Hubbard Date: Wed, 3 Jun 2026 19:53:15 -0700 Subject: [PATCH 66/73] libfs: drop redundant SB_I_NOEXEC/SB_I_NODEV in init_pseudo() callers init_pseudo() now sets SB_I_NOEXEC and SB_I_NODEV by default, so the per-caller assignments are redundant. Drop them. Signed-off-by: John Hubbard Link: https://patch.msgid.link/20260604025315.245910-3-jhubbard@nvidia.com Signed-off-by: Christian Brauner (Amutable) --- fs/aio.c | 1 - fs/anon_inodes.c | 2 -- fs/nsfs.c | 1 - fs/pidfs.c | 2 -- mm/secretmem.c | 2 -- virt/kvm/guest_memfd.c | 2 -- 6 files changed, 10 deletions(-) diff --git a/fs/aio.c b/fs/aio.c index 722476560848..f57fa21a2503 100644 --- a/fs/aio.c +++ b/fs/aio.c @@ -318,7 +318,6 @@ static int aio_init_fs_context(struct fs_context *fc) pfc = init_pseudo(fc, AIO_RING_MAGIC); if (!pfc) return -ENOMEM; - fc->s_iflags |= SB_I_NOEXEC; pfc->ops = &aio_super_operations; return 0; } diff --git a/fs/anon_inodes.c b/fs/anon_inodes.c index b8381c7fb636..a7b9b948e33d 100644 --- a/fs/anon_inodes.c +++ b/fs/anon_inodes.c @@ -86,8 +86,6 @@ static int anon_inodefs_init_fs_context(struct fs_context *fc) struct pseudo_fs_context *ctx = init_pseudo(fc, ANON_INODE_FS_MAGIC); if (!ctx) return -ENOMEM; - fc->s_iflags |= SB_I_NOEXEC; - fc->s_iflags |= SB_I_NODEV; ctx->dops = &anon_inodefs_dentry_operations; return 0; } diff --git a/fs/nsfs.c b/fs/nsfs.c index 51e8c9430477..c43c127cc035 100644 --- a/fs/nsfs.c +++ b/fs/nsfs.c @@ -664,7 +664,6 @@ static int nsfs_init_fs_context(struct fs_context *fc) struct pseudo_fs_context *ctx = init_pseudo(fc, NSFS_MAGIC); if (!ctx) return -ENOMEM; - fc->s_iflags |= SB_I_NOEXEC | SB_I_NODEV; ctx->s_d_flags |= DCACHE_DONTCACHE; ctx->ops = &nsfs_ops; ctx->eops = &nsfs_export_operations; diff --git a/fs/pidfs.c b/fs/pidfs.c index 1cce4f34a051..c363416766f1 100644 --- a/fs/pidfs.c +++ b/fs/pidfs.c @@ -1115,8 +1115,6 @@ static int pidfs_init_fs_context(struct fs_context *fc) if (!ctx) return -ENOMEM; - fc->s_iflags |= SB_I_NOEXEC; - fc->s_iflags |= SB_I_NODEV; ctx->s_d_flags |= DCACHE_DONTCACHE; ctx->ops = &pidfs_sops; ctx->eops = &pidfs_export_operations; diff --git a/mm/secretmem.c b/mm/secretmem.c index 5f57ac4720d3..4877c262cb1f 100644 --- a/mm/secretmem.c +++ b/mm/secretmem.c @@ -245,8 +245,6 @@ static int secretmem_init_fs_context(struct fs_context *fc) if (!ctx) return -ENOMEM; - fc->s_iflags |= SB_I_NOEXEC; - fc->s_iflags |= SB_I_NODEV; return 0; } diff --git a/virt/kvm/guest_memfd.c b/virt/kvm/guest_memfd.c index 69c9d6d546b2..80f201035d77 100644 --- a/virt/kvm/guest_memfd.c +++ b/virt/kvm/guest_memfd.c @@ -973,8 +973,6 @@ static int kvm_gmem_init_fs_context(struct fs_context *fc) if (!init_pseudo(fc, GUEST_MEMFD_MAGIC)) return -ENOMEM; - fc->s_iflags |= SB_I_NOEXEC; - fc->s_iflags |= SB_I_NODEV; ctx = fc->fs_private; ctx->ops = &kvm_gmem_super_operations; From 1a571226cb82503839c6c284d3c7a172c93f19c4 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Thu, 4 Jun 2026 11:52:02 +0200 Subject: [PATCH 67/73] fs/read_write: Do not export __kernel_write() to the entire world Since we have EXPORT_SYMBOL_FOR_MODULES(), we may narrow the __kernel_write() export to the only which really needs it. With that being done, update the respective comment. Signed-off-by: Andy Shevchenko Link: https://patch.msgid.link/20260604095233.284067-1-andriy.shevchenko@linux.intel.com Signed-off-by: Christian Brauner (Amutable) --- fs/read_write.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/fs/read_write.c b/fs/read_write.c index 50bff7edc91f..e8c14e2760b2 100644 --- a/fs/read_write.c +++ b/fs/read_write.c @@ -641,13 +641,12 @@ ssize_t __kernel_write(struct file *file, const void *buf, size_t count, loff_t return __kernel_write_iter(file, &iter, pos); } /* - * This "EXPORT_SYMBOL_GPL()" is more of a "EXPORT_SYMBOL_DONTUSE()", - * but autofs is one of the few internal kernel users that actually + * autofs is one of the few internal kernel users that actually * wants this _and_ can be built as a module. So we need to export * this symbol for autofs, even though it really isn't appropriate * for any other kernel modules. */ -EXPORT_SYMBOL_GPL(__kernel_write); +EXPORT_SYMBOL_FOR_MODULES(__kernel_write, "autofs4"); ssize_t kernel_write(struct file *file, const void *buf, size_t count, loff_t *pos) From 9af8c8a54f6ef1ec8e97836e456827dd5161b355 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Tue, 26 May 2026 14:02:22 +0200 Subject: [PATCH 68/73] bpf: add bpf_real_inode() kfunc Add a sleepable BPF kfunc that resolves the real inode backing a dentry via d_real_inode(). On overlay/union filesystems the inode attached to the dentry is the overlay inode which does not carry the underlying device information. d_real_inode() resolves through the overlay and returns the inode from the lower, real filesystem. This is used in the RestrictFilesytemAccess bpf program that has been merged into systemd a little while ago. Link: https://github.com/systemd/systemd/pull/41340 [1] Link: https://patch.msgid.link/20260526-work-bpf-verity-v2-1-cd0b1850d31b@kernel.org Reviewed-by: Amir Goldstein Signed-off-by: Christian Brauner (Amutable) --- fs/bpf_fs_kfuncs.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/fs/bpf_fs_kfuncs.c b/fs/bpf_fs_kfuncs.c index e4e51a1d0de2..761fbe0fec5d 100644 --- a/fs/bpf_fs_kfuncs.c +++ b/fs/bpf_fs_kfuncs.c @@ -353,6 +353,21 @@ __bpf_kfunc int bpf_cgroup_read_xattr(struct cgroup *cgroup, const char *name__s } #endif /* CONFIG_CGROUPS */ +/** + * bpf_real_inode - get the real inode backing a dentry + * @dentry: dentry to resolve + * + * If the dentry is on a union/overlay filesystem, return the underlying, real + * inode that hosts the data. Otherwise return the inode attached to the + * dentry itself. + * + * Return: The real inode backing the dentry, or NULL for a negative dentry. + */ +__bpf_kfunc struct inode *bpf_real_inode(struct dentry *dentry) +{ + return d_real_inode(dentry); +} + __bpf_kfunc_end_defs(); BTF_KFUNCS_START(bpf_fs_kfunc_set_ids) @@ -363,6 +378,7 @@ BTF_ID_FLAGS(func, bpf_get_dentry_xattr, KF_SLEEPABLE) BTF_ID_FLAGS(func, bpf_get_file_xattr, KF_SLEEPABLE) BTF_ID_FLAGS(func, bpf_set_dentry_xattr, KF_SLEEPABLE) BTF_ID_FLAGS(func, bpf_remove_dentry_xattr, KF_SLEEPABLE) +BTF_ID_FLAGS(func, bpf_real_inode, KF_SLEEPABLE | KF_RET_NULL) BTF_KFUNCS_END(bpf_fs_kfunc_set_ids) static int bpf_fs_kfuncs_filter(const struct bpf_prog *prog, u32 kfunc_id) From 0da79c259ad0554b36761a7135d4f92eb7c46263 Mon Sep 17 00:00:00 2001 From: Jori Koolstra Date: Fri, 5 Jun 2026 00:24:05 +0200 Subject: [PATCH 69/73] vfs: uapi: retire octal and hex numbers in favor of (1 << n) for O_ flags A recent build failure[1] exposed the diffculty of working with the current octal and hex definitions of O_ flags when trying to find a gap for a new flag. This difficulty is compounded by the fact that O_ flags may have architectural specific values. Replace the hex/octal #defines, which are hard to parse when looking for free bits, with explicit bit shifts like (1 << 11). Also, add comments that identify which architectures redefine some of the seemingly free ("cursed") bits in uapi/asm-generic/fcntl.h. These should not be used to define new O_ flags (for now, at least). The translastion was done with Claude Opus 4.8, and verified with a (non-AI) gawk script. The accounting of which architectures claim which bit-gaps in uapi/asm-generic/fcntl.h is also done by hand. [1]: https://lore.kernel.org/all/agruPPybCx8q2XcJ@sirena.org.uk/ Assisted-by: Claude:Opus 4.8 Signed-off-by: Jori Koolstra Link: https://patch.msgid.link/20260604222405.5382-1-jkoolstra@xs4all.nl Signed-off-by: Christian Brauner (Amutable) --- arch/alpha/include/uapi/asm/fcntl.h | 32 ++++++++--------- arch/arm/include/uapi/asm/fcntl.h | 8 ++--- arch/arm64/include/uapi/asm/fcntl.h | 8 ++--- arch/m68k/include/uapi/asm/fcntl.h | 8 ++--- arch/mips/include/uapi/asm/fcntl.h | 22 ++++++------ arch/parisc/include/uapi/asm/fcntl.h | 28 +++++++-------- arch/powerpc/include/uapi/asm/fcntl.h | 8 ++--- arch/sparc/include/uapi/asm/fcntl.h | 34 +++++++++--------- include/uapi/asm-generic/fcntl.h | 50 ++++++++++++++++----------- 9 files changed, 103 insertions(+), 95 deletions(-) diff --git a/arch/alpha/include/uapi/asm/fcntl.h b/arch/alpha/include/uapi/asm/fcntl.h index 50bdc8e8a271..c7e1c5cf646d 100644 --- a/arch/alpha/include/uapi/asm/fcntl.h +++ b/arch/alpha/include/uapi/asm/fcntl.h @@ -2,20 +2,20 @@ #ifndef _ALPHA_FCNTL_H #define _ALPHA_FCNTL_H -#define O_CREAT 01000 /* not fcntl */ -#define O_TRUNC 02000 /* not fcntl */ -#define O_EXCL 04000 /* not fcntl */ -#define O_NOCTTY 010000 /* not fcntl */ +#define O_CREAT (1 << 9) /* not fcntl */ +#define O_TRUNC (1 << 10) /* not fcntl */ +#define O_EXCL (1 << 11) /* not fcntl */ +#define O_NOCTTY (1 << 12) /* not fcntl */ -#define O_NONBLOCK 00004 -#define O_APPEND 00010 -#define O_DSYNC 040000 /* used to be O_SYNC, see below */ -#define O_DIRECTORY 0100000 /* must be a directory */ -#define O_NOFOLLOW 0200000 /* don't follow links */ -#define O_LARGEFILE 0400000 /* will be set by the kernel on every open */ -#define O_DIRECT 02000000 /* direct disk access - should check with OSF/1 */ -#define O_NOATIME 04000000 -#define O_CLOEXEC 010000000 /* set close_on_exec */ +#define O_NONBLOCK (1 << 2) +#define O_APPEND (1 << 3) +#define O_DSYNC (1 << 14) /* used to be O_SYNC, see below */ +#define O_DIRECTORY (1 << 15) /* must be a directory */ +#define O_NOFOLLOW (1 << 16) /* don't follow links */ +#define O_LARGEFILE (1 << 17) /* will be set by the kernel on every open */ +#define O_DIRECT (1 << 19) /* direct disk access - should check with OSF/1 */ +#define O_NOATIME (1 << 20) +#define O_CLOEXEC (1 << 21) /* set close_on_exec */ /* * Before Linux 2.6.33 only O_DSYNC semantics were implemented, but using * the O_SYNC flag. We continue to use the existing numerical value @@ -29,11 +29,11 @@ * * Note: __O_SYNC must never be used directly. */ -#define __O_SYNC 020000000 +#define __O_SYNC (1 << 22) #define O_SYNC (__O_SYNC|O_DSYNC) -#define O_PATH 040000000 -#define __O_TMPFILE 0100000000 +#define O_PATH (1 << 23) +#define __O_TMPFILE (1 << 24) #define F_GETLK 7 #define F_SETLK 8 diff --git a/arch/arm/include/uapi/asm/fcntl.h b/arch/arm/include/uapi/asm/fcntl.h index e6b5d7141c05..b576ff00beb2 100644 --- a/arch/arm/include/uapi/asm/fcntl.h +++ b/arch/arm/include/uapi/asm/fcntl.h @@ -2,10 +2,10 @@ #ifndef _ARM_FCNTL_H #define _ARM_FCNTL_H -#define O_DIRECTORY 040000 /* must be a directory */ -#define O_NOFOLLOW 0100000 /* don't follow links */ -#define O_DIRECT 0200000 /* direct disk access hint - currently ignored */ -#define O_LARGEFILE 0400000 +#define O_DIRECTORY (1 << 14) /* must be a directory */ +#define O_NOFOLLOW (1 << 15) /* don't follow links */ +#define O_DIRECT (1 << 16) /* direct disk access hint - currently ignored */ +#define O_LARGEFILE (1 << 17) #include diff --git a/arch/arm64/include/uapi/asm/fcntl.h b/arch/arm64/include/uapi/asm/fcntl.h index f8db34f2622d..e503fdb74ecb 100644 --- a/arch/arm64/include/uapi/asm/fcntl.h +++ b/arch/arm64/include/uapi/asm/fcntl.h @@ -20,10 +20,10 @@ /* * Using our own definitions for AArch32 (compat) support. */ -#define O_DIRECTORY 040000 /* must be a directory */ -#define O_NOFOLLOW 0100000 /* don't follow links */ -#define O_DIRECT 0200000 /* direct disk access hint - currently ignored */ -#define O_LARGEFILE 0400000 +#define O_DIRECTORY (1 << 14) /* must be a directory */ +#define O_NOFOLLOW (1 << 15) /* don't follow links */ +#define O_DIRECT (1 << 16) /* direct disk access hint - currently ignored */ +#define O_LARGEFILE (1 << 17) #include diff --git a/arch/m68k/include/uapi/asm/fcntl.h b/arch/m68k/include/uapi/asm/fcntl.h index c6861e6ee313..66c0e5515105 100644 --- a/arch/m68k/include/uapi/asm/fcntl.h +++ b/arch/m68k/include/uapi/asm/fcntl.h @@ -2,10 +2,10 @@ #ifndef _M68K_FCNTL_H #define _M68K_FCNTL_H -#define O_DIRECTORY 040000 /* must be a directory */ -#define O_NOFOLLOW 0100000 /* don't follow links */ -#define O_DIRECT 0200000 /* direct disk access hint - currently ignored */ -#define O_LARGEFILE 0400000 +#define O_DIRECTORY (1 << 14) /* must be a directory */ +#define O_NOFOLLOW (1 << 15) /* don't follow links */ +#define O_DIRECT (1 << 16) /* direct disk access hint - currently ignored */ +#define O_LARGEFILE (1 << 17) #include diff --git a/arch/mips/include/uapi/asm/fcntl.h b/arch/mips/include/uapi/asm/fcntl.h index 0369a38e3d4f..549fc65d849d 100644 --- a/arch/mips/include/uapi/asm/fcntl.h +++ b/arch/mips/include/uapi/asm/fcntl.h @@ -11,15 +11,15 @@ #include -#define O_APPEND 0x0008 -#define O_DSYNC 0x0010 /* used to be O_SYNC, see below */ -#define O_NONBLOCK 0x0080 -#define O_CREAT 0x0100 /* not fcntl */ -#define O_TRUNC 0x0200 /* not fcntl */ -#define O_EXCL 0x0400 /* not fcntl */ -#define O_NOCTTY 0x0800 /* not fcntl */ -#define FASYNC 0x1000 /* fcntl, for BSD compatibility */ -#define O_LARGEFILE 0x2000 /* allow large file opens */ +#define O_APPEND (1 << 3) +#define O_DSYNC (1 << 4) /* used to be O_SYNC, see below */ +#define O_NONBLOCK (1 << 7) +#define O_CREAT (1 << 8) /* not fcntl */ +#define O_TRUNC (1 << 9) /* not fcntl */ +#define O_EXCL (1 << 10) /* not fcntl */ +#define O_NOCTTY (1 << 11) /* not fcntl */ +#define FASYNC (1 << 12) /* fcntl, for BSD compatibility */ +#define O_LARGEFILE (1 << 13) /* allow large file opens */ /* * Before Linux 2.6.33 only O_DSYNC semantics were implemented, but using * the O_SYNC flag. We continue to use the existing numerical value @@ -33,9 +33,9 @@ * * Note: __O_SYNC must never be used directly. */ -#define __O_SYNC 0x4000 +#define __O_SYNC (1 << 14) #define O_SYNC (__O_SYNC|O_DSYNC) -#define O_DIRECT 0x8000 /* direct disk access hint */ +#define O_DIRECT (1 << 15) /* direct disk access hint */ #define F_GETLK 14 #define F_SETLK 6 diff --git a/arch/parisc/include/uapi/asm/fcntl.h b/arch/parisc/include/uapi/asm/fcntl.h index 03dee816cb13..2e1bb18eefb8 100644 --- a/arch/parisc/include/uapi/asm/fcntl.h +++ b/arch/parisc/include/uapi/asm/fcntl.h @@ -2,23 +2,23 @@ #ifndef _PARISC_FCNTL_H #define _PARISC_FCNTL_H -#define O_APPEND 000000010 -#define O_CREAT 000000400 /* not fcntl */ -#define O_EXCL 000002000 /* not fcntl */ -#define O_LARGEFILE 000004000 -#define __O_SYNC 000100000 +#define O_APPEND (1 << 3) +#define O_CREAT (1 << 8) /* not fcntl */ +#define O_EXCL (1 << 10) /* not fcntl */ +#define O_LARGEFILE (1 << 11) +#define __O_SYNC (1 << 15) #define O_SYNC (__O_SYNC|O_DSYNC) -#define O_NONBLOCK 000200000 -#define O_NOCTTY 000400000 /* not fcntl */ -#define O_DSYNC 001000000 -#define O_NOATIME 004000000 -#define O_CLOEXEC 010000000 /* set close_on_exec */ +#define O_NONBLOCK (1 << 16) +#define O_NOCTTY (1 << 17) /* not fcntl */ +#define O_DSYNC (1 << 18) +#define O_NOATIME (1 << 20) +#define O_CLOEXEC (1 << 21) /* set close_on_exec */ -#define O_DIRECTORY 000010000 /* must be a directory */ -#define O_NOFOLLOW 000000200 /* don't follow links */ +#define O_DIRECTORY (1 << 12) /* must be a directory */ +#define O_NOFOLLOW (1 << 7) /* don't follow links */ -#define O_PATH 020000000 -#define __O_TMPFILE 040000000 +#define O_PATH (1 << 22) +#define __O_TMPFILE (1 << 23) #define F_GETLK64 8 #define F_SETLK64 9 diff --git a/arch/powerpc/include/uapi/asm/fcntl.h b/arch/powerpc/include/uapi/asm/fcntl.h index 65ce08322a89..003bc5ea78e1 100644 --- a/arch/powerpc/include/uapi/asm/fcntl.h +++ b/arch/powerpc/include/uapi/asm/fcntl.h @@ -2,10 +2,10 @@ #ifndef _ASM_FCNTL_H #define _ASM_FCNTL_H -#define O_DIRECTORY 040000 /* must be a directory */ -#define O_NOFOLLOW 0100000 /* don't follow links */ -#define O_LARGEFILE 0200000 -#define O_DIRECT 0400000 /* direct disk access hint */ +#define O_DIRECTORY (1 << 14) /* must be a directory */ +#define O_NOFOLLOW (1 << 15) /* don't follow links */ +#define O_LARGEFILE (1 << 16) +#define O_DIRECT (1 << 17) /* direct disk access hint */ #include diff --git a/arch/sparc/include/uapi/asm/fcntl.h b/arch/sparc/include/uapi/asm/fcntl.h index 67dae75e5274..29c5639bc3fa 100644 --- a/arch/sparc/include/uapi/asm/fcntl.h +++ b/arch/sparc/include/uapi/asm/fcntl.h @@ -2,23 +2,23 @@ #ifndef _SPARC_FCNTL_H #define _SPARC_FCNTL_H -#define O_APPEND 0x0008 -#define FASYNC 0x0040 /* fcntl, for BSD compatibility */ -#define O_CREAT 0x0200 /* not fcntl */ -#define O_TRUNC 0x0400 /* not fcntl */ -#define O_EXCL 0x0800 /* not fcntl */ -#define O_DSYNC 0x2000 /* used to be O_SYNC, see below */ -#define O_NONBLOCK 0x4000 +#define O_APPEND (1 << 3) +#define FASYNC (1 << 6) /* fcntl, for BSD compatibility */ +#define O_CREAT (1 << 9) /* not fcntl */ +#define O_TRUNC (1 << 10) /* not fcntl */ +#define O_EXCL (1 << 11) /* not fcntl */ +#define O_DSYNC (1 << 13) /* used to be O_SYNC, see below */ +#define O_NONBLOCK (1 << 14) #if defined(__sparc__) && defined(__arch64__) -#define O_NDELAY 0x0004 +#define O_NDELAY (1 << 2) #else -#define O_NDELAY (0x0004 | O_NONBLOCK) +#define O_NDELAY ((1 << 2) | O_NONBLOCK) #endif -#define O_NOCTTY 0x8000 /* not fcntl */ -#define O_LARGEFILE 0x40000 -#define O_DIRECT 0x100000 /* direct disk access hint */ -#define O_NOATIME 0x200000 -#define O_CLOEXEC 0x400000 +#define O_NOCTTY (1 << 15) /* not fcntl */ +#define O_LARGEFILE (1 << 18) +#define O_DIRECT (1 << 20) /* direct disk access hint */ +#define O_NOATIME (1 << 21) +#define O_CLOEXEC (1 << 22) /* * Before Linux 2.6.33 only O_DSYNC semantics were implemented, but using * the O_SYNC flag. We continue to use the existing numerical value @@ -32,11 +32,11 @@ * * Note: __O_SYNC must never be used directly. */ -#define __O_SYNC 0x800000 +#define __O_SYNC (1 << 23) #define O_SYNC (__O_SYNC|O_DSYNC) -#define O_PATH 0x1000000 -#define __O_TMPFILE 0x2000000 +#define O_PATH (1 << 24) +#define __O_TMPFILE (1 << 25) #define F_GETOWN 5 /* for sockets. */ #define F_SETOWN 6 /* for sockets. */ diff --git a/include/uapi/asm-generic/fcntl.h b/include/uapi/asm-generic/fcntl.h index 613475285643..359622b083d5 100644 --- a/include/uapi/asm-generic/fcntl.h +++ b/include/uapi/asm-generic/fcntl.h @@ -15,51 +15,55 @@ * When introducing new O_* bits, please check its uniqueness in fcntl_init(). */ -#define O_ACCMODE 00000003 -#define O_RDONLY 00000000 -#define O_WRONLY 00000001 -#define O_RDWR 00000002 +#define O_ACCMODE 3 +#define O_RDONLY 0 +#define O_WRONLY (1 << 0) +#define O_RDWR (1 << 1) +/* (1 << 2) must not be used -- it collides with flags on alpha, sparc */ +/* (1 << 3) must not be used -- it collides with flags on alpha, mips, parisc, sparc */ +/* (1 << 4) must not be used -- it collides with flags on mips */ +/* (1 << 5) is free */ #ifndef O_CREAT -#define O_CREAT 00000100 /* not fcntl */ +#define O_CREAT (1 << 6) /* not fcntl */ #endif #ifndef O_EXCL -#define O_EXCL 00000200 /* not fcntl */ +#define O_EXCL (1 << 7) /* not fcntl */ #endif #ifndef O_NOCTTY -#define O_NOCTTY 00000400 /* not fcntl */ +#define O_NOCTTY (1 << 8) /* not fcntl */ #endif #ifndef O_TRUNC -#define O_TRUNC 00001000 /* not fcntl */ +#define O_TRUNC (1 << 9) /* not fcntl */ #endif #ifndef O_APPEND -#define O_APPEND 00002000 +#define O_APPEND (1 << 10) #endif #ifndef O_NONBLOCK -#define O_NONBLOCK 00004000 +#define O_NONBLOCK (1 << 11) #endif #ifndef O_DSYNC -#define O_DSYNC 00010000 /* used to be O_SYNC, see below */ +#define O_DSYNC (1 << 12) /* used to be O_SYNC, see below */ #endif #ifndef FASYNC -#define FASYNC 00020000 /* fcntl, for BSD compatibility */ +#define FASYNC (1 << 13) /* fcntl, for BSD compatibility */ #endif #ifndef O_DIRECT -#define O_DIRECT 00040000 /* direct disk access hint */ +#define O_DIRECT (1 << 14) /* direct disk access hint */ #endif #ifndef O_LARGEFILE -#define O_LARGEFILE 00100000 +#define O_LARGEFILE (1 << 15) #endif #ifndef O_DIRECTORY -#define O_DIRECTORY 00200000 /* must be a directory */ +#define O_DIRECTORY (1 << 16) /* must be a directory */ #endif #ifndef O_NOFOLLOW -#define O_NOFOLLOW 00400000 /* don't follow links */ +#define O_NOFOLLOW (1 << 17) /* don't follow links */ #endif #ifndef O_NOATIME -#define O_NOATIME 01000000 +#define O_NOATIME (1 << 18) #endif #ifndef O_CLOEXEC -#define O_CLOEXEC 02000000 /* set close_on_exec */ +#define O_CLOEXEC (1 << 19) /* set close_on_exec */ #endif /* @@ -76,16 +80,16 @@ * Note: __O_SYNC must never be used directly. */ #ifndef O_SYNC -#define __O_SYNC 04000000 +#define __O_SYNC (1 << 20) #define O_SYNC (__O_SYNC|O_DSYNC) #endif #ifndef O_PATH -#define O_PATH 010000000 +#define O_PATH (1 << 21) #endif #ifndef __O_TMPFILE -#define __O_TMPFILE 020000000 +#define __O_TMPFILE (1 << 22) #endif /* a horrid kludge trying to make sure that this will fail on old kernels */ @@ -95,6 +99,10 @@ #define O_NDELAY O_NONBLOCK #endif +/* (1 << 23) must not be used -- it collides with flags on alpha, parisc, sparc */ +/* (1 << 24) must not be used -- it collides with flags on alpha, sparc */ +/* (1 << 25) must not be used -- it collides with flags on sparc */ + #define F_DUPFD 0 /* dup */ #define F_GETFD 1 /* get close_on_exec */ #define F_SETFD 2 /* set/clear close_on_exec */ From 4bbcff264b678859cc404669bd145bcd6819804b Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Sun, 7 Jun 2026 11:40:28 +0200 Subject: [PATCH 70/73] filelock: fix break_lease() stub signature for CONFIG_FILE_LOCKING=n The CONFIG_FILE_LOCKING=n stub for break_lease() takes a 'bool wait' argument, whereas the CONFIG_FILE_LOCKING=y version and every caller pass an openmode as an 'unsigned int mode'. The mismatch was introduced when __break_lease() was reworked to use flags: only the stub was switched to 'bool wait', a stray leftover from the neighbouring break_layout() helper. The real prototype kept 'unsigned int mode'. This was harmless until O_WRONLY changed from the octal literal 00000001 to (1 << 0). clang's -Wtautological-constant-compare then fires on the implicit shift-to-bool conversion at the first FILE_LOCKING=n caller: fs/open.c:112:29: warning: converting the result of '<<' to a boolean always evaluates to true [-Wtautological-constant-compare] 112 | error = break_lease(inode, O_WRONLY); Restore the stub's parameter to 'unsigned int mode' so it matches the real prototype and every caller. The stub still just returns 0, so there is no functional change; it removes the type inconsistency and silences the warning. Root cause diagnosed by Nathan Chancellor. Fixes: 4be9f3cc582a ("filelock: rework the __break_lease API to use flags") Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202606071029.DKCs8WOs-lkp@intel.com/ Signed-off-by: Christian Brauner (Amutable) --- include/linux/filelock.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/linux/filelock.h b/include/linux/filelock.h index 5f0a2fb31450..77e1cc4afbaa 100644 --- a/include/linux/filelock.h +++ b/include/linux/filelock.h @@ -564,7 +564,7 @@ static inline bool is_delegated(struct delegated_inode *di) return false; } -static inline int break_lease(struct inode *inode, bool wait) +static inline int break_lease(struct inode *inode, unsigned int mode) { return 0; } From c5d6cac28646b0d5d81ef632be748ae93c1f36c7 Mon Sep 17 00:00:00 2001 From: Jeff Layton Date: Thu, 29 Jan 2026 16:47:43 -0500 Subject: [PATCH 71/73] vfs: add FS_USERNS_DELEGATABLE flag and set it for NFS Commit e1c5ae59c0f2 ("fs: don't allow non-init s_user_ns for filesystems without FS_USERNS_MOUNT") prevents the mount of any filesystem inside a container that doesn't have FS_USERNS_MOUNT set. This broke NFS mounts in our containerized environment. We have a daemon somewhat like systemd-mountfsd running in the init_ns. A process does a fsopen() inside the container and passes it to the daemon via unix socket. The daemon then vets that the request is for an allowed NFS server and performs the mount. This now fails because the fc->user_ns is set to the value in the container and NFS doesn't set FS_USERNS_MOUNT. We don't want to add FS_USERNS_MOUNT to NFS since that would allow the container to mount any NFS server (even malicious ones). Add a new FS_USERNS_DELEGATABLE flag, and enable it on NFS. Fixes: e1c5ae59c0f2 ("fs: don't allow non-init s_user_ns for filesystems without FS_USERNS_MOUNT") Signed-off-by: Jeff Layton Link: https://patch.msgid.link/20260129-twmount-v1-1-4874ed2a15c4@kernel.org Acked-by: Anna Schumaker Reviewed-by: Alexander Mikhalitsyn Reviewed-by: Jeff Layton Signed-off-by: Christian Brauner (Amutable) --- fs/nfs/fs_context.c | 8 ++++++-- fs/super.c | 11 ++++++----- include/linux/fs.h | 1 + 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/fs/nfs/fs_context.c b/fs/nfs/fs_context.c index c105882edd16..1967de7d1dff 100644 --- a/fs/nfs/fs_context.c +++ b/fs/nfs/fs_context.c @@ -1769,7 +1769,9 @@ struct file_system_type nfs_fs_type = { .init_fs_context = nfs_init_fs_context, .parameters = nfs_fs_parameters, .kill_sb = nfs_kill_super, - .fs_flags = FS_RENAME_DOES_D_MOVE|FS_BINARY_MOUNTDATA, + .fs_flags = FS_RENAME_DOES_D_MOVE | + FS_BINARY_MOUNTDATA | + FS_USERNS_DELEGATABLE, }; MODULE_ALIAS_FS("nfs"); EXPORT_SYMBOL_GPL(nfs_fs_type); @@ -1781,7 +1783,9 @@ struct file_system_type nfs4_fs_type = { .init_fs_context = nfs_init_fs_context, .parameters = nfs_fs_parameters, .kill_sb = nfs_kill_super, - .fs_flags = FS_RENAME_DOES_D_MOVE|FS_BINARY_MOUNTDATA, + .fs_flags = FS_RENAME_DOES_D_MOVE | + FS_BINARY_MOUNTDATA | + FS_USERNS_DELEGATABLE, }; MODULE_ALIAS_FS("nfs4"); MODULE_ALIAS("nfs4"); diff --git a/fs/super.c b/fs/super.c index 5d46a0d5b616..d254105e29b2 100644 --- a/fs/super.c +++ b/fs/super.c @@ -741,12 +741,13 @@ struct super_block *sget_fc(struct fs_context *fc, int err; /* - * Never allow s_user_ns != &init_user_ns when FS_USERNS_MOUNT is - * not set, as the filesystem is likely unprepared to handle it. - * This can happen when fsconfig() is called from init_user_ns with - * an fs_fd opened in another user namespace. + * Never allow s_user_ns != &init_user_ns when FS_USERNS_MOUNT or + * FS_USERNS_DELEGATABLE is not set, as the filesystem is likely + * unprepared to handle it. This can happen when fsconfig() is called + * from init_user_ns with an fs_fd opened in another user namespace. */ - if (user_ns != &init_user_ns && !(fc->fs_type->fs_flags & FS_USERNS_MOUNT)) { + if (user_ns != &init_user_ns && + !(fc->fs_type->fs_flags & (FS_USERNS_MOUNT | FS_USERNS_DELEGATABLE))) { errorfc(fc, "VFS: Mounting from non-initial user namespace is not allowed"); return ERR_PTR(-EPERM); } diff --git a/include/linux/fs.h b/include/linux/fs.h index 11559c513dfb..10d35a68f597 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h @@ -2281,6 +2281,7 @@ struct file_system_type { #define FS_MGTIME 64 /* FS uses multigrain timestamps */ #define FS_LBS 128 /* FS supports LBS */ #define FS_POWER_FREEZE 256 /* Always freeze on suspend/hibernate */ +#define FS_USERNS_DELEGATABLE 1024 /* Can be mounted inside userns from outside */ #define FS_RENAME_DOES_D_MOVE 32768 /* FS will handle d_move() during rename() internally. */ int (*init_fs_context)(struct fs_context *); const struct fs_parameter_spec *parameters; From de654d66ff30e75d9308fd4d4f1627addef7923e Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Wed, 10 Jun 2026 07:06:42 +0200 Subject: [PATCH 72/73] iomap: pass the correct len to fserror_report_io in __iomap_write_begin len is size of the (larger) write request, plen is the range for which the read failed here. Fixes: a9d573ee88af ("iomap: report file I/O errors to the VFS") Signed-off-by: Christoph Hellwig Link: https://patch.msgid.link/20260610050642.1906695-1-hch@lst.de Reviewed-by: "Darrick J. Wong" Signed-off-by: Christian Brauner (Amutable) --- fs/iomap/buffered-io.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/iomap/buffered-io.c b/fs/iomap/buffered-io.c index d7b648421a70..bcf4559e9aa7 100644 --- a/fs/iomap/buffered-io.c +++ b/fs/iomap/buffered-io.c @@ -850,7 +850,7 @@ static int __iomap_write_begin(const struct iomap_iter *iter, if (status < 0) fserror_report_io(iter->inode, FSERR_BUFFERED_READ, pos, - len, status, GFP_NOFS); + plen, status, GFP_NOFS); if (status) return status; } From aa5c4fe3ba0cb2af90bbcfa7a8ef4fefcd5c2370 Mon Sep 17 00:00:00 2001 From: Li Wang Date: Thu, 28 May 2026 18:42:08 +0800 Subject: [PATCH 73/73] backing-file: fix backing_file_open() kerneldoc parameter The kerneldoc for backing_file_open() documented a @user_path argument, but the function takes const struct file *user_file. The user path is derived as &user_file->f_path. Update the @-tag to @user_file and adjust the description accordingly. Also fix the "reuqested" typo to 'requested' in the old comment. Signed-off-by: Li Wang Link: https://patch.msgid.link/20260528104208.395757-1-liwang@kylinos.cn Reviewed-by: Amir Goldstein Signed-off-by: Christian Brauner (Amutable) --- fs/backing-file.c | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/fs/backing-file.c b/fs/backing-file.c index 1f3bbfc75882..080c99696cd0 100644 --- a/fs/backing-file.c +++ b/fs/backing-file.c @@ -18,17 +18,18 @@ /** * backing_file_open - open a backing file for kernel internal use - * @user_path: path that the user reuqested to open + * @user_file: file the user requested to open * @flags: open flags * @real_path: path of the backing file * @cred: credentials for open * * Open a backing file for a stackable filesystem (e.g., overlayfs). - * @user_path may be on the stackable filesystem and @real_path on the - * underlying filesystem. In this case, we want to be able to return the - * @user_path of the stackable filesystem. This is done by embedding the - * returned file into a container structure that also stores the stacked - * file's path, which can be retrieved using backing_file_user_path(). + * @user_file->f_path may be on the stackable filesystem and @real_path + * on the underlying filesystem. In this case, we want to be able to + * return the path of the stackable filesystem. This is done by + * embedding the returned file into a container structure that also + * stores the stacked file's path, which can be retrieved using + * backing_file_user_path(). */ struct file *backing_file_open(const struct file *user_file, int flags, const struct path *real_path,