From: Sasha Levin Date: Tue, 30 Jun 2026 22:23:09 +0000 (-0400) Subject: Fixes for all trees X-Git-Tag: v5.10.260~61 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=76522806dbfb58941f6fa4b6116b891b66689613;p=thirdparty%2Fkernel%2Fstable-queue.git Fixes for all trees Signed-off-by: Sasha Levin --- diff --git a/queue-5.10/ring-buffer-remove-ring_buffer_read_prepare_sync.patch b/queue-5.10/ring-buffer-remove-ring_buffer_read_prepare_sync.patch new file mode 100644 index 0000000000..7bd2834427 --- /dev/null +++ b/queue-5.10/ring-buffer-remove-ring_buffer_read_prepare_sync.patch @@ -0,0 +1,229 @@ +From 5eac6abea01da6185af6280909203b6fe7dcece7 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Tue, 30 Jun 2026 06:06:34 +0000 +Subject: ring-buffer: Remove ring_buffer_read_prepare_sync() + +From: Bjoern Doebel + +[ Upstream commit 119a5d573622ae90ba730d18acfae9bb75d77b9a ] + +When the ring buffer was first introduced, reading the non-consuming +"trace" file required disabling the writing of the ring buffer. To make +sure the writing was fully disabled before iterating the buffer with a +non-consuming read, it would set the disable flag of the buffer and then +call an RCU synchronization to make sure all the buffers were +synchronized. + +The function ring_buffer_read_start() originally would initialize the +iterator and call an RCU synchronization, but this was for each individual +per CPU buffer where this would get called many times on a machine with +many CPUs before the trace file could be read. The commit 72c9ddfd4c5bf +("ring-buffer: Make non-consuming read less expensive with lots of cpus.") +separated ring_buffer_read_start into ring_buffer_read_prepare(), +ring_buffer_read_sync() and then ring_buffer_read_start() to allow each of +the per CPU buffers to be prepared, call the read_buffer_read_sync() once, +and then the ring_buffer_read_start() for each of the CPUs which made +things much faster. + +The commit 1039221cc278 ("ring-buffer: Do not disable recording when there +is an iterator") removed the requirement of disabling the recording of the +ring buffer in order to iterate it, but it did not remove the +synchronization that was happening that was required to wait for all the +buffers to have no more writers. It's now OK for the buffers to have +writers and no synchronization is needed. + +Remove the synchronization and put back the interface for the ring buffer +iterator back before commit 72c9ddfd4c5bf was applied. + +Cc: Mathieu Desnoyers +Link: https://lore.kernel.org/20250630180440.3eabb514@batman.local.home +Reported-by: David Howells +Fixes: 1039221cc278 ("ring-buffer: Do not disable recording when there is an iterator") +Tested-by: David Howells +Reviewed-by: Masami Hiramatsu (Google) +Signed-off-by: Steven Rostedt (Google) + +Assisted-by: Kiro:claude-opus-4.8 +[doebel@amazon.de: move patch section using guard() macro into a +separate block to address declaration after statement warning.] +Signed-off-by: Bjoern Doebel +Signed-off-by: Sasha Levin +--- + include/linux/ring_buffer.h | 4 +-- + kernel/trace/ring_buffer.c | 72 ++++++++----------------------------- + kernel/trace/trace.c | 14 +++----- + kernel/trace/trace_kdb.c | 8 ++--- + 4 files changed, 22 insertions(+), 76 deletions(-) + +diff --git a/include/linux/ring_buffer.h b/include/linux/ring_buffer.h +index 7d5a78f49d43dd..be5c1209224695 100644 +--- a/include/linux/ring_buffer.h ++++ b/include/linux/ring_buffer.h +@@ -128,9 +128,7 @@ ring_buffer_consume(struct trace_buffer *buffer, int cpu, u64 *ts, + unsigned long *lost_events); + + struct ring_buffer_iter * +-ring_buffer_read_prepare(struct trace_buffer *buffer, int cpu, gfp_t flags); +-void ring_buffer_read_prepare_sync(void); +-void ring_buffer_read_start(struct ring_buffer_iter *iter); ++ring_buffer_read_start(struct trace_buffer *buffer, int cpu, gfp_t flags); + void ring_buffer_read_finish(struct ring_buffer_iter *iter); + + struct ring_buffer_event * +diff --git a/kernel/trace/ring_buffer.c b/kernel/trace/ring_buffer.c +index 03a7127efc5a8a..1089daa17b09e2 100644 +--- a/kernel/trace/ring_buffer.c ++++ b/kernel/trace/ring_buffer.c +@@ -4858,28 +4858,20 @@ ring_buffer_consume(struct trace_buffer *buffer, int cpu, u64 *ts, + EXPORT_SYMBOL_GPL(ring_buffer_consume); + + /** +- * ring_buffer_read_prepare - Prepare for a non consuming read of the buffer ++ * ring_buffer_read_start - start a non consuming read of the buffer + * @buffer: The ring buffer to read from + * @cpu: The cpu buffer to iterate over + * @flags: gfp flags to use for memory allocation + * +- * This performs the initial preparations necessary to iterate +- * through the buffer. Memory is allocated, buffer recording +- * is disabled, and the iterator pointer is returned to the caller. +- * +- * Disabling buffer recording prevents the reading from being +- * corrupted. This is not a consuming read, so a producer is not +- * expected. +- * +- * After a sequence of ring_buffer_read_prepare calls, the user is +- * expected to make at least one call to ring_buffer_read_prepare_sync. +- * Afterwards, ring_buffer_read_start is invoked to get things going +- * for real. ++ * This creates an iterator to allow non-consuming iteration through ++ * the buffer. If the buffer is disabled for writing, it will produce ++ * the same information each time, but if the buffer is still writing ++ * then the first hit of a write will cause the iteration to stop. + * +- * This overall must be paired with ring_buffer_read_finish. ++ * Must be paired with ring_buffer_read_finish. + */ + struct ring_buffer_iter * +-ring_buffer_read_prepare(struct trace_buffer *buffer, int cpu, gfp_t flags) ++ring_buffer_read_start(struct trace_buffer *buffer, int cpu, gfp_t flags) + { + struct ring_buffer_per_cpu *cpu_buffer; + struct ring_buffer_iter *iter; +@@ -4904,51 +4896,15 @@ ring_buffer_read_prepare(struct trace_buffer *buffer, int cpu, gfp_t flags) + + atomic_inc(&cpu_buffer->resize_disabled); + +- return iter; +-} +-EXPORT_SYMBOL_GPL(ring_buffer_read_prepare); ++ { ++ guard(raw_spinlock_irqsave)(&cpu_buffer->reader_lock); + +-/** +- * ring_buffer_read_prepare_sync - Synchronize a set of prepare calls +- * +- * All previously invoked ring_buffer_read_prepare calls to prepare +- * iterators will be synchronized. Afterwards, read_buffer_read_start +- * calls on those iterators are allowed. +- */ +-void +-ring_buffer_read_prepare_sync(void) +-{ +- synchronize_rcu(); +-} +-EXPORT_SYMBOL_GPL(ring_buffer_read_prepare_sync); +- +-/** +- * ring_buffer_read_start - start a non consuming read of the buffer +- * @iter: The iterator returned by ring_buffer_read_prepare +- * +- * This finalizes the startup of an iteration through the buffer. +- * The iterator comes from a call to ring_buffer_read_prepare and +- * an intervening ring_buffer_read_prepare_sync must have been +- * performed. +- * +- * Must be paired with ring_buffer_read_finish. +- */ +-void +-ring_buffer_read_start(struct ring_buffer_iter *iter) +-{ +- struct ring_buffer_per_cpu *cpu_buffer; +- unsigned long flags; +- +- if (!iter) +- return; +- +- cpu_buffer = iter->cpu_buffer; ++ arch_spin_lock(&cpu_buffer->lock); ++ rb_iter_reset(iter); ++ arch_spin_unlock(&cpu_buffer->lock); ++ } + +- raw_spin_lock_irqsave(&cpu_buffer->reader_lock, flags); +- arch_spin_lock(&cpu_buffer->lock); +- rb_iter_reset(iter); +- arch_spin_unlock(&cpu_buffer->lock); +- raw_spin_unlock_irqrestore(&cpu_buffer->reader_lock, flags); ++ return iter; + } + EXPORT_SYMBOL_GPL(ring_buffer_read_start); + +diff --git a/kernel/trace/trace.c b/kernel/trace/trace.c +index 5bcd4cbeeb4fe1..ed32d3c4f0e76b 100644 +--- a/kernel/trace/trace.c ++++ b/kernel/trace/trace.c +@@ -4480,21 +4480,15 @@ __tracing_open(struct inode *inode, struct file *file, bool snapshot) + if (iter->cpu_file == RING_BUFFER_ALL_CPUS) { + for_each_tracing_cpu(cpu) { + iter->buffer_iter[cpu] = +- ring_buffer_read_prepare(iter->array_buffer->buffer, +- cpu, GFP_KERNEL); +- } +- ring_buffer_read_prepare_sync(); +- for_each_tracing_cpu(cpu) { +- ring_buffer_read_start(iter->buffer_iter[cpu]); ++ ring_buffer_read_start(iter->array_buffer->buffer, ++ cpu, GFP_KERNEL); + tracing_iter_reset(iter, cpu); + } + } else { + cpu = iter->cpu_file; + iter->buffer_iter[cpu] = +- ring_buffer_read_prepare(iter->array_buffer->buffer, +- cpu, GFP_KERNEL); +- ring_buffer_read_prepare_sync(); +- ring_buffer_read_start(iter->buffer_iter[cpu]); ++ ring_buffer_read_start(iter->array_buffer->buffer, ++ cpu, GFP_KERNEL); + tracing_iter_reset(iter, cpu); + } + +diff --git a/kernel/trace/trace_kdb.c b/kernel/trace/trace_kdb.c +index 9da76104f7a28b..18d1551db2b0d5 100644 +--- a/kernel/trace/trace_kdb.c ++++ b/kernel/trace/trace_kdb.c +@@ -43,17 +43,15 @@ static void ftrace_dump_buf(int skip_entries, long cpu_file) + if (cpu_file == RING_BUFFER_ALL_CPUS) { + for_each_tracing_cpu(cpu) { + iter.buffer_iter[cpu] = +- ring_buffer_read_prepare(iter.array_buffer->buffer, +- cpu, GFP_ATOMIC); +- ring_buffer_read_start(iter.buffer_iter[cpu]); ++ ring_buffer_read_start(iter.array_buffer->buffer, ++ cpu, GFP_ATOMIC); + tracing_iter_reset(&iter, cpu); + } + } else { + iter.cpu_file = cpu_file; + iter.buffer_iter[cpu_file] = +- ring_buffer_read_prepare(iter.array_buffer->buffer, ++ ring_buffer_read_start(iter.array_buffer->buffer, + cpu_file, GFP_ATOMIC); +- ring_buffer_read_start(iter.buffer_iter[cpu_file]); + tracing_iter_reset(&iter, cpu_file); + } + +-- +2.53.0 + diff --git a/queue-5.10/series b/queue-5.10/series index 70b846084f..66b432cd84 100644 --- a/queue-5.10/series +++ b/queue-5.10/series @@ -54,3 +54,4 @@ batman-adv-tt-track-roam-count-per-vid.patch batman-adv-dat-prevent-false-sharing-between-vlans.patch batman-adv-tvlv-enforce-2-byte-alignment.patch batman-adv-tvlv-avoid-race-of-cifsnotfound-handler-s.patch +ring-buffer-remove-ring_buffer_read_prepare_sync.patch diff --git a/queue-5.15/ring-buffer-remove-ring_buffer_read_prepare_sync.patch b/queue-5.15/ring-buffer-remove-ring_buffer_read_prepare_sync.patch new file mode 100644 index 0000000000..0f0636f942 --- /dev/null +++ b/queue-5.15/ring-buffer-remove-ring_buffer_read_prepare_sync.patch @@ -0,0 +1,229 @@ +From e1a071f4798bf2dc9569a55421b67b6a3f163a35 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Tue, 30 Jun 2026 06:03:21 +0000 +Subject: ring-buffer: Remove ring_buffer_read_prepare_sync() + +From: Bjoern Doebel + +[ Upstream commit 119a5d573622ae90ba730d18acfae9bb75d77b9a ] + +When the ring buffer was first introduced, reading the non-consuming +"trace" file required disabling the writing of the ring buffer. To make +sure the writing was fully disabled before iterating the buffer with a +non-consuming read, it would set the disable flag of the buffer and then +call an RCU synchronization to make sure all the buffers were +synchronized. + +The function ring_buffer_read_start() originally would initialize the +iterator and call an RCU synchronization, but this was for each individual +per CPU buffer where this would get called many times on a machine with +many CPUs before the trace file could be read. The commit 72c9ddfd4c5bf +("ring-buffer: Make non-consuming read less expensive with lots of cpus.") +separated ring_buffer_read_start into ring_buffer_read_prepare(), +ring_buffer_read_sync() and then ring_buffer_read_start() to allow each of +the per CPU buffers to be prepared, call the read_buffer_read_sync() once, +and then the ring_buffer_read_start() for each of the CPUs which made +things much faster. + +The commit 1039221cc278 ("ring-buffer: Do not disable recording when there +is an iterator") removed the requirement of disabling the recording of the +ring buffer in order to iterate it, but it did not remove the +synchronization that was happening that was required to wait for all the +buffers to have no more writers. It's now OK for the buffers to have +writers and no synchronization is needed. + +Remove the synchronization and put back the interface for the ring buffer +iterator back before commit 72c9ddfd4c5bf was applied. + +Cc: Mathieu Desnoyers +Link: https://lore.kernel.org/20250630180440.3eabb514@batman.local.home +Reported-by: David Howells +Fixes: 1039221cc278 ("ring-buffer: Do not disable recording when there is an iterator") +Tested-by: David Howells +Reviewed-by: Masami Hiramatsu (Google) +Signed-off-by: Steven Rostedt (Google) + +Assisted-by: Kiro:claude-opus-4.8 +[doebel@amazon.de: move patch section using guard() macro into a +separate block to address declaration after statement warning.] +Signed-off-by: Bjoern Doebel +Signed-off-by: Sasha Levin +--- + include/linux/ring_buffer.h | 4 +-- + kernel/trace/ring_buffer.c | 72 ++++++++----------------------------- + kernel/trace/trace.c | 14 +++----- + kernel/trace/trace_kdb.c | 8 ++--- + 4 files changed, 22 insertions(+), 76 deletions(-) + +diff --git a/include/linux/ring_buffer.h b/include/linux/ring_buffer.h +index 3e7bfc0f65aee2..b53335ed2d0efc 100644 +--- a/include/linux/ring_buffer.h ++++ b/include/linux/ring_buffer.h +@@ -130,9 +130,7 @@ ring_buffer_consume(struct trace_buffer *buffer, int cpu, u64 *ts, + unsigned long *lost_events); + + struct ring_buffer_iter * +-ring_buffer_read_prepare(struct trace_buffer *buffer, int cpu, gfp_t flags); +-void ring_buffer_read_prepare_sync(void); +-void ring_buffer_read_start(struct ring_buffer_iter *iter); ++ring_buffer_read_start(struct trace_buffer *buffer, int cpu, gfp_t flags); + void ring_buffer_read_finish(struct ring_buffer_iter *iter); + + struct ring_buffer_event * +diff --git a/kernel/trace/ring_buffer.c b/kernel/trace/ring_buffer.c +index e44115db0efe35..e7c472729542d9 100644 +--- a/kernel/trace/ring_buffer.c ++++ b/kernel/trace/ring_buffer.c +@@ -5037,28 +5037,20 @@ ring_buffer_consume(struct trace_buffer *buffer, int cpu, u64 *ts, + EXPORT_SYMBOL_GPL(ring_buffer_consume); + + /** +- * ring_buffer_read_prepare - Prepare for a non consuming read of the buffer ++ * ring_buffer_read_start - start a non consuming read of the buffer + * @buffer: The ring buffer to read from + * @cpu: The cpu buffer to iterate over + * @flags: gfp flags to use for memory allocation + * +- * This performs the initial preparations necessary to iterate +- * through the buffer. Memory is allocated, buffer recording +- * is disabled, and the iterator pointer is returned to the caller. +- * +- * Disabling buffer recording prevents the reading from being +- * corrupted. This is not a consuming read, so a producer is not +- * expected. +- * +- * After a sequence of ring_buffer_read_prepare calls, the user is +- * expected to make at least one call to ring_buffer_read_prepare_sync. +- * Afterwards, ring_buffer_read_start is invoked to get things going +- * for real. ++ * This creates an iterator to allow non-consuming iteration through ++ * the buffer. If the buffer is disabled for writing, it will produce ++ * the same information each time, but if the buffer is still writing ++ * then the first hit of a write will cause the iteration to stop. + * +- * This overall must be paired with ring_buffer_read_finish. ++ * Must be paired with ring_buffer_read_finish. + */ + struct ring_buffer_iter * +-ring_buffer_read_prepare(struct trace_buffer *buffer, int cpu, gfp_t flags) ++ring_buffer_read_start(struct trace_buffer *buffer, int cpu, gfp_t flags) + { + struct ring_buffer_per_cpu *cpu_buffer; + struct ring_buffer_iter *iter; +@@ -5083,51 +5075,15 @@ ring_buffer_read_prepare(struct trace_buffer *buffer, int cpu, gfp_t flags) + + atomic_inc(&cpu_buffer->resize_disabled); + +- return iter; +-} +-EXPORT_SYMBOL_GPL(ring_buffer_read_prepare); ++ { ++ guard(raw_spinlock_irqsave)(&cpu_buffer->reader_lock); + +-/** +- * ring_buffer_read_prepare_sync - Synchronize a set of prepare calls +- * +- * All previously invoked ring_buffer_read_prepare calls to prepare +- * iterators will be synchronized. Afterwards, read_buffer_read_start +- * calls on those iterators are allowed. +- */ +-void +-ring_buffer_read_prepare_sync(void) +-{ +- synchronize_rcu(); +-} +-EXPORT_SYMBOL_GPL(ring_buffer_read_prepare_sync); +- +-/** +- * ring_buffer_read_start - start a non consuming read of the buffer +- * @iter: The iterator returned by ring_buffer_read_prepare +- * +- * This finalizes the startup of an iteration through the buffer. +- * The iterator comes from a call to ring_buffer_read_prepare and +- * an intervening ring_buffer_read_prepare_sync must have been +- * performed. +- * +- * Must be paired with ring_buffer_read_finish. +- */ +-void +-ring_buffer_read_start(struct ring_buffer_iter *iter) +-{ +- struct ring_buffer_per_cpu *cpu_buffer; +- unsigned long flags; +- +- if (!iter) +- return; +- +- cpu_buffer = iter->cpu_buffer; ++ arch_spin_lock(&cpu_buffer->lock); ++ rb_iter_reset(iter); ++ arch_spin_unlock(&cpu_buffer->lock); ++ } + +- raw_spin_lock_irqsave(&cpu_buffer->reader_lock, flags); +- arch_spin_lock(&cpu_buffer->lock); +- rb_iter_reset(iter); +- arch_spin_unlock(&cpu_buffer->lock); +- raw_spin_unlock_irqrestore(&cpu_buffer->reader_lock, flags); ++ return iter; + } + EXPORT_SYMBOL_GPL(ring_buffer_read_start); + +diff --git a/kernel/trace/trace.c b/kernel/trace/trace.c +index 537360be8e4e0d..1a29a9d9e86853 100644 +--- a/kernel/trace/trace.c ++++ b/kernel/trace/trace.c +@@ -4803,21 +4803,15 @@ __tracing_open(struct inode *inode, struct file *file, bool snapshot) + if (iter->cpu_file == RING_BUFFER_ALL_CPUS) { + for_each_tracing_cpu(cpu) { + iter->buffer_iter[cpu] = +- ring_buffer_read_prepare(iter->array_buffer->buffer, +- cpu, GFP_KERNEL); +- } +- ring_buffer_read_prepare_sync(); +- for_each_tracing_cpu(cpu) { +- ring_buffer_read_start(iter->buffer_iter[cpu]); ++ ring_buffer_read_start(iter->array_buffer->buffer, ++ cpu, GFP_KERNEL); + tracing_iter_reset(iter, cpu); + } + } else { + cpu = iter->cpu_file; + iter->buffer_iter[cpu] = +- ring_buffer_read_prepare(iter->array_buffer->buffer, +- cpu, GFP_KERNEL); +- ring_buffer_read_prepare_sync(); +- ring_buffer_read_start(iter->buffer_iter[cpu]); ++ ring_buffer_read_start(iter->array_buffer->buffer, ++ cpu, GFP_KERNEL); + tracing_iter_reset(iter, cpu); + } + +diff --git a/kernel/trace/trace_kdb.c b/kernel/trace/trace_kdb.c +index 59857a1ee44cdf..628c25693cef2f 100644 +--- a/kernel/trace/trace_kdb.c ++++ b/kernel/trace/trace_kdb.c +@@ -43,17 +43,15 @@ static void ftrace_dump_buf(int skip_entries, long cpu_file) + if (cpu_file == RING_BUFFER_ALL_CPUS) { + for_each_tracing_cpu(cpu) { + iter.buffer_iter[cpu] = +- ring_buffer_read_prepare(iter.array_buffer->buffer, +- cpu, GFP_ATOMIC); +- ring_buffer_read_start(iter.buffer_iter[cpu]); ++ ring_buffer_read_start(iter.array_buffer->buffer, ++ cpu, GFP_ATOMIC); + tracing_iter_reset(&iter, cpu); + } + } else { + iter.cpu_file = cpu_file; + iter.buffer_iter[cpu_file] = +- ring_buffer_read_prepare(iter.array_buffer->buffer, ++ ring_buffer_read_start(iter.array_buffer->buffer, + cpu_file, GFP_ATOMIC); +- ring_buffer_read_start(iter.buffer_iter[cpu_file]); + tracing_iter_reset(&iter, cpu_file); + } + +-- +2.53.0 + diff --git a/queue-5.15/series b/queue-5.15/series index 0aa956bebf..611e85faf5 100644 --- a/queue-5.15/series +++ b/queue-5.15/series @@ -50,3 +50,4 @@ batman-adv-tt-track-roam-count-per-vid.patch batman-adv-dat-prevent-false-sharing-between-vlans.patch batman-adv-tvlv-enforce-2-byte-alignment.patch batman-adv-tvlv-avoid-race-of-cifsnotfound-handler-s.patch +ring-buffer-remove-ring_buffer_read_prepare_sync.patch diff --git a/queue-6.12/fs-constify-file-ptr-in-backing_file-accessor-helper.patch b/queue-6.12/fs-constify-file-ptr-in-backing_file-accessor-helper.patch new file mode 100644 index 0000000000..09a952f040 --- /dev/null +++ b/queue-6.12/fs-constify-file-ptr-in-backing_file-accessor-helper.patch @@ -0,0 +1,121 @@ +From ee4e158f36acd6c50b8863200f233cc26e868503 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Mon, 29 Jun 2026 15:03:36 +0800 +Subject: fs: constify file ptr in backing_file accessor helpers + +From: Amir Goldstein + +[ Upstream commit 4e301d858af17ae2ce56886296e5458c5a08219a ] + +Add internal helper backing_file_set_user_path() for the only +two cases that need to modify backing_file fields. + +Signed-off-by: Amir Goldstein +Link: https://lore.kernel.org/20250607115304.2521155-2-amir73il@gmail.com +Signed-off-by: Christian Brauner +Signed-off-by: Cai Xinchen +Signed-off-by: Sasha Levin +--- + fs/backing-file.c | 4 ++-- + fs/file_table.c | 13 ++++++++----- + fs/internal.h | 1 + + include/linux/fs.h | 6 +++--- + 4 files changed, 14 insertions(+), 10 deletions(-) + +diff --git a/fs/backing-file.c b/fs/backing-file.c +index 09a9be945d45e6..892361c31c3de9 100644 +--- a/fs/backing-file.c ++++ b/fs/backing-file.c +@@ -41,7 +41,7 @@ struct file *backing_file_open(const struct path *user_path, int flags, + return f; + + path_get(user_path); +- *backing_file_user_path(f) = *user_path; ++ backing_file_set_user_path(f, user_path); + error = vfs_open(real_path, f); + if (error) { + fput(f); +@@ -65,7 +65,7 @@ struct file *backing_tmpfile_open(const struct path *user_path, int flags, + return f; + + path_get(user_path); +- *backing_file_user_path(f) = *user_path; ++ backing_file_set_user_path(f, user_path); + error = vfs_tmpfile(real_idmap, real_parentpath, f, mode); + if (error) { + fput(f); +diff --git a/fs/file_table.c b/fs/file_table.c +index 2a08bc93b0b9c1..75a1908d51a9f5 100644 +--- a/fs/file_table.c ++++ b/fs/file_table.c +@@ -48,17 +48,20 @@ struct backing_file { + struct path user_path; + }; + +-static inline struct backing_file *backing_file(struct file *f) +-{ +- return container_of(f, struct backing_file, file); +-} ++#define backing_file(f) container_of(f, struct backing_file, file) + +-struct path *backing_file_user_path(struct file *f) ++struct path *backing_file_user_path(const struct file *f) + { + return &backing_file(f)->user_path; + } + EXPORT_SYMBOL_GPL(backing_file_user_path); + ++void backing_file_set_user_path(struct file *f, const struct path *path) ++{ ++ backing_file(f)->user_path = *path; ++} ++EXPORT_SYMBOL_GPL(backing_file_set_user_path); ++ + static inline void backing_file_free(struct backing_file *ff) + { + path_put(&ff->user_path); +diff --git a/fs/internal.h b/fs/internal.h +index 8c1b7acbbe8faa..a4352d333c617a 100644 +--- a/fs/internal.h ++++ b/fs/internal.h +@@ -100,6 +100,7 @@ extern void chroot_fs_refs(const struct path *, const struct path *); + struct file *alloc_empty_file(int flags, const struct cred *cred); + struct file *alloc_empty_file_noaccount(int flags, const struct cred *cred); + struct file *alloc_empty_backing_file(int flags, const struct cred *cred); ++void backing_file_set_user_path(struct file *f, const struct path *path); + + static inline void file_put_write_access(struct file *file) + { +diff --git a/include/linux/fs.h b/include/linux/fs.h +index 87720e1b54192c..70bbc00a2bd250 100644 +--- a/include/linux/fs.h ++++ b/include/linux/fs.h +@@ -2738,7 +2738,7 @@ struct file *dentry_open(const struct path *path, int flags, + const struct cred *creds); + struct file *dentry_create(const struct path *path, int flags, umode_t mode, + const struct cred *cred); +-struct path *backing_file_user_path(struct file *f); ++struct path *backing_file_user_path(const struct file *f); + + /* + * When mmapping a file on a stackable filesystem (e.g., overlayfs), the file +@@ -2750,14 +2750,14 @@ struct path *backing_file_user_path(struct file *f); + * by fstat() on that same fd. + */ + /* Get the path to display in /proc//maps */ +-static inline const struct path *file_user_path(struct file *f) ++static inline const struct path *file_user_path(const struct file *f) + { + if (unlikely(f->f_mode & FMODE_BACKING)) + return backing_file_user_path(f); + return &f->f_path; + } + /* Get the inode whose inode number to display in /proc//maps */ +-static inline const struct inode *file_user_inode(struct file *f) ++static inline const struct inode *file_user_inode(const struct file *f) + { + if (unlikely(f->f_mode & FMODE_BACKING)) + return d_inode(backing_file_user_path(f)->dentry); +-- +2.53.0 + diff --git a/queue-6.12/inet-add-indirect-call-wrapper-for-getfrag-calls.patch b/queue-6.12/inet-add-indirect-call-wrapper-for-getfrag-calls.patch new file mode 100644 index 0000000000..29ec238600 --- /dev/null +++ b/queue-6.12/inet-add-indirect-call-wrapper-for-getfrag-calls.patch @@ -0,0 +1,104 @@ +From 60aebc08cbb02e6a3ec4d23a1428cd9b56ba7b01 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Tue, 3 Dec 2024 17:36:17 +0000 +Subject: inet: add indirect call wrapper for getfrag() calls + +From: Eric Dumazet + +[ Upstream commit 5204ccbfa22358f95afd031a3f337e6d9a74baea ] + +UDP send path suffers from one indirect call to ip_generic_getfrag() + +We can use INDIRECT_CALL_1() to avoid it. + +Signed-off-by: Eric Dumazet +Reviewed-by: Willem de Bruijn +Reviewed-by: Brian Vazquez +Link: https://patch.msgid.link/20241203173617.2595451-1-edumazet@google.com +Signed-off-by: Jakub Kicinski +Stable-dep-of: eca856950f7c ("ipv4: account for fraggap on the paged allocation path") +Signed-off-by: Sasha Levin +--- + net/ipv4/ip_output.c | 13 +++++++++---- + net/ipv6/ip6_output.c | 13 ++++++++----- + 2 files changed, 17 insertions(+), 9 deletions(-) + +diff --git a/net/ipv4/ip_output.c b/net/ipv4/ip_output.c +index ba51fc42531c26..0d0d6ebdd3a849 100644 +--- a/net/ipv4/ip_output.c ++++ b/net/ipv4/ip_output.c +@@ -1169,7 +1169,10 @@ static int __ip_append_data(struct sock *sk, + /* [!] NOTE: copy will be negative if pagedlen>0 + * because then the equation reduces to -fraggap. + */ +- if (copy > 0 && getfrag(from, data + transhdrlen, offset, copy, fraggap, skb) < 0) { ++ if (copy > 0 && ++ INDIRECT_CALL_1(getfrag, ip_generic_getfrag, ++ from, data + transhdrlen, offset, ++ copy, fraggap, skb) < 0) { + err = -EFAULT; + kfree_skb(skb); + goto error; +@@ -1213,8 +1216,9 @@ static int __ip_append_data(struct sock *sk, + unsigned int off; + + off = skb->len; +- if (getfrag(from, skb_put(skb, copy), +- offset, copy, off, skb) < 0) { ++ if (INDIRECT_CALL_1(getfrag, ip_generic_getfrag, ++ from, skb_put(skb, copy), ++ offset, copy, off, skb) < 0) { + __skb_trim(skb, off); + err = -EFAULT; + goto error; +@@ -1254,7 +1258,8 @@ static int __ip_append_data(struct sock *sk, + get_page(pfrag->page); + } + copy = min_t(int, copy, pfrag->size - pfrag->offset); +- if (getfrag(from, ++ if (INDIRECT_CALL_1(getfrag, ip_generic_getfrag, ++ from, + page_address(pfrag->page) + pfrag->offset, + offset, copy, skb->len, skb) < 0) + goto error_efault; +diff --git a/net/ipv6/ip6_output.c b/net/ipv6/ip6_output.c +index 3804ead05a35ae..50b41be5d38d0e 100644 +--- a/net/ipv6/ip6_output.c ++++ b/net/ipv6/ip6_output.c +@@ -1695,8 +1695,9 @@ static int __ip6_append_data(struct sock *sk, + pskb_trim_unique(skb_prev, maxfraglen); + } + if (copy > 0 && +- getfrag(from, data + transhdrlen, offset, +- copy, fraggap, skb) < 0) { ++ INDIRECT_CALL_1(getfrag, ip_generic_getfrag, ++ from, data + transhdrlen, offset, ++ copy, fraggap, skb) < 0) { + err = -EFAULT; + kfree_skb(skb); + goto error; +@@ -1740,8 +1741,9 @@ static int __ip6_append_data(struct sock *sk, + unsigned int off; + + off = skb->len; +- if (getfrag(from, skb_put(skb, copy), +- offset, copy, off, skb) < 0) { ++ if (INDIRECT_CALL_1(getfrag, ip_generic_getfrag, ++ from, skb_put(skb, copy), ++ offset, copy, off, skb) < 0) { + __skb_trim(skb, off); + err = -EFAULT; + goto error; +@@ -1781,7 +1783,8 @@ static int __ip6_append_data(struct sock *sk, + get_page(pfrag->page); + } + copy = min_t(int, copy, pfrag->size - pfrag->offset); +- if (getfrag(from, ++ if (INDIRECT_CALL_1(getfrag, ip_generic_getfrag, ++ from, + page_address(pfrag->page) + pfrag->offset, + offset, copy, skb->len, skb) < 0) + goto error_efault; +-- +2.53.0 + diff --git a/queue-6.12/ipv4-account-for-fraggap-on-the-paged-allocation-pat.patch b/queue-6.12/ipv4-account-for-fraggap-on-the-paged-allocation-pat.patch new file mode 100644 index 0000000000..2f9b8b423b --- /dev/null +++ b/queue-6.12/ipv4-account-for-fraggap-on-the-paged-allocation-pat.patch @@ -0,0 +1,67 @@ +From 692c70e7ed2b528cf703ce2f6263caa9380fc03a Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Tue, 16 Jun 2026 22:38:29 +0900 +Subject: ipv4: account for fraggap on the paged allocation path + +From: Wongi Lee + +[ Upstream commit eca856950f7cb1a221e02b99d758409f2c5cec42 ] + +In __ip_append_data(), when the paged-allocation branch is taken, +alloclen and pagedlen are computed as + + alloclen = fragheaderlen + transhdrlen; + pagedlen = datalen - transhdrlen; + +datalen already includes fraggap, but the fraggap bytes carried over +from the previous skb are copied into the new skb's linear area at +offset transhdrlen by the subsequent skb_copy_and_csum_bits(). The +linear area is therefore undersized by fraggap bytes while pagedlen is +overstated by the same amount. + +The non-paged branch sets alloclen to fraglen, which already accounts +for fraggap because datalen does. Bring the paged branch in line by +adding fraggap to alloclen and subtracting it from pagedlen. + +After this adjustment, copy no longer collapses to -fraggap on the +paged path, so remove the stale comment describing that old arithmetic. + +Fixes: 8eb77cc73977 ("ipv4: avoid partial copy for zc") +Signed-off-by: Jungwoo Lee +Signed-off-by: Wongi Lee +Reviewed-by: Ido Schimmel +Link: https://patch.msgid.link/ajFR1eLAIs42TN3g@DESKTOP-19IMU7U.localdomain +Signed-off-by: Jakub Kicinski +Signed-off-by: Sasha Levin +--- + net/ipv4/ip_output.c | 7 ++----- + 1 file changed, 2 insertions(+), 5 deletions(-) + +diff --git a/net/ipv4/ip_output.c b/net/ipv4/ip_output.c +index 0d0d6ebdd3a849..41031c5c430334 100644 +--- a/net/ipv4/ip_output.c ++++ b/net/ipv4/ip_output.c +@@ -1118,8 +1118,8 @@ static int __ip_append_data(struct sock *sk, + !(rt->dst.dev->features & NETIF_F_SG))) + alloclen = fraglen; + else { +- alloclen = fragheaderlen + transhdrlen; +- pagedlen = datalen - transhdrlen; ++ alloclen = fragheaderlen + transhdrlen + fraggap; ++ pagedlen = datalen - transhdrlen - fraggap; + } + + alloclen += alloc_extra; +@@ -1166,9 +1166,6 @@ static int __ip_append_data(struct sock *sk, + } + + copy = datalen - transhdrlen - fraggap - pagedlen; +- /* [!] NOTE: copy will be negative if pagedlen>0 +- * because then the equation reduces to -fraggap. +- */ + if (copy > 0 && + INDIRECT_CALL_1(getfrag, ip_generic_getfrag, + from, data + transhdrlen, offset, +-- +2.53.0 + diff --git a/queue-6.12/lsm-add-backing_file-lsm-hooks.patch b/queue-6.12/lsm-add-backing_file-lsm-hooks.patch new file mode 100644 index 0000000000..47a7115cb7 --- /dev/null +++ b/queue-6.12/lsm-add-backing_file-lsm-hooks.patch @@ -0,0 +1,569 @@ +From e76e97ac1e9aca9725530e959355309c863cf334 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Mon, 29 Jun 2026 15:03:37 +0800 +Subject: lsm: add backing_file LSM hooks + +From: Paul Moore + +[ Upstream commit 6af36aeb147a06dea47c49859cd6ca5659aeb987 ] + +Stacked filesystems such as overlayfs do not currently provide the +necessary mechanisms for LSMs to properly enforce access controls on the +mmap() and mprotect() operations. In order to resolve this gap, a LSM +security blob is being added to the backing_file struct and the following +new LSM hooks are being created: + + security_backing_file_alloc() + security_backing_file_free() + security_mmap_backing_file() + +The first two hooks are to manage the lifecycle of the LSM security blob +in the backing_file struct, while the third provides a new mmap() access +control point for the underlying backing file. It is also expected that +LSMs will likely want to update their security_file_mprotect() callback +to address issues with their mprotect() controls, but that does not +require a change to the security_file_mprotect() LSM hook. + +There are a three other small changes to support these new LSM hooks: +* Pass the user file associated with a backing file down to +alloc_empty_backing_file() so it can be included in the +security_backing_file_alloc() hook. +* Add getter and setter functions for the backing_file struct LSM blob +as the backing_file struct remains private to fs/file_table.c. +* Constify the file struct field in the LSM common_audit_data struct to +better support LSMs that need to pass a const file struct pointer into +the common LSM audit code. + +Thanks to Arnd Bergmann for identifying the missing EXPORT_SYMBOL_GPL() +and supplying a fixup. + +Cc: stable@vger.kernel.org +Cc: linux-fsdevel@vger.kernel.org +Cc: linux-unionfs@vger.kernel.org +Cc: linux-erofs@lists.ozlabs.org +Reviewed-by: Amir Goldstein +Reviewed-by: Serge Hallyn +Reviewed-by: Christian Brauner +Signed-off-by: Paul Moore +[ Mainline declares lsm_backing_file_cache in security/lsm.h. Linux 6.12.y +does not have security/lsm_init.c or security/lsm.h; the cache variable +is defined locally as static struct kmem_cache *lsm_backing_file_cache in +security/security.c. ] +Signed-off-by: Cai Xinchen +Signed-off-by: Sasha Levin +--- + fs/backing-file.c | 18 ++++-- + fs/file_table.c | 27 +++++++-- + fs/fuse/passthrough.c | 2 +- + fs/internal.h | 3 +- + fs/overlayfs/dir.c | 2 +- + fs/overlayfs/file.c | 3 +- + include/linux/backing-file.h | 4 +- + include/linux/fs.h | 13 ++++ + include/linux/lsm_audit.h | 2 +- + include/linux/lsm_hook_defs.h | 5 ++ + include/linux/lsm_hooks.h | 1 + + include/linux/security.h | 22 +++++++ + security/security.c | 109 ++++++++++++++++++++++++++++++++++ + 13 files changed, 195 insertions(+), 16 deletions(-) + +diff --git a/fs/backing-file.c b/fs/backing-file.c +index 892361c31c3de9..53690754810f1d 100644 +--- a/fs/backing-file.c ++++ b/fs/backing-file.c +@@ -12,6 +12,7 @@ + #include + #include + #include ++#include + + #include "internal.h" + +@@ -29,14 +30,15 @@ + * 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 path *user_path, int flags, ++struct file *backing_file_open(const struct file *user_file, int flags, + const struct path *real_path, + const struct cred *cred) + { ++ const struct path *user_path = &user_file->f_path; + struct file *f; + int error; + +- f = alloc_empty_backing_file(flags, cred); ++ f = alloc_empty_backing_file(flags, cred, user_file); + if (IS_ERR(f)) + return f; + +@@ -52,15 +54,16 @@ struct file *backing_file_open(const struct path *user_path, int flags, + } + EXPORT_SYMBOL_GPL(backing_file_open); + +-struct file *backing_tmpfile_open(const struct path *user_path, int flags, ++struct file *backing_tmpfile_open(const struct file *user_file, int flags, + const struct path *real_parentpath, + umode_t mode, const struct cred *cred) + { + struct mnt_idmap *real_idmap = mnt_idmap(real_parentpath->mnt); ++ const struct path *user_path = &user_file->f_path; + struct file *f; + int error; + +- f = alloc_empty_backing_file(flags, cred); ++ f = alloc_empty_backing_file(flags, cred, user_file); + if (IS_ERR(f)) + return f; + +@@ -326,6 +329,7 @@ EXPORT_SYMBOL_GPL(backing_file_splice_write); + int backing_file_mmap(struct file *file, struct vm_area_struct *vma, + struct backing_file_ctx *ctx) + { ++ struct file *user_file = vma->vm_file; + const struct cred *old_cred; + int ret; + +@@ -339,6 +343,12 @@ int backing_file_mmap(struct file *file, struct vm_area_struct *vma, + vma_set_file(vma, file); + + old_cred = override_creds(ctx->cred); ++ ret = security_mmap_backing_file(vma, file, user_file); ++ if (ret) { ++ revert_creds(old_cred); ++ return ret; ++ } ++ + ret = call_mmap(vma->vm_file, vma); + revert_creds(old_cred); + +diff --git a/fs/file_table.c b/fs/file_table.c +index 75a1908d51a9f5..f78bcff79f1a04 100644 +--- a/fs/file_table.c ++++ b/fs/file_table.c +@@ -46,6 +46,9 @@ static struct percpu_counter nr_files __cacheline_aligned_in_smp; + struct backing_file { + struct file file; + struct path user_path; ++#ifdef CONFIG_SECURITY ++ void *security; ++#endif + }; + + #define backing_file(f) container_of(f, struct backing_file, file) +@@ -62,8 +65,21 @@ void backing_file_set_user_path(struct file *f, const struct path *path) + } + EXPORT_SYMBOL_GPL(backing_file_set_user_path); + ++#ifdef CONFIG_SECURITY ++void *backing_file_security(const struct file *f) ++{ ++ return backing_file(f)->security; ++} ++ ++void backing_file_set_security(struct file *f, void *security) ++{ ++ backing_file(f)->security = security; ++} ++#endif /* CONFIG_SECURITY */ ++ + static inline void backing_file_free(struct backing_file *ff) + { ++ security_backing_file_free(&ff->file); + path_put(&ff->user_path); + kfree(ff); + } +@@ -262,10 +278,12 @@ struct file *alloc_empty_file_noaccount(int flags, const struct cred *cred) + return f; + } + +-static int init_backing_file(struct backing_file *ff) ++static int init_backing_file(struct backing_file *ff, ++ const struct file *user_file) + { + memset(&ff->user_path, 0, sizeof(ff->user_path)); +- return 0; ++ backing_file_set_security(&ff->file, NULL); ++ return security_backing_file_alloc(&ff->file, user_file); + } + + /* +@@ -275,7 +293,8 @@ static int init_backing_file(struct backing_file *ff) + * This is only for kernel internal use, and the allocate file must not be + * installed into file tables or such. + */ +-struct file *alloc_empty_backing_file(int flags, const struct cred *cred) ++struct file *alloc_empty_backing_file(int flags, const struct cred *cred, ++ const struct file *user_file) + { + struct backing_file *ff; + int error; +@@ -292,7 +311,7 @@ struct file *alloc_empty_backing_file(int flags, const struct cred *cred) + + /* The f_mode flags must be set before fput(). */ + ff->file.f_mode |= FMODE_BACKING | FMODE_NOACCOUNT; +- error = init_backing_file(ff); ++ error = init_backing_file(ff, user_file); + if (unlikely(error)) { + fput(&ff->file); + return ERR_PTR(error); +diff --git a/fs/fuse/passthrough.c b/fs/fuse/passthrough.c +index 6bfd09dda9e3e6..140e150be0de85 100644 +--- a/fs/fuse/passthrough.c ++++ b/fs/fuse/passthrough.c +@@ -326,7 +326,7 @@ struct fuse_backing *fuse_passthrough_open(struct file *file, + goto out; + + /* Allocate backing file per fuse file to store fuse path */ +- backing_file = backing_file_open(&file->f_path, file->f_flags, ++ backing_file = backing_file_open(file, file->f_flags, + &fb->file->f_path, fb->cred); + err = PTR_ERR(backing_file); + if (IS_ERR(backing_file)) { +diff --git a/fs/internal.h b/fs/internal.h +index a4352d333c617a..997acc721f6175 100644 +--- a/fs/internal.h ++++ b/fs/internal.h +@@ -99,7 +99,8 @@ extern void chroot_fs_refs(const struct path *, const struct path *); + */ + struct file *alloc_empty_file(int flags, const struct cred *cred); + struct file *alloc_empty_file_noaccount(int flags, const struct cred *cred); +-struct file *alloc_empty_backing_file(int flags, const struct cred *cred); ++struct file *alloc_empty_backing_file(int flags, const struct cred *cred, ++ const struct file *user_file); + void backing_file_set_user_path(struct file *f, const struct path *path); + + static inline void file_put_write_access(struct file *file) +diff --git a/fs/overlayfs/dir.c b/fs/overlayfs/dir.c +index ab65e98a1defdd..1c8009bf194bc1 100644 +--- a/fs/overlayfs/dir.c ++++ b/fs/overlayfs/dir.c +@@ -1320,7 +1320,7 @@ static int ovl_create_tmpfile(struct file *file, struct dentry *dentry, + goto out_revert_creds; + + ovl_path_upper(dentry->d_parent, &realparentpath); +- realfile = backing_tmpfile_open(&file->f_path, flags, &realparentpath, ++ realfile = backing_tmpfile_open(file, flags, &realparentpath, + mode, current_cred()); + err = PTR_ERR_OR_ZERO(realfile); + pr_debug("tmpfile/open(%pd2, 0%o) = %i\n", realparentpath.dentry, mode, err); +diff --git a/fs/overlayfs/file.c b/fs/overlayfs/file.c +index 94095058da34ec..3765e1defa1981 100644 +--- a/fs/overlayfs/file.c ++++ b/fs/overlayfs/file.c +@@ -47,8 +47,7 @@ static struct file *ovl_open_realfile(const struct file *file, + } else { + if (!inode_owner_or_capable(real_idmap, realinode)) + flags &= ~O_NOATIME; +- +- realfile = backing_file_open(file_user_path((struct file *) file), ++ realfile = backing_file_open(file, + flags, realpath, current_cred()); + } + revert_creds(old_cred); +diff --git a/include/linux/backing-file.h b/include/linux/backing-file.h +index 2eed0ffb5e8f83..cd18acd7ac5b29 100644 +--- a/include/linux/backing-file.h ++++ b/include/linux/backing-file.h +@@ -19,10 +19,10 @@ struct backing_file_ctx { + void (*end_write)(struct file *, loff_t, ssize_t); + }; + +-struct file *backing_file_open(const struct path *user_path, int flags, ++struct file *backing_file_open(const struct file *user_file, int flags, + const struct path *real_path, + const struct cred *cred); +-struct file *backing_tmpfile_open(const struct path *user_path, int flags, ++struct file *backing_tmpfile_open(const struct file *user_file, int flags, + const struct path *real_parentpath, + umode_t mode, const struct cred *cred); + ssize_t backing_file_read_iter(struct file *file, struct iov_iter *iter, +diff --git a/include/linux/fs.h b/include/linux/fs.h +index 70bbc00a2bd250..0eb43147dc877e 100644 +--- a/include/linux/fs.h ++++ b/include/linux/fs.h +@@ -2740,6 +2740,19 @@ struct file *dentry_create(const struct path *path, int flags, umode_t mode, + const struct cred *cred); + struct path *backing_file_user_path(const struct file *f); + ++#ifdef CONFIG_SECURITY ++void *backing_file_security(const struct file *f); ++void backing_file_set_security(struct file *f, void *security); ++#else ++static inline void *backing_file_security(const struct file *f) ++{ ++ return NULL; ++} ++static inline void backing_file_set_security(struct file *f, void *security) ++{ ++} ++#endif /* CONFIG_SECURITY */ ++ + /* + * When mmapping a file on a stackable filesystem (e.g., overlayfs), the file + * stored in ->vm_file is a backing file whose f_inode is on the underlying +diff --git a/include/linux/lsm_audit.h b/include/linux/lsm_audit.h +index 97a8b21eb03339..c0a2839253fa1f 100644 +--- a/include/linux/lsm_audit.h ++++ b/include/linux/lsm_audit.h +@@ -93,7 +93,7 @@ struct common_audit_data { + #endif + char *kmod_name; + struct lsm_ioctlop_audit *op; +- struct file *file; ++ const struct file *file; + struct lsm_ibpkey_audit *ibpkey; + struct lsm_ibendport_audit *ibendport; + int reason; +diff --git a/include/linux/lsm_hook_defs.h b/include/linux/lsm_hook_defs.h +index 9eca013aa5e1f6..addb34abffa18a 100644 +--- a/include/linux/lsm_hook_defs.h ++++ b/include/linux/lsm_hook_defs.h +@@ -188,6 +188,9 @@ LSM_HOOK(int, 0, file_permission, struct file *file, int mask) + LSM_HOOK(int, 0, file_alloc_security, struct file *file) + LSM_HOOK(void, LSM_RET_VOID, file_release, struct file *file) + LSM_HOOK(void, LSM_RET_VOID, file_free_security, struct file *file) ++LSM_HOOK(int, 0, backing_file_alloc, struct file *backing_file, ++ const struct file *user_file) ++LSM_HOOK(void, LSM_RET_VOID, backing_file_free, struct file *backing_file) + LSM_HOOK(int, 0, file_ioctl, struct file *file, unsigned int cmd, + unsigned long arg) + LSM_HOOK(int, 0, file_ioctl_compat, struct file *file, unsigned int cmd, +@@ -195,6 +198,8 @@ LSM_HOOK(int, 0, file_ioctl_compat, struct file *file, unsigned int cmd, + LSM_HOOK(int, 0, mmap_addr, unsigned long addr) + LSM_HOOK(int, 0, mmap_file, struct file *file, unsigned long reqprot, + unsigned long prot, unsigned long flags) ++LSM_HOOK(int, 0, mmap_backing_file, struct vm_area_struct *vma, ++ struct file *backing_file, struct file *user_file) + LSM_HOOK(int, 0, file_mprotect, struct vm_area_struct *vma, + unsigned long reqprot, unsigned long prot) + LSM_HOOK(int, 0, file_lock, struct file *file, unsigned int cmd) +diff --git a/include/linux/lsm_hooks.h b/include/linux/lsm_hooks.h +index 090d1d3e19fed6..0876cf11e200c6 100644 +--- a/include/linux/lsm_hooks.h ++++ b/include/linux/lsm_hooks.h +@@ -104,6 +104,7 @@ struct security_hook_list { + struct lsm_blob_sizes { + int lbs_cred; + int lbs_file; ++ int lbs_backing_file; + int lbs_ib; + int lbs_inode; + int lbs_sock; +diff --git a/include/linux/security.h b/include/linux/security.h +index 2c6db949ad1a16..e4300f2ff11b55 100644 +--- a/include/linux/security.h ++++ b/include/linux/security.h +@@ -421,11 +421,17 @@ int security_file_permission(struct file *file, int mask); + int security_file_alloc(struct file *file); + void security_file_release(struct file *file); + void security_file_free(struct file *file); ++int security_backing_file_alloc(struct file *backing_file, ++ const struct file *user_file); ++void security_backing_file_free(struct file *backing_file); + int security_file_ioctl(struct file *file, unsigned int cmd, unsigned long arg); + int security_file_ioctl_compat(struct file *file, unsigned int cmd, + unsigned long arg); + int security_mmap_file(struct file *file, unsigned long prot, + unsigned long flags); ++int security_mmap_backing_file(struct vm_area_struct *vma, ++ struct file *backing_file, ++ struct file *user_file); + int security_mmap_addr(unsigned long addr); + int security_file_mprotect(struct vm_area_struct *vma, unsigned long reqprot, + unsigned long prot); +@@ -1065,6 +1071,15 @@ static inline void security_file_release(struct file *file) + static inline void security_file_free(struct file *file) + { } + ++static inline int security_backing_file_alloc(struct file *backing_file, ++ const struct file *user_file) ++{ ++ return 0; ++} ++ ++static inline void security_backing_file_free(struct file *backing_file) ++{ } ++ + static inline int security_file_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) + { +@@ -1084,6 +1099,13 @@ static inline int security_mmap_file(struct file *file, unsigned long prot, + return 0; + } + ++static inline int security_mmap_backing_file(struct vm_area_struct *vma, ++ struct file *backing_file, ++ struct file *user_file) ++{ ++ return 0; ++} ++ + static inline int security_mmap_addr(unsigned long addr) + { + return cap_mmap_addr(addr); +diff --git a/security/security.c b/security/security.c +index 6e4deac6ec0737..dd6b922c12de05 100644 +--- a/security/security.c ++++ b/security/security.c +@@ -95,6 +95,7 @@ const char *const lockdown_reasons[LOCKDOWN_CONFIDENTIALITY_MAX + 1] = { + static BLOCKING_NOTIFIER_HEAD(blocking_lsm_notifier_chain); + + static struct kmem_cache *lsm_file_cache; ++static struct kmem_cache *lsm_backing_file_cache; + static struct kmem_cache *lsm_inode_cache; + + char *lsm_names; +@@ -266,6 +267,7 @@ static void __init lsm_set_blob_sizes(struct lsm_blob_sizes *needed) + + lsm_set_blob_size(&needed->lbs_cred, &blob_sizes.lbs_cred); + lsm_set_blob_size(&needed->lbs_file, &blob_sizes.lbs_file); ++ lsm_set_blob_size(&needed->lbs_backing_file, &blob_sizes.lbs_backing_file); + lsm_set_blob_size(&needed->lbs_ib, &blob_sizes.lbs_ib); + /* + * The inode blob gets an rcu_head in addition to +@@ -468,6 +470,7 @@ static void __init ordered_lsm_init(void) + + init_debug("cred blob size = %d\n", blob_sizes.lbs_cred); + init_debug("file blob size = %d\n", blob_sizes.lbs_file); ++ init_debug("lsm_backing_file_cache = %d\n", blob_sizes.lbs_backing_file); + init_debug("ib blob size = %d\n", blob_sizes.lbs_ib); + init_debug("inode blob size = %d\n", blob_sizes.lbs_inode); + init_debug("ipc blob size = %d\n", blob_sizes.lbs_ipc); +@@ -490,6 +493,11 @@ static void __init ordered_lsm_init(void) + lsm_file_cache = kmem_cache_create("lsm_file_cache", + blob_sizes.lbs_file, 0, + SLAB_PANIC, NULL); ++ if (blob_sizes.lbs_backing_file) ++ lsm_backing_file_cache = kmem_cache_create( ++ "lsm_backing_file_cache", ++ blob_sizes.lbs_backing_file, ++ 0, SLAB_PANIC, NULL); + if (blob_sizes.lbs_inode) + lsm_inode_cache = kmem_cache_create("lsm_inode_cache", + blob_sizes.lbs_inode, 0, +@@ -666,6 +674,30 @@ int unregister_blocking_lsm_notifier(struct notifier_block *nb) + } + EXPORT_SYMBOL(unregister_blocking_lsm_notifier); + ++/** ++ * lsm_backing_file_alloc - allocate a composite backing file blob ++ * @backing_file: the backing file ++ * ++ * Allocate the backing file blob for all the modules. ++ * ++ * Returns 0, or -ENOMEM if memory can't be allocated. ++ */ ++static int lsm_backing_file_alloc(struct file *backing_file) ++{ ++ void *blob; ++ ++ if (!lsm_backing_file_cache) { ++ backing_file_set_security(backing_file, NULL); ++ return 0; ++ } ++ ++ blob = kmem_cache_zalloc(lsm_backing_file_cache, GFP_KERNEL); ++ backing_file_set_security(backing_file, blob); ++ if (!blob) ++ return -ENOMEM; ++ return 0; ++} ++ + /** + * lsm_blob_alloc - allocate a composite blob + * @dest: the destination for the blob +@@ -2893,6 +2925,57 @@ void security_file_free(struct file *file) + } + } + ++/** ++ * security_backing_file_alloc() - Allocate and setup a backing file blob ++ * @backing_file: the backing file ++ * @user_file: the associated user visible file ++ * ++ * Allocate a backing file LSM blob and perform any necessary initialization of ++ * the LSM blob. There will be some operations where the LSM will not have ++ * access to @user_file after this point, so any important state associated ++ * with @user_file that is important to the LSM should be captured in the ++ * backing file's LSM blob. ++ * ++ * LSM's should avoid taking a reference to @user_file in this hook as it will ++ * result in problems later when the system attempts to drop/put the file ++ * references due to a circular dependency. ++ * ++ * Return: Return 0 if the hook is successful, negative values otherwise. ++ */ ++int security_backing_file_alloc(struct file *backing_file, ++ const struct file *user_file) ++{ ++ int rc; ++ ++ rc = lsm_backing_file_alloc(backing_file); ++ if (rc) ++ return rc; ++ rc = call_int_hook(backing_file_alloc, backing_file, user_file); ++ if (unlikely(rc)) ++ security_backing_file_free(backing_file); ++ ++ return rc; ++} ++ ++/** ++ * security_backing_file_free() - Free a backing file blob ++ * @backing_file: the backing file ++ * ++ * Free any LSM state associate with a backing file's LSM blob, including the ++ * blob itself. ++ */ ++void security_backing_file_free(struct file *backing_file) ++{ ++ void *blob = backing_file_security(backing_file); ++ ++ call_void_hook(backing_file_free, backing_file); ++ ++ if (blob) { ++ backing_file_set_security(backing_file, NULL); ++ kmem_cache_free(lsm_backing_file_cache, blob); ++ } ++} ++ + /** + * security_file_ioctl() - Check if an ioctl is allowed + * @file: associated file +@@ -2981,6 +3064,32 @@ int security_mmap_file(struct file *file, unsigned long prot, + flags); + } + ++/** ++ * security_mmap_backing_file - Check if mmap'ing a backing file is allowed ++ * @vma: the vm_area_struct for the mmap'd region ++ * @backing_file: the backing file being mmap'd ++ * @user_file: the user file being mmap'd ++ * ++ * Check permissions for a mmap operation on a stacked filesystem. This hook ++ * is called after the security_mmap_file() and is responsible for authorizing ++ * the mmap on @backing_file. It is important to note that the mmap operation ++ * on @user_file has already been authorized and the @vma->vm_file has been ++ * set to @backing_file. ++ * ++ * Return: Returns 0 if permission is granted. ++ */ ++int security_mmap_backing_file(struct vm_area_struct *vma, ++ struct file *backing_file, ++ struct file *user_file) ++{ ++ /* recommended by the stackable filesystem devs */ ++ if (WARN_ON_ONCE(!(backing_file->f_mode & FMODE_BACKING))) ++ return -EIO; ++ ++ return call_int_hook(mmap_backing_file, vma, backing_file, user_file); ++} ++EXPORT_SYMBOL_GPL(security_mmap_backing_file); ++ + /** + * security_mmap_addr() - Check if mmap'ing an address is allowed + * @addr: address +-- +2.53.0 + diff --git a/queue-6.12/selinux-fix-overlayfs-mmap-and-mprotect-access-check.patch b/queue-6.12/selinux-fix-overlayfs-mmap-and-mprotect-access-check.patch new file mode 100644 index 0000000000..89002fde3c --- /dev/null +++ b/queue-6.12/selinux-fix-overlayfs-mmap-and-mprotect-access-check.patch @@ -0,0 +1,426 @@ +From e2a6ca4834e885271a38539223767282388191d2 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Mon, 29 Jun 2026 15:03:38 +0800 +Subject: selinux: fix overlayfs mmap() and mprotect() access checks + +From: Paul Moore + +[ Upstream commit 82544d36b1729153c8aeb179e84750f0c085d3b1 ] + +The existing SELinux security model for overlayfs is to allow access if +the current task is able to access the top level file (the "user" file) +and the mounter's credentials are sufficient to access the lower +level file (the "backing" file). Unfortunately, the current code does +not properly enforce these access controls for both mmap() and mprotect() +operations on overlayfs filesystems. + +This patch makes use of the newly created security_mmap_backing_file() +LSM hook to provide the missing backing file enforcement for mmap() +operations, and leverages the backing file API and new LSM blob to +provide the necessary information to properly enforce the mprotect() +access controls. + +Cc: stable@vger.kernel.org +Acked-by: Amir Goldstein +Signed-off-by: Paul Moore +Signed-off-by: Cai Xinchen +Signed-off-by: Sasha Levin +--- + security/selinux/hooks.c | 242 ++++++++++++++++++++++-------- + security/selinux/include/objsec.h | 11 ++ + 2 files changed, 189 insertions(+), 64 deletions(-) + +diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c +index 8e31d3b60fc62e..1b89c8d5fa2fc3 100644 +--- a/security/selinux/hooks.c ++++ b/security/selinux/hooks.c +@@ -1724,49 +1724,72 @@ static inline int file_path_has_perm(const struct cred *cred, + static int bpf_fd_pass(const struct file *file, u32 sid); + #endif + +-/* Check whether a task can use an open file descriptor to +- access an inode in a given way. Check access to the +- descriptor itself, and then use dentry_has_perm to +- check a particular permission to the file. +- Access to the descriptor is implicitly granted if it +- has the same SID as the process. If av is zero, then +- access to the file is not checked, e.g. for cases +- where only the descriptor is affected like seek. */ +-static int file_has_perm(const struct cred *cred, +- struct file *file, +- u32 av) ++static int __file_has_perm(const struct cred *cred, const struct file *file, ++ u32 av, bool bf_user_file) ++ + { +- struct file_security_struct *fsec = selinux_file(file); +- struct inode *inode = file_inode(file); + struct common_audit_data ad; +- u32 sid = cred_sid(cred); ++ struct inode *inode; ++ u32 ssid = cred_sid(cred); ++ u32 tsid_fd; + int rc; + +- ad.type = LSM_AUDIT_DATA_FILE; +- ad.u.file = file; ++ if (bf_user_file) { ++ struct backing_file_security_struct *bfsec; ++ const struct path *path; + +- if (sid != fsec->sid) { +- rc = avc_has_perm(sid, fsec->sid, +- SECCLASS_FD, +- FD__USE, +- &ad); ++ if (WARN_ON(!(file->f_mode & FMODE_BACKING))) ++ return -EIO; ++ ++ bfsec = selinux_backing_file(file); ++ path = backing_file_user_path(file); ++ tsid_fd = bfsec->uf_sid; ++ inode = d_inode(path->dentry); ++ ++ ad.type = LSM_AUDIT_DATA_PATH; ++ ad.u.path = *path; ++ } else { ++ struct file_security_struct *fsec = selinux_file(file); ++ ++ tsid_fd = fsec->sid; ++ inode = file_inode(file); ++ ++ ad.type = LSM_AUDIT_DATA_FILE; ++ ad.u.file = file; ++ } ++ ++ if (ssid != tsid_fd) { ++ rc = avc_has_perm(ssid, tsid_fd, SECCLASS_FD, FD__USE, &ad); + if (rc) +- goto out; ++ return rc; + } + + #ifdef CONFIG_BPF_SYSCALL +- rc = bpf_fd_pass(file, cred_sid(cred)); ++ /* regardless of backing vs user file, use the underlying file here */ ++ rc = bpf_fd_pass(file, ssid); + if (rc) + return rc; + #endif + + /* av is zero if only checking access to the descriptor. */ +- rc = 0; + if (av) +- rc = inode_has_perm(cred, inode, av, &ad); ++ return inode_has_perm(cred, inode, av, &ad); + +-out: +- return rc; ++ return 0; ++} ++ ++/* Check whether a task can use an open file descriptor to ++ access an inode in a given way. Check access to the ++ descriptor itself, and then use dentry_has_perm to ++ check a particular permission to the file. ++ Access to the descriptor is implicitly granted if it ++ has the same SID as the process. If av is zero, then ++ access to the file is not checked, e.g. for cases ++ where only the descriptor is affected like seek. */ ++static inline int file_has_perm(const struct cred *cred, ++ const struct file *file, u32 av) ++{ ++ return __file_has_perm(cred, file, av, false); + } + + /* +@@ -3653,6 +3676,17 @@ static int selinux_file_alloc_security(struct file *file) + return 0; + } + ++static int selinux_backing_file_alloc(struct file *backing_file, ++ const struct file *user_file) ++{ ++ struct backing_file_security_struct *bfsec; ++ ++ bfsec = selinux_backing_file(backing_file); ++ bfsec->uf_sid = selinux_file(user_file)->sid; ++ ++ return 0; ++} ++ + /* + * Check whether a task has the ioctl permission and cmd + * operation to an inode. +@@ -3770,42 +3804,55 @@ static int selinux_file_ioctl_compat(struct file *file, unsigned int cmd, + + static int default_noexec __ro_after_init; + +-static int file_map_prot_check(struct file *file, unsigned long prot, int shared) ++static int __file_map_prot_check(const struct cred *cred, ++ const struct file *file, unsigned long prot, ++ bool shared, bool bf_user_file) + { +- const struct cred *cred = current_cred(); +- u32 sid = cred_sid(cred); +- int rc = 0; ++ struct inode *inode = NULL; ++ bool prot_exec = prot & PROT_EXEC; ++ bool prot_write = prot & PROT_WRITE; ++ ++ if (file) { ++ if (bf_user_file) ++ inode = d_inode(backing_file_user_path(file)->dentry); ++ else ++ inode = file_inode(file); ++ } ++ ++ if (default_noexec && prot_exec && ++ (!file || IS_PRIVATE(inode) || (!shared && prot_write))) { ++ int rc; ++ u32 sid = cred_sid(cred); + +- if (default_noexec && +- (prot & PROT_EXEC) && (!file || IS_PRIVATE(file_inode(file)) || +- (!shared && (prot & PROT_WRITE)))) { + /* +- * We are making executable an anonymous mapping or a +- * private file mapping that will also be writable. +- * This has an additional check. ++ * We are making executable an anonymous mapping or a private ++ * file mapping that will also be writable. + */ +- rc = avc_has_perm(sid, sid, SECCLASS_PROCESS, +- PROCESS__EXECMEM, NULL); ++ rc = avc_has_perm(sid, sid, SECCLASS_PROCESS, PROCESS__EXECMEM, ++ NULL); + if (rc) +- goto error; ++ return rc; + } + + if (file) { +- /* read access is always possible with a mapping */ ++ /* "read" always possible, "write" only if shared */ + u32 av = FILE__READ; +- +- /* write access only matters if the mapping is shared */ +- if (shared && (prot & PROT_WRITE)) ++ if (shared && prot_write) + av |= FILE__WRITE; +- +- if (prot & PROT_EXEC) ++ if (prot_exec) + av |= FILE__EXECUTE; + +- return file_has_perm(cred, file, av); ++ return __file_has_perm(cred, file, av, bf_user_file); + } + +-error: +- return rc; ++ return 0; ++} ++ ++static inline int file_map_prot_check(const struct cred *cred, ++ const struct file *file, ++ unsigned long prot, bool shared) ++{ ++ return __file_map_prot_check(cred, file, prot, shared, false); + } + + static int selinux_mmap_addr(unsigned long addr) +@@ -3821,36 +3868,80 @@ static int selinux_mmap_addr(unsigned long addr) + return rc; + } + +-static int selinux_mmap_file(struct file *file, +- unsigned long reqprot __always_unused, +- unsigned long prot, unsigned long flags) ++static int selinux_mmap_file_common(const struct cred *cred, struct file *file, ++ unsigned long prot, bool shared) + { +- struct common_audit_data ad; +- int rc; +- + if (file) { ++ int rc; ++ struct common_audit_data ad; ++ + ad.type = LSM_AUDIT_DATA_FILE; + ad.u.file = file; +- rc = inode_has_perm(current_cred(), file_inode(file), +- FILE__MAP, &ad); ++ rc = inode_has_perm(cred, file_inode(file), FILE__MAP, &ad); + if (rc) + return rc; + } + +- return file_map_prot_check(file, prot, +- (flags & MAP_TYPE) == MAP_SHARED); ++ return file_map_prot_check(cred, file, prot, shared); ++} ++ ++static int selinux_mmap_file(struct file *file, ++ unsigned long reqprot __always_unused, ++ unsigned long prot, unsigned long flags) ++{ ++ return selinux_mmap_file_common(current_cred(), file, prot, ++ (flags & MAP_TYPE) == MAP_SHARED); ++} ++ ++/** ++ * selinux_mmap_backing_file - Check mmap permissions on a backing file ++ * @vma: memory region ++ * @backing_file: stacked filesystem backing file ++ * @user_file: user visible file ++ * ++ * This is called after selinux_mmap_file() on stacked filesystems, and it ++ * is this function's responsibility to verify access to @backing_file and ++ * setup the SELinux state for possible later use in the mprotect() code path. ++ * ++ * By the time this function is called, mmap() access to @user_file has already ++ * been authorized and @vma->vm_file has been set to point to @backing_file. ++ * ++ * Return zero on success, negative values otherwise. ++ */ ++static int selinux_mmap_backing_file(struct vm_area_struct *vma, ++ struct file *backing_file, ++ struct file *user_file __always_unused) ++{ ++ unsigned long prot = 0; ++ ++ /* translate vma->vm_flags perms into PROT perms */ ++ if (vma->vm_flags & VM_READ) ++ prot |= PROT_READ; ++ if (vma->vm_flags & VM_WRITE) ++ prot |= PROT_WRITE; ++ if (vma->vm_flags & VM_EXEC) ++ prot |= PROT_EXEC; ++ ++ return selinux_mmap_file_common(backing_file->f_cred, backing_file, ++ prot, vma->vm_flags & VM_SHARED); + } + + static int selinux_file_mprotect(struct vm_area_struct *vma, + unsigned long reqprot __always_unused, + unsigned long prot) + { ++ int rc; + const struct cred *cred = current_cred(); + u32 sid = cred_sid(cred); ++ const struct file *file = vma->vm_file; ++ bool backing_file; ++ bool shared = vma->vm_flags & VM_SHARED; ++ ++ /* check if we need to trigger the "backing files are awful" mode */ ++ backing_file = file && (file->f_mode & FMODE_BACKING); + + if (default_noexec && + (prot & PROT_EXEC) && !(vma->vm_flags & VM_EXEC)) { +- int rc = 0; + /* + * We don't use the vma_is_initial_heap() helper as it has + * a history of problems and is currently broken on systems +@@ -3864,11 +3955,15 @@ static int selinux_file_mprotect(struct vm_area_struct *vma, + vma->vm_end <= vma->vm_mm->brk) { + rc = avc_has_perm(sid, sid, SECCLASS_PROCESS, + PROCESS__EXECHEAP, NULL); +- } else if (!vma->vm_file && (vma_is_initial_stack(vma) || ++ if (rc) ++ return rc; ++ } else if (!file && (vma_is_initial_stack(vma) || + vma_is_stack_for_current(vma))) { + rc = avc_has_perm(sid, sid, SECCLASS_PROCESS, + PROCESS__EXECSTACK, NULL); +- } else if (vma->vm_file && vma->anon_vma) { ++ if (rc) ++ return rc; ++ } else if (file && vma->anon_vma) { + /* + * We are making executable a file mapping that has + * had some COW done. Since pages might have been +@@ -3876,13 +3971,29 @@ static int selinux_file_mprotect(struct vm_area_struct *vma, + * modified content. This typically should only + * occur for text relocations. + */ +- rc = file_has_perm(cred, vma->vm_file, FILE__EXECMOD); ++ rc = __file_has_perm(cred, file, FILE__EXECMOD, ++ backing_file); ++ if (rc) ++ return rc; ++ if (backing_file) { ++ rc = file_has_perm(file->f_cred, file, ++ FILE__EXECMOD); ++ if (rc) ++ return rc; ++ } + } ++ } ++ ++ rc = __file_map_prot_check(cred, file, prot, shared, backing_file); ++ if (rc) ++ return rc; ++ if (backing_file) { ++ rc = file_map_prot_check(file->f_cred, file, prot, shared); + if (rc) + return rc; + } + +- return file_map_prot_check(vma->vm_file, prot, vma->vm_flags&VM_SHARED); ++ return 0; + } + + static int selinux_file_lock(struct file *file, unsigned int cmd) +@@ -6960,6 +7071,7 @@ static void selinux_bpf_token_free(struct bpf_token *token) + struct lsm_blob_sizes selinux_blob_sizes __ro_after_init = { + .lbs_cred = sizeof(struct task_security_struct), + .lbs_file = sizeof(struct file_security_struct), ++ .lbs_backing_file = sizeof(struct backing_file_security_struct), + .lbs_inode = sizeof(struct inode_security_struct), + .lbs_ipc = sizeof(struct ipc_security_struct), + .lbs_key = sizeof(struct key_security_struct), +@@ -7165,9 +7277,11 @@ static struct security_hook_list selinux_hooks[] __ro_after_init = { + + LSM_HOOK_INIT(file_permission, selinux_file_permission), + LSM_HOOK_INIT(file_alloc_security, selinux_file_alloc_security), ++ LSM_HOOK_INIT(backing_file_alloc, selinux_backing_file_alloc), + LSM_HOOK_INIT(file_ioctl, selinux_file_ioctl), + LSM_HOOK_INIT(file_ioctl_compat, selinux_file_ioctl_compat), + LSM_HOOK_INIT(mmap_file, selinux_mmap_file), ++ LSM_HOOK_INIT(mmap_backing_file, selinux_mmap_backing_file), + LSM_HOOK_INIT(mmap_addr, selinux_mmap_addr), + LSM_HOOK_INIT(file_mprotect, selinux_file_mprotect), + LSM_HOOK_INIT(file_lock, selinux_file_lock), +diff --git a/security/selinux/include/objsec.h b/security/selinux/include/objsec.h +index c88cae81ee4c52..dc42282a2c0521 100644 +--- a/security/selinux/include/objsec.h ++++ b/security/selinux/include/objsec.h +@@ -61,6 +61,10 @@ struct file_security_struct { + u32 pseqno; /* Policy seqno at the time of file open */ + }; + ++struct backing_file_security_struct { ++ u32 uf_sid; /* associated user file fsec->sid */ ++}; ++ + struct superblock_security_struct { + u32 sid; /* SID of file system superblock */ + u32 def_sid; /* default SID for labeling */ +@@ -159,6 +163,13 @@ static inline struct file_security_struct *selinux_file(const struct file *file) + return file->f_security + selinux_blob_sizes.lbs_file; + } + ++static inline struct backing_file_security_struct * ++selinux_backing_file(const struct file *backing_file) ++{ ++ void *blob = backing_file_security(backing_file); ++ return blob + selinux_blob_sizes.lbs_backing_file; ++} ++ + static inline struct inode_security_struct * + selinux_inode(const struct inode *inode) + { +-- +2.53.0 + diff --git a/queue-6.12/series b/queue-6.12/series index d8cc9aabff..a876d12321 100644 --- a/queue-6.12/series +++ b/queue-6.12/series @@ -122,3 +122,8 @@ batman-adv-dat-prevent-false-sharing-between-vlans.patch batman-adv-tvlv-enforce-2-byte-alignment.patch batman-adv-tvlv-avoid-race-of-cifsnotfound-handler-s.patch ipv6-account-for-fraggap-on-the-paged-allocation-path.patch +fs-constify-file-ptr-in-backing_file-accessor-helper.patch +lsm-add-backing_file-lsm-hooks.patch +selinux-fix-overlayfs-mmap-and-mprotect-access-check.patch +inet-add-indirect-call-wrapper-for-getfrag-calls.patch +ipv4-account-for-fraggap-on-the-paged-allocation-pat.patch diff --git a/queue-6.18/ipv4-account-for-fraggap-on-the-paged-allocation-pat.patch b/queue-6.18/ipv4-account-for-fraggap-on-the-paged-allocation-pat.patch new file mode 100644 index 0000000000..bb679dc008 --- /dev/null +++ b/queue-6.18/ipv4-account-for-fraggap-on-the-paged-allocation-pat.patch @@ -0,0 +1,67 @@ +From 24511df18c0b61698a053802753bf43a61f19eda Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Tue, 16 Jun 2026 22:38:29 +0900 +Subject: ipv4: account for fraggap on the paged allocation path + +From: Wongi Lee + +[ Upstream commit eca856950f7cb1a221e02b99d758409f2c5cec42 ] + +In __ip_append_data(), when the paged-allocation branch is taken, +alloclen and pagedlen are computed as + + alloclen = fragheaderlen + transhdrlen; + pagedlen = datalen - transhdrlen; + +datalen already includes fraggap, but the fraggap bytes carried over +from the previous skb are copied into the new skb's linear area at +offset transhdrlen by the subsequent skb_copy_and_csum_bits(). The +linear area is therefore undersized by fraggap bytes while pagedlen is +overstated by the same amount. + +The non-paged branch sets alloclen to fraglen, which already accounts +for fraggap because datalen does. Bring the paged branch in line by +adding fraggap to alloclen and subtracting it from pagedlen. + +After this adjustment, copy no longer collapses to -fraggap on the +paged path, so remove the stale comment describing that old arithmetic. + +Fixes: 8eb77cc73977 ("ipv4: avoid partial copy for zc") +Signed-off-by: Jungwoo Lee +Signed-off-by: Wongi Lee +Reviewed-by: Ido Schimmel +Link: https://patch.msgid.link/ajFR1eLAIs42TN3g@DESKTOP-19IMU7U.localdomain +Signed-off-by: Jakub Kicinski +Signed-off-by: Sasha Levin +--- + net/ipv4/ip_output.c | 7 ++----- + 1 file changed, 2 insertions(+), 5 deletions(-) + +diff --git a/net/ipv4/ip_output.c b/net/ipv4/ip_output.c +index 7c005263262ff3..7eaf35a6e24ba6 100644 +--- a/net/ipv4/ip_output.c ++++ b/net/ipv4/ip_output.c +@@ -1117,8 +1117,8 @@ static int __ip_append_data(struct sock *sk, + !(rt->dst.dev->features & NETIF_F_SG))) + alloclen = fraglen; + else { +- alloclen = fragheaderlen + transhdrlen; +- pagedlen = datalen - transhdrlen; ++ alloclen = fragheaderlen + transhdrlen + fraggap; ++ pagedlen = datalen - transhdrlen - fraggap; + } + + alloclen += alloc_extra; +@@ -1165,9 +1165,6 @@ static int __ip_append_data(struct sock *sk, + } + + copy = datalen - transhdrlen - fraggap - pagedlen; +- /* [!] NOTE: copy will be negative if pagedlen>0 +- * because then the equation reduces to -fraggap. +- */ + if (copy > 0 && + INDIRECT_CALL_1(getfrag, ip_generic_getfrag, + from, data + transhdrlen, offset, +-- +2.53.0 + diff --git a/queue-6.18/series b/queue-6.18/series index 649ed85b9c..5e6eeaf60c 100644 --- a/queue-6.18/series +++ b/queue-6.18/series @@ -30,3 +30,4 @@ batman-adv-dat-prevent-false-sharing-between-vlans.patch batman-adv-tvlv-enforce-2-byte-alignment.patch batman-adv-tvlv-avoid-race-of-cifsnotfound-handler-s.patch ipv6-account-for-fraggap-on-the-paged-allocation-path.patch +ipv4-account-for-fraggap-on-the-paged-allocation-pat.patch diff --git a/queue-6.6/inet-add-indirect-call-wrapper-for-getfrag-calls.patch b/queue-6.6/inet-add-indirect-call-wrapper-for-getfrag-calls.patch new file mode 100644 index 0000000000..9e189518ba --- /dev/null +++ b/queue-6.6/inet-add-indirect-call-wrapper-for-getfrag-calls.patch @@ -0,0 +1,104 @@ +From de1abd9a59aa57ee3f255cd3d4688745660c32c3 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Tue, 3 Dec 2024 17:36:17 +0000 +Subject: inet: add indirect call wrapper for getfrag() calls + +From: Eric Dumazet + +[ Upstream commit 5204ccbfa22358f95afd031a3f337e6d9a74baea ] + +UDP send path suffers from one indirect call to ip_generic_getfrag() + +We can use INDIRECT_CALL_1() to avoid it. + +Signed-off-by: Eric Dumazet +Reviewed-by: Willem de Bruijn +Reviewed-by: Brian Vazquez +Link: https://patch.msgid.link/20241203173617.2595451-1-edumazet@google.com +Signed-off-by: Jakub Kicinski +Stable-dep-of: eca856950f7c ("ipv4: account for fraggap on the paged allocation path") +Signed-off-by: Sasha Levin +--- + net/ipv4/ip_output.c | 13 +++++++++---- + net/ipv6/ip6_output.c | 13 ++++++++----- + 2 files changed, 17 insertions(+), 9 deletions(-) + +diff --git a/net/ipv4/ip_output.c b/net/ipv4/ip_output.c +index 305d0e2786a2a8..fb1d8be8c6757c 100644 +--- a/net/ipv4/ip_output.c ++++ b/net/ipv4/ip_output.c +@@ -1168,7 +1168,10 @@ static int __ip_append_data(struct sock *sk, + /* [!] NOTE: copy will be negative if pagedlen>0 + * because then the equation reduces to -fraggap. + */ +- if (copy > 0 && getfrag(from, data + transhdrlen, offset, copy, fraggap, skb) < 0) { ++ if (copy > 0 && ++ INDIRECT_CALL_1(getfrag, ip_generic_getfrag, ++ from, data + transhdrlen, offset, ++ copy, fraggap, skb) < 0) { + err = -EFAULT; + kfree_skb(skb); + goto error; +@@ -1212,8 +1215,9 @@ static int __ip_append_data(struct sock *sk, + unsigned int off; + + off = skb->len; +- if (getfrag(from, skb_put(skb, copy), +- offset, copy, off, skb) < 0) { ++ if (INDIRECT_CALL_1(getfrag, ip_generic_getfrag, ++ from, skb_put(skb, copy), ++ offset, copy, off, skb) < 0) { + __skb_trim(skb, off); + err = -EFAULT; + goto error; +@@ -1253,7 +1257,8 @@ static int __ip_append_data(struct sock *sk, + get_page(pfrag->page); + } + copy = min_t(int, copy, pfrag->size - pfrag->offset); +- if (getfrag(from, ++ if (INDIRECT_CALL_1(getfrag, ip_generic_getfrag, ++ from, + page_address(pfrag->page) + pfrag->offset, + offset, copy, skb->len, skb) < 0) + goto error_efault; +diff --git a/net/ipv6/ip6_output.c b/net/ipv6/ip6_output.c +index 9a08ee6f52da0f..2e2cd0e0d48a5d 100644 +--- a/net/ipv6/ip6_output.c ++++ b/net/ipv6/ip6_output.c +@@ -1695,8 +1695,9 @@ static int __ip6_append_data(struct sock *sk, + pskb_trim_unique(skb_prev, maxfraglen); + } + if (copy > 0 && +- getfrag(from, data + transhdrlen, offset, +- copy, fraggap, skb) < 0) { ++ INDIRECT_CALL_1(getfrag, ip_generic_getfrag, ++ from, data + transhdrlen, offset, ++ copy, fraggap, skb) < 0) { + err = -EFAULT; + kfree_skb(skb); + goto error; +@@ -1740,8 +1741,9 @@ static int __ip6_append_data(struct sock *sk, + unsigned int off; + + off = skb->len; +- if (getfrag(from, skb_put(skb, copy), +- offset, copy, off, skb) < 0) { ++ if (INDIRECT_CALL_1(getfrag, ip_generic_getfrag, ++ from, skb_put(skb, copy), ++ offset, copy, off, skb) < 0) { + __skb_trim(skb, off); + err = -EFAULT; + goto error; +@@ -1781,7 +1783,8 @@ static int __ip6_append_data(struct sock *sk, + get_page(pfrag->page); + } + copy = min_t(int, copy, pfrag->size - pfrag->offset); +- if (getfrag(from, ++ if (INDIRECT_CALL_1(getfrag, ip_generic_getfrag, ++ from, + page_address(pfrag->page) + pfrag->offset, + offset, copy, skb->len, skb) < 0) + goto error_efault; +-- +2.53.0 + diff --git a/queue-6.6/ipv4-account-for-fraggap-on-the-paged-allocation-pat.patch b/queue-6.6/ipv4-account-for-fraggap-on-the-paged-allocation-pat.patch new file mode 100644 index 0000000000..1333328138 --- /dev/null +++ b/queue-6.6/ipv4-account-for-fraggap-on-the-paged-allocation-pat.patch @@ -0,0 +1,67 @@ +From 167ae11f067edf463a14d0aa5c312fb48f311f4d Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Tue, 16 Jun 2026 22:38:29 +0900 +Subject: ipv4: account for fraggap on the paged allocation path + +From: Wongi Lee + +[ Upstream commit eca856950f7cb1a221e02b99d758409f2c5cec42 ] + +In __ip_append_data(), when the paged-allocation branch is taken, +alloclen and pagedlen are computed as + + alloclen = fragheaderlen + transhdrlen; + pagedlen = datalen - transhdrlen; + +datalen already includes fraggap, but the fraggap bytes carried over +from the previous skb are copied into the new skb's linear area at +offset transhdrlen by the subsequent skb_copy_and_csum_bits(). The +linear area is therefore undersized by fraggap bytes while pagedlen is +overstated by the same amount. + +The non-paged branch sets alloclen to fraglen, which already accounts +for fraggap because datalen does. Bring the paged branch in line by +adding fraggap to alloclen and subtracting it from pagedlen. + +After this adjustment, copy no longer collapses to -fraggap on the +paged path, so remove the stale comment describing that old arithmetic. + +Fixes: 8eb77cc73977 ("ipv4: avoid partial copy for zc") +Signed-off-by: Jungwoo Lee +Signed-off-by: Wongi Lee +Reviewed-by: Ido Schimmel +Link: https://patch.msgid.link/ajFR1eLAIs42TN3g@DESKTOP-19IMU7U.localdomain +Signed-off-by: Jakub Kicinski +Signed-off-by: Sasha Levin +--- + net/ipv4/ip_output.c | 7 ++----- + 1 file changed, 2 insertions(+), 5 deletions(-) + +diff --git a/net/ipv4/ip_output.c b/net/ipv4/ip_output.c +index fb1d8be8c6757c..8502cd34b578c0 100644 +--- a/net/ipv4/ip_output.c ++++ b/net/ipv4/ip_output.c +@@ -1117,8 +1117,8 @@ static int __ip_append_data(struct sock *sk, + !(rt->dst.dev->features & NETIF_F_SG))) + alloclen = fraglen; + else { +- alloclen = fragheaderlen + transhdrlen; +- pagedlen = datalen - transhdrlen; ++ alloclen = fragheaderlen + transhdrlen + fraggap; ++ pagedlen = datalen - transhdrlen - fraggap; + } + + alloclen += alloc_extra; +@@ -1165,9 +1165,6 @@ static int __ip_append_data(struct sock *sk, + } + + copy = datalen - transhdrlen - fraggap - pagedlen; +- /* [!] NOTE: copy will be negative if pagedlen>0 +- * because then the equation reduces to -fraggap. +- */ + if (copy > 0 && + INDIRECT_CALL_1(getfrag, ip_generic_getfrag, + from, data + transhdrlen, offset, +-- +2.53.0 + diff --git a/queue-6.6/series b/queue-6.6/series index c1638c2f0c..fab229318e 100644 --- a/queue-6.6/series +++ b/queue-6.6/series @@ -114,3 +114,5 @@ batman-adv-dat-prevent-false-sharing-between-vlans.patch batman-adv-tvlv-enforce-2-byte-alignment.patch batman-adv-tvlv-avoid-race-of-cifsnotfound-handler-s.patch ipv6-account-for-fraggap-on-the-paged-allocation-path.patch +inet-add-indirect-call-wrapper-for-getfrag-calls.patch +ipv4-account-for-fraggap-on-the-paged-allocation-pat.patch diff --git a/queue-7.1/drm-amd-display-fix-ism-dc_lock-deadlock-during-susp.patch b/queue-7.1/drm-amd-display-fix-ism-dc_lock-deadlock-during-susp.patch new file mode 100644 index 0000000000..cbedfc6a1f --- /dev/null +++ b/queue-7.1/drm-amd-display-fix-ism-dc_lock-deadlock-during-susp.patch @@ -0,0 +1,202 @@ +From 1173171b2a9d13aa72313568288e9561bc378c15 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Sun, 28 Jun 2026 16:01:48 +0200 +Subject: drm/amd/display: Fix ISM dc_lock deadlock during suspend + +From: Ray Wu + +[ Upstream commit 3714fe242592e3699ac5e2c19d68b275a210be7d ] + +CachyOS users reported a regression in shutdown/reboot behavior on 7.1 +kernels: the display turns off, but the machine does not power down. +Reverting ISM fixes the regression, and this upstream fix addresses the +same ISM dc_lock/workqueue deadlock in the suspend/shutdown paths. + +[Why] +System hang observed during suspend/resume while video is playing. +amdgpu_dm_ism_disable() is called under dc_lock and waits for ISM +delayed work via disable_delayed_work_sync(). The work handlers +themselves take dc_lock, producing an ABBA deadlock when a worker is +in flight at suspend time. + +[How] +Split the disable path into two phases with opposite locking +contracts: + 1. amdgpu_dm_ism_disable() -- quiesces workers, must NOT hold + dc_lock. + 2. amdgpu_dm_ism_force_full_power() (new) -- drives the ISM FSM + back to FULL_POWER_RUNNING, must hold dc_lock. + +Fixes: 754003486c3c ("drm/amd/display: Add Idle state manager(ISM)") +Link: https://github.com/CachyOS/linux-cachyos/issues/900 +Cc: stable@vger.kernel.org # 7.1.y +Reviewed-by: Sun peng (Leo) Li +Signed-off-by: Ray Wu +Signed-off-by: Ivan Lipski +Tested-by: Dan Wheeler +Signed-off-by: Alex Deucher +Signed-off-by: Peter Jung +Signed-off-by: Sasha Levin +--- + .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 25 +++++++-- + .../drm/amd/display/amdgpu_dm/amdgpu_dm_ism.c | 56 ++++++++++++++++--- + .../drm/amd/display/amdgpu_dm/amdgpu_dm_ism.h | 1 + + 3 files changed, 70 insertions(+), 12 deletions(-) + +diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +index f8c13bad4ac237..560ab329891146 100644 +--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c ++++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +@@ -2260,9 +2260,16 @@ static void amdgpu_dm_fini(struct amdgpu_device *adev) + adev->dm.idle_workqueue = NULL; + } + +- /* Disable ISM before dc_destroy() invalidates dm->dc */ ++ /* ++ * Disable ISM before dc_destroy() invalidates dm->dc. ++ * ++ * Quiesce workers first without dc_lock (they take dc_lock ++ * themselves, so syncing under it would deadlock), then drive the ++ * FSM back to FULL_POWER_RUNNING under dc_lock. ++ */ ++ amdgpu_dm_ism_disable(&adev->dm); + scoped_guard(mutex, &adev->dm.dc_lock) +- amdgpu_dm_ism_disable(&adev->dm); ++ amdgpu_dm_ism_force_full_power(&adev->dm); + + amdgpu_dm_destroy_drm_device(&adev->dm); + +@@ -3290,9 +3297,14 @@ static int dm_suspend(struct amdgpu_ip_block *ip_block) + if (amdgpu_in_reset(adev)) { + enum dc_status res; + ++ /* Quiesce ISM workers before taking dc_lock (workers take ++ * dc_lock themselves; syncing under it would deadlock). ++ */ ++ amdgpu_dm_ism_disable(dm); ++ + mutex_lock(&dm->dc_lock); + +- amdgpu_dm_ism_disable(dm); ++ amdgpu_dm_ism_force_full_power(dm); + dc_allow_idle_optimizations(adev->dm.dc, false); + + dm->cached_dc_state = dc_state_create_copy(dm->dc->current_state); +@@ -3326,8 +3338,13 @@ static int dm_suspend(struct amdgpu_ip_block *ip_block) + + amdgpu_dm_irq_suspend(adev); + ++ /* ++ * Quiesce ISM workers before taking dc_lock (workers take dc_lock ++ * themselves; syncing under it would deadlock). ++ */ ++ amdgpu_dm_ism_disable(dm); + scoped_guard(mutex, &dm->dc_lock) +- amdgpu_dm_ism_disable(dm); ++ amdgpu_dm_ism_force_full_power(dm); + + hpd_rx_irq_work_suspend(dm); + +diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_ism.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_ism.c +index a64e95860e99b3..b32c8d3ac15234 100644 +--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_ism.c ++++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_ism.c +@@ -524,13 +524,20 @@ static void dm_ism_sso_delayed_work_func(struct work_struct *work) + } + + /** +- * amdgpu_dm_ism_disable - Disable the ISM ++ * amdgpu_dm_ism_disable - Quiesce ISM workers + * + * @dm: The amdgpu display manager + * +- * Disable the idle state manager by disabling any ISM work, canceling pending +- * work, and waiting for in-progress work to finish. After disabling, the system +- * is left in DM_ISM_STATE_FULL_POWER_RUNNING state. ++ * Cancels and disables any pending or in-flight ISM delayed work and waits ++ * for in-progress work to finish. After this returns, no ISM worker can run ++ * and subsequent mod_delayed_work() calls become no-ops via ++ * clear_pending_if_disabled(). ++ * ++ * Must NOT be called with dc_lock held: the workers themselves take dc_lock, ++ * so a synchronous wait under dc_lock would deadlock. ++ * ++ * The caller is responsible for driving the FSM back to FULL_POWER_RUNNING ++ * (under dc_lock) by calling amdgpu_dm_ism_force_full_power(). + */ + void amdgpu_dm_ism_disable(struct amdgpu_display_manager *dm) + { +@@ -538,21 +545,54 @@ void amdgpu_dm_ism_disable(struct amdgpu_display_manager *dm) + struct amdgpu_crtc *acrtc; + struct amdgpu_dm_ism *ism; + +- ASSERT(mutex_is_locked(&dm->dc_lock)); ++ /* ++ * Caller must NOT hold dc_lock: the ISM delayed work handlers ++ * acquire dc_lock themselves, so waiting for them via ++ * disable_delayed_work_sync() while holding dc_lock would ++ * self-deadlock against an in-flight worker. ++ */ ++ lockdep_assert_not_held(&dm->dc_lock); + + drm_for_each_crtc(crtc, dm->ddev) { + acrtc = to_amdgpu_crtc(crtc); + ism = &acrtc->ism; + +- /* Cancel and disable any pending work */ + disable_delayed_work_sync(&ism->delayed_work); + disable_delayed_work_sync(&ism->sso_delayed_work); ++ } ++} ++ ++/** ++ * amdgpu_dm_ism_force_full_power - Force every CRTC's ISM FSM to FULL_POWER ++ * ++ * @dm: The amdgpu display manager ++ * ++ * Sends DM_ISM_EVENT_EXIT_IDLE_REQUESTED to every CRTC's ISM, leaving each ++ * FSM in FULL_POWER_RUNNING. Intended to be paired with ++ * amdgpu_dm_ism_disable(): callers should first quiesce workers (without ++ * dc_lock), then take dc_lock and call this helper. ++ * ++ * Must be called with dc_lock held. ++ */ ++void amdgpu_dm_ism_force_full_power(struct amdgpu_display_manager *dm) ++{ ++ struct drm_crtc *crtc; ++ struct amdgpu_crtc *acrtc; ++ ++ /* ++ * Caller must hold dc_lock: commit_event() drives the FSM and ++ * may touch dc state via dc_allow_idle_optimizations() etc. ++ */ ++ lockdep_assert_held(&dm->dc_lock); ++ ++ drm_for_each_crtc(crtc, dm->ddev) { ++ acrtc = to_amdgpu_crtc(crtc); + + /* + * When disabled, leave in FULL_POWER_RUNNING state. +- * EXIT_IDLE will not queue any work ++ * EXIT_IDLE will not queue any work. + */ +- amdgpu_dm_ism_commit_event(ism, ++ amdgpu_dm_ism_commit_event(&acrtc->ism, + DM_ISM_EVENT_EXIT_IDLE_REQUESTED); + } + } +diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_ism.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_ism.h +index fde0ddc8d4e4bc..964408cd9a839c 100644 +--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_ism.h ++++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_ism.h +@@ -146,6 +146,7 @@ void amdgpu_dm_ism_fini(struct amdgpu_dm_ism *ism); + void amdgpu_dm_ism_commit_event(struct amdgpu_dm_ism *ism, + enum amdgpu_dm_ism_event event); + void amdgpu_dm_ism_disable(struct amdgpu_display_manager *dm); ++void amdgpu_dm_ism_force_full_power(struct amdgpu_display_manager *dm); + void amdgpu_dm_ism_enable(struct amdgpu_display_manager *dm); + + #endif +-- +2.53.0 + diff --git a/queue-7.1/ipv4-account-for-fraggap-on-the-paged-allocation-pat.patch b/queue-7.1/ipv4-account-for-fraggap-on-the-paged-allocation-pat.patch new file mode 100644 index 0000000000..004d5de3ce --- /dev/null +++ b/queue-7.1/ipv4-account-for-fraggap-on-the-paged-allocation-pat.patch @@ -0,0 +1,67 @@ +From dbf0e7a1bc3c0226f961bcee64b2815392e10a59 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Tue, 16 Jun 2026 22:38:29 +0900 +Subject: ipv4: account for fraggap on the paged allocation path + +From: Wongi Lee + +[ Upstream commit eca856950f7cb1a221e02b99d758409f2c5cec42 ] + +In __ip_append_data(), when the paged-allocation branch is taken, +alloclen and pagedlen are computed as + + alloclen = fragheaderlen + transhdrlen; + pagedlen = datalen - transhdrlen; + +datalen already includes fraggap, but the fraggap bytes carried over +from the previous skb are copied into the new skb's linear area at +offset transhdrlen by the subsequent skb_copy_and_csum_bits(). The +linear area is therefore undersized by fraggap bytes while pagedlen is +overstated by the same amount. + +The non-paged branch sets alloclen to fraglen, which already accounts +for fraggap because datalen does. Bring the paged branch in line by +adding fraggap to alloclen and subtracting it from pagedlen. + +After this adjustment, copy no longer collapses to -fraggap on the +paged path, so remove the stale comment describing that old arithmetic. + +Fixes: 8eb77cc73977 ("ipv4: avoid partial copy for zc") +Signed-off-by: Jungwoo Lee +Signed-off-by: Wongi Lee +Reviewed-by: Ido Schimmel +Link: https://patch.msgid.link/ajFR1eLAIs42TN3g@DESKTOP-19IMU7U.localdomain +Signed-off-by: Jakub Kicinski +Signed-off-by: Sasha Levin +--- + net/ipv4/ip_output.c | 7 ++----- + 1 file changed, 2 insertions(+), 5 deletions(-) + +diff --git a/net/ipv4/ip_output.c b/net/ipv4/ip_output.c +index 5bcd73cbdb41c0..ec790bad1679a6 100644 +--- a/net/ipv4/ip_output.c ++++ b/net/ipv4/ip_output.c +@@ -1117,8 +1117,8 @@ static int __ip_append_data(struct sock *sk, + !(rt->dst.dev->features & NETIF_F_SG))) + alloclen = fraglen; + else { +- alloclen = fragheaderlen + transhdrlen; +- pagedlen = datalen - transhdrlen; ++ alloclen = fragheaderlen + transhdrlen + fraggap; ++ pagedlen = datalen - transhdrlen - fraggap; + } + + alloclen += alloc_extra; +@@ -1165,9 +1165,6 @@ static int __ip_append_data(struct sock *sk, + } + + copy = datalen - transhdrlen - fraggap - pagedlen; +- /* [!] NOTE: copy will be negative if pagedlen>0 +- * because then the equation reduces to -fraggap. +- */ + if (copy > 0 && + INDIRECT_CALL_1(getfrag, ip_generic_getfrag, + from, data + transhdrlen, offset, +-- +2.53.0 + diff --git a/queue-7.1/series b/queue-7.1/series index 5dd759f67e..481da317d9 100644 --- a/queue-7.1/series +++ b/queue-7.1/series @@ -26,3 +26,4 @@ batman-adv-dat-prevent-false-sharing-between-vlans.patch batman-adv-tvlv-enforce-2-byte-alignment.patch batman-adv-tvlv-avoid-race-of-cifsnotfound-handler-s.patch ipv6-account-for-fraggap-on-the-paged-allocation-path.patch +ipv4-account-for-fraggap-on-the-paged-allocation-pat.patch