From c610d2d0787961cdd6fc1de69d9be1ff3687e1a6 Mon Sep 17 00:00:00 2001 From: Yu Peng Date: Wed, 8 Jul 2026 16:02:32 +0800 Subject: fs: annotate inode timestamp accessors syzbot reported a KCSAN race between fill_mg_cmtime() and inode_set_ctime_to_ts() on inode->i_ctime_{sec,nsec}. stat/getattr can sample inode timestamps while update paths store new values concurrently, so KCSAN can report benign races on these fields. Annotate the timestamp accessors with READ_ONCE()/WRITE_ONCE(), and use the ctime accessor for the remaining ctime loads. This avoids the KCSAN reports without changing timestamp semantics. Fixes: 4e40eff0b573 ("fs: add infrastructure for multigrain timestamps") Reported-by: syzbot+8b3bd9f8a06658479d4a@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=8b3bd9f8a06658479d4a Signed-off-by: Yu Peng Link: https://patch.msgid.link/20260708080232.2564807-1-pengyu@kylinos.cn Reviewed-by: Jeff Layton Signed-off-by: Christian Brauner (Amutable) --- include/linux/fs.h | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) (limited to 'include') diff --git a/include/linux/fs.h b/include/linux/fs.h index d10897b3a1e3..e5b97e324db1 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h @@ -1598,12 +1598,12 @@ struct timespec64 inode_set_ctime_deleg(struct inode *inode, static inline time64_t inode_get_atime_sec(const struct inode *inode) { - return inode->i_atime_sec; + return READ_ONCE(inode->i_atime_sec); } static inline long inode_get_atime_nsec(const struct inode *inode) { - return inode->i_atime_nsec; + return READ_ONCE(inode->i_atime_nsec); } static inline struct timespec64 inode_get_atime(const struct inode *inode) @@ -1617,8 +1617,8 @@ static inline struct timespec64 inode_get_atime(const struct inode *inode) static inline struct timespec64 inode_set_atime_to_ts(struct inode *inode, struct timespec64 ts) { - inode->i_atime_sec = ts.tv_sec; - inode->i_atime_nsec = ts.tv_nsec; + WRITE_ONCE(inode->i_atime_sec, ts.tv_sec); + WRITE_ONCE(inode->i_atime_nsec, ts.tv_nsec); return ts; } @@ -1633,12 +1633,12 @@ static inline struct timespec64 inode_set_atime(struct inode *inode, static inline time64_t inode_get_mtime_sec(const struct inode *inode) { - return inode->i_mtime_sec; + return READ_ONCE(inode->i_mtime_sec); } static inline long inode_get_mtime_nsec(const struct inode *inode) { - return inode->i_mtime_nsec; + return READ_ONCE(inode->i_mtime_nsec); } static inline struct timespec64 inode_get_mtime(const struct inode *inode) @@ -1651,8 +1651,8 @@ static inline struct timespec64 inode_get_mtime(const struct inode *inode) static inline struct timespec64 inode_set_mtime_to_ts(struct inode *inode, struct timespec64 ts) { - inode->i_mtime_sec = ts.tv_sec; - inode->i_mtime_nsec = ts.tv_nsec; + WRITE_ONCE(inode->i_mtime_sec, ts.tv_sec); + WRITE_ONCE(inode->i_mtime_nsec, ts.tv_nsec); return ts; } @@ -1677,12 +1677,12 @@ static inline struct timespec64 inode_set_mtime(struct inode *inode, static inline time64_t inode_get_ctime_sec(const struct inode *inode) { - return inode->i_ctime_sec; + return READ_ONCE(inode->i_ctime_sec); } static inline long inode_get_ctime_nsec(const struct inode *inode) { - return inode->i_ctime_nsec & ~I_CTIME_QUERIED; + return READ_ONCE(inode->i_ctime_nsec) & ~I_CTIME_QUERIED; } static inline struct timespec64 inode_get_ctime(const struct inode *inode) -- cgit v1.2.3 From f7f4665dc520fd8bbc1db20e59945773e03744a0 Mon Sep 17 00:00:00 2001 From: Breno Leitao Date: Mon, 20 Jul 2026 03:39:33 -0700 Subject: fs/pipe: unify the page pools into a single per-pipe pool Pipes keep two separate page caches: a) The per-pipe, lock-protected tmp_page[2] b) An on-stack anon_pipe_prealloc burst pool of up to eight pages filled before the lock Converge them into a single per-pipe pool (struct anon_pipe_prealloc embedded in pipe_inode_info) with the same budget as before: up to PIPE_PREALLOC_MAX (8) pages, trimmed back to PIPE_PREALLOC_KEEP (2) after each operation. tmp_page[2] is removed. Pages are still allocated and freed outside pipe->mutex; only the assignment into the pool is done under it. The pool count is also read locklessly in the prefill path, so it is annotated __data_racy. anon_pipe_prefill_and_lock() tops the pool up to the write's page count -- and returns with pipe->mutex held, so a write acquires the lock only once. anon_pipe_trim_and_unlock() trims the pool under that same lock before dropping it, then frees the excess. Signed-off-by: Breno Leitao Link: https://patch.msgid.link/20260720-b4-pipe-unification-v5-1-9002a3fe5e6d@debian.org Reviewed-by: Mateusz Guzik Reviewed-by: Oleg Nesterov Signed-off-by: Christian Brauner (Amutable) --- include/linux/pipe_fs_i.h | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) (limited to 'include') diff --git a/include/linux/pipe_fs_i.h b/include/linux/pipe_fs_i.h index 7f6a92ac9704..9e8b60ad806a 100644 --- a/include/linux/pipe_fs_i.h +++ b/include/linux/pipe_fs_i.h @@ -14,6 +14,9 @@ #define PIPE_BUF_FLAG_LOSS 0x40 /* Message loss happened after this buffer */ #endif +#define PIPE_PREALLOC_MAX 8 /* max pages in prealloc pool */ +#define PIPE_PREALLOC_KEEP 2 /* keep at least this many after trim */ + /** * struct pipe_buffer - a linux kernel pipe buffer * @page: the page containing the data for the pipe buffer @@ -58,6 +61,21 @@ union pipe_index { }; }; +/** + * struct anon_pipe_prealloc - per-pipe page preallocation pool + * @pages: array of cached pages (pool) + * @count: number of pages currently in the pool + * + * Each pipe keeps a small bounded pool of preallocated pages to reduce + * allocation overhead during writes. The pool is bounded at PIPE_PREALLOC_MAX + * and trimmed down to PIPE_PREALLOC_KEEP after a write completes. + */ +struct anon_pipe_prealloc { + struct page *pages[PIPE_PREALLOC_MAX]; + + unsigned int __data_racy count; +}; + /** * struct pipe_inode_info - a linux kernel pipe * @mutex: mutex protecting the whole thing @@ -68,7 +86,7 @@ union pipe_index { * @max_usage: The maximum number of slots that may be used in the ring * @ring_size: total number of buffers (should be a power of 2) * @nr_accounted: The amount this pipe accounts for in user->pipe_bufs - * @tmp_page: cached released page + * @prealloc: per-pipe page preallocation pool * @readers: number of current readers of this pipe * @writers: number of current writers of this pipe * @files: number of struct file referring this pipe (protected by ->i_lock) @@ -99,7 +117,7 @@ struct pipe_inode_info { #ifdef CONFIG_WATCH_QUEUE bool note_loss; #endif - struct page *tmp_page[2]; + struct anon_pipe_prealloc prealloc; struct fasync_struct *fasync_readers; struct fasync_struct *fasync_writers; struct pipe_buffer *bufs; -- cgit v1.2.3 From 42c8ed5835921a7b2523517fd6caad1d79b69146 Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Thu, 23 Jul 2026 20:10:21 -0700 Subject: nstree: add/fix struct ns_id_req kernel-doc member fields - drop non-existent @filter - add missing descriptions for @ns_type and @spare2 - change the descriptions of @ns_id and @user_ns_id based on their commit to prevent these kernel-doc warnings: Warning: ../include/uapi/linux/nsfs.h:117 struct member 'ns_type' not described in 'ns_id_req' Warning: ../include/uapi/linux/nsfs.h:117 struct member 'spare2' not described in 'ns_id_req' Warning: ../include/uapi/linux/nsfs.h:117 Excess struct member 'filter' description in 'ns_id_req' Fixes: 76b6f5dfb3fd ("nstree: add listns()") Signed-off-by: Randy Dunlap Link: https://patch.msgid.link/20260724031021.814599-1-rdunlap@infradead.org Signed-off-by: Christian Brauner (Amutable) --- include/uapi/linux/nsfs.h | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'include') diff --git a/include/uapi/linux/nsfs.h b/include/uapi/linux/nsfs.h index a25e38d1c874..007fed5971b4 100644 --- a/include/uapi/linux/nsfs.h +++ b/include/uapi/linux/nsfs.h @@ -96,9 +96,10 @@ enum ns_type { * struct ns_id_req - namespace ID request structure * @size: size of this structure * @spare: reserved for future use - * @filter: filter mask - * @ns_id: last namespace id - * @user_ns_id: owning user namespace ID + * @ns_id: last namespace ID + * @ns_type: bit mask of namespace types to include + * @spare2: reserved for future use + * @user_ns_id: filter on this user namespace ID (or 0) * * Structure for passing namespace ID and miscellaneous parameters to * statns(2) and listns(2). -- cgit v1.2.3 From 91e27ed8a387c156f72175748de48db9ede74237 Mon Sep 17 00:00:00 2001 From: Mateusz Guzik Date: Fri, 24 Jul 2026 19:14:21 +0200 Subject: lockref: tidy up dead count handling 1. put the dead val into a macro so that it can be used in other places 2. __lockref_is_dead(): - drop the __ suffix, this is not an internal routine - drop the spurious cast, the value is already a signed int - use READ_ONCE to prevent any compile shenanigans 3. provide lockref_is_dead_or_zero() Signed-off-by: Mateusz Guzik Link: https://patch.msgid.link/20260724171422.429284-2-mjguzik@gmail.com Signed-off-by: Christian Brauner (Amutable) --- include/linux/lockref.h | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) (limited to 'include') diff --git a/include/linux/lockref.h b/include/linux/lockref.h index 6ded24cdb4a8..ddfb7d3b8cec 100644 --- a/include/linux/lockref.h +++ b/include/linux/lockref.h @@ -34,6 +34,8 @@ struct lockref { }; }; +#define __LOCKREF_DEAD_VAL -128 + /** * lockref_init - Initialize a lockref * @lockref: pointer to lockref structure @@ -55,9 +57,15 @@ void lockref_mark_dead(struct lockref *lockref); bool lockref_get_not_dead(struct lockref *lockref); /* Must be called under spinlock for reliable results */ -static inline bool __lockref_is_dead(const struct lockref *l) +static inline bool lockref_is_dead(const struct lockref *l) +{ + return (READ_ONCE(l->count) == __LOCKREF_DEAD_VAL); +} + +static inline bool lockref_is_dead_or_zero(const struct lockref *l) { - return ((int)l->count < 0); + int count = READ_ONCE(l->count); + return (count == __LOCKREF_DEAD_VAL || count == 0); } #endif /* __LINUX_LOCKREF_H */ -- cgit v1.2.3 From 4902e56525076d2f7241e3a2f612d19be84900a9 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Mon, 27 Jul 2026 13:57:30 -0600 Subject: seq_file: rename mangle_path to seq_mangle_path The symbol mangle_path conflicts with a gcov symbol which can break the build of ARCH=um with gcov, and it's also not very specific and descriptive. Rename mangle_path() to seq_mangle_path(), and also remove the export since it's not needed or used by any modules. Signed-off-by: Johannes Berg Signed-off-by: Alex Hung Link: https://patch.msgid.link/20260727195730.2306887-1-alex.hung@amd.com Signed-off-by: Christian Brauner (Amutable) --- include/linux/seq_file.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'include') diff --git a/include/linux/seq_file.h b/include/linux/seq_file.h index 2fb266ea69fa..dc0e8c62d9e0 100644 --- a/include/linux/seq_file.h +++ b/include/linux/seq_file.h @@ -104,7 +104,7 @@ static inline void seq_setwidth(struct seq_file *m, size_t size) } void seq_pad(struct seq_file *m, char c); -char *mangle_path(char *s, const char *p, const char *esc); +char *seq_mangle_path(char *s, const char *p, const char *esc); int seq_open(struct file *, const struct seq_operations *); ssize_t seq_read(struct file *, char __user *, size_t, loff_t *); ssize_t seq_read_iter(struct kiocb *iocb, struct iov_iter *iter); -- cgit v1.2.3 From bf6c571a80a1ae9141610ce94efcf4e5b93bb805 Mon Sep 17 00:00:00 2001 From: Jann Horn Date: Mon, 3 Aug 2026 21:46:19 +0200 Subject: fs: document semantics of kstat::{uid,gid} fields The uid stored in struct kstat is logically a vfsuid; file systems initialize it by converting a kuid (filesystem perspective) to a vfsuid (mount perspective), then use vfsuid_into_kuid(), which essentially just typecasts from vfsuid to kuid. For now, just add a comment to note this mismatch between C type and semantic type. Below are some notes for anyone who wants to refactor this in the future. There are probably two options to refactor this away: 1. Change the type of kstat::uid to vfsuid_t, and perform the conversion from vfsuid to userspace-uid in the VFS layer. This wouldn't change machine code, just be more semantically correct. 2. Change the semantics of kstat::uid to really be a kuid_t, and let the VFS layer take care of doing the translation from kuid to vfsuid that is currently done in filesystem code (or in generic_fillattr, on behalf of the filesystem code). Option 2 is probably neater since it moves more logic into the generic VFS layer, and this is something that is expected to work the same way in all file systems? The following coccinelle script: ``` virtual context @@ struct kstat *stat; @@ * stat->uid @@ struct kstat *stat; @@ * stat->gid @@ struct kstat stat; @@ * stat.uid @@ struct kstat stat; @@ * stat.gid ``` detects 43 field accesses to these uid/gid fields. Signed-off-by: Jann Horn Link: https://patch.msgid.link/20260803-vfs-comment-stat-uid-v1-1-162d062b737c@google.com Reviewed-by: Jan Kara Signed-off-by: Christian Brauner (Amutable) --- include/linux/stat.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'include') diff --git a/include/linux/stat.h b/include/linux/stat.h index e3d00e7bb26d..9c5709132862 100644 --- a/include/linux/stat.h +++ b/include/linux/stat.h @@ -41,8 +41,8 @@ struct kstat { u64 ino; dev_t dev; dev_t rdev; - kuid_t uid; - kgid_t gid; + kuid_t uid; /* This is logically a vfsuid_t. */ + kgid_t gid; /* This is logically a vfsgid_t. */ loff_t size; struct timespec64 atime; struct timespec64 mtime; -- cgit v1.2.3 From f1cebdec9739911cf066e6ce2e02c511f0d124d5 Mon Sep 17 00:00:00 2001 From: Oleg Nesterov Date: Mon, 3 Aug 2026 14:46:25 +0200 Subject: pipe: only enable the extra wake_up(rd_wait) for EPOLLET consumers pipe_poll() unconditionally sets ->poll_usage on the first call, forcing anon_pipe_write() to wake up readers on every write even if the pipe was not empty. The reason is that some legacy epoll(EPOLLET) users depend on historical per-write wakeups, see commit 3a34b13a88ca ("pipe: make pipe writes always wake up readers"). Test-case: #include #include #include int main(void) { int pfd[2], efd; struct epoll_event evt = { .events = EPOLLIN | EPOLLET }; pipe(pfd); efd = epoll_create1(0); epoll_ctl(efd, EPOLL_CTL_ADD, pfd[0], &evt); for (int i = 0; i < 2; ++i) { write(pfd[1], "", 1); assert(epoll_wait(efd, &evt, 1, 0) == 1); } return 0; } it fails if WRITE_ONCE(poll_usage, true) is removed from pipe_poll(). However, without EPOLLET in .events, it does not need the extra wakeup and succeeds even if write() is called only once before the main loop. Currently io_uring without (unsupported) IORING_POLL_ADD_LEVEL always sets EPOLLET, and in IORING_POLL_ADD_MULTI mode it depends on per-write wakeups the same way: #include #include #include #include #include #include int main(void) { struct io_uring_params p = {}; int fd, pfd[2]; pipe(pfd); fd = syscall(SYS_io_uring_setup, 2, &p); assert(fd >= 0); void *ring = mmap(0, p.cq_off.cqes + p.cq_entries * sizeof(struct io_uring_cqe), PROT_READ | PROT_WRITE, MAP_SHARED, fd, IORING_OFF_SQ_RING); assert(ring != MAP_FAILED); *(unsigned *)(ring + p.sq_off.tail) = 1; struct io_uring_sqe *sqes = mmap(0, p.sq_entries * sizeof(*sqes), PROT_READ | PROT_WRITE, MAP_SHARED, fd, IORING_OFF_SQES); assert(sqes != MAP_FAILED); sqes[0].opcode = IORING_OP_POLL_ADD; sqes[0].fd = pfd[0]; sqes[0].len = IORING_POLL_ADD_MULTI; sqes[0].poll32_events = EPOLLIN; syscall(SYS_io_uring_enter, fd, 1, 0, 0, 0, 0); unsigned *cq_head = ring + p.cq_off.head; unsigned *cq_tail = ring + p.cq_off.tail; for (int i = 0; i < 2; ++i) { write(pfd[1], "", 1); syscall(SYS_io_uring_enter, fd, 0, 0, IORING_ENTER_GETEVENTS, 0, 0); assert(*cq_tail == ++*cq_head); } return 0; } the 2nd assert() in the main loop fails without ->poll_usage == true. Rename ->poll_usage to ->pseudo_edgetrigger to make the purpose clearer, update the comments, and change pipe_poll() to set ->pseudo_edgetrigger only if wait->_key & EPOLLET is true. This check should catch both users, and this way poll/select and epoll without EPOLLET users will not pay for the extra wakeup. Signed-off-by: Oleg Nesterov Link: https://patch.msgid.link/anCNoW-x0bcB2ggg@redhat.com Signed-off-by: Christian Brauner (Amutable) --- include/linux/pipe_fs_i.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'include') diff --git a/include/linux/pipe_fs_i.h b/include/linux/pipe_fs_i.h index 9e8b60ad806a..6402930282e5 100644 --- a/include/linux/pipe_fs_i.h +++ b/include/linux/pipe_fs_i.h @@ -92,7 +92,7 @@ struct anon_pipe_prealloc { * @files: number of struct file referring this pipe (protected by ->i_lock) * @r_counter: reader counter * @w_counter: writer counter - * @poll_usage: is this pipe used for epoll, which has crazy wakeups? + * @pseudo_edgetrigger: has an EPOLLET consumer, enable per-write wakeups * @fasync_readers: reader side fasync * @fasync_writers: writer side fasync * @bufs: the circular array of pipe buffers @@ -113,7 +113,7 @@ struct pipe_inode_info { unsigned int files; unsigned int r_counter; unsigned int w_counter; - bool poll_usage; + bool pseudo_edgetrigger; #ifdef CONFIG_WATCH_QUEUE bool note_loss; #endif -- cgit v1.2.3