From 667d0fb32149f023b8b34a1f6f3d384556eafb5a Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Tue, 23 Jun 2026 12:00:22 -0700 Subject: driver core: add missing kernel-doc for union members MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The use of __SYSFS_FUNCTION_ALTERNATIVE() adds an anonymous union (or struct if CONFIG_CFI=y).* Describe the additional struct/union members to avoid docs build warnings. Warning: include/linux/device.h:117 struct member 'show_const' not described in 'device_attribute' Warning: include/linux/device.h:117 struct member 'store_const' not described in 'device_attribute' *: kernel-doc ignores CONFIG_ symbols in source files; it is using the first definition of __SYSFS_FUNCTION_ALTERNATIVE(), which is struct instead of union. Fixes: 434506b86a6c ("driver core: Allow the constification of device attributes") Signed-off-by: Randy Dunlap Reviewed-by: Thomas Weißschuh Link: https://patch.msgid.link/20260623190023.407781-1-rdunlap@infradead.org Signed-off-by: Danilo Krummrich --- include/linux/device.h | 2 ++ 1 file changed, 2 insertions(+) (limited to 'include') diff --git a/include/linux/device.h b/include/linux/device.h index 7b2baffdd2f5..aee79fd6b32b 100644 --- a/include/linux/device.h +++ b/include/linux/device.h @@ -99,7 +99,9 @@ struct device_type { * struct device_attribute - Interface for exporting device attributes. * @attr: sysfs attribute definition. * @show: Show handler. + * @show_const: Show handler (read-only). * @store: Store handler. + * @store_const: Store handler (read-only). */ struct device_attribute { struct attribute attr; -- cgit v1.2.3 From 867621ba203027338b525af6729719c544135336 Mon Sep 17 00:00:00 2001 From: Md Sadre Alam Date: Mon, 15 Jun 2026 11:39:08 +0530 Subject: dmaengine: qcom: bam_dma: Fix command element mask field for BAM v1.6.0+ BAM version 1.6.0 and later changed the behavior of the mask field in command elements for read operations. In older BAM versions, or prior implementation assumptions, the mask field was effectively ignored for read commands. However, starting from BAM v1.6.0, the mask field for read commands is repurposed to carry the upper 4 bits of the destination address, enabling support for 36-bit addressing. For write commands, the mask field continues to function as a traditional write mask. The current driver sets mask = 0xffffffff for all command elements. While this works for write operations, it breaks read operations on BAM v1.6.0+ hardware. In such cases, the hardware interprets the upper address bits as 0xf, resulting in an invalid destination address (0xf_xxxxxxxx instead of 0x0_xxxxxxxx). This leads to failures such as NAND enumeration issues observed on platforms like IPQ5424. Fix this by assigning the mask field based on command type: - For read commands: set mask = 0 (upper address bits = 0) - For write commands: retain mask = 0xffffffff Also update the bam_cmd_element structure documentation to reflect the dual purpose of the mask field across BAM versions. This ensures correct behavior on BAM v1.6.0+ while maintaining backward compatibility with older hardware. Fixes: dfebb055f73a2 ("dmaengine: qcom: bam_dma: wrapper functions for command descriptor") Tested-by: Lakshmi Sowjanya D Signed-off-by: Md Sadre Alam Reviewed-by: Frank Li Reviewed-by: Dmitry Baryshkov Cc: stable@vger.kernel.org Signed-off-by: Varadarajan Narayanan Link: https://patch.msgid.link/20260615060908.1263171-1-varadarajan.narayanan@oss.qualcomm.com Signed-off-by: Vinod Koul --- include/linux/dma/qcom_bam_dma.h | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) (limited to 'include') diff --git a/include/linux/dma/qcom_bam_dma.h b/include/linux/dma/qcom_bam_dma.h index 68fc0e643b1b..d9d07a9ab313 100644 --- a/include/linux/dma/qcom_bam_dma.h +++ b/include/linux/dma/qcom_bam_dma.h @@ -13,9 +13,12 @@ * supported by BAM DMA Engine. * * @cmd_and_addr - upper 8 bits command and lower 24 bits register address. - * @data - for write command: content to be written into peripheral register. - * for read command: dest addr to write peripheral register value. - * @mask - register mask. + * @data - For write command: content to be written into peripheral register. + * For read command: lower 32 bits of destination address. + * @mask - For write command: register write mask. + * For read command on BAM v1.6.0+: upper 4 bits of destination address. + * For read command on BAM < v1.6.0: ignored by hardware. + * Setting to 0 ensures 32-bit addressing compatibility. * @reserved - for future usage. * */ @@ -42,6 +45,10 @@ enum bam_command_type { * @addr: target address * @cmd: BAM command * @data: actual data for write and dest addr for read in le32 + * + * For BAM v1.6.0+, the mask field behavior depends on command type: + * - Write commands: mask = write mask (typically 0xffffffff) + * - Read commands: mask = upper 4 bits of destination address (0 for 32-bit) */ static inline void bam_prep_ce_le32(struct bam_cmd_element *bam_ce, u32 addr, @@ -50,7 +57,11 @@ bam_prep_ce_le32(struct bam_cmd_element *bam_ce, u32 addr, bam_ce->cmd_and_addr = cpu_to_le32((addr & 0xffffff) | ((cmd & 0xff) << 24)); bam_ce->data = data; - bam_ce->mask = cpu_to_le32(0xffffffff); + if (cmd == BAM_READ_COMMAND) + bam_ce->mask = cpu_to_le32(0x0); /* 32-bit addressing */ + else + bam_ce->mask = cpu_to_le32(0xffffffff); /* Write mask */ + bam_ce->reserved = 0; } /* @@ -60,7 +71,7 @@ bam_prep_ce_le32(struct bam_cmd_element *bam_ce, u32 addr, * @bam_ce: BAM command element * @addr: target address * @cmd: BAM command - * @data: actual data for write and dest addr for read + * @data: actual data for write and destination address for read */ static inline void bam_prep_ce(struct bam_cmd_element *bam_ce, u32 addr, -- cgit v1.2.3 From fadeedd7cfc5d73d33fa3d7ac54b9b27aabd09d2 Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Sun, 12 Jul 2026 07:23:55 -1000 Subject: sched/psi: Create the psimon kthread outside of cgroup_mutex a5b98009f16d ("sched/psi: fix race between file release and pressure write") made pressure_write() hold cgroup_mutex across psi_trigger_create(), which forks the psimon kthread for the first rtpoll trigger. As kthread creation depends on the whole fork path, the commit inadvertently created a lot of unwanted locking dependencies from cgroup_mutex. sched_ext got hit by one: its enable path blocks forks and then grabs cgroup_mutex, so a pressure write racing a scheduler enable deadlocks, with every other fork piling up behind. Fix it by splitting trigger creation so that the worker is forked with cgroup_mutex dropped and the kernfs active reference left broken. The latter matters because rmdir and cgroup.pressure writes drain active references under cgroup_mutex. Publishing the trigger last keeps error reporting synchronous and preserves the of->priv lifetime rules. The trigger registered in the first stage pins the group's rtpoll machinery across the unlocked window, leaving only creation races to resolve. The catch-up poll on installation covers scheduling attempts dropped while there was no worker. v2: Retagged sched/psi (was cgroup). Fixes: a5b98009f16d ("sched/psi: fix race between file release and pressure write") Cc: stable@vger.kernel.org Cc: Edward Adam Davis Cc: Chen Ridong Reported-by: Matt Fleming Closes: https://lore.kernel.org/all/20260710100441.2653477-1-matt@readmodwrite.com/ Signed-off-by: Tejun Heo Acked-by: Johannes Weiner Tested-by: Matt Fleming Acked-by: Suren Baghdasaryan --- include/linux/psi.h | 4 ++- kernel/cgroup/cgroup.c | 23 ++++++++++++++++- kernel/sched/psi.c | 69 ++++++++++++++++++++++++++++++++++++++------------ 3 files changed, 78 insertions(+), 18 deletions(-) (limited to 'include') diff --git a/include/linux/psi.h b/include/linux/psi.h index e0745873e3f2..7966e3ac03b9 100644 --- a/include/linux/psi.h +++ b/include/linux/psi.h @@ -25,7 +25,9 @@ void psi_memstall_leave(unsigned long *flags); int psi_show(struct seq_file *s, struct psi_group *group, enum psi_res res); struct psi_trigger *psi_trigger_create(struct psi_group *group, char *buf, enum psi_res res, struct file *file, - struct kernfs_open_file *of); + struct kernfs_open_file *of, + bool *need_rtpoll_worker); +int psi_trigger_create_rtpoll_worker(struct psi_group *group); void psi_trigger_destroy(struct psi_trigger *t); __poll_t psi_trigger_poll(void **trigger_ptr, struct file *file, diff --git a/kernel/cgroup/cgroup.c b/kernel/cgroup/cgroup.c index 38f8d9df8fbc..b5b461d4418b 100644 --- a/kernel/cgroup/cgroup.c +++ b/kernel/cgroup/cgroup.c @@ -3996,6 +3996,7 @@ static ssize_t pressure_write(struct kernfs_open_file *of, char *buf, struct psi_trigger *new; struct cgroup *cgrp; struct psi_group *psi; + bool need_rtpoll_worker; ssize_t ret = 0; cgrp = cgroup_kn_lock_live(of->kn, false); @@ -4015,12 +4016,32 @@ static ssize_t pressure_write(struct kernfs_open_file *of, char *buf, } psi = cgroup_psi(cgrp); - new = psi_trigger_create(psi, buf, res, of->file, of); + new = psi_trigger_create(psi, buf, res, of->file, of, + &need_rtpoll_worker); if (IS_ERR(new)) { ret = PTR_ERR(new); goto out_unlock; } + /* + * The worker fork must run with neither cgroup_mutex nor the file's + * kernfs active reference held. The latter is broken since + * cgroup_kn_lock_live(). @of->priv may be released while unlocked, so + * recheck before publishing @new. + */ + if (need_rtpoll_worker) { + cgroup_unlock(); + ret = psi_trigger_create_rtpoll_worker(psi); + cgroup_lock(); + + if (!ret && !of->priv) + ret = -ENODEV; + if (ret) { + psi_trigger_destroy(new); + goto out_unlock; + } + } + smp_store_release(&ctx->psi.trigger, new); out_unlock: diff --git a/kernel/sched/psi.c b/kernel/sched/psi.c index d9c9d9480a45..565ec7b80743 100644 --- a/kernel/sched/psi.c +++ b/kernel/sched/psi.c @@ -1292,9 +1292,44 @@ int psi_show(struct seq_file *m, struct psi_group *group, enum psi_res res) return 0; } +/* + * Create @group's rtpoll worker after psi_trigger_create() reported the need + * for one. kthread creation depends on the whole fork path and we don't want + * all of that nested inside cgroup_mutex, so the caller must drop it and any + * other lock that forks can wait behind. If two callers race, the loser stops + * its never-woken kthread. + */ +int psi_trigger_create_rtpoll_worker(struct psi_group *group) +{ + struct task_struct *task; + + task = kthread_create(psi_rtpoll_worker, group, "psimon"); + if (IS_ERR(task)) + return PTR_ERR(task); + + scoped_guard(mutex, &group->rtpoll_trigger_lock) { + if (!rcu_access_pointer(group->rtpoll_task)) { + atomic_set(&group->rtpoll_wakeup, 0); + wake_up_process(task); + rcu_assign_pointer(group->rtpoll_task, task); + + /* + * Poll once to catch up on scheduling attempts dropped + * while there was no rtpoll worker. + */ + psi_schedule_rtpoll_work(group, 1, true); + return 0; + } + } + + kthread_stop(task); + return 0; +} + struct psi_trigger *psi_trigger_create(struct psi_group *group, char *buf, enum psi_res res, struct file *file, - struct kernfs_open_file *of) + struct kernfs_open_file *of, + bool *need_rtpoll_worker) { struct psi_trigger *t; enum psi_states state; @@ -1302,6 +1337,8 @@ struct psi_trigger *psi_trigger_create(struct psi_group *group, char *buf, bool privileged; u32 window_us; + *need_rtpoll_worker = false; + if (static_branch_likely(&psi_disabled)) return ERR_PTR(-EOPNOTSUPP); @@ -1362,26 +1399,14 @@ struct psi_trigger *psi_trigger_create(struct psi_group *group, char *buf, if (privileged) { mutex_lock(&group->rtpoll_trigger_lock); - if (!rcu_access_pointer(group->rtpoll_task)) { - struct task_struct *task; - - task = kthread_create(psi_rtpoll_worker, group, "psimon"); - if (IS_ERR(task)) { - kfree(t); - mutex_unlock(&group->rtpoll_trigger_lock); - return ERR_CAST(task); - } - atomic_set(&group->rtpoll_wakeup, 0); - wake_up_process(task); - rcu_assign_pointer(group->rtpoll_task, task); - } - list_add(&t->node, &group->rtpoll_triggers); group->rtpoll_min_period = min(group->rtpoll_min_period, div_u64(t->win.size, UPDATES_PER_WINDOW)); group->rtpoll_nr_triggers[t->state]++; group->rtpoll_states |= (1 << t->state); + *need_rtpoll_worker = !rcu_access_pointer(group->rtpoll_task); + mutex_unlock(&group->rtpoll_trigger_lock); } else { mutex_lock(&group->avgs_lock); @@ -1541,6 +1566,8 @@ static ssize_t psi_write(struct file *file, const char __user *user_buf, size_t buf_size; struct seq_file *seq; struct psi_trigger *new; + bool need_rtpoll_worker; + int ret; if (static_branch_likely(&psi_disabled)) return -EOPNOTSUPP; @@ -1565,12 +1592,22 @@ static ssize_t psi_write(struct file *file, const char __user *user_buf, return -EBUSY; } - new = psi_trigger_create(&psi_system, buf, res, file, NULL); + new = psi_trigger_create(&psi_system, buf, res, file, NULL, + &need_rtpoll_worker); if (IS_ERR(new)) { mutex_unlock(&seq->lock); return PTR_ERR(new); } + if (need_rtpoll_worker) { + ret = psi_trigger_create_rtpoll_worker(&psi_system); + if (ret) { + psi_trigger_destroy(new); + mutex_unlock(&seq->lock); + return ret; + } + } + smp_store_release(&seq->private, new); mutex_unlock(&seq->lock); -- cgit v1.2.3 From 477869bfafea65492d23de62c1b5208147c09dd2 Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Thu, 16 Jul 2026 10:43:25 -1000 Subject: sched_ext: Reject setting disallow from init_task outside the enable path The p->scx.disallow revert assumes the root enable path, where the switching loop reads the reverted policy right afterwards and leaves the task off SCX. The sub-scheduler disable path also reaches it when re-initializing the returned tasks on a root parent. Nothing reads the policy there: the task is enabled on root anyway and keeps running on the ext class with a silently rewritten policy. Kill the sched instead, matching the fork and non-root branches, and update the disallow documentation, which equated !fork with the load path and pointed at a stale debugfs path for nr_rejected. Fixes: 337ec00b1d9c ("sched_ext: Implement cgroup sub-sched enabling and disabling") Signed-off-by: Tejun Heo Reviewed-by: Andrea Righi --- include/linux/sched/ext.h | 10 +++++----- kernel/sched/ext/ext.c | 3 +++ 2 files changed, 8 insertions(+), 5 deletions(-) (limited to 'include') diff --git a/include/linux/sched/ext.h b/include/linux/sched/ext.h index 20b2343aa344..87e353f7e011 100644 --- a/include/linux/sched/ext.h +++ b/include/linux/sched/ext.h @@ -244,11 +244,11 @@ struct sched_ext_entity { * to %SCHED_EXT with -%EACCES. * * Can be set from ops.init_task() while the BPF scheduler is being - * loaded (!scx_init_task_args->fork). If set and the task's policy is - * already %SCHED_EXT, the task's policy is rejected and forcefully - * reverted to %SCHED_NORMAL. The number of such events are reported - * through /sys/kernel/debug/sched_ext::nr_rejected. Setting this flag - * during fork is not allowed. + * loaded. If set and the task's policy is already %SCHED_EXT, the + * task's policy is rejected and forcefully reverted to %SCHED_NORMAL. + * The number of such events are reported through + * /sys/kernel/sched_ext/nr_rejected. Setting this flag from any other + * ops.init_task() invocation, such as during fork, fails the scheduler. */ bool disallow; /* reject switching into SCX */ diff --git a/kernel/sched/ext/ext.c b/kernel/sched/ext/ext.c index e3fa7b2fac9d..46f135bddd46 100644 --- a/kernel/sched/ext/ext.c +++ b/kernel/sched/ext/ext.c @@ -3606,6 +3606,9 @@ static int __scx_init_task(struct scx_sched *sch, struct task_struct *p, bool fo } else if (unlikely(fork)) { scx_error(sch, "ops.init_task() set task->scx.disallow for %s[%d] during fork", p->comm, p->pid); + } else if (unlikely(scx_enable_state() != SCX_ENABLING)) { + scx_error(sch, "ops.init_task() set task->scx.disallow for %s[%d] outside the enable path", + p->comm, p->pid); } else { struct rq *rq; struct rq_flags rf; -- cgit v1.2.3 From de4660898b7aa7e03d3b120a6bfa6b26211e4e77 Mon Sep 17 00:00:00 2001 From: Qi Zheng Date: Fri, 10 Jul 2026 23:43:18 +0800 Subject: mm: mglru: fix stale batch updates after memcg reparenting The mglru page table walker batches per-generation size deltas in walk->nr_pages while walking page tables without holding the lruvec lock. The reset_batch_size() later folds those deltas into walk->lruvec under the lruvec lock. The page table walker can run concurrently with the memcg reparenting path as follows: CPU0 CPU1 ==== ==== walk_mm --> walk_page_range --> update_batch_size --> walk->nr_pages += delta mem_cgroup_css_offline --> memcg_reparent_objcgs --> lock lruvec lru_gen_reparent_memcg --> reparent child folios to parent unlock lruvec lock lruvec reset_batch_size --> child lrugen->nr_pages += delta This will trigger the following warning in lru_gen_exit_memcg(): VM_WARN_ON_ONCE(memchr_inv(lruvec->lrugen.nr_pages, 0, sizeof(lruvec->lrugen.nr_pages))); And the user-visible impact of underestimated nr_pages in MGLRU was premature OOMs because MGLRU does not try to reclaim memory when nr_pages reaches zero, but there are still more pages. To fix it, make reset_batch_size() check CSS_DYING under RCU before flushing the pending batch. A non-dying memcg keeps the original lruvec stable against RCU-delayed offlining; a dying memcg redirects the deltas to the first non-dying ancestor. Link: https://lore.kernel.org/20260710154318.75388-1-qi.zheng@linux.dev Fixes: f304652609ea ("mm: vmscan: prepare for reparenting MGLRU folios") Signed-off-by: Qi Zheng Reported-by: Peiyang He Closes: https://lore.kernel.org/all/5A9E929D82717101+12fcf643-efb8-4b9a-a53a-1e28cc894f0b@smail.nju.edu.cn Reviewed-by: Harry Yoo (Oracle) Acked-by: Johannes Weiner Acked-by: Shakeel Butt Cc: Axel Rasmussen Cc: Barry Song Cc: David Hildenbrand Cc: Kairui Song Cc: Lorenzo Stoakes Cc: Michal Hocko Cc: Muchun Song Cc: Roman Gushchin Cc: Wei Xu Cc: Yuanchu Xie Cc: Signed-off-by: Andrew Morton --- include/linux/memcontrol.h | 25 +++++++++++++++++++++++++ mm/vmscan.c | 11 ++++------- 2 files changed, 29 insertions(+), 7 deletions(-) (limited to 'include') diff --git a/include/linux/memcontrol.h b/include/linux/memcontrol.h index e1f46a0016fc..957260677678 100644 --- a/include/linux/memcontrol.h +++ b/include/linux/memcontrol.h @@ -1472,6 +1472,31 @@ static inline void lruvec_lock_irq(struct lruvec *lruvec) spin_lock_irq(&lruvec->lru_lock); } +static inline struct lruvec *lruvec_live_lock_irq(struct lruvec *lruvec) +{ +#ifdef CONFIG_MEMCG + struct pglist_data *pgdat = lruvec_pgdat(lruvec); + struct mem_cgroup *memcg = lruvec_memcg(lruvec); + + rcu_read_lock(); + + /* + * The memcg can be NULL when the memory controller is disabled. + * Otherwise, the caller keeps the memcg owning @lruvec alive. + */ + while (unlikely(memcg && css_is_dying(&memcg->css))) { + memcg = parent_mem_cgroup(memcg); + lruvec = mem_cgroup_lruvec(memcg, pgdat); + } + + spin_lock_irq(&lruvec->lru_lock); +#else + lruvec_lock_irq(lruvec); +#endif + + return lruvec; +} + static inline void lruvec_unlock(struct lruvec *lruvec) { spin_unlock(&lruvec->lru_lock); diff --git a/mm/vmscan.c b/mm/vmscan.c index 35c3bb15ae96..1a142c58700d 100644 --- a/mm/vmscan.c +++ b/mm/vmscan.c @@ -3265,7 +3265,7 @@ static void update_batch_size(struct lru_gen_mm_walk *walk, struct folio *folio, static void reset_batch_size(struct lru_gen_mm_walk *walk) { int gen, type, zone; - struct lruvec *lruvec = walk->lruvec; + struct lruvec *lruvec = lruvec_live_lock_irq(walk->lruvec); struct lru_gen_folio *lrugen = &lruvec->lrugen; walk->batched = 0; @@ -3285,6 +3285,8 @@ static void reset_batch_size(struct lru_gen_mm_walk *walk) lru += LRU_ACTIVE; __update_lru_size(lruvec, lru, zone, delta); } + + lruvec_unlock_irq(lruvec); } static int should_skip_vma(unsigned long start, unsigned long end, struct mm_walk *args) @@ -3779,11 +3781,8 @@ static void walk_mm(struct mm_struct *mm, struct lru_gen_mm_walk *walk) mmap_read_unlock(mm); } - if (walk->batched) { - lruvec_lock_irq(lruvec); + if (walk->batched) reset_batch_size(walk); - lruvec_unlock_irq(lruvec); - } cond_resched(); } while (err == -EAGAIN); @@ -4867,9 +4866,7 @@ retry: walk = current->reclaim_state->mm_walk; if (walk && walk->batched) { walk->lruvec = lruvec; - lruvec_lock_irq(lruvec); reset_batch_size(walk); - lruvec_unlock_irq(lruvec); } mod_lruvec_state(lruvec, PGDEMOTE_KSWAPD + reclaimer_offset(sc), -- cgit v1.2.3 From 11055a46f398779b69aa36afb7c9f4124529a075 Mon Sep 17 00:00:00 2001 From: Christian Loehle Date: Wed, 22 Jul 2026 10:38:24 +0100 Subject: ACPI: CPPC: Check all controls for fast switching ACPI 6.2, Section 6.2.11.2 permits _CPC registers to use flexible address spaces. Linux advertises that capability through _OSC and parses the address space of each _CPC register independently. A directly accessible DESIRED_PERF combined with PCC-backed limits is therefore a valid configuration. cppc_allow_fast_switch() only checks DESIRED_PERF, although the fast-switch callback passes DESIRED_PERF, MIN_PERF and MAX_PERF to cppc_set_perf(). If a limit uses PCC, that function can sleep while called from scheduler context. Allow fast switching only when every supported control used by the callback has an address space already accepted for fast access. Check the complete policy domain, including initialized CPUs that are currently offline and may later become the policy's managing CPU. Fixes: 658fa7b1c47a ("ACPI: CPPC: Add cppc_get_perf() API to read performance controls") Cc: stable@vger.kernel.org Signed-off-by: Christian Loehle Link: https://patch.msgid.link/20260722093825.1030594-2-christian.loehle@arm.com Signed-off-by: Rafael J. Wysocki --- drivers/acpi/cppc_acpi.c | 22 +++++++++++++++++----- drivers/cpufreq/cppc_cpufreq.c | 2 +- include/acpi/cppc_acpi.h | 5 +++-- 3 files changed, 21 insertions(+), 8 deletions(-) (limited to 'include') diff --git a/drivers/acpi/cppc_acpi.c b/drivers/acpi/cppc_acpi.c index 9f572f481241..1d3a94100491 100644 --- a/drivers/acpi/cppc_acpi.c +++ b/drivers/acpi/cppc_acpi.c @@ -475,17 +475,29 @@ bool acpi_cpc_valid(void) } EXPORT_SYMBOL_GPL(acpi_cpc_valid); -bool cppc_allow_fast_switch(void) +bool cppc_allow_fast_switch(const struct cpumask *cpus) { - struct cpc_register_resource *desired_reg; + struct cpc_register_resource *desired_reg, *min_reg, *max_reg; struct cpc_desc *cpc_ptr; int cpu; - for_each_online_cpu(cpu) { + for_each_cpu(cpu, cpus) { cpc_ptr = per_cpu(cpc_desc_ptr, cpu); + if (!cpc_ptr) + return false; desired_reg = &cpc_ptr->cpc_regs[DESIRED_PERF]; - if (!CPC_IN_SYSTEM_MEMORY(desired_reg) && - !CPC_IN_SYSTEM_IO(desired_reg)) + min_reg = &cpc_ptr->cpc_regs[MIN_PERF]; + max_reg = &cpc_ptr->cpc_regs[MAX_PERF]; + + if (!CPC_SUPPORTED(desired_reg) || + (!CPC_IN_SYSTEM_MEMORY(desired_reg) && + !CPC_IN_SYSTEM_IO(desired_reg)) || + (CPC_SUPPORTED(min_reg) && + !CPC_IN_SYSTEM_MEMORY(min_reg) && + !CPC_IN_SYSTEM_IO(min_reg)) || + (CPC_SUPPORTED(max_reg) && + !CPC_IN_SYSTEM_MEMORY(max_reg) && + !CPC_IN_SYSTEM_IO(max_reg))) return false; } diff --git a/drivers/cpufreq/cppc_cpufreq.c b/drivers/cpufreq/cppc_cpufreq.c index f6cea0c54dd9..b943bf78d3f5 100644 --- a/drivers/cpufreq/cppc_cpufreq.c +++ b/drivers/cpufreq/cppc_cpufreq.c @@ -693,7 +693,7 @@ static int cppc_cpufreq_cpu_init(struct cpufreq_policy *policy) goto out; } - policy->fast_switch_possible = cppc_allow_fast_switch(); + policy->fast_switch_possible = cppc_allow_fast_switch(policy->cpus); policy->dvfs_possible_from_any_cpu = true; /* diff --git a/include/acpi/cppc_acpi.h b/include/acpi/cppc_acpi.h index 8693890a7275..8c191b9ac18f 100644 --- a/include/acpi/cppc_acpi.h +++ b/include/acpi/cppc_acpi.h @@ -170,7 +170,7 @@ extern u64 cppc_get_dmi_max_khz(void); extern unsigned int cppc_perf_to_khz(struct cppc_perf_caps *caps, unsigned int perf); extern unsigned int cppc_khz_to_perf(struct cppc_perf_caps *caps, unsigned int freq); extern bool acpi_cpc_valid(void); -extern bool cppc_allow_fast_switch(void); +bool cppc_allow_fast_switch(const struct cpumask *cpus); extern int acpi_get_psd_map(unsigned int cpu, struct cppc_cpudata *cpu_data); extern int cppc_get_transition_latency(int cpu); extern bool cpc_ffh_supported(void); @@ -234,7 +234,8 @@ static inline bool acpi_cpc_valid(void) { return false; } -static inline bool cppc_allow_fast_switch(void) + +static inline bool cppc_allow_fast_switch(const struct cpumask *cpus) { return false; } -- cgit v1.2.3 From db3d0e0e5d4bc5ab4fe445b9f413d1b486508ca5 Mon Sep 17 00:00:00 2001 From: Xiang Mei Date: Sun, 12 Jul 2026 16:42:01 -0700 Subject: netfilter: nf_conntrack_sip: widen NAT rewrite delta to s32 in sip_help_tcp() sip_help_tcp() stores the size change of each NAT-rewritten SIP message in s16 diff and accumulates it in s16 tdiff, but a single message can grow by more than S16_MAX while the packet stays under the 65535 enlarge_skb() limit: nf_nat_sip() rewrites every matching URI, and a long Contact list expands the message by tens of kilobytes. diff then wraps, and "datalen = datalen + diff - msglen" yields a huge unsigned datalen, so the next iteration's ct_sip_get_header() reads past the linearized skb tail. Widen diff, tdiff and the seq_adjust hook to s32. Both are bounded by the 65535 byte packet limit, and the seqadj core is already s32 (nf_ct_seqadj_set() takes s32), so no previously accepted input is rejected. BUG: KASAN: use-after-free in ct_sip_get_header (net/netfilter/nf_conntrack_sip.c:464) Read of size 1 at addr ffff888010800000 by task ksoftirqd/1/25 ct_sip_get_header (net/netfilter/nf_conntrack_sip.c:464) sip_help_tcp (net/netfilter/nf_conntrack_sip.c:1694) nf_confirm (net/netfilter/nf_conntrack_proto.c:183) nf_hook_slow (net/netfilter/core.c:619) ip6_output (net/ipv6/ip6_output.c:246) ip6_forward (net/ipv6/ip6_output.c:690) ipv6_rcv (net/ipv6/ip6_input.c:351) __netif_receive_skb_one_core (net/core/dev.c:6212) process_backlog (net/core/dev.c:6676) __napi_poll (net/core/dev.c:7735) net_rx_action (net/core/dev.c:7955) handle_softirqs (kernel/softirq.c:622) run_ksoftirqd (kernel/softirq.c:1076) ... Fixes: f5b321bd37fb ("netfilter: nf_conntrack_sip: add TCP support") Reported-by: Weiming Shi Link: https://patch.msgid.link/netfilter-devel/20260712234201.3213635-1-xmei5@asu.edu Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Xiang Mei Signed-off-by: Pablo Neira Ayuso --- include/linux/netfilter/nf_conntrack_sip.h | 2 +- net/netfilter/nf_conntrack_sip.c | 2 +- net/netfilter/nf_nat_sip.c | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) (limited to 'include') diff --git a/include/linux/netfilter/nf_conntrack_sip.h b/include/linux/netfilter/nf_conntrack_sip.h index dbc614dfe0d5..aafa0c04f917 100644 --- a/include/linux/netfilter/nf_conntrack_sip.h +++ b/include/linux/netfilter/nf_conntrack_sip.h @@ -115,7 +115,7 @@ struct nf_nat_sip_hooks { unsigned int *datalen); void (*seq_adjust)(struct sk_buff *skb, - unsigned int protoff, s16 off); + unsigned int protoff, s32 off); unsigned int (*expect)(struct sk_buff *skb, unsigned int protoff, diff --git a/net/netfilter/nf_conntrack_sip.c b/net/netfilter/nf_conntrack_sip.c index f3f90a866338..e4a70d1d77b0 100644 --- a/net/netfilter/nf_conntrack_sip.c +++ b/net/netfilter/nf_conntrack_sip.c @@ -1663,7 +1663,7 @@ static int sip_help_tcp(struct sk_buff *skb, unsigned int protoff, unsigned int matchoff, matchlen; unsigned int msglen, origlen; const char *dptr, *end; - s16 diff, tdiff = 0; + s32 diff, tdiff = 0; int ret = NF_ACCEPT; unsigned long clen; bool term; diff --git a/net/netfilter/nf_nat_sip.c b/net/netfilter/nf_nat_sip.c index aea02f6aff09..a93eaf0f7d30 100644 --- a/net/netfilter/nf_nat_sip.c +++ b/net/netfilter/nf_nat_sip.c @@ -321,7 +321,7 @@ next: } static void nf_nat_sip_seq_adjust(struct sk_buff *skb, unsigned int protoff, - s16 off) + s32 off) { enum ip_conntrack_info ctinfo; struct nf_conn *ct = nf_ct_get(skb, &ctinfo); -- cgit v1.2.3 From 4aa63842fc92de1bce59d4709a0d32e718890bb2 Mon Sep 17 00:00:00 2001 From: Pablo Neira Ayuso Date: Mon, 13 Jul 2026 00:26:04 +0200 Subject: netfilter: nf_conntrack_expect: add and use nf_ct_expect_related_pair() Add a new function to insert a pair of expectations, this is required by the SIP and H323 NAT helpers. The spinlock is held to check if there is a slot for both expectations, in such case, insert them. This removes the need for nf_ct_unexpect_related() inside the loop to find a pair of consecutive ports, otherwise inserting expectations whose dead flag is already set on can happen. Bump master_help->expecting for the expectation class after checking if the expectation fits in the master expectation list, which is needed for this new _pair() function variant to run the eviction routine including the preallocated slot for the first expectation in the pair. Fixes: b8b09dc2bf35 ("netfilter: nf_conntrack_expect: use conntrack GC to reap expectations") Reported-by: Jaeyeong Lee Link: https://patch.msgid.link/178377968720.33756.12204817361601593230@proton.me/ Signed-off-by: Pablo Neira Ayuso --- include/net/netfilter/nf_conntrack_expect.h | 3 +++ net/ipv4/netfilter/nf_nat_h323.c | 22 ++++++------------ net/netfilter/nf_conntrack_expect.c | 35 ++++++++++++++++++++++++++++- net/netfilter/nf_nat_sip.c | 20 +++++------------ 4 files changed, 50 insertions(+), 30 deletions(-) (limited to 'include') diff --git a/include/net/netfilter/nf_conntrack_expect.h b/include/net/netfilter/nf_conntrack_expect.h index c024345c9bd8..26d6babd92fc 100644 --- a/include/net/netfilter/nf_conntrack_expect.h +++ b/include/net/netfilter/nf_conntrack_expect.h @@ -161,6 +161,9 @@ static inline int nf_ct_expect_related(struct nf_conntrack_expect *expect, return nf_ct_expect_related_report(expect, 0, 0, flags); } +int nf_ct_expect_related_pair(struct nf_conntrack_expect *expect[], + unsigned int flag); + struct nf_conn_help; void nf_ct_expectation_gc(struct nf_conn_help *master_help); diff --git a/net/ipv4/netfilter/nf_nat_h323.c b/net/ipv4/netfilter/nf_nat_h323.c index 183e8a3ff2ba..6bcd6734769b 100644 --- a/net/ipv4/netfilter/nf_nat_h323.c +++ b/net/ipv4/netfilter/nf_nat_h323.c @@ -182,6 +182,7 @@ static int nat_rtp_rtcp(struct sk_buff *skb, struct nf_conn *ct, struct nf_conntrack_expect *rtp_exp, struct nf_conntrack_expect *rtcp_exp) { + struct nf_conntrack_expect *rtp_pair[2] = { rtp_exp, rtcp_exp }; struct nf_ct_h323_master *info = nfct_help_data(ct); int dir = CTINFO2DIR(ctinfo); int i; @@ -227,22 +228,13 @@ static int nat_rtp_rtcp(struct sk_buff *skb, struct nf_conn *ct, int ret; rtp_exp->tuple.dst.u.udp.port = htons(nated_port); - ret = nf_ct_expect_related(rtp_exp, 0); + rtcp_exp->tuple.dst.u.udp.port = htons(nated_port + 1); + ret = nf_ct_expect_related_pair(rtp_pair, 0); if (ret == 0) { - rtcp_exp->tuple.dst.u.udp.port = - htons(nated_port + 1); - ret = nf_ct_expect_related(rtcp_exp, 0); - if (ret == 0) - break; - else if (ret == -EBUSY) { - nf_ct_unexpect_related(rtp_exp); - continue; - } else if (ret < 0) { - nf_ct_unexpect_related(rtp_exp); - nated_port = 0; - break; - } - } else if (ret != -EBUSY) { + break; + } else if (ret == -EBUSY) { + continue; + } else if (ret < 0) { nated_port = 0; break; } diff --git a/net/netfilter/nf_conntrack_expect.c b/net/netfilter/nf_conntrack_expect.c index 7ae68d60586a..8a3b9e33e94f 100644 --- a/net/netfilter/nf_conntrack_expect.c +++ b/net/netfilter/nf_conntrack_expect.c @@ -427,7 +427,6 @@ static void nf_ct_expect_insert(struct nf_conntrack_expect *exp, exp->timeout += helper->expect_policy[exp->class].timeout * HZ; hlist_add_head_rcu(&exp->lnode, &master_help->expectations); - master_help->expecting[exp->class]++; hlist_add_head_rcu(&exp->hnode, &nf_ct_expect_hash[h]); cnet = nf_ct_pernet(net); @@ -534,6 +533,7 @@ int nf_ct_expect_related_report(struct nf_conntrack_expect *expect, if (ret < 0) goto out; + master_help->expecting[expect->class]++; nf_ct_expect_insert(expect, master_help); nf_ct_expect_event_report(IPEXP_NEW, expect, portid, report); @@ -546,6 +546,39 @@ out: } EXPORT_SYMBOL_GPL(nf_ct_expect_related_report); +int nf_ct_expect_related_pair(struct nf_conntrack_expect *expect[], + unsigned int flags) +{ + struct nf_conn_help *master_help; + int i, ret; + + spin_lock_bh(&nf_conntrack_expect_lock); + master_help = nfct_help(expect[0]->master); + if (!master_help || master_help != nfct_help(expect[1]->master)) { + ret = -EINVAL; + goto out; + } + + for (i = 0; i < 2; i++) { + ret = __nf_ct_expect_check(expect[i], master_help, flags); + if (ret < 0) { + if (i == 1) + master_help->expecting[expect[0]->class]--; + goto out; + } + master_help->expecting[expect[i]->class]++; + } + + for (i = 0; i < 2; i++) { + nf_ct_expect_insert(expect[i], master_help); + nf_ct_expect_event_report(IPEXP_NEW, expect[i], 0, 0); + } +out: + spin_unlock_bh(&nf_conntrack_expect_lock); + return ret; +} +EXPORT_SYMBOL_GPL(nf_ct_expect_related_pair); + void nf_ct_expect_iterate_destroy(bool (*iter)(struct nf_conntrack_expect *e, void *data), void *data) { diff --git a/net/netfilter/nf_nat_sip.c b/net/netfilter/nf_nat_sip.c index a93eaf0f7d30..133bd713fe0c 100644 --- a/net/netfilter/nf_nat_sip.c +++ b/net/netfilter/nf_nat_sip.c @@ -592,6 +592,7 @@ static unsigned int nf_nat_sdp_media(struct sk_buff *skb, unsigned int protoff, unsigned int medialen, union nf_inet_addr *rtp_addr) { + struct nf_conntrack_expect *rtp_pair[2] = { rtp_exp, rtcp_exp }; enum ip_conntrack_info ctinfo; struct nf_conn *ct = nf_ct_get(skb, &ctinfo); enum ip_conntrack_dir dir = CTINFO2DIR(ctinfo); @@ -622,24 +623,15 @@ static unsigned int nf_nat_sdp_media(struct sk_buff *skb, unsigned int protoff, int ret; rtp_exp->tuple.dst.u.udp.port = htons(port); - ret = nf_ct_expect_related(rtp_exp, - NF_CT_EXP_F_SKIP_MASTER); - if (ret == -EBUSY) - continue; - else if (ret < 0) { - port = 0; - break; - } rtcp_exp->tuple.dst.u.udp.port = htons(port + 1); - ret = nf_ct_expect_related(rtcp_exp, - NF_CT_EXP_F_SKIP_MASTER); + + ret = nf_ct_expect_related_pair(rtp_pair, + NF_CT_EXP_F_SKIP_MASTER); if (ret == 0) break; - else if (ret == -EBUSY) { - nf_ct_unexpect_related(rtp_exp); + else if (ret == -EBUSY) continue; - } else if (ret < 0) { - nf_ct_unexpect_related(rtp_exp); + else if (ret < 0) { port = 0; break; } -- cgit v1.2.3 From d6f0248f04a96249660591e47fcf37ba98ac7ea3 Mon Sep 17 00:00:00 2001 From: Wei Liu Date: Tue, 30 Jun 2026 14:57:54 -0700 Subject: mshv: fix hv_input_get_system_property struct Keep it in sync with the correct definition. The old code worked by chance. Fixes: e68bda71a2384 ("hyperv: Add new Hyper-V headers in include/hyperv") Cc: stable@kernel.org Signed-off-by: Wei Liu --- include/hyperv/hvhdk_mini.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'include') diff --git a/include/hyperv/hvhdk_mini.h b/include/hyperv/hvhdk_mini.h index b4cb2fa26e9b..035ba20870f7 100644 --- a/include/hyperv/hvhdk_mini.h +++ b/include/hyperv/hvhdk_mini.h @@ -184,8 +184,9 @@ enum hv_dynamic_processor_feature_property { struct hv_input_get_system_property { u32 property_id; /* enum hv_system_property */ + u32 reserved; union { - u32 as_uint32; + u64 as_uint64; #if IS_ENABLED(CONFIG_X86) /* enum hv_dynamic_processor_feature_property */ u32 hv_processor_feature; -- cgit v1.2.3 From cbfe2b24a1ea9de35032dbdd100fdc700f5be92d Mon Sep 17 00:00:00 2001 From: Carlos López Date: Wed, 15 Jul 2026 12:51:37 +0200 Subject: KVM: arm64: vgic: Fix race between LPI release and re-registration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix a potential race between decrementing an LPI's reference count and evicting that structure from the LPI xarray. LPI structures are maintained in the VGIC LPI xarray (dist->lpi_xa). When the reference count of an LPI structure drops to zero, vgic_release_lpi_locked() removes the structure from the xarray and frees it under the xarray lock. However, the release of an LPI can race with a concurrent LPI re-registration with the same INTID via vgic_add_lpi() on another CPU, since the reference count drop and the xarray eviction are not performed in a single atomic step. This can happen e.g. if the guest issues a DISCARD while the LPI is still referenced from a vCPU's active-pending list (ap_list), and the same INTID is re-mapped via MAPTI. Particularly, vgic_release_lpi_locked() is called from two distinct paths: direct release via vgic_put_irq(), and deferred release via vgic_release_deleted_lpis(). During direct release, the issue can result in deleting a newly registered LPI from the xarray: CPU0 (Releasing LPI) CPU1 (Adding new LPI) ==================== ===================== vgic_put_irq() __vgic_put_irq() refcount_dec_and_test() vgic_add_lpi() xa_lock_irqsave() old_irq = xa_load(.., intid) vgic_try_get_irq_ref(old_irq) == false new IRQ inserted --> __xa_store(.., intid, ..) xa_unlock_irqrestore() xa_lock_irqsave(); vgic_release_lpi_locked() __xa_erase(.., irq->intid) <-- BUG: new IRQ is erased kfree_rcu(old_irq) During the deferred release path, the old IRQ can be leaked: CPU0 (Releasing LPI) CPU1 (Adding new LPI) ==================== ===================== vgic_put_irq_norelease() __vgic_put_irq() refcount_dec_and_test() irq->pending_release = true vgic_add_lpi() xa_lock_irqsave() old_irq = xa_load(.., intid) vgic_try_get_irq_ref(oldirq) == false BUG: old IRQ overwritten --> __xa_store(.., intid, ..) xa_unlock_irqrestore() vgic_release_deleted_lpis() xa_lock_irqsave() xa_for_each() { .. } <-- old IRQ with pending_release = true is gone, so it cannot be released To fix the direct release path, move the reference count drop inside the xarray lock, making sure that vgic_add_lpi() never encounters the to-be-released LPI. In the deferred release path, the refcount drop must happen under a raw spinlock, so the xarray lock cannot be grabbed, and the same solution does not work. Instead, update vgic_add_lpi(), so that if it evicts an LPI from the xarray, it takes on the responsibility of freeing it. Consequently, an LPI may now be freed concurrently after a deferred release drops the refcount, so accessing the pending_release field is no longer safe from use-after-free. Delete all uses of the flag, and update vgic_release_deleted_lpis() to identify orphaned LPIs purely based on their refcount. Reported-by: Claude:claude-opus-4-6 Fixes: 3a08a6ca7c37 ("KVM: arm64: vgic-v3: Use bare refcount for VGIC LPIs") Fixes: d54594accf73 ("KVM: arm64: vgic-v3: Erase LPIs from xarray outside of raw spinlocks") Signed-off-by: Carlos López Link: https://patch.msgid.link/20260715105137.3973823-4-clopez@suse.de Signed-off-by: Marc Zyngier --- arch/arm64/kvm/vgic/vgic-its.c | 24 ++++++++++++++++-------- arch/arm64/kvm/vgic/vgic.c | 18 ++++++++---------- include/kvm/arm_vgic.h | 3 --- 3 files changed, 24 insertions(+), 21 deletions(-) (limited to 'include') diff --git a/arch/arm64/kvm/vgic/vgic-its.c b/arch/arm64/kvm/vgic/vgic-its.c index 740b39875728..5c5d1772147d 100644 --- a/arch/arm64/kvm/vgic/vgic-its.c +++ b/arch/arm64/kvm/vgic/vgic-its.c @@ -116,18 +116,26 @@ static struct vgic_irq *vgic_add_lpi(struct kvm *kvm, u32 intid, kfree(irq); irq = oldirq; } else { - ret = xa_err(__xa_store(&dist->lpi_xa, intid, irq, 0)); - } - - xa_unlock_irqrestore(&dist->lpi_xa, flags); + /* + * The entry is either empty or contains a dead LPI (refcount=0) + * from the deferred release path, pending cleanup by + * vgic_release_deleted_lpis(). Evict and free it if present. + */ + oldirq = __xa_store(&dist->lpi_xa, intid, irq, 0); + ret = xa_err(oldirq); + if (ret) { + xa_unlock_irqrestore(&dist->lpi_xa, flags); + kfree(irq); - if (ret) { - xa_release(&dist->lpi_xa, intid); - kfree(irq); + return ERR_PTR(ret); + } - return ERR_PTR(ret); + if (oldirq && !WARN_ON_ONCE(refcount_read(&oldirq->refcount))) + kfree_rcu(oldirq, rcu); } + xa_unlock_irqrestore(&dist->lpi_xa, flags); + /* * We "cache" the configuration table entries in our struct vgic_irq's. * However we only have those structs for mapped IRQs, so we read in diff --git a/arch/arm64/kvm/vgic/vgic.c b/arch/arm64/kvm/vgic/vgic.c index ccb7e3a90cd0..74bace10a22e 100644 --- a/arch/arm64/kvm/vgic/vgic.c +++ b/arch/arm64/kvm/vgic/vgic.c @@ -146,11 +146,7 @@ static __must_check bool __vgic_put_irq(struct kvm *kvm, struct vgic_irq *irq) static __must_check bool vgic_put_irq_norelease(struct kvm *kvm, struct vgic_irq *irq) { - if (!__vgic_put_irq(kvm, irq)) - return false; - - irq->pending_release = true; - return true; + return __vgic_put_irq(kvm, irq); } void vgic_put_irq(struct kvm *kvm, struct vgic_irq *irq) @@ -167,12 +163,14 @@ void vgic_put_irq(struct kvm *kvm, struct vgic_irq *irq) guard(spinlock_irqsave)(&dist->lpi_xa.xa_lock); } - if (!__vgic_put_irq(kvm, irq)) + if (!irq_is_lpi(kvm, irq->intid)) return; - xa_lock_irqsave(&dist->lpi_xa, flags); - vgic_release_lpi_locked(dist, irq); - xa_unlock_irqrestore(&dist->lpi_xa, flags); + if (refcount_dec_and_lock_irqsave(&irq->refcount, + &dist->lpi_xa.xa_lock, &flags)) { + vgic_release_lpi_locked(dist, irq); + xa_unlock_irqrestore(&dist->lpi_xa, flags); + } } static void vgic_release_deleted_lpis(struct kvm *kvm) @@ -184,7 +182,7 @@ static void vgic_release_deleted_lpis(struct kvm *kvm) xa_lock_irqsave(&dist->lpi_xa, flags); xa_for_each(&dist->lpi_xa, intid, irq) { - if (irq->pending_release) + if (!refcount_read(&irq->refcount)) vgic_release_lpi_locked(dist, irq); } diff --git a/include/kvm/arm_vgic.h b/include/kvm/arm_vgic.h index fe49fb56dc3c..cefddc9c621d 100644 --- a/include/kvm/arm_vgic.h +++ b/include/kvm/arm_vgic.h @@ -247,9 +247,6 @@ struct vgic_irq { * affinity reg (v3). */ - bool pending_release:1; /* Used for LPIs only, unreferenced IRQ - * pending a release */ - bool pending_latch:1; /* The pending latch state used to calculate * the pending state for both level * and edge triggered IRQs. */ -- cgit v1.2.3 From f4f699790590bd0896c48a71e9232a65198f92f0 Mon Sep 17 00:00:00 2001 From: Pablo Neira Ayuso Date: Thu, 16 Jul 2026 10:13:37 +0200 Subject: netfilter: nf_tables: make nft_object rhltable per table The nft_object rhltable is global, this allows for accessing objects that are being dismangled from lookup path by other existing netns. Given the nft_obj_destroy() releases the object inmediately, this might lead to use-after-free of these objects that are being released. Make the existing rhltable per table to address this issue to deal with with the nft_rcv_nl_event() path too. Update nft_obj_lookup() to take the table as non-const, otherwise, compiler complains when passing the objname_ht to rhltable_lookup(). Fixes: 4d44175aa5bb ("netfilter: nf_tables: handle nft_object lookups via rhltable") Suggested-by: Florian Westphal Signed-off-by: Pablo Neira Ayuso --- include/net/netfilter/nf_tables.h | 4 +++- net/netfilter/nf_tables_api.c | 34 ++++++++++++++++------------------ 2 files changed, 19 insertions(+), 19 deletions(-) (limited to 'include') diff --git a/include/net/netfilter/nf_tables.h b/include/net/netfilter/nf_tables.h index 9d844354c4d9..3be612145c13 100644 --- a/include/net/netfilter/nf_tables.h +++ b/include/net/netfilter/nf_tables.h @@ -1294,6 +1294,7 @@ static inline void nft_use_inc_restore(u32 *use) * @sets: sets in the table * @objects: stateful objects in the table * @flowtables: flow tables in the table + * @objname_ht: hashtable for objects lookup by name * @hgenerator: handle generator state * @handle: table handle * @use: number of chain references to this table @@ -1313,6 +1314,7 @@ struct nft_table { struct list_head sets; struct list_head objects; struct list_head flowtables; + struct rhltable objname_ht; u64 hgenerator; u64 handle; u32 use; @@ -1400,7 +1402,7 @@ static inline void *nft_obj_data(const struct nft_object *obj) #define nft_expr_obj(expr) *((struct nft_object **)nft_expr_priv(expr)) struct nft_object *nft_obj_lookup(const struct net *net, - const struct nft_table *table, + struct nft_table *table, const struct nlattr *nla, u32 objtype, u8 genmask); diff --git a/net/netfilter/nf_tables_api.c b/net/netfilter/nf_tables_api.c index a9eaf9455c77..af357f6c5070 100644 --- a/net/netfilter/nf_tables_api.c +++ b/net/netfilter/nf_tables_api.c @@ -45,8 +45,6 @@ enum { NFT_VALIDATE_DO, }; -static struct rhltable nft_objname_ht; - static u32 nft_chain_hash(const void *data, u32 len, u32 seed); static u32 nft_chain_hash_obj(const void *data, u32 len, u32 seed); static int nft_chain_hash_cmp(struct rhashtable_compare_arg *, const void *); @@ -1635,6 +1633,10 @@ static int nf_tables_newtable(struct sk_buff *skb, const struct nfnl_info *info, if (err) goto err_chain_ht; + err = rhltable_init(&table->objname_ht, &nft_objname_ht_params); + if (err < 0) + goto err_obj_ht; + INIT_LIST_HEAD(&table->chains); INIT_LIST_HEAD(&table->sets); INIT_LIST_HEAD(&table->objects); @@ -1653,6 +1655,8 @@ static int nf_tables_newtable(struct sk_buff *skb, const struct nfnl_info *info, list_add_tail_rcu(&table->list, &nft_net->tables); return 0; err_trans: + rhltable_destroy(&table->objname_ht); +err_obj_ht: rhltable_destroy(&table->chains_ht); err_chain_ht: kfree(table->udata); @@ -1819,6 +1823,7 @@ static void nf_tables_table_destroy(struct nft_table *table) return; rhltable_destroy(&table->chains_ht); + rhltable_destroy(&table->objname_ht); kfree(table->name); kfree(table->udata); kfree(table); @@ -8086,7 +8091,7 @@ void nft_unregister_obj(struct nft_object_type *obj_type) EXPORT_SYMBOL_GPL(nft_unregister_obj); struct nft_object *nft_obj_lookup(const struct net *net, - const struct nft_table *table, + struct nft_table *table, const struct nlattr *nla, u32 objtype, u8 genmask) { @@ -8102,7 +8107,7 @@ struct nft_object *nft_obj_lookup(const struct net *net, !lockdep_commit_lock_is_held(net)); rcu_read_lock(); - list = rhltable_lookup(&nft_objname_ht, &k, nft_objname_ht_params); + list = rhltable_lookup(&table->objname_ht, &k, nft_objname_ht_params); if (!list) goto out; @@ -8382,7 +8387,7 @@ static int nf_tables_newobj(struct sk_buff *skb, const struct nfnl_info *info, if (err < 0) goto err_trans; - err = rhltable_insert(&nft_objname_ht, &obj->rhlhead, + err = rhltable_insert(&table->objname_ht, &obj->rhlhead, nft_objname_ht_params); if (err < 0) goto err_obj_ht; @@ -8567,8 +8572,8 @@ nf_tables_getobj_single(u32 portid, const struct nfnl_info *info, struct netlink_ext_ack *extack = info->extack; u8 genmask = nft_genmask_cur(info->net); u8 family = info->nfmsg->nfgen_family; - const struct nft_table *table; struct net *net = info->net; + struct nft_table *table; struct nft_object *obj; struct sk_buff *skb2; u32 objtype; @@ -10437,9 +10442,9 @@ static void nf_tables_commit_chain(struct net *net, struct nft_chain *chain) nf_tables_commit_chain_free_rules_old(g0); } -static void nft_obj_del(struct nft_object *obj) +static void nft_obj_del(struct nft_table *table, struct nft_object *obj) { - rhltable_remove(&nft_objname_ht, &obj->rhlhead, nft_objname_ht_params); + rhltable_remove(&table->objname_ht, &obj->rhlhead, nft_objname_ht_params); list_del_rcu(&obj->list); } @@ -11124,7 +11129,7 @@ static int nf_tables_commit(struct net *net, struct sk_buff *skb) break; case NFT_MSG_DELOBJ: case NFT_MSG_DESTROYOBJ: - nft_obj_del(nft_trans_obj(trans)); + nft_obj_del(table, nft_trans_obj(trans)); nf_tables_obj_notify(&ctx, nft_trans_obj(trans), trans->msg_type); break; @@ -11416,7 +11421,7 @@ static int __nf_tables_abort(struct net *net, enum nfnl_abort_action action) nft_trans_destroy(trans); } else { nft_use_dec_restore(&table->use); - nft_obj_del(nft_trans_obj(trans)); + nft_obj_del(table, nft_trans_obj(trans)); } break; case NFT_MSG_DELOBJ: @@ -12043,7 +12048,7 @@ static void __nft_release_table(struct net *net, struct nft_table *table) nft_set_destroy(&ctx, set); } list_for_each_entry_safe(obj, ne, &table->objects, list) { - nft_obj_del(obj); + nft_obj_del(table, obj); nft_use_dec(&table->use); nft_obj_destroy(&ctx, obj); } @@ -12225,10 +12230,6 @@ static int __init nf_tables_module_init(void) if (err < 0) goto err_netdev_notifier; - err = rhltable_init(&nft_objname_ht, &nft_objname_ht_params); - if (err < 0) - goto err_rht_objname; - err = nft_offload_init(); if (err < 0) goto err_offload; @@ -12251,8 +12252,6 @@ err_nfnl_subsys: err_netlink_notifier: nft_offload_exit(); err_offload: - rhltable_destroy(&nft_objname_ht); -err_rht_objname: unregister_netdevice_notifier(&nf_tables_flowtable_notifier); err_netdev_notifier: nf_tables_core_module_exit(); @@ -12274,7 +12273,6 @@ static void __exit nf_tables_module_exit(void) unregister_pernet_subsys(&nf_tables_net_ops); cancel_work_sync(&trans_gc_work); rcu_barrier(); - rhltable_destroy(&nft_objname_ht); nf_tables_core_module_exit(); } -- cgit v1.2.3 From e876b75b9020a97bbdc79721e7fc749024891c65 Mon Sep 17 00:00:00 2001 From: Julian Anastasov Date: Wed, 22 Jul 2026 13:15:15 +0300 Subject: ipvs: fix the checksum validations ip_vs_in_icmp_v6() is missing checksum validation for ICMPv6 packets from clients. In fact, as for TCP/UDP we should validate the checksum for ICMP packets only when we mangle the packets on MASQ or on reply for tunnel. Also, Sashiko points out that handle_response_icmp() being common for IPv4 and IPv6 is missing the pseudo-header calculation while validating ICMPv6 messages from real servers which is a problem if checksum is not validated by the hardware. Fix the problems by creating ip_vs_checksum_common_check() helper and use it for TCP/UDP/ICMP both for IPv4 and IPv6. Rely on the nf_checksum() for validating the ICMP messages but use it also for TCP and UDP. Use correct IP offset for IP_VS_DBG_RL_PKT for TCP/UDP/SCTP. IPVS packets (TCP/UDP/SCTP/ICMP) do not need checksum validation on LOCAL_OUT (local clients or local real servers) and on FORWARD (traffic from servers on LAN). Do it only on LOCAL_IN, in case nf_checksum() is not called on PRE_ROUTING. Also, ip_vs_checksum_complete() can be marked static. Fixes: 2a3b791e6e11 ("IPVS: Add/adjust Netfilter hook functions and helpers for v6") Link: https://sashiko.dev/#/patchset/20260708180315.77413-1-ja%40ssi.bg Signed-off-by: Julian Anastasov Signed-off-by: Pablo Neira Ayuso --- include/net/ip_vs.h | 31 ++++++++++++++++++++-- net/netfilter/ipvs/ip_vs_core.c | 20 +++++++++++--- net/netfilter/ipvs/ip_vs_proto_sctp.c | 15 ++++++----- net/netfilter/ipvs/ip_vs_proto_tcp.c | 44 ++++++------------------------ net/netfilter/ipvs/ip_vs_proto_udp.c | 50 ++++++++--------------------------- 5 files changed, 74 insertions(+), 86 deletions(-) (limited to 'include') diff --git a/include/net/ip_vs.h b/include/net/ip_vs.h index 417ff51f62fc..d8f9ddb0fb38 100644 --- a/include/net/ip_vs.h +++ b/include/net/ip_vs.h @@ -25,7 +25,9 @@ #include /* for union nf_inet_addr */ #include #include /* for struct ipv6hdr */ +#include #include +#include #if IS_ENABLED(CONFIG_NF_CONNTRACK) #include #endif @@ -2066,8 +2068,6 @@ void ip_vs_nat_icmp_v6(struct sk_buff *skb, struct ip_vs_protocol *pp, struct ip_vs_conn *cp, int dir); #endif -__sum16 ip_vs_checksum_complete(struct sk_buff *skb, int offset); - static inline __wsum ip_vs_check_diff4(__be32 old, __be32 new, __wsum oldsum) { __be32 diff[2] = { ~old, new }; @@ -2093,6 +2093,33 @@ static inline __wsum ip_vs_check_diff2(__be16 old, __be16 new, __wsum oldsum) return csum_partial(diff, sizeof(diff), oldsum); } +static inline bool ip_vs_checksum_needed(struct sk_buff *skb, int af) +{ + /* Checksum unnecessary or already validated? */ + if (skb_csum_unnecessary(skb)) + return false; + /* LOCAL_OUT ? */ + if (!skb->dev || skb->dev->flags & IFF_LOOPBACK) + return false; + /* !LOCAL_IN (FORWARD) ? */ + if (af == AF_INET6) { + if (!(dst_rt6_info(skb_dst(skb))->rt6i_flags & RTF_LOCAL)) + return false; + } else { + if (!(skb_rtable(skb)->rt_flags & RTCF_LOCAL)) + return false; + } + return true; +} + +static inline bool ip_vs_checksum_common_check(struct sk_buff *skb, + int offset, int proto, int af) +{ + if (!ip_vs_checksum_needed(skb, af)) + return true; + return !nf_checksum(skb, NF_INET_LOCAL_IN, offset, proto, af); +} + /* Forget current conntrack (unconfirmed) and attach notrack entry */ static inline void ip_vs_notrack(struct sk_buff *skb) { diff --git a/net/netfilter/ipvs/ip_vs_core.c b/net/netfilter/ipvs/ip_vs_core.c index bafab93451d0..c8b512725e6e 100644 --- a/net/netfilter/ipvs/ip_vs_core.c +++ b/net/netfilter/ipvs/ip_vs_core.c @@ -867,7 +867,7 @@ static int sysctl_nat_icmp_send(struct netns_ipvs *ipvs) { return 0; } #endif -__sum16 ip_vs_checksum_complete(struct sk_buff *skb, int offset) +static __sum16 ip_vs_checksum_complete(struct sk_buff *skb, int offset) { return csum_fold(skb_checksum(skb, offset, skb->len - offset, 0)); } @@ -1038,13 +1038,14 @@ static int handle_response_icmp(int af, struct sk_buff *skb, unsigned int offset, unsigned int ihl, unsigned int hooknum) { + int iproto = af == AF_INET6 ? IPPROTO_ICMPV6 : IPPROTO_ICMP; unsigned int verdict = NF_DROP; if (IP_VS_FWD_METHOD(cp) != IP_VS_CONN_F_MASQ) goto after_nat; /* Ensure the checksum is correct */ - if (!skb_csum_unnecessary(skb) && ip_vs_checksum_complete(skb, ihl)) { + if (!ip_vs_checksum_common_check(skb, ihl, iproto, af)) { /* Failed checksum! */ IP_VS_DBG_BUF(1, "Forward ICMP: failed checksum from %s!\n", IP_VS_DBG_ADDR(af, snet)); @@ -1898,7 +1899,8 @@ ip_vs_in_icmp(struct netns_ipvs *ipvs, struct sk_buff *skb, int *related, verdict = NF_DROP; /* Ensure the checksum is correct */ - if (!skb_csum_unnecessary(skb) && ip_vs_checksum_complete(skb, ihl)) { + if ((IP_VS_FWD_METHOD(cp) == IP_VS_CONN_F_MASQ || tunnel) && + !ip_vs_checksum_common_check(skb, ihl, IPPROTO_ICMP, AF_INET)) { /* Failed checksum! */ IP_VS_DBG(1, "Incoming ICMP: failed checksum from %pI4!\n", &iph->saddr); @@ -2064,6 +2066,18 @@ static int ip_vs_in_icmp_v6(struct netns_ipvs *ipvs, struct sk_buff *skb, goto out; } + verdict = NF_DROP; + + /* Ensure the checksum is correct */ + if (IP_VS_FWD_METHOD(cp) == IP_VS_CONN_F_MASQ && + !ip_vs_checksum_common_check(skb, iph->len, IPPROTO_ICMPV6, + AF_INET6)) { + /* Failed checksum! */ + IP_VS_DBG(1, "Incoming ICMPv6: failed checksum from %pI6c!\n", + &iph->saddr); + goto out; + } + /* do the statistics and put it back */ ip_vs_in_stats(cp, skb); diff --git a/net/netfilter/ipvs/ip_vs_proto_sctp.c b/net/netfilter/ipvs/ip_vs_proto_sctp.c index c67317be17df..f6f732b7dfa8 100644 --- a/net/netfilter/ipvs/ip_vs_proto_sctp.c +++ b/net/netfilter/ipvs/ip_vs_proto_sctp.c @@ -11,7 +11,7 @@ static int sctp_csum_check(int af, struct sk_buff *skb, struct ip_vs_protocol *pp, - unsigned int sctphoff); + struct ip_vs_iphdr *iph); static int sctp_conn_schedule(struct netns_ipvs *ipvs, int af, struct sk_buff *skb, @@ -109,7 +109,7 @@ sctp_snat_handler(struct sk_buff *skb, struct ip_vs_protocol *pp, int ret; /* Some checks before mangling */ - if (!sctp_csum_check(cp->af, skb, pp, sctphoff)) + if (!sctp_csum_check(cp->af, skb, pp, iph)) return 0; /* Call application helper if needed */ @@ -157,7 +157,7 @@ sctp_dnat_handler(struct sk_buff *skb, struct ip_vs_protocol *pp, int ret; /* Some checks before mangling */ - if (!sctp_csum_check(cp->af, skb, pp, sctphoff)) + if (!sctp_csum_check(cp->af, skb, pp, iph)) return 0; /* Call application helper if needed */ @@ -187,19 +187,22 @@ sctp_dnat_handler(struct sk_buff *skb, struct ip_vs_protocol *pp, static int sctp_csum_check(int af, struct sk_buff *skb, struct ip_vs_protocol *pp, - unsigned int sctphoff) + struct ip_vs_iphdr *iph) { + unsigned int sctphoff = iph->len; struct sctphdr *sh; __le32 cmp, val; + if (!ip_vs_checksum_needed(skb, af)) + return 1; sh = (struct sctphdr *)(skb->data + sctphoff); cmp = sh->checksum; val = sctp_compute_cksum(skb, sctphoff); if (val != cmp) { /* CRC failure, dump it. */ - IP_VS_DBG_RL_PKT(0, af, pp, skb, 0, - "Failed checksum for"); + IP_VS_DBG_RL_PKT(0, af, pp, skb, iph->off, + "Failed checksum for"); return 0; } return 1; diff --git a/net/netfilter/ipvs/ip_vs_proto_tcp.c b/net/netfilter/ipvs/ip_vs_proto_tcp.c index f86b763efcc4..533fce3e5e4e 100644 --- a/net/netfilter/ipvs/ip_vs_proto_tcp.c +++ b/net/netfilter/ipvs/ip_vs_proto_tcp.c @@ -29,7 +29,7 @@ static int tcp_csum_check(int af, struct sk_buff *skb, struct ip_vs_protocol *pp, - unsigned int tcphoff); + struct ip_vs_iphdr *iph); static int tcp_conn_schedule(struct netns_ipvs *ipvs, int af, struct sk_buff *skb, @@ -166,7 +166,7 @@ tcp_snat_handler(struct sk_buff *skb, struct ip_vs_protocol *pp, int ret; /* Some checks before mangling */ - if (!tcp_csum_check(cp->af, skb, pp, tcphoff)) + if (!tcp_csum_check(cp->af, skb, pp, iph)) return 0; /* Call application helper if needed */ @@ -244,7 +244,7 @@ tcp_dnat_handler(struct sk_buff *skb, struct ip_vs_protocol *pp, int ret; /* Some checks before mangling */ - if (!tcp_csum_check(cp->af, skb, pp, tcphoff)) + if (!tcp_csum_check(cp->af, skb, pp, iph)) return 0; /* @@ -302,41 +302,13 @@ tcp_dnat_handler(struct sk_buff *skb, struct ip_vs_protocol *pp, static int tcp_csum_check(int af, struct sk_buff *skb, struct ip_vs_protocol *pp, - unsigned int tcphoff) + struct ip_vs_iphdr *iph) { - switch (skb->ip_summed) { - case CHECKSUM_NONE: - skb->csum = skb_checksum(skb, tcphoff, skb->len - tcphoff, 0); - fallthrough; - case CHECKSUM_COMPLETE: -#ifdef CONFIG_IP_VS_IPV6 - if (af == AF_INET6) { - if (csum_ipv6_magic(&ipv6_hdr(skb)->saddr, - &ipv6_hdr(skb)->daddr, - skb->len - tcphoff, - IPPROTO_TCP, - skb->csum)) { - IP_VS_DBG_RL_PKT(0, af, pp, skb, 0, - "Failed checksum for"); - return 0; - } - } else -#endif - if (csum_tcpudp_magic(ip_hdr(skb)->saddr, - ip_hdr(skb)->daddr, - skb->len - tcphoff, - ip_hdr(skb)->protocol, - skb->csum)) { - IP_VS_DBG_RL_PKT(0, af, pp, skb, 0, - "Failed checksum for"); - return 0; - } - break; - default: - /* No need to checksum. */ - break; + if (!ip_vs_checksum_common_check(skb, iph->len, IPPROTO_TCP, af)) { + IP_VS_DBG_RL_PKT(0, af, pp, skb, iph->off, + "Failed checksum for"); + return 0; } - return 1; } diff --git a/net/netfilter/ipvs/ip_vs_proto_udp.c b/net/netfilter/ipvs/ip_vs_proto_udp.c index 58f9e255927e..de3597347542 100644 --- a/net/netfilter/ipvs/ip_vs_proto_udp.c +++ b/net/netfilter/ipvs/ip_vs_proto_udp.c @@ -25,7 +25,7 @@ static int udp_csum_check(int af, struct sk_buff *skb, struct ip_vs_protocol *pp, - unsigned int udphoff); + struct ip_vs_iphdr *iph); static int udp_conn_schedule(struct netns_ipvs *ipvs, int af, struct sk_buff *skb, @@ -155,7 +155,7 @@ udp_snat_handler(struct sk_buff *skb, struct ip_vs_protocol *pp, int ret; /* Some checks before mangling */ - if (!udp_csum_check(cp->af, skb, pp, udphoff)) + if (!udp_csum_check(cp->af, skb, pp, iph)) return 0; /* @@ -238,7 +238,7 @@ udp_dnat_handler(struct sk_buff *skb, struct ip_vs_protocol *pp, int ret; /* Some checks before mangling */ - if (!udp_csum_check(cp->af, skb, pp, udphoff)) + if (!udp_csum_check(cp->af, skb, pp, iph)) return 0; /* @@ -298,48 +298,20 @@ udp_dnat_handler(struct sk_buff *skb, struct ip_vs_protocol *pp, static int udp_csum_check(int af, struct sk_buff *skb, struct ip_vs_protocol *pp, - unsigned int udphoff) + struct ip_vs_iphdr *iph) { struct udphdr _udph, *uh; - uh = skb_header_pointer(skb, udphoff, sizeof(_udph), &_udph); + uh = skb_header_pointer(skb, iph->len, sizeof(_udph), &_udph); if (uh == NULL) return 0; - if (uh->check != 0) { - switch (skb->ip_summed) { - case CHECKSUM_NONE: - skb->csum = skb_checksum(skb, udphoff, - skb->len - udphoff, 0); - fallthrough; - case CHECKSUM_COMPLETE: -#ifdef CONFIG_IP_VS_IPV6 - if (af == AF_INET6) { - if (csum_ipv6_magic(&ipv6_hdr(skb)->saddr, - &ipv6_hdr(skb)->daddr, - skb->len - udphoff, - IPPROTO_UDP, - skb->csum)) { - IP_VS_DBG_RL_PKT(0, af, pp, skb, 0, - "Failed checksum for"); - return 0; - } - } else -#endif - if (csum_tcpudp_magic(ip_hdr(skb)->saddr, - ip_hdr(skb)->daddr, - skb->len - udphoff, - ip_hdr(skb)->protocol, - skb->csum)) { - IP_VS_DBG_RL_PKT(0, af, pp, skb, 0, - "Failed checksum for"); - return 0; - } - break; - default: - /* No need to checksum. */ - break; - } + if (!uh->check) + return 1; + if (!ip_vs_checksum_common_check(skb, iph->len, IPPROTO_UDP, af)) { + IP_VS_DBG_RL_PKT(0, af, pp, skb, iph->off, + "Failed checksum for"); + return 0; } return 1; } -- cgit v1.2.3 From 15cab31a3730e05f0767b922a7450e5d784b2607 Mon Sep 17 00:00:00 2001 From: Julian Anastasov Date: Wed, 22 Jul 2026 13:15:16 +0300 Subject: ipvs: fix places with wrong packet offsets The offsets we use to packet headers and payloads should be based on skb->data. We even already respect non-zero network offset in ip_vs_fill_iph_skb() but some places do it wrongly and support only zero offset which is expected for the IP layer where IPVS has hooks. Change all places that instead of skb->data use offsets based on the network header (skb_network_header, ip_hdr, etc) because this doubles the network offset as noted by Sashiko. For ip_vs_nat_icmp_v6() we can even rely on the IPv6 header parsing done by the caller. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Link: https://sashiko.dev/#/patchset/20260710143733.29741-2-fw%40strlen.de Signed-off-by: Julian Anastasov Signed-off-by: Pablo Neira Ayuso --- include/net/ip_vs.h | 15 ++-- net/netfilter/ipvs/ip_vs_app.c | 4 +- net/netfilter/ipvs/ip_vs_core.c | 133 +++++++++++++++++----------------- net/netfilter/ipvs/ip_vs_proto_sctp.c | 4 +- net/netfilter/ipvs/ip_vs_proto_tcp.c | 4 +- net/netfilter/ipvs/ip_vs_proto_udp.c | 4 +- net/netfilter/ipvs/ip_vs_xmit.c | 26 ++++--- 7 files changed, 97 insertions(+), 93 deletions(-) (limited to 'include') diff --git a/include/net/ip_vs.h b/include/net/ip_vs.h index d8f9ddb0fb38..4a10a01d6e2f 100644 --- a/include/net/ip_vs.h +++ b/include/net/ip_vs.h @@ -1974,8 +1974,9 @@ int ip_vs_tunnel_xmit(struct sk_buff *skb, struct ip_vs_conn *cp, int ip_vs_dr_xmit(struct sk_buff *skb, struct ip_vs_conn *cp, struct ip_vs_protocol *pp, struct ip_vs_iphdr *iph); int ip_vs_icmp_xmit(struct sk_buff *skb, struct ip_vs_conn *cp, - struct ip_vs_protocol *pp, int offset, - unsigned int hooknum, struct ip_vs_iphdr *iph); + struct ip_vs_protocol *pp, unsigned int toff, + unsigned int wlen, unsigned int hooknum, + struct ip_vs_iphdr *ciph); void ip_vs_dest_dst_rcu_free(struct rcu_head *head); #ifdef CONFIG_IP_VS_IPV6 @@ -1988,8 +1989,9 @@ int ip_vs_tunnel_xmit_v6(struct sk_buff *skb, struct ip_vs_conn *cp, int ip_vs_dr_xmit_v6(struct sk_buff *skb, struct ip_vs_conn *cp, struct ip_vs_protocol *pp, struct ip_vs_iphdr *iph); int ip_vs_icmp_xmit_v6(struct sk_buff *skb, struct ip_vs_conn *cp, - struct ip_vs_protocol *pp, int offset, - unsigned int hooknum, struct ip_vs_iphdr *iph); + struct ip_vs_protocol *pp, unsigned int toff, + unsigned int wlen, unsigned int hooknum, + struct ip_vs_iphdr *ciph); #endif #ifdef CONFIG_SYSCTL @@ -2061,11 +2063,12 @@ static inline bool ip_vs_conn_use_hash2(struct ip_vs_conn *cp) } void ip_vs_nat_icmp(struct sk_buff *skb, struct ip_vs_protocol *pp, - struct ip_vs_conn *cp, int dir); + struct ip_vs_conn *cp, int dir, unsigned int toff); #ifdef CONFIG_IP_VS_IPV6 void ip_vs_nat_icmp_v6(struct sk_buff *skb, struct ip_vs_protocol *pp, - struct ip_vs_conn *cp, int dir); + struct ip_vs_conn *cp, int dir, unsigned int toff, + struct ip_vs_iphdr *ciph); #endif static inline __wsum ip_vs_check_diff4(__be32 old, __be32 new, __wsum oldsum) diff --git a/net/netfilter/ipvs/ip_vs_app.c b/net/netfilter/ipvs/ip_vs_app.c index b0e00be85cb1..11cbdbaf561d 100644 --- a/net/netfilter/ipvs/ip_vs_app.c +++ b/net/netfilter/ipvs/ip_vs_app.c @@ -367,7 +367,7 @@ static inline int app_tcp_pkt_out(struct ip_vs_conn *cp, struct sk_buff *skb, if (skb_ensure_writable(skb, ipvsh->len + sizeof(*th))) return 0; - th = (struct tcphdr *)(skb_network_header(skb) + ipvsh->len); + th = (struct tcphdr *)(skb->data + ipvsh->len); /* * Remember seq number in case this pkt gets resized @@ -443,7 +443,7 @@ static inline int app_tcp_pkt_in(struct ip_vs_conn *cp, struct sk_buff *skb, if (skb_ensure_writable(skb, ipvsh->len + sizeof(*th))) return 0; - th = (struct tcphdr *)(skb_network_header(skb) + ipvsh->len); + th = (struct tcphdr *)(skb->data + ipvsh->len); /* * Remember seq number in case this pkt gets resized diff --git a/net/netfilter/ipvs/ip_vs_core.c b/net/netfilter/ipvs/ip_vs_core.c index c8b512725e6e..cd5eb71543ec 100644 --- a/net/netfilter/ipvs/ip_vs_core.c +++ b/net/netfilter/ipvs/ip_vs_core.c @@ -924,13 +924,12 @@ static int ip_vs_route_me_harder(struct netns_ipvs *ipvs, int af, * - inout: 1=in->out, 0=out->in */ void ip_vs_nat_icmp(struct sk_buff *skb, struct ip_vs_protocol *pp, - struct ip_vs_conn *cp, int inout) + struct ip_vs_conn *cp, int inout, unsigned int toff) { struct iphdr *iph = ip_hdr(skb); - unsigned int icmp_offset = iph->ihl*4; - struct icmphdr *icmph = (struct icmphdr *)(skb_network_header(skb) + - icmp_offset); + struct icmphdr *icmph = (struct icmphdr *)(skb->data + toff); struct iphdr *ciph = (struct iphdr *)(icmph + 1); + unsigned int coff __maybe_unused = toff + sizeof(struct icmphdr); if (inout) { iph->saddr = cp->vaddr.ip; @@ -957,48 +956,45 @@ void ip_vs_nat_icmp(struct sk_buff *skb, struct ip_vs_protocol *pp, /* And finally the ICMP checksum */ icmph->checksum = 0; - icmph->checksum = ip_vs_checksum_complete(skb, icmp_offset); + icmph->checksum = ip_vs_checksum_complete(skb, toff); skb->ip_summed = CHECKSUM_UNNECESSARY; if (inout) - IP_VS_DBG_PKT(11, AF_INET, pp, skb, (void *)ciph - (void *)iph, - "Forwarding altered outgoing ICMP"); + IP_VS_DBG_PKT(11, AF_INET, pp, skb, coff, + "Forwarding altered outgoing ICMP"); else - IP_VS_DBG_PKT(11, AF_INET, pp, skb, (void *)ciph - (void *)iph, - "Forwarding altered incoming ICMP"); + IP_VS_DBG_PKT(11, AF_INET, pp, skb, coff, + "Forwarding altered incoming ICMP"); } #ifdef CONFIG_IP_VS_IPV6 void ip_vs_nat_icmp_v6(struct sk_buff *skb, struct ip_vs_protocol *pp, - struct ip_vs_conn *cp, int inout) + struct ip_vs_conn *cp, int inout, unsigned int toff, + struct ip_vs_iphdr *ciph) { struct ipv6hdr *iph = ipv6_hdr(skb); - unsigned int icmp_offset = 0; - unsigned int offs = 0; /* header offset*/ int protocol; struct icmp6hdr *icmph; - struct ipv6hdr *ciph; - unsigned short fragoffs; + struct ipv6hdr *cih; - ipv6_find_hdr(skb, &icmp_offset, IPPROTO_ICMPV6, &fragoffs, NULL); - icmph = (struct icmp6hdr *)(skb_network_header(skb) + icmp_offset); - offs = icmp_offset + sizeof(struct icmp6hdr); - ciph = (struct ipv6hdr *)(skb_network_header(skb) + offs); + icmph = (struct icmp6hdr *)(skb->data + toff); + cih = (struct ipv6hdr *)(skb->data + ciph->off); - protocol = ipv6_find_hdr(skb, &offs, -1, &fragoffs, NULL); + protocol = ciph->protocol; if (inout) { iph->saddr = cp->vaddr.in6; - ciph->daddr = cp->vaddr.in6; + cih->daddr = cp->vaddr.in6; } else { iph->daddr = cp->daddr.in6; - ciph->saddr = cp->daddr.in6; + cih->saddr = cp->daddr.in6; } /* the TCP/UDP/SCTP port */ - if (!fragoffs && (IPPROTO_TCP == protocol || IPPROTO_UDP == protocol || - IPPROTO_SCTP == protocol)) { - __be16 *ports = (void *)(skb_network_header(skb) + offs); + if (!ciph->fragoffs && + (protocol == IPPROTO_TCP || protocol == IPPROTO_UDP || + protocol == IPPROTO_SCTP)) { + __be16 *ports = (void *)(skb->data + ciph->len); IP_VS_DBG(11, "%s() changed port %d to %d\n", __func__, ntohs(inout ? ports[1] : ports[0]), @@ -1011,19 +1007,17 @@ void ip_vs_nat_icmp_v6(struct sk_buff *skb, struct ip_vs_protocol *pp, /* And finally the ICMP checksum */ icmph->icmp6_cksum = ~csum_ipv6_magic(&iph->saddr, &iph->daddr, - skb->len - icmp_offset, + skb->len - toff, IPPROTO_ICMPV6, 0); - skb->csum_start = skb_network_header(skb) - skb->head + icmp_offset; + skb->csum_start = skb_headroom(skb) + toff; skb->csum_offset = offsetof(struct icmp6hdr, icmp6_cksum); skb->ip_summed = CHECKSUM_PARTIAL; if (inout) - IP_VS_DBG_PKT(11, AF_INET6, pp, skb, - (void *)ciph - (void *)iph, + IP_VS_DBG_PKT(11, AF_INET6, pp, skb, ciph->off, "Forwarding altered outgoing ICMPv6"); else - IP_VS_DBG_PKT(11, AF_INET6, pp, skb, - (void *)ciph - (void *)iph, + IP_VS_DBG_PKT(11, AF_INET6, pp, skb, ciph->off, "Forwarding altered incoming ICMPv6"); } #endif @@ -1033,37 +1027,38 @@ void ip_vs_nat_icmp_v6(struct sk_buff *skb, struct ip_vs_protocol *pp, */ static int handle_response_icmp(int af, struct sk_buff *skb, union nf_inet_addr *snet, - __u8 protocol, struct ip_vs_conn *cp, + struct ip_vs_conn *cp, struct ip_vs_protocol *pp, - unsigned int offset, unsigned int ihl, - unsigned int hooknum) + struct ip_vs_iphdr *ciph, + unsigned int toff, unsigned int hooknum) { int iproto = af == AF_INET6 ? IPPROTO_ICMPV6 : IPPROTO_ICMP; unsigned int verdict = NF_DROP; + unsigned int ctoff = ciph->len; if (IP_VS_FWD_METHOD(cp) != IP_VS_CONN_F_MASQ) goto after_nat; /* Ensure the checksum is correct */ - if (!ip_vs_checksum_common_check(skb, ihl, iproto, af)) { + if (!ip_vs_checksum_common_check(skb, toff, iproto, af)) { /* Failed checksum! */ IP_VS_DBG_BUF(1, "Forward ICMP: failed checksum from %s!\n", IP_VS_DBG_ADDR(af, snet)); goto out; } - if (IPPROTO_TCP == protocol || IPPROTO_UDP == protocol || - IPPROTO_SCTP == protocol) - offset += 2 * sizeof(__u16); - if (skb_ensure_writable(skb, offset)) + if (ciph->protocol == IPPROTO_TCP || ciph->protocol == IPPROTO_UDP || + ciph->protocol == IPPROTO_SCTP) + ctoff += 2 * sizeof(__u16); + if (skb_ensure_writable(skb, ctoff)) goto out; #ifdef CONFIG_IP_VS_IPV6 if (af == AF_INET6) - ip_vs_nat_icmp_v6(skb, pp, cp, 1); + ip_vs_nat_icmp_v6(skb, pp, cp, 1, toff, ciph); else #endif - ip_vs_nat_icmp(skb, pp, cp, 1); + ip_vs_nat_icmp(skb, pp, cp, 1, toff); if (ip_vs_route_me_harder(cp->ipvs, af, skb, hooknum)) goto out; @@ -1091,9 +1086,9 @@ out: * Currently handles error types - unreachable, quench, ttl exceeded. */ static int ip_vs_out_icmp(struct netns_ipvs *ipvs, struct sk_buff *skb, - int *related, unsigned int hooknum) + int *related, unsigned int hooknum, + struct ip_vs_iphdr *ipvsh) { - struct iphdr *iph; struct icmphdr _icmph, *ic; struct iphdr _ciph, *cih; /* The ip header contained within the ICMP */ struct ip_vs_iphdr ciph; @@ -1108,17 +1103,19 @@ static int ip_vs_out_icmp(struct netns_ipvs *ipvs, struct sk_buff *skb, if (ip_is_fragment(ip_hdr(skb))) { if (ip_vs_gather_frags(ipvs, skb, ip_vs_defrag_user(hooknum))) return NF_STOLEN; + if (!ip_vs_fill_iph_skb(AF_INET, skb, false, ipvsh)) + return NF_ACCEPT; } - iph = ip_hdr(skb); - offset = ihl = iph->ihl * 4; + ihl = ipvsh->len; + offset = ipvsh->len; ic = skb_header_pointer(skb, offset, sizeof(_icmph), &_icmph); if (ic == NULL) return NF_DROP; IP_VS_DBG(12, "Outgoing ICMP (%d,%d) %pI4->%pI4\n", ic->type, ntohs(icmp_id(ic)), - &iph->saddr, &iph->daddr); + &ipvsh->saddr.ip, &ipvsh->daddr.ip); /* * Work through seeing if this is for us. @@ -1137,7 +1134,7 @@ static int ip_vs_out_icmp(struct netns_ipvs *ipvs, struct sk_buff *skb, /* Now find the contained IP header */ offset += sizeof(_icmph); cih = skb_header_pointer(skb, offset, sizeof(_ciph), &_ciph); - if (cih == NULL) + if (!(cih && cih->version == 4 && cih->ihl >= 5)) return NF_ACCEPT; /* The packet looks wrong, ignore */ pp = ip_vs_proto_get(cih->protocol); @@ -1160,9 +1157,9 @@ static int ip_vs_out_icmp(struct netns_ipvs *ipvs, struct sk_buff *skb, if (!cp) return NF_ACCEPT; - snet.ip = iph->saddr; - return handle_response_icmp(AF_INET, skb, &snet, cih->protocol, cp, - pp, ciph.len, ihl, hooknum); + snet.ip = ipvsh->saddr.ip; + return handle_response_icmp(AF_INET, skb, &snet, cp, pp, &ciph, ihl, + hooknum); } #ifdef CONFIG_IP_VS_IPV6 @@ -1175,7 +1172,6 @@ static int ip_vs_out_icmp_v6(struct netns_ipvs *ipvs, struct sk_buff *skb, struct ip_vs_conn *cp; struct ip_vs_protocol *pp; union nf_inet_addr snet; - unsigned int offset; *related = 1; ic = frag_safe_skb_hp(skb, ipvsh->len, sizeof(_icmph), &_icmph); @@ -1218,9 +1214,8 @@ static int ip_vs_out_icmp_v6(struct netns_ipvs *ipvs, struct sk_buff *skb, return NF_ACCEPT; snet.in6 = ciph.saddr.in6; - offset = ciph.len; - return handle_response_icmp(AF_INET6, skb, &snet, ciph.protocol, cp, - pp, offset, ipvsh->len, hooknum); + return handle_response_icmp(AF_INET6, skb, &snet, cp, pp, &ciph, + ipvsh->len, hooknum); } #endif @@ -1546,7 +1541,8 @@ ip_vs_out_hook(void *priv, struct sk_buff *skb, const struct nf_hook_state *stat #endif if (unlikely(iph.protocol == IPPROTO_ICMP)) { int related; - int verdict = ip_vs_out_icmp(ipvs, skb, &related, hooknum); + int verdict = ip_vs_out_icmp(ipvs, skb, &related, + hooknum, &iph); if (related) return verdict; @@ -1754,9 +1750,8 @@ unk: */ static int ip_vs_in_icmp(struct netns_ipvs *ipvs, struct sk_buff *skb, int *related, - unsigned int hooknum) + unsigned int hooknum, struct ip_vs_iphdr *iph) { - struct iphdr *iph; struct icmphdr _icmph, *ic; struct iphdr _ciph, *cih; /* The ip header contained within the ICMP */ struct ip_vs_iphdr ciph; @@ -1766,7 +1761,7 @@ ip_vs_in_icmp(struct netns_ipvs *ipvs, struct sk_buff *skb, int *related, unsigned int offset, offset2, ihl, verdict; bool tunnel, new_cp = false; union nf_inet_addr *raddr; - char *outer_proto = "IPIP"; + char *outer_proto __maybe_unused = "IPIP"; unsigned int hlen_ipip; int ulen = 0; @@ -1776,17 +1771,19 @@ ip_vs_in_icmp(struct netns_ipvs *ipvs, struct sk_buff *skb, int *related, if (ip_is_fragment(ip_hdr(skb))) { if (ip_vs_gather_frags(ipvs, skb, ip_vs_defrag_user(hooknum))) return NF_STOLEN; + if (!ip_vs_fill_iph_skb(AF_INET, skb, false, iph)) + return NF_ACCEPT; } - iph = ip_hdr(skb); - offset = ihl = iph->ihl * 4; + ihl = iph->len; + offset = iph->len; ic = skb_header_pointer(skb, offset, sizeof(_icmph), &_icmph); if (ic == NULL) return NF_DROP; IP_VS_DBG(12, "Incoming ICMP (%d,%d) %pI4->%pI4\n", ic->type, ntohs(icmp_id(ic)), - &iph->saddr, &iph->daddr); + &iph->saddr.ip, &iph->daddr.ip); /* * Work through seeing if this is for us. @@ -1903,7 +1900,7 @@ ip_vs_in_icmp(struct netns_ipvs *ipvs, struct sk_buff *skb, int *related, !ip_vs_checksum_common_check(skb, ihl, IPPROTO_ICMP, AF_INET)) { /* Failed checksum! */ IP_VS_DBG(1, "Incoming ICMP: failed checksum from %pI4!\n", - &iph->saddr); + &iph->saddr.ip); goto out; } @@ -1974,7 +1971,8 @@ ignore_tunnel: if (IPPROTO_TCP == cih->protocol || IPPROTO_UDP == cih->protocol || IPPROTO_SCTP == cih->protocol) offset += 2 * sizeof(__u16); - verdict = ip_vs_icmp_xmit(skb, cp, pp, offset, hooknum, &ciph); + verdict = ip_vs_icmp_xmit(skb, cp, pp, iph->len, offset, hooknum, + &ciph); out: if (likely(!new_cp)) @@ -2087,7 +2085,8 @@ static int ip_vs_in_icmp_v6(struct netns_ipvs *ipvs, struct sk_buff *skb, IPPROTO_SCTP == ciph.protocol) offset += 2 * sizeof(__u16); /* Also mangle ports */ - verdict = ip_vs_icmp_xmit_v6(skb, cp, pp, offset, hooknum, &ciph); + verdict = ip_vs_icmp_xmit_v6(skb, cp, pp, iph->len, offset, hooknum, + &ciph); out: if (likely(!new_cp)) @@ -2166,7 +2165,7 @@ ip_vs_in_hook(void *priv, struct sk_buff *skb, const struct nf_hook_state *state if (unlikely(iph.protocol == IPPROTO_ICMP)) { int related; int verdict = ip_vs_in_icmp(ipvs, skb, &related, - hooknum); + hooknum, &iph); if (related) return verdict; @@ -2302,6 +2301,7 @@ ip_vs_forward_icmp(void *priv, struct sk_buff *skb, const struct nf_hook_state *state) { struct netns_ipvs *ipvs = net_ipvs(state->net); + struct ip_vs_iphdr iphdr; int r; /* ipvs enabled in this netns ? */ @@ -2311,10 +2311,9 @@ ip_vs_forward_icmp(void *priv, struct sk_buff *skb, if (state->pf == NFPROTO_IPV4) { if (ip_hdr(skb)->protocol != IPPROTO_ICMP) return NF_ACCEPT; + ip_vs_fill_iph_skb(AF_INET, skb, false, &iphdr); #ifdef CONFIG_IP_VS_IPV6 } else { - struct ip_vs_iphdr iphdr; - ip_vs_fill_iph_skb(AF_INET6, skb, false, &iphdr); if (iphdr.protocol != IPPROTO_ICMPV6) @@ -2324,7 +2323,7 @@ ip_vs_forward_icmp(void *priv, struct sk_buff *skb, #endif } - return ip_vs_in_icmp(ipvs, skb, &r, state->hook); + return ip_vs_in_icmp(ipvs, skb, &r, state->hook, &iphdr); } static const struct nf_hook_ops ip_vs_ops4[] = { diff --git a/net/netfilter/ipvs/ip_vs_proto_sctp.c b/net/netfilter/ipvs/ip_vs_proto_sctp.c index f6f732b7dfa8..3dbd3096e163 100644 --- a/net/netfilter/ipvs/ip_vs_proto_sctp.c +++ b/net/netfilter/ipvs/ip_vs_proto_sctp.c @@ -121,7 +121,7 @@ sctp_snat_handler(struct sk_buff *skb, struct ip_vs_protocol *pp, payload_csum = true; } - sctph = (void *) skb_network_header(skb) + sctphoff; + sctph = (void *)skb->data + sctphoff; /* Only update csum if we really have to */ if (sctph->source != cp->vport || payload_csum || @@ -169,7 +169,7 @@ sctp_dnat_handler(struct sk_buff *skb, struct ip_vs_protocol *pp, payload_csum = true; } - sctph = (void *) skb_network_header(skb) + sctphoff; + sctph = (void *)skb->data + sctphoff; /* Only update csum if we really have to */ if (sctph->dest != cp->dport || payload_csum || diff --git a/net/netfilter/ipvs/ip_vs_proto_tcp.c b/net/netfilter/ipvs/ip_vs_proto_tcp.c index 533fce3e5e4e..99a286fdc90c 100644 --- a/net/netfilter/ipvs/ip_vs_proto_tcp.c +++ b/net/netfilter/ipvs/ip_vs_proto_tcp.c @@ -179,7 +179,7 @@ tcp_snat_handler(struct sk_buff *skb, struct ip_vs_protocol *pp, payload_csum = true; } - tcph = (void *)skb_network_header(skb) + tcphoff; + tcph = (void *)skb->data + tcphoff; tcph->source = cp->vport; /* Adjust TCP checksums */ @@ -260,7 +260,7 @@ tcp_dnat_handler(struct sk_buff *skb, struct ip_vs_protocol *pp, payload_csum = true; } - tcph = (void *)skb_network_header(skb) + tcphoff; + tcph = (void *)skb->data + tcphoff; tcph->dest = cp->dport; /* diff --git a/net/netfilter/ipvs/ip_vs_proto_udp.c b/net/netfilter/ipvs/ip_vs_proto_udp.c index de3597347542..f32785682402 100644 --- a/net/netfilter/ipvs/ip_vs_proto_udp.c +++ b/net/netfilter/ipvs/ip_vs_proto_udp.c @@ -170,7 +170,7 @@ udp_snat_handler(struct sk_buff *skb, struct ip_vs_protocol *pp, payload_csum = true; } - udph = (void *)skb_network_header(skb) + udphoff; + udph = (void *)skb->data + udphoff; udph->source = cp->vport; /* @@ -254,7 +254,7 @@ udp_dnat_handler(struct sk_buff *skb, struct ip_vs_protocol *pp, payload_csum = true; } - udph = (void *)skb_network_header(skb) + udphoff; + udph = (void *)skb->data + udphoff; udph->dest = cp->dport; /* diff --git a/net/netfilter/ipvs/ip_vs_xmit.c b/net/netfilter/ipvs/ip_vs_xmit.c index 9fef4335da13..c23401c789de 100644 --- a/net/netfilter/ipvs/ip_vs_xmit.c +++ b/net/netfilter/ipvs/ip_vs_xmit.c @@ -1502,8 +1502,9 @@ tx_error: */ int ip_vs_icmp_xmit(struct sk_buff *skb, struct ip_vs_conn *cp, - struct ip_vs_protocol *pp, int offset, unsigned int hooknum, - struct ip_vs_iphdr *iph) + struct ip_vs_protocol *pp, unsigned int toff, + unsigned int wlen, unsigned int hooknum, + struct ip_vs_iphdr *ciph) { struct rtable *rt; /* Route to the other host */ int rc; @@ -1515,7 +1516,7 @@ ip_vs_icmp_xmit(struct sk_buff *skb, struct ip_vs_conn *cp, translate address/port back */ if (IP_VS_FWD_METHOD(cp) != IP_VS_CONN_F_MASQ) { if (cp->packet_xmit) - rc = cp->packet_xmit(skb, cp, pp, iph); + rc = cp->packet_xmit(skb, cp, pp, ciph); else rc = NF_ACCEPT; /* do not touch skb anymore */ @@ -1533,7 +1534,7 @@ ip_vs_icmp_xmit(struct sk_buff *skb, struct ip_vs_conn *cp, IP_VS_RT_MODE_LOCAL | IP_VS_RT_MODE_NON_LOCAL | IP_VS_RT_MODE_RDR : IP_VS_RT_MODE_NON_LOCAL; local = __ip_vs_get_out_rt(cp->ipvs, cp->af, skb, cp->dest, cp->daddr.ip, rt_mode, - NULL, iph); + NULL, ciph); if (local < 0) goto tx_error; rt = skb_rtable(skb); @@ -1565,13 +1566,13 @@ ip_vs_icmp_xmit(struct sk_buff *skb, struct ip_vs_conn *cp, } /* copy-on-write the packet before mangling it */ - if (skb_ensure_writable(skb, offset)) + if (skb_ensure_writable(skb, wlen)) goto tx_error; if (skb_cow(skb, rt->dst.dev->hard_header_len)) goto tx_error; - ip_vs_nat_icmp(skb, pp, cp, 0); + ip_vs_nat_icmp(skb, pp, cp, 0, toff); /* Another hack: avoid icmp_send in ip_fragment */ skb->ignore_df = 1; @@ -1587,8 +1588,9 @@ ip_vs_icmp_xmit(struct sk_buff *skb, struct ip_vs_conn *cp, #ifdef CONFIG_IP_VS_IPV6 int ip_vs_icmp_xmit_v6(struct sk_buff *skb, struct ip_vs_conn *cp, - struct ip_vs_protocol *pp, int offset, unsigned int hooknum, - struct ip_vs_iphdr *ipvsh) + struct ip_vs_protocol *pp, unsigned int toff, + unsigned int wlen, unsigned int hooknum, + struct ip_vs_iphdr *ciph) { struct rt6_info *rt; /* Route to the other host */ int rc; @@ -1600,7 +1602,7 @@ ip_vs_icmp_xmit_v6(struct sk_buff *skb, struct ip_vs_conn *cp, translate address/port back */ if (IP_VS_FWD_METHOD(cp) != IP_VS_CONN_F_MASQ) { if (cp->packet_xmit) - rc = cp->packet_xmit(skb, cp, pp, ipvsh); + rc = cp->packet_xmit(skb, cp, pp, ciph); else rc = NF_ACCEPT; /* do not touch skb anymore */ @@ -1617,7 +1619,7 @@ ip_vs_icmp_xmit_v6(struct sk_buff *skb, struct ip_vs_conn *cp, IP_VS_RT_MODE_LOCAL | IP_VS_RT_MODE_NON_LOCAL | IP_VS_RT_MODE_RDR : IP_VS_RT_MODE_NON_LOCAL; local = __ip_vs_get_out_rt_v6(cp->ipvs, cp->af, skb, cp->dest, - &cp->daddr.in6, NULL, ipvsh, 0, rt_mode); + &cp->daddr.in6, NULL, ciph, 0, rt_mode); if (local < 0) goto tx_error; rt = dst_rt6_info(skb_dst(skb)); @@ -1649,13 +1651,13 @@ ip_vs_icmp_xmit_v6(struct sk_buff *skb, struct ip_vs_conn *cp, } /* copy-on-write the packet before mangling it */ - if (skb_ensure_writable(skb, offset)) + if (skb_ensure_writable(skb, wlen)) goto tx_error; if (skb_cow(skb, rt->dst.dev->hard_header_len)) goto tx_error; - ip_vs_nat_icmp_v6(skb, pp, cp, 0); + ip_vs_nat_icmp_v6(skb, pp, cp, 0, toff, ciph); /* Another hack: avoid icmp_send in ip_fragment */ skb->ignore_df = 1; -- cgit v1.2.3 From 342e24a339b90e8e339a0f8c151ca479b8565661 Mon Sep 17 00:00:00 2001 From: Julian Anastasov Date: Wed, 22 Jul 2026 13:15:17 +0300 Subject: ipvs: do not mangle ICMP replies for non-first fragments Sashiko warns that ip_vs_nat_icmp() unconditionally mangles the payload for embedded non-first IPv4 fragments. The problem is in the very old inverted pp->dont_defrag check which should not continue when embedded is a non-first TCP/UDP/SCTP fragment. Check for embedded non-first fragment is also missing from ip_vs_out_icmp_v6(), it is needed before any connection lookups that expect ports after the network headers. Drop the blocking code from ip_vs_in_icmp_v6() which prevents ICMPv6 from local clients to use non-MASQ forwarding. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Link: https://sashiko.dev/#/patchset/20260720201122.79882-1-ja%40ssi.bg Signed-off-by: Julian Anastasov Signed-off-by: Pablo Neira Ayuso --- include/net/ip_vs.h | 11 ++++---- net/netfilter/ipvs/ip_vs_core.c | 61 ++++++++++++++--------------------------- net/netfilter/ipvs/ip_vs_xmit.c | 28 +++++++++++++++---- 3 files changed, 48 insertions(+), 52 deletions(-) (limited to 'include') diff --git a/include/net/ip_vs.h b/include/net/ip_vs.h index 4a10a01d6e2f..e6ca930a3507 100644 --- a/include/net/ip_vs.h +++ b/include/net/ip_vs.h @@ -1975,8 +1975,7 @@ int ip_vs_dr_xmit(struct sk_buff *skb, struct ip_vs_conn *cp, struct ip_vs_protocol *pp, struct ip_vs_iphdr *iph); int ip_vs_icmp_xmit(struct sk_buff *skb, struct ip_vs_conn *cp, struct ip_vs_protocol *pp, unsigned int toff, - unsigned int wlen, unsigned int hooknum, - struct ip_vs_iphdr *ciph); + unsigned int hooknum, struct ip_vs_iphdr *ciph); void ip_vs_dest_dst_rcu_free(struct rcu_head *head); #ifdef CONFIG_IP_VS_IPV6 @@ -1990,8 +1989,7 @@ int ip_vs_dr_xmit_v6(struct sk_buff *skb, struct ip_vs_conn *cp, struct ip_vs_protocol *pp, struct ip_vs_iphdr *iph); int ip_vs_icmp_xmit_v6(struct sk_buff *skb, struct ip_vs_conn *cp, struct ip_vs_protocol *pp, unsigned int toff, - unsigned int wlen, unsigned int hooknum, - struct ip_vs_iphdr *ciph); + unsigned int hooknum, struct ip_vs_iphdr *ciph); #endif #ifdef CONFIG_SYSCTL @@ -2063,12 +2061,13 @@ static inline bool ip_vs_conn_use_hash2(struct ip_vs_conn *cp) } void ip_vs_nat_icmp(struct sk_buff *skb, struct ip_vs_protocol *pp, - struct ip_vs_conn *cp, int dir, unsigned int toff); + struct ip_vs_conn *cp, int dir, unsigned int toff, + bool has_ports); #ifdef CONFIG_IP_VS_IPV6 void ip_vs_nat_icmp_v6(struct sk_buff *skb, struct ip_vs_protocol *pp, struct ip_vs_conn *cp, int dir, unsigned int toff, - struct ip_vs_iphdr *ciph); + bool has_ports, struct ip_vs_iphdr *ciph); #endif static inline __wsum ip_vs_check_diff4(__be32 old, __be32 new, __wsum oldsum) diff --git a/net/netfilter/ipvs/ip_vs_core.c b/net/netfilter/ipvs/ip_vs_core.c index cd5eb71543ec..7efa209a517b 100644 --- a/net/netfilter/ipvs/ip_vs_core.c +++ b/net/netfilter/ipvs/ip_vs_core.c @@ -924,7 +924,8 @@ static int ip_vs_route_me_harder(struct netns_ipvs *ipvs, int af, * - inout: 1=in->out, 0=out->in */ void ip_vs_nat_icmp(struct sk_buff *skb, struct ip_vs_protocol *pp, - struct ip_vs_conn *cp, int inout, unsigned int toff) + struct ip_vs_conn *cp, int inout, unsigned int toff, + bool has_ports) { struct iphdr *iph = ip_hdr(skb); struct icmphdr *icmph = (struct icmphdr *)(skb->data + toff); @@ -944,8 +945,7 @@ void ip_vs_nat_icmp(struct sk_buff *skb, struct ip_vs_protocol *pp, } /* the TCP/UDP/SCTP port */ - if (IPPROTO_TCP == ciph->protocol || IPPROTO_UDP == ciph->protocol || - IPPROTO_SCTP == ciph->protocol) { + if (has_ports) { __be16 *ports = (void *)ciph + ciph->ihl*4; if (inout) @@ -970,18 +970,15 @@ void ip_vs_nat_icmp(struct sk_buff *skb, struct ip_vs_protocol *pp, #ifdef CONFIG_IP_VS_IPV6 void ip_vs_nat_icmp_v6(struct sk_buff *skb, struct ip_vs_protocol *pp, struct ip_vs_conn *cp, int inout, unsigned int toff, - struct ip_vs_iphdr *ciph) + bool has_ports, struct ip_vs_iphdr *ciph) { struct ipv6hdr *iph = ipv6_hdr(skb); - int protocol; struct icmp6hdr *icmph; struct ipv6hdr *cih; icmph = (struct icmp6hdr *)(skb->data + toff); cih = (struct ipv6hdr *)(skb->data + ciph->off); - protocol = ciph->protocol; - if (inout) { iph->saddr = cp->vaddr.in6; cih->daddr = cp->vaddr.in6; @@ -991,9 +988,7 @@ void ip_vs_nat_icmp_v6(struct sk_buff *skb, struct ip_vs_protocol *pp, } /* the TCP/UDP/SCTP port */ - if (!ciph->fragoffs && - (protocol == IPPROTO_TCP || protocol == IPPROTO_UDP || - protocol == IPPROTO_SCTP)) { + if (has_ports) { __be16 *ports = (void *)(skb->data + ciph->len); IP_VS_DBG(11, "%s() changed port %d to %d\n", __func__, @@ -1035,6 +1030,7 @@ static int handle_response_icmp(int af, struct sk_buff *skb, int iproto = af == AF_INET6 ? IPPROTO_ICMPV6 : IPPROTO_ICMP; unsigned int verdict = NF_DROP; unsigned int ctoff = ciph->len; + bool has_ports = false; if (IP_VS_FWD_METHOD(cp) != IP_VS_CONN_F_MASQ) goto after_nat; @@ -1048,17 +1044,19 @@ static int handle_response_icmp(int af, struct sk_buff *skb, } if (ciph->protocol == IPPROTO_TCP || ciph->protocol == IPPROTO_UDP || - ciph->protocol == IPPROTO_SCTP) + ciph->protocol == IPPROTO_SCTP) { ctoff += 2 * sizeof(__u16); + has_ports = true; + } if (skb_ensure_writable(skb, ctoff)) goto out; #ifdef CONFIG_IP_VS_IPV6 if (af == AF_INET6) - ip_vs_nat_icmp_v6(skb, pp, cp, 1, toff, ciph); + ip_vs_nat_icmp_v6(skb, pp, cp, 1, toff, has_ports, ciph); else #endif - ip_vs_nat_icmp(skb, pp, cp, 1, toff); + ip_vs_nat_icmp(skb, pp, cp, 1, toff, has_ports); if (ip_vs_route_me_harder(cp->ipvs, af, skb, hooknum)) goto out; @@ -1142,8 +1140,7 @@ static int ip_vs_out_icmp(struct netns_ipvs *ipvs, struct sk_buff *skb, return NF_ACCEPT; /* Is the embedded protocol header present? */ - if (unlikely(cih->frag_off & htons(IP_OFFSET) && - pp->dont_defrag)) + if (unlikely(cih->frag_off & htons(IP_OFFSET) && !pp->dont_defrag)) return NF_ACCEPT; IP_VS_DBG_PKT(11, AF_INET, pp, skb, offset, @@ -1207,6 +1204,10 @@ static int ip_vs_out_icmp_v6(struct netns_ipvs *ipvs, struct sk_buff *skb, if (!pp) return NF_ACCEPT; + /* Is the embedded protocol header present? */ + if (unlikely(ciph.fragoffs && !pp->dont_defrag)) + return NF_ACCEPT; + /* The embedded headers contain source and dest in reverse order */ cp = INDIRECT_CALL_1(pp->conn_out_get, ip_vs_conn_out_get_proto, ipvs, AF_INET6, skb, &ciph); @@ -1865,8 +1866,7 @@ ip_vs_in_icmp(struct netns_ipvs *ipvs, struct sk_buff *skb, int *related, pp = pd->pp; /* Is the embedded protocol header present? */ - if (unlikely(cih->frag_off & htons(IP_OFFSET) && - pp->dont_defrag)) + if (unlikely(cih->frag_off & htons(IP_OFFSET) && !pp->dont_defrag)) return NF_ACCEPT; IP_VS_DBG_PKT(11, AF_INET, pp, skb, offset, @@ -1874,7 +1874,6 @@ ip_vs_in_icmp(struct netns_ipvs *ipvs, struct sk_buff *skb, int *related, offset2 = offset; ip_vs_fill_iph_skb_icmp(AF_INET, skb, offset, !tunnel, &ciph); - offset = ciph.len; /* The embedded headers contain source and dest in reverse order. * For IPIP/UDP/GRE tunnel this is error for request, not for reply. @@ -1968,11 +1967,7 @@ ignore_tunnel: /* do the statistics and put it back */ ip_vs_in_stats(cp, skb); - if (IPPROTO_TCP == cih->protocol || IPPROTO_UDP == cih->protocol || - IPPROTO_SCTP == cih->protocol) - offset += 2 * sizeof(__u16); - verdict = ip_vs_icmp_xmit(skb, cp, pp, iph->len, offset, hooknum, - &ciph); + verdict = ip_vs_icmp_xmit(skb, cp, pp, iph->len, hooknum, &ciph); out: if (likely(!new_cp)) @@ -2032,8 +2027,8 @@ static int ip_vs_in_icmp_v6(struct netns_ipvs *ipvs, struct sk_buff *skb, return NF_ACCEPT; pp = pd->pp; - /* Cannot handle fragmented embedded protocol */ - if (ciph.fragoffs) + /* Is the embedded protocol header present? */ + if (ciph.fragoffs && !pp->dont_defrag) return NF_ACCEPT; IP_VS_DBG_PKT(11, AF_INET6, pp, skb, offset, @@ -2057,13 +2052,6 @@ static int ip_vs_in_icmp_v6(struct netns_ipvs *ipvs, struct sk_buff *skb, new_cp = true; } - /* VS/TUN, VS/DR and LOCALNODE just let it go */ - if ((hooknum == NF_INET_LOCAL_OUT) && - (IP_VS_FWD_METHOD(cp) != IP_VS_CONN_F_MASQ)) { - verdict = NF_ACCEPT; - goto out; - } - verdict = NF_DROP; /* Ensure the checksum is correct */ @@ -2079,14 +2067,7 @@ static int ip_vs_in_icmp_v6(struct netns_ipvs *ipvs, struct sk_buff *skb, /* do the statistics and put it back */ ip_vs_in_stats(cp, skb); - /* Need to mangle contained IPv6 header in ICMPv6 packet */ - offset = ciph.len; - if (IPPROTO_TCP == ciph.protocol || IPPROTO_UDP == ciph.protocol || - IPPROTO_SCTP == ciph.protocol) - offset += 2 * sizeof(__u16); /* Also mangle ports */ - - verdict = ip_vs_icmp_xmit_v6(skb, cp, pp, iph->len, offset, hooknum, - &ciph); + verdict = ip_vs_icmp_xmit_v6(skb, cp, pp, iph->len, hooknum, &ciph); out: if (likely(!new_cp)) diff --git a/net/netfilter/ipvs/ip_vs_xmit.c b/net/netfilter/ipvs/ip_vs_xmit.c index c23401c789de..0b0c5304993a 100644 --- a/net/netfilter/ipvs/ip_vs_xmit.c +++ b/net/netfilter/ipvs/ip_vs_xmit.c @@ -1503,13 +1503,14 @@ tx_error: int ip_vs_icmp_xmit(struct sk_buff *skb, struct ip_vs_conn *cp, struct ip_vs_protocol *pp, unsigned int toff, - unsigned int wlen, unsigned int hooknum, - struct ip_vs_iphdr *ciph) + unsigned int hooknum, struct ip_vs_iphdr *ciph) { struct rtable *rt; /* Route to the other host */ int rc; int local; int rt_mode, was_input; + bool has_ports = false; + unsigned int wlen; /* The ICMP packet for VS/TUN, VS/DR and LOCALNODE will be forwarded directly here, because there is no need to @@ -1565,6 +1566,13 @@ ip_vs_icmp_xmit(struct sk_buff *skb, struct ip_vs_conn *cp, goto tx_error; } + wlen = ciph->len; + if (ciph->protocol == IPPROTO_TCP || ciph->protocol == IPPROTO_UDP || + ciph->protocol == IPPROTO_SCTP) { + wlen += 2 * sizeof(__u16); /* Also mangle ports */ + has_ports = true; + } + /* copy-on-write the packet before mangling it */ if (skb_ensure_writable(skb, wlen)) goto tx_error; @@ -1572,7 +1580,7 @@ ip_vs_icmp_xmit(struct sk_buff *skb, struct ip_vs_conn *cp, if (skb_cow(skb, rt->dst.dev->hard_header_len)) goto tx_error; - ip_vs_nat_icmp(skb, pp, cp, 0, toff); + ip_vs_nat_icmp(skb, pp, cp, 0, toff, has_ports); /* Another hack: avoid icmp_send in ip_fragment */ skb->ignore_df = 1; @@ -1589,10 +1597,11 @@ ip_vs_icmp_xmit(struct sk_buff *skb, struct ip_vs_conn *cp, int ip_vs_icmp_xmit_v6(struct sk_buff *skb, struct ip_vs_conn *cp, struct ip_vs_protocol *pp, unsigned int toff, - unsigned int wlen, unsigned int hooknum, - struct ip_vs_iphdr *ciph) + unsigned int hooknum, struct ip_vs_iphdr *ciph) { + bool has_ports = false; struct rt6_info *rt; /* Route to the other host */ + unsigned int wlen; int rc; int local; int rt_mode; @@ -1650,6 +1659,13 @@ ip_vs_icmp_xmit_v6(struct sk_buff *skb, struct ip_vs_conn *cp, goto tx_error; } + wlen = ciph->len; + if (ciph->protocol == IPPROTO_TCP || ciph->protocol == IPPROTO_UDP || + ciph->protocol == IPPROTO_SCTP) { + wlen += 2 * sizeof(__u16); /* Also mangle ports */ + has_ports = true; + } + /* copy-on-write the packet before mangling it */ if (skb_ensure_writable(skb, wlen)) goto tx_error; @@ -1657,7 +1673,7 @@ ip_vs_icmp_xmit_v6(struct sk_buff *skb, struct ip_vs_conn *cp, if (skb_cow(skb, rt->dst.dev->hard_header_len)) goto tx_error; - ip_vs_nat_icmp_v6(skb, pp, cp, 0, toff, ciph); + ip_vs_nat_icmp_v6(skb, pp, cp, 0, toff, has_ports, ciph); /* Another hack: avoid icmp_send in ip_fragment */ skb->ignore_df = 1; -- cgit v1.2.3 From bd44a6dcd4248883de90f5dad53ae80066e27096 Mon Sep 17 00:00:00 2001 From: Jason Xing Date: Sun, 19 Jul 2026 15:56:05 +0200 Subject: xsk: drain continuation descs after overflow in xsk_build_skb() Fix generic xmit path multi-buffer logic when packets are either too big (count of descriptors exceed MAX_SKB_FRAGS) or an invalid descriptor is included in fragmented packet. Introduce xdp_sock::drain_cont and act upon this flag - when it is set, keep on consuming descriptors from AF_XDP Tx ring and put them directly onto Cq. Previously these descriptors were silently lost and could never be reached again. Fixes: cf24f5a5feea ("xsk: add support for AF_XDP multi-buffer on Tx path") Closes: https://lore.kernel.org/all/20260425041726.85FB3C2BCB2@smtp.kernel.org/ Reviewed-by: Jason Xing Co-developed-by: Maciej Fijalkowski # wrapped cq addr submission onto routine Signed-off-by: Maciej Fijalkowski Signed-off-by: Jason Xing Acked-by: Stanislav Fomichev Link: https://patch.msgid.link/20260719135609.147823-3-maciej.fijalkowski@intel.com Signed-off-by: Jakub Kicinski --- include/net/xdp_sock.h | 1 + net/xdp/xsk.c | 45 ++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 43 insertions(+), 3 deletions(-) (limited to 'include') diff --git a/include/net/xdp_sock.h b/include/net/xdp_sock.h index ebac60a3d8a1..8b51876efbed 100644 --- a/include/net/xdp_sock.h +++ b/include/net/xdp_sock.h @@ -80,6 +80,7 @@ struct xdp_sock { * call of __xsk_generic_xmit(). */ struct sk_buff *skb; + bool drain_cont; struct list_head map_list; /* Protects map_list */ diff --git a/net/xdp/xsk.c b/net/xdp/xsk.c index a7a83dc4546a..12a845d012f6 100644 --- a/net/xdp/xsk.c +++ b/net/xdp/xsk.c @@ -737,6 +737,19 @@ static void xsk_cq_submit_addr_locked(struct xsk_buff_pool *pool, spin_unlock_irqrestore(&pool->cq_prod_lock, flags); } +static void xsk_cq_submit_addr_single_locked(struct xsk_buff_pool *pool, + struct xdp_desc *desc) +{ + unsigned long flags; + u32 idx; + + spin_lock_irqsave(&pool->cq_prod_lock, flags); + idx = xskq_get_prod(pool->cq); + xskq_prod_write_addr(pool->cq, idx, desc->addr); + xskq_prod_submit_n(pool->cq, 1); + spin_unlock_irqrestore(&pool->cq_prod_lock, flags); +} + static void xsk_cq_cancel_locked(struct xsk_buff_pool *pool, u32 n) { spin_lock(&pool->cq->cq_cached_prod_lock); @@ -1028,13 +1041,14 @@ free_err: static int __xsk_generic_xmit(struct sock *sk) { struct xdp_sock *xs = xdp_sk(sk); - bool sent_frame = false; struct xdp_desc desc; struct sk_buff *skb; + u32 cached_cons; u32 max_batch; int err = 0; mutex_lock(&xs->mutex); + cached_cons = xs->tx->cached_cons; /* Since we dropped the RCU read lock, the socket state might have changed. */ if (unlikely(!xsk_is_bound(xs))) { @@ -1063,11 +1077,21 @@ static int __xsk_generic_xmit(struct sock *sk) goto out; } + if (unlikely(xs->drain_cont)) { + xsk_cq_submit_addr_single_locked(xs->pool, &desc); + xs->tx->invalid_descs++; + xskq_cons_release(xs->tx); + xs->drain_cont = xp_mb_desc(&desc); + continue; + } + skb = xsk_build_skb(xs, &desc); if (IS_ERR(skb)) { err = PTR_ERR(skb); if (err != -EOVERFLOW) goto out; + if (xp_mb_desc(&desc)) + xs->drain_cont = true; err = 0; continue; } @@ -1096,18 +1120,33 @@ static int __xsk_generic_xmit(struct sock *sk) goto out; } - sent_frame = true; xs->skb = NULL; } if (xskq_has_descs(xs->tx)) { + bool drain = xs->skb || xs->drain_cont || xp_mb_desc(&desc); + + err = xsk_cq_reserve_locked(xs->pool); + if (err) { + xs->tx->invalid_descs--; + if (xs->skb) + xsk_drop_skb(xs->skb); + xs->drain_cont = drain; + err = -EAGAIN; + goto out; + } + if (xs->skb) xsk_drop_skb(xs->skb); + + xsk_cq_submit_addr_single_locked(xs->pool, &desc); + xskq_cons_release(xs->tx); + xs->drain_cont = xp_mb_desc(&desc); } out: - if (sent_frame) + if (xs->tx->cached_cons != cached_cons) __xsk_tx_release(xs); mutex_unlock(&xs->mutex); -- cgit v1.2.3 From 08c9a8e794b4694c100dafcb80e069e29ad81b64 Mon Sep 17 00:00:00 2001 From: Maciej Fijalkowski Date: Sun, 19 Jul 2026 15:56:06 +0200 Subject: xsk: provide sufficient space in pool->tx_descs The temporary Tx descriptor array in an XSK buffer pool is currently sized from the Tx ring of the socket that creates the pool. This is insufficient for shared-UMEM Tx. A later socket may have a larger Tx ring and submit a valid multi-buffer packet containing more descriptors than the first socket's ring, while still remaining within the device's xdp_zc_max_segs limit. A packet-framed batch parser bounded by the temporary array cannot reach the end-of-packet descriptor in that case. It leaves the packet on the Tx ring and encounters the same packet on every subsequent attempt, stalling Tx processing for that socket. Size the temporary descriptor array to the larger of the first Tx ring and the device's xdp_zc_max_segs capability. This keeps the array large enough to inspect one maximum-sized valid packet. Larger shared Tx rings do not require further resizing, as they can be processed over multiple batches. Following commit will actually address the data path side. Fixes: d5581966040f ("xsk: support ZC Tx multi-buffer in batch API") Reviewed-by: Jason Xing Signed-off-by: Maciej Fijalkowski Acked-by: Stanislav Fomichev Link: https://patch.msgid.link/20260719135609.147823-4-maciej.fijalkowski@intel.com Signed-off-by: Jakub Kicinski --- include/net/xsk_buff_pool.h | 6 ++++-- net/xdp/xsk.c | 10 +++++++--- net/xdp/xsk_buff_pool.c | 12 ++++++++---- 3 files changed, 19 insertions(+), 9 deletions(-) (limited to 'include') diff --git a/include/net/xsk_buff_pool.h b/include/net/xsk_buff_pool.h index ccb3b350001f..f5e737a83055 100644 --- a/include/net/xsk_buff_pool.h +++ b/include/net/xsk_buff_pool.h @@ -102,12 +102,14 @@ struct xsk_buff_pool { /* AF_XDP core. */ struct xsk_buff_pool *xp_create_and_assign_umem(struct xdp_sock *xs, - struct xdp_umem *umem); + struct xdp_umem *umem, + u32 max_segs); int xp_assign_dev(struct xsk_buff_pool *pool, struct net_device *dev, u16 queue_id, u16 flags); int xp_assign_dev_shared(struct xsk_buff_pool *pool, struct xdp_sock *umem_xs, struct net_device *dev, u16 queue_id); -int xp_alloc_tx_descs(struct xsk_buff_pool *pool, struct xdp_sock *xs); +int xp_alloc_tx_descs(struct xsk_buff_pool *pool, struct xdp_sock *xs, + u32 max_segs); void xp_destroy(struct xsk_buff_pool *pool); void xp_get_pool(struct xsk_buff_pool *pool); bool xp_put_pool(struct xsk_buff_pool *pool); diff --git a/net/xdp/xsk.c b/net/xdp/xsk.c index 12a845d012f6..091792d1d82d 100644 --- a/net/xdp/xsk.c +++ b/net/xdp/xsk.c @@ -1525,7 +1525,8 @@ static int xsk_bind(struct socket *sock, struct sockaddr_unsized *addr, int addr * and/or device. */ xs->pool = xp_create_and_assign_umem(xs, - umem_xs->umem); + umem_xs->umem, + dev->xdp_zc_max_segs); if (!xs->pool) { err = -ENOMEM; sockfd_put(sock); @@ -1557,7 +1558,8 @@ static int xsk_bind(struct socket *sock, struct sockaddr_unsized *addr, int addr * utilizes */ if (xs->tx && !xs->pool->tx_descs) { - err = xp_alloc_tx_descs(xs->pool, xs); + err = xp_alloc_tx_descs(xs->pool, xs, + dev->xdp_zc_max_segs); if (err) { xp_put_pool(xs->pool); xs->pool = NULL; @@ -1575,7 +1577,9 @@ static int xsk_bind(struct socket *sock, struct sockaddr_unsized *addr, int addr goto out_unlock; } else { /* This xsk has its own umem. */ - xs->pool = xp_create_and_assign_umem(xs, xs->umem); + xs->pool = xp_create_and_assign_umem(xs, xs->umem, + dev->xdp_zc_max_segs); + if (!xs->pool) { err = -ENOMEM; goto out_unlock; diff --git a/net/xdp/xsk_buff_pool.c b/net/xdp/xsk_buff_pool.c index 1f28a9641571..12c9fb29af05 100644 --- a/net/xdp/xsk_buff_pool.c +++ b/net/xdp/xsk_buff_pool.c @@ -42,9 +42,12 @@ void xp_destroy(struct xsk_buff_pool *pool) kvfree(pool); } -int xp_alloc_tx_descs(struct xsk_buff_pool *pool, struct xdp_sock *xs) +int xp_alloc_tx_descs(struct xsk_buff_pool *pool, struct xdp_sock *xs, + u32 max_segs) { - pool->tx_descs = kvzalloc_objs(*pool->tx_descs, xs->tx->nentries); + u32 nentries = max(xs->tx->nentries, max_segs); + + pool->tx_descs = kvzalloc_objs(*pool->tx_descs, nentries); if (!pool->tx_descs) return -ENOMEM; @@ -52,7 +55,8 @@ int xp_alloc_tx_descs(struct xsk_buff_pool *pool, struct xdp_sock *xs) } struct xsk_buff_pool *xp_create_and_assign_umem(struct xdp_sock *xs, - struct xdp_umem *umem) + struct xdp_umem *umem, + u32 max_segs) { bool unaligned = umem->flags & XDP_UMEM_UNALIGNED_CHUNK_FLAG; struct xsk_buff_pool *pool; @@ -69,7 +73,7 @@ struct xsk_buff_pool *xp_create_and_assign_umem(struct xdp_sock *xs, goto out; if (xs->tx) - if (xp_alloc_tx_descs(pool, xs)) + if (xp_alloc_tx_descs(pool, xs, max_segs)) goto out; pool->chunk_mask = ~((u64)umem->chunk_size - 1); -- cgit v1.2.3 From 72f2b4516faf55d4dfac2414649d3cffa5fd2c5e Mon Sep 17 00:00:00 2001 From: Maciej Fijalkowski Date: Sun, 19 Jul 2026 15:56:07 +0200 Subject: xsk: reclaim invalid Tx descriptors in ZC batch path The zero-copy Tx batch parser stops when it encounters an invalid descriptor. If this happens after one or more continuation descriptors, the Tx consumer can be advanced past fragments that are neither submitted to the driver nor returned to userspace through the completion ring. A similar problem occurs when a packet exceeds xdp_zc_max_segs. The descriptors consumed up to the limit are released without completion, and the remaining continuation descriptors can subsequently be interpreted as the beginning of another packet. Parse Tx batches in packet units and distinguish descriptors belonging to complete valid packets from descriptors consumed while draining an invalid or oversized packet. Return the former to the driver and append the latter to the CQ address area so userspace can reclaim their UMEM frames. Treat a standalone invalid descriptor as a one-descriptor reclaim-only packet. Advancing the Tx-ring consumer releases the ring slot, but does not by itself return ownership of the referenced UMEM frame to userspace. Once draining starts, continue until the packet's end-of-packet descriptor is consumed. Preserve the drain state on the socket when EOP has not yet been supplied, so draining can continue during a later call. Leave incomplete but otherwise valid packets on the Tx ring. Shared-UMEM pools using multi-buffer Tx also need packet-framed parsing. Walk their Tx sockets one packet at a time, preserving the existing per-socket fairness scheme, instead of using the legacy one-descriptor fallback. Keep that fallback for shared pools that do not use multi-buffer Tx. Since the drain state is maintained per socket and both the singular and shared paths can resume an interrupted drain, changing the socket list from singular to shared requires no special bind-time transition. CQ entries are positional, and drivers may complete only part of the Tx work returned by xsk_tx_peek_release_desc_batch(). Therefore, reclaim-only entries cannot be published immediately when earlier driver-visible descriptors are still outstanding. Track the number of driver-visible CQ entries preceding the reclaim entries. Let xsk_tx_completed() publish partial hardware Tx completions, and publish the reclaim entries only after every earlier Tx descriptor has completed. Complete a reclaim-only batch immediately when there is no driver-visible work in front of it, and prevent another Tx batch from being appended while reclaim entries remain pending. Also cap batch processing by the size of the pool's temporary descriptor array, as Tx rings belonging to sockets sharing a UMEM may have different sizes. This ensures that every invalid Tx descriptor consumed by the ZC batch path is either submitted to the driver as part of a valid packet or returned to userspace without violating CQ completion ordering. Reviewed-by: Jason Xing Signed-off-by: Maciej Fijalkowski Acked-by: Stanislav Fomichev Fixes: cf24f5a5feea ("xsk: add support for AF_XDP multi-buffer on Tx path") Link: https://patch.msgid.link/20260719135609.147823-5-maciej.fijalkowski@intel.com Signed-off-by: Jakub Kicinski --- Documentation/networking/af_xdp.rst | 54 ++++++----- include/net/xsk_buff_pool.h | 3 + net/xdp/xsk.c | 187 ++++++++++++++++++++++++++++++++---- net/xdp/xsk_buff_pool.c | 1 + net/xdp/xsk_queue.h | 65 +++++++++---- 5 files changed, 248 insertions(+), 62 deletions(-) (limited to 'include') diff --git a/Documentation/networking/af_xdp.rst b/Documentation/networking/af_xdp.rst index 50d92084a49c..cc3f0d16b28f 100644 --- a/Documentation/networking/af_xdp.rst +++ b/Documentation/networking/af_xdp.rst @@ -43,12 +43,13 @@ UMEM also has two rings: the FILL ring and the COMPLETION ring. The FILL ring is used by the application to send down addr for the kernel to fill in with RX packet data. References to these frames will then appear in the RX ring once each packet has been received. The -COMPLETION ring, on the other hand, contains frame addr that the -kernel has transmitted completely and can now be used again by user -space, for either TX or RX. Thus, the frame addrs appearing in the -COMPLETION ring are addrs that were previously transmitted using the -TX ring. In summary, the RX and FILL rings are used for the RX path -and the TX and COMPLETION rings are used for the TX path. +COMPLETION ring, on the other hand, contains frame addresses from Tx +descriptors that the kernel has finished processing and that can now be +used again by user space, for either Tx or Rx. This includes frames whose +transmission has completed as well as frames referenced by invalid Tx +descriptors rejected by the kernel. A completion therefore returns +ownership of a frame to user space, but does not by itself guarantee that +the packet was successfully transmitted. The socket is then finally bound with a bind() call to a device and a specific queue id on that device, and it is not until bind is @@ -169,14 +170,15 @@ chunks mode, then the incoming addr will be left untouched. UMEM Completion Ring ~~~~~~~~~~~~~~~~~~~~ -The COMPLETION Ring is used transfer ownership of UMEM frames from +The COMPLETION Ring is used to transfer ownership of UMEM frames from kernel-space to user-space. Just like the FILL ring, UMEM indices are -used. - -Frames passed from the kernel to user-space are frames that has been -sent (TX ring) and can be used by user-space again. - -The user application consumes UMEM addrs from this ring. +used. Frames passed from the kernel to user-space are frames referenced +by Tx descriptors that the kernel has finished processing and can be +used by user-space again. This includes both frames whose transmission +has completed and frames referenced by invalid Tx descriptors that were +rejected and reclaimed by the kernel. A completion entry does not +guarantee successful packet transmission. The user application consumes +UMEM addrs from this ring. RX Ring @@ -504,21 +506,25 @@ will be treated as an invalid descriptor. These are the semantics for producing packets onto AF_XDP Tx ring consisting of multiple frames: -* When an invalid descriptor is found, all the other - descriptors/frames of this packet are marked as invalid and not - completed. The next descriptor is treated as the start of a new - packet, even if this was not the intent (because we cannot guess - the intent). As before, if your program is producing invalid - descriptors you have a bug that must be fixed. +* When an invalid descriptor is found, the complete packet is treated as + invalid. The kernel consumes descriptors through the descriptor marking + the end of the packet and returns all their frame addresses through the + COMPLETION ring. A standalone invalid descriptor is treated as a + one-descriptor invalid packet. The descriptor following the end of the + invalid packet is treated as the start of a new packet. As before, if + your program is producing invalid descriptors you have a bug that must + be fixed. Rejected descriptors are reported in the ``tx_invalid_descs`` + statistic. * Zero length descriptors are treated as invalid descriptors. * For copy mode, the maximum supported number of frames in a packet is - equal to CONFIG_MAX_SKB_FRAGS + 1. If it is exceeded, all - descriptors accumulated so far are dropped and treated as - invalid. To produce an application that will work on any system - regardless of this config setting, limit the number of frags to 18, - as the minimum value of the config is 17. + equal to CONFIG_MAX_SKB_FRAGS + 1. If it is exceeded, all descriptors + through the end of the oversized packet are consumed, treated as invalid, + and their frame addresses are returned through the COMPLETION ring. To + produce an application that will work on any system regardless of this + config setting, limit the number of frags to 18, as the minimum value of + the config is 17. * For zero-copy mode, the limit is up to what the NIC HW supports. Usually at least five on the NICs we have checked. We diff --git a/include/net/xsk_buff_pool.h b/include/net/xsk_buff_pool.h index f5e737a83055..2bb1d122b1bc 100644 --- a/include/net/xsk_buff_pool.h +++ b/include/net/xsk_buff_pool.h @@ -78,6 +78,9 @@ struct xsk_buff_pool { u32 chunk_size; u32 chunk_shift; u32 frame_len; + u32 tx_descs_nentries; + u32 reclaim_descs; + u32 tx_zc_pending_descs; u32 xdp_zc_max_segs; u8 tx_metadata_len; /* inherited from umem */ u8 cached_need_wakeup; diff --git a/net/xdp/xsk.c b/net/xdp/xsk.c index 091792d1d82d..f906d51b6699 100644 --- a/net/xdp/xsk.c +++ b/net/xdp/xsk.c @@ -499,6 +499,23 @@ void __xsk_map_flush(struct list_head *flush_list) void xsk_tx_completed(struct xsk_buff_pool *pool, u32 nb_entries) { + u32 reclaim_descs = READ_ONCE(pool->reclaim_descs); + + if (unlikely(reclaim_descs)) { + u32 pending_descs = READ_ONCE(pool->tx_zc_pending_descs); + + if (nb_entries < pending_descs) { + WRITE_ONCE(pool->tx_zc_pending_descs, + pending_descs - nb_entries); + xskq_prod_submit_n(pool->cq, nb_entries); + return; + } + + WRITE_ONCE(pool->tx_zc_pending_descs, 0); + nb_entries += reclaim_descs; + WRITE_ONCE(pool->reclaim_descs, 0); + } + xskq_prod_submit_n(pool->cq, nb_entries); } EXPORT_SYMBOL(xsk_tx_completed); @@ -574,24 +591,157 @@ static u32 xsk_tx_peek_release_fallback(struct xsk_buff_pool *pool, u32 max_entr return nb_pkts; } +static void xsk_tx_commit_batch(struct xsk_buff_pool *pool, + struct xsk_tx_batch *batch) +{ + u32 nb_descs = xsk_tx_batch_cq_descs(batch); + u32 cq_cached_prod; + + if (!nb_descs) + return; + + cq_cached_prod = pool->cq->cached_prod; + xskq_prod_write_addr_batch(pool->cq, pool->tx_descs, nb_descs); + + if (unlikely(batch->reclaim_descs)) { + u32 cq_pending_descs; + + /* CQ is positional. Descriptors already written but not + * submitted must complete before any reclaim-only descriptors + * appended below. + */ + cq_pending_descs = cq_cached_prod - xskq_get_prod(pool->cq); + + WRITE_ONCE(pool->tx_zc_pending_descs, + batch->tx_descs + cq_pending_descs); + WRITE_ONCE(pool->reclaim_descs, batch->reclaim_descs); + if (unlikely(!pool->tx_zc_pending_descs)) + xsk_tx_completed(pool, 0); + } +} + +static struct xsk_tx_batch +__xsk_tx_peek_release_desc_batch(struct xsk_buff_pool *pool, struct xdp_sock *xs, + struct xdp_desc *descs, u32 max_descs) +{ + struct xsk_tx_batch batch = {}; + u32 entries; + + entries = xskq_cons_nb_entries(xs->tx, max_descs); + if (!entries) + return batch; + + batch = xskq_cons_read_desc_batch(xs, pool, descs, max_descs); + if (!xsk_tx_batch_cq_descs(&batch)) { + xs->tx->queue_empty_descs++; + } else { + __xskq_cons_release(xs->tx); + xs->sk.sk_write_space(&xs->sk); + } + return batch; +} + +static struct xsk_tx_batch +xsk_tx_peek_release_shared_desc_batch(struct xsk_buff_pool *pool, u32 max_descs) +{ + u32 cq_descs_before, cq_descs_after; + struct xsk_tx_batch sum_batch = {}; + bool budget_exhausted; + u32 per_socket_budget; + struct xdp_sock *xs; + + /* The fairness quota must allow one maximum-sized valid packet. */ + per_socket_budget = max_t(u32, MAX_PER_SOCKET_BUDGET, + pool->xdp_zc_max_segs); + +again: + budget_exhausted = false; + cq_descs_before = xsk_tx_batch_cq_descs(&sum_batch); + list_for_each_entry_rcu(xs, &pool->xsk_tx_list, tx_list) { + u32 budget, budget_left, offset, remaining, used; + struct xsk_tx_batch curr_batch; + + /* Once reclaim-only descriptors have been appended to the CQ + * address area, do not append driver-visible Tx descriptors + * from another socket after them. xsk_tx_completed() relies on + * all driver-visible descriptors preceding all reclaim-only + * descriptors in CQ order. + */ + if (sum_batch.reclaim_descs) + break; + + /* be gentle when playing with pool->tx_descs */ + offset = xsk_tx_batch_cq_descs(&sum_batch); + if (offset >= max_descs) + break; + + if (xs->tx_budget_spent >= per_socket_budget) { + if (xskq_cons_nb_entries(xs->tx, 1)) + budget_exhausted = true; + continue; + } + + budget_left = per_socket_budget - xs->tx_budget_spent; + remaining = max_descs - offset; + budget = min(remaining, budget_left); + + curr_batch = __xsk_tx_peek_release_desc_batch(pool, xs, + pool->tx_descs + offset, + budget); + used = xsk_tx_batch_cq_descs(&curr_batch); + if (!used) { + if (curr_batch.budget_limited && budget_left < remaining) + budget_exhausted = true; + continue; + } + + xs->tx_budget_spent += used; + sum_batch.tx_descs += curr_batch.tx_descs; + sum_batch.reclaim_descs = curr_batch.reclaim_descs; + } + + cq_descs_after = xsk_tx_batch_cq_descs(&sum_batch); + + if (sum_batch.reclaim_descs || cq_descs_after >= max_descs) + return sum_batch; + + /* Continue filling the batch while this pass made progress */ + if (cq_descs_before != cq_descs_after) + goto again; + + if (!budget_exhausted) + return sum_batch; + + list_for_each_entry_rcu(xs, &pool->xsk_tx_list, tx_list) + xs->tx_budget_spent = 0; + goto again; +} + u32 xsk_tx_peek_release_desc_batch(struct xsk_buff_pool *pool, u32 nb_pkts) { + struct xsk_tx_batch batch = {}; struct xdp_sock *xs; + bool umem_shared; rcu_read_lock(); - if (!list_is_singular(&pool->xsk_tx_list)) { - /* Fallback to the non-batched version */ - rcu_read_unlock(); - return xsk_tx_peek_release_fallback(pool, nb_pkts); - } + if (unlikely(READ_ONCE(pool->reclaim_descs))) + goto out; - xs = list_first_or_null_rcu(&pool->xsk_tx_list, struct xdp_sock, tx_list); - if (!xs) { - nb_pkts = 0; + xs = list_first_or_null_rcu(&pool->xsk_tx_list, struct xdp_sock, + tx_list); + if (!xs) goto out; - } - nb_pkts = xskq_cons_nb_entries(xs->tx, nb_pkts); + nb_pkts = min(nb_pkts, pool->tx_descs_nentries); + if (!nb_pkts) + goto out; + + umem_shared = !list_is_singular(&pool->xsk_tx_list); + + if (umem_shared && !(pool->umem->flags & XDP_UMEM_SG_FLAG)) { + rcu_read_unlock(); + return xsk_tx_peek_release_fallback(pool, nb_pkts); + } /* This is the backpressure mechanism for the Tx path. Try to * reserve space in the completion queue for all packets, but @@ -603,19 +753,16 @@ u32 xsk_tx_peek_release_desc_batch(struct xsk_buff_pool *pool, u32 nb_pkts) if (!nb_pkts) goto out; - nb_pkts = xskq_cons_read_desc_batch(xs->tx, pool, nb_pkts); - if (!nb_pkts) { - xs->tx->queue_empty_descs++; - goto out; - } - - __xskq_cons_release(xs->tx); - xskq_prod_write_addr_batch(pool->cq, pool->tx_descs, nb_pkts); - xs->sk.sk_write_space(&xs->sk); + batch = umem_shared ? + xsk_tx_peek_release_shared_desc_batch(pool, nb_pkts) : + __xsk_tx_peek_release_desc_batch(pool, xs, + pool->tx_descs, + nb_pkts); + xsk_tx_commit_batch(pool, &batch); out: rcu_read_unlock(); - return nb_pkts; + return batch.tx_descs; } EXPORT_SYMBOL(xsk_tx_peek_release_desc_batch); diff --git a/net/xdp/xsk_buff_pool.c b/net/xdp/xsk_buff_pool.c index 12c9fb29af05..a4089480b22b 100644 --- a/net/xdp/xsk_buff_pool.c +++ b/net/xdp/xsk_buff_pool.c @@ -51,6 +51,7 @@ int xp_alloc_tx_descs(struct xsk_buff_pool *pool, struct xdp_sock *xs, if (!pool->tx_descs) return -ENOMEM; + pool->tx_descs_nentries = nentries; return 0; } diff --git a/net/xdp/xsk_queue.h b/net/xdp/xsk_queue.h index 3e3fbb73d23e..1bc42c8902f4 100644 --- a/net/xdp/xsk_queue.h +++ b/net/xdp/xsk_queue.h @@ -58,6 +58,17 @@ struct parsed_desc { u32 valid; }; +struct xsk_tx_batch { + u32 tx_descs; + u32 reclaim_descs; + bool budget_limited; +}; + +static inline u32 xsk_tx_batch_cq_descs(const struct xsk_tx_batch *batch) +{ + return batch->tx_descs + batch->reclaim_descs; +} + /* The structure of the shared state of the rings are a simple * circular buffer, as outlined in * Documentation/core-api/circular-buffers.rst. For the Rx and @@ -263,17 +274,18 @@ static inline void parse_desc(struct xsk_queue *q, struct xsk_buff_pool *pool, parsed->mb = xp_mb_desc(desc); } -static inline -u32 xskq_cons_read_desc_batch(struct xsk_queue *q, struct xsk_buff_pool *pool, - u32 max) +static inline struct xsk_tx_batch +xskq_cons_read_desc_batch(struct xdp_sock *xs, struct xsk_buff_pool *pool, + struct xdp_desc *descs, u32 max) { - u32 cached_cons = q->cached_cons, nb_entries = 0; - struct xdp_desc *descs = pool->tx_descs; - u32 total_descs = 0, nr_frags = 0; + bool drain = READ_ONCE(xs->drain_cont); + u32 cached_cons, nb_entries = 0; + struct xsk_tx_batch batch = {}; + struct xsk_queue *q = xs->tx; + u32 nr_frags = 0; + + cached_cons = q->cached_cons; - /* track first entry, if stumble upon *any* invalid descriptor, rewind - * current packet that consists of frags and stop the processing - */ while (cached_cons != q->cached_prod && nb_entries < max) { struct xdp_rxtx_ring *ring = (struct xdp_rxtx_ring *)q->ring; u32 idx = cached_cons & q->ring_mask; @@ -283,25 +295,42 @@ u32 xskq_cons_read_desc_batch(struct xsk_queue *q, struct xsk_buff_pool *pool, cached_cons++; parse_desc(q, pool, &descs[nb_entries], &parsed); if (unlikely(!parsed.valid)) - break; + drain = true; + + nr_frags++; + nb_entries++; if (likely(!parsed.mb)) { - total_descs += (nr_frags + 1); - nr_frags = 0; - } else { - nr_frags++; - if (nr_frags == pool->xdp_zc_max_segs) { + if (unlikely(drain)) { + batch.reclaim_descs = nr_frags; + WRITE_ONCE(xs->drain_cont, false); nr_frags = 0; break; } + + batch.tx_descs += nr_frags; + nr_frags = 0; + continue; + } + + if (nr_frags == pool->xdp_zc_max_segs) + drain = true; + } + + if (nr_frags) { + if (drain) { + batch.reclaim_descs = nr_frags; + WRITE_ONCE(xs->drain_cont, true); + } else { + if (nb_entries == max) + batch.budget_limited = true; + cached_cons -= nr_frags; } - nb_entries++; } - cached_cons -= nr_frags; /* Release valid plus any invalid entries */ xskq_cons_release_n(q, cached_cons - q->cached_cons); - return total_descs; + return batch; } /* Functions for consumers */ -- cgit v1.2.3 From 738e6f32e61d80b554e37015ecb7bc620b88001c Mon Sep 17 00:00:00 2001 From: Peiyang He Date: Mon, 20 Jul 2026 16:50:16 +0800 Subject: iommu/iommufd: Fix IOPF group ownership UAF iopf_group_alloc() links each last-page IOPF group into the generic IOPF pending list before invoking the domain fault handler. iommufd_fault_iopf_handler() also queued an accepted group in the IOMMUFD deliver list without removing it from the generic pending list. When detach or HWPT replacement drops the device's IOPF reference count to zero, an IOMMU driver may call iopf_queue_remove_device(). That function responds to and frees groups through the generic pending list without removing the same groups from IOMMUFD's deliver list or response xarray. A later read, response, or cleanup can then access the freed group and cause a UAF. Fix this by dequeuing an accepted group from the generic pending list before IOMMUFD queues it for userspace response. Make iopf_group_response() send a response regardless of pending-list membership, so the dequeued group can still be completed by IOMMUFD. Link: https://patch.msgid.link/r/3CFD314D0FE4D7EC+20260720085017.3998878-2-peiyang_he@smail.nju.edu.cn Closes: https://lore.kernel.org/all/B4F28798E2E784CA+d29f723c-b2b5-4b67-8d1c-4f7b9b0b27cb@smail.nju.edu.cn/ Fixes: 34765cbc679c ("iommufd: Associate fault object with iommufd_hw_pgtable") Cc: stable@vger.kernel.org Tested-by: Peiyang He Assisted-by: Codex:gpt-5.6-sol Signed-off-by: Peiyang He Reviewed-by: Kevin Tian Signed-off-by: Jason Gunthorpe --- drivers/iommu/io-pgfault.c | 24 +++++++++++++++++++----- drivers/iommu/iommufd/eventq.c | 2 ++ include/linux/iommu.h | 5 +++++ 3 files changed, 26 insertions(+), 5 deletions(-) (limited to 'include') diff --git a/drivers/iommu/io-pgfault.c b/drivers/iommu/io-pgfault.c index cca52a34d0ed..c16ff1fc4b95 100644 --- a/drivers/iommu/io-pgfault.c +++ b/drivers/iommu/io-pgfault.c @@ -332,16 +332,30 @@ void iopf_group_response(struct iopf_group *group, .code = status, }; - /* Only send response if there is a fault report pending */ mutex_lock(&fault_param->lock); - if (!list_empty(&group->pending_node)) { - ops->page_response(dev, &group->last_fault, &resp); - list_del_init(&group->pending_node); - } + ops->page_response(dev, &group->last_fault, &resp); + list_del_init(&group->pending_node); mutex_unlock(&fault_param->lock); } EXPORT_SYMBOL_GPL(iopf_group_response); +/** + * iopf_group_dequeue - Dequeue a page fault group from the pending list + * @group: the group to dequeue + * + * The fault handler is responsible for responding to the group after + * this function returns. + */ +void iopf_group_dequeue(struct iopf_group *group) +{ + struct iommu_fault_param *fault_param = group->fault_param; + + mutex_lock(&fault_param->lock); + list_del_init(&group->pending_node); + mutex_unlock(&fault_param->lock); +} +EXPORT_SYMBOL_GPL(iopf_group_dequeue); + /** * iopf_queue_discard_partial - Remove all pending partial fault * @queue: the queue whose partial faults need to be discarded diff --git a/drivers/iommu/iommufd/eventq.c b/drivers/iommu/iommufd/eventq.c index 5129e3bf5461..747dd5155121 100644 --- a/drivers/iommu/iommufd/eventq.c +++ b/drivers/iommu/iommufd/eventq.c @@ -484,6 +484,8 @@ int iommufd_fault_iopf_handler(struct iopf_group *group) hwpt = group->attach_handle->domain->iommufd_hwpt; fault = hwpt->fault; + iopf_group_dequeue(group); + spin_lock(&fault->common.lock); list_add_tail(&group->node, &fault->common.deliver); spin_unlock(&fault->common.lock); diff --git a/include/linux/iommu.h b/include/linux/iommu.h index d20aa6f6863a..ac43b8b93f14 100644 --- a/include/linux/iommu.h +++ b/include/linux/iommu.h @@ -1704,6 +1704,7 @@ void iopf_free_group(struct iopf_group *group); int iommu_report_device_fault(struct device *dev, struct iopf_fault *evt); void iopf_group_response(struct iopf_group *group, enum iommu_page_response_code status); +void iopf_group_dequeue(struct iopf_group *group); #else static inline int iopf_queue_add_device(struct iopf_queue *queue, struct device *dev) @@ -1749,5 +1750,9 @@ static inline void iopf_group_response(struct iopf_group *group, enum iommu_page_response_code status) { } + +static inline void iopf_group_dequeue(struct iopf_group *group) +{ +} #endif /* CONFIG_IOMMU_IOPF */ #endif /* __LINUX_IOMMU_H */ -- cgit v1.2.3 From 3dbbbf656b850c9c8de05df6ad4a1dfc6ff02845 Mon Sep 17 00:00:00 2001 From: Xingui Yang Date: Thu, 16 Jul 2026 16:11:45 +0800 Subject: scsi: libsas: Fix HA resume deadlock and hisi_sas disk-wake race Commit fbefe22811c3 ("scsi: libsas: Don't always drain event workqueue for HA resume") introduced sas_resume_ha_no_sync() to avoid a deadlock: the PHYE_RESUME_TIMEOUT handler, running on the HA event workqueue, calls sas_deform_port() -> sas_destruct_devices(), which removes SCSI devices and waits for the host to become runtime-active. But the host cannot resume until sas_resume_ha() -> sas_drain_work() returns, and the drain is blocked on that very handler. However skipping the drain reintroduces a race: hisi_sas returns from resume before all PHY UP work and libsas discovery work finish. The controller may then autosuspend while disks are still waking up. The disks issue IO to a suspended controller, the IO fails, and the disks get disabled. Fix the deadlock at its source by moving the PHYE_RESUME_TIMEOUT notification to after sas_drain_work(). By then the host resume is about to complete, so device removal through device_link no longer blocks on the resume and the cycle is broken. With the deadlock gone, restore sas_resume_ha() (the draining variant) in hisi_sas and remove sas_resume_ha_no_sync(). The reorder is safe for the other libsas consumers (isci, pm8001, aic94xx, mvsas). During suspend, sas_suspend_devices() calls sas_notify_lldd_dev_gone() for each device, which sets dev->lldd_dev to NULL. When scsi_unblock_requests re-enables I/O in resume, any I/O to a timed-out phy's disk is immediately rejected by the LLDD before reaching hardware: isci returns SAS_DEVICE_UNKNOWN (mapped to DID_BAD_TARGET), and pm8001 returns SAS_PHY_DOWN (mapped to DID_NO_CONNECT). Both complete directly via scsi_done() without entering SCSI EH. This is identical in both the old and new ordering since lldd_dev_gone runs during suspend, before resume. The reorder only affects when the PHYE_RESUME_TIMEOUT handler runs (synchronized by sas_drain_work() vs. asynchronous after resume returns), not whether I/O can reach the device. aic94xx and mvsas do not register any PM ops and never reach this code path. Fixes: fbefe22811c3 ("scsi: libsas: Don't always drain event workqueue for HA resume") Signed-off-by: Xingui Yang Reviewed-by: John Garry Link: https://patch.msgid.link/20260716081145.3950172-1-yangxingui@huawei.com Signed-off-by: Martin K. Petersen --- drivers/scsi/hisi_sas/hisi_sas_v3_hw.c | 10 +-------- drivers/scsi/libsas/sas_init.c | 37 +++++++++++++++++----------------- include/scsi/libsas.h | 1 - 3 files changed, 19 insertions(+), 29 deletions(-) (limited to 'include') diff --git a/drivers/scsi/hisi_sas/hisi_sas_v3_hw.c b/drivers/scsi/hisi_sas/hisi_sas_v3_hw.c index 213d5b5dea94..8a2500993e19 100644 --- a/drivers/scsi/hisi_sas/hisi_sas_v3_hw.c +++ b/drivers/scsi/hisi_sas/hisi_sas_v3_hw.c @@ -5261,15 +5261,7 @@ static int _resume_v3_hw(struct device *device) return rc; } phys_init_v3_hw(hisi_hba); - - /* - * If a directly-attached disk is removed during suspend, a deadlock - * may occur, as the PHYE_RESUME_TIMEOUT processing will require the - * hisi_hba->device to be active, which can only happen when resume - * completes. So don't wait for the HA event workqueue to drain upon - * resume. - */ - sas_resume_ha_no_sync(sha); + sas_resume_ha(sha); clear_bit(HISI_SAS_RESETTING_BIT, &hisi_hba->flags); dev_warn(dev, "end of resuming controller\n"); diff --git a/drivers/scsi/libsas/sas_init.c b/drivers/scsi/libsas/sas_init.c index 0bec236f0fb5..c3f3d05b46de 100644 --- a/drivers/scsi/libsas/sas_init.c +++ b/drivers/scsi/libsas/sas_init.c @@ -410,7 +410,7 @@ static void sas_resume_insert_broadcast_ha(struct sas_ha_struct *ha) } } -static void _sas_resume_ha(struct sas_ha_struct *ha, bool drain) +void sas_resume_ha(struct sas_ha_struct *ha) { const unsigned long tmo = msecs_to_jiffies(25000); int i; @@ -426,6 +426,23 @@ static void _sas_resume_ha(struct sas_ha_struct *ha, bool drain) dev_info(ha->dev, "waiting up to 25 seconds for %d phy%s to resume\n", i, i > 1 ? "s" : ""); wait_event_timeout(ha->eh_wait_q, phys_suspended(ha) == 0, tmo); + + /* + * All phys are back up or timed out. Turn on I/O and drain + * pending work. + */ + scsi_unblock_requests(ha->shost); + sas_drain_work(ha); + + /* + * Send PHYE_RESUME_TIMEOUT after sas_drain_work(). The handler + * calls sas_deform_port() -> sas_destruct_devices(), which removes + * SCSI devices and, for LLDDs using device_link() PM sync, waits + * for the host to be runtime-active. Sending it before the drain + * would deadlock: the drain waits for the handler, the handler + * waits for host resume, and host resume waits for the drain to + * finish. + */ for (i = 0; i < ha->num_phys; i++) { struct asd_sas_phy *phy = ha->sas_phy[i]; @@ -436,12 +453,6 @@ static void _sas_resume_ha(struct sas_ha_struct *ha, bool drain) } } - /* all phys are back up or timed out, turn on i/o so we can - * flush out disks that did not return - */ - scsi_unblock_requests(ha->shost); - if (drain) - sas_drain_work(ha); clear_bit(SAS_HA_RESUMING, &ha->state); sas_queue_deferred_work(ha); @@ -450,20 +461,8 @@ static void _sas_resume_ha(struct sas_ha_struct *ha, bool drain) */ sas_resume_insert_broadcast_ha(ha); } - -void sas_resume_ha(struct sas_ha_struct *ha) -{ - _sas_resume_ha(ha, true); -} EXPORT_SYMBOL(sas_resume_ha); -/* A no-sync variant, which does not call sas_drain_ha(). */ -void sas_resume_ha_no_sync(struct sas_ha_struct *ha) -{ - _sas_resume_ha(ha, false); -} -EXPORT_SYMBOL(sas_resume_ha_no_sync); - void sas_suspend_ha(struct sas_ha_struct *ha) { int i; diff --git a/include/scsi/libsas.h b/include/scsi/libsas.h index 163f23c92b41..36d4cb567837 100644 --- a/include/scsi/libsas.h +++ b/include/scsi/libsas.h @@ -680,7 +680,6 @@ extern int sas_register_ha(struct sas_ha_struct *); extern int sas_unregister_ha(struct sas_ha_struct *); extern void sas_prep_resume_ha(struct sas_ha_struct *sas_ha); extern void sas_resume_ha(struct sas_ha_struct *sas_ha); -extern void sas_resume_ha_no_sync(struct sas_ha_struct *sas_ha); extern void sas_suspend_ha(struct sas_ha_struct *sas_ha); int sas_phy_reset(struct sas_phy *phy, int hard_reset); -- cgit v1.2.3 From 4ecef577d1ceddaa906720c71c06672433aba318 Mon Sep 17 00:00:00 2001 From: Charles Keepax Date: Tue, 21 Jul 2026 15:36:30 +0100 Subject: ASoC: SDCA: Rename sdca_irq_allocate() to include devm Make it more clear sdca_irq_allocate() uses devm allocations by adding it into the name. Signed-off-by: Charles Keepax Link: https://patch.msgid.link/20260721143636.361814-2-ckeepax@opensource.cirrus.com Signed-off-by: Mark Brown --- include/sound/sdca_interrupts.h | 5 +++-- sound/soc/sdca/sdca_class.c | 4 ++-- sound/soc/sdca/sdca_interrupts.c | 8 ++++---- 3 files changed, 9 insertions(+), 8 deletions(-) (limited to 'include') diff --git a/include/sound/sdca_interrupts.h b/include/sound/sdca_interrupts.h index a515cc3df097..28fd44eb9334 100644 --- a/include/sound/sdca_interrupts.h +++ b/include/sound/sdca_interrupts.h @@ -86,8 +86,9 @@ int sdca_irq_populate(struct sdca_function_data *function, void sdca_irq_cleanup(struct device *dev, struct sdca_function_data *function, struct sdca_interrupt_info *info); -struct sdca_interrupt_info *sdca_irq_allocate(struct device *dev, - struct regmap *regmap, int irq); + +struct sdca_interrupt_info *devm_sdca_irq_allocate(struct device *dev, + struct regmap *regmap, int irq); void sdca_irq_enable_early(struct sdca_function_data *function, struct sdca_interrupt_info *info); diff --git a/sound/soc/sdca/sdca_class.c b/sound/soc/sdca/sdca_class.c index 8d7b007a068f..d7444f442c71 100644 --- a/sound/soc/sdca/sdca_class.c +++ b/sound/soc/sdca/sdca_class.c @@ -111,8 +111,8 @@ static void class_boot_work(struct work_struct *work) regcache_cache_only(drv->dev_regmap, false); - drv->irq_info = sdca_irq_allocate(drv->dev, drv->dev_regmap, - drv->sdw->irq); + drv->irq_info = devm_sdca_irq_allocate(drv->dev, drv->dev_regmap, + drv->sdw->irq); if (IS_ERR(drv->irq_info)) goto err; diff --git a/sound/soc/sdca/sdca_interrupts.c b/sound/soc/sdca/sdca_interrupts.c index 4539a52a8e32..1e4efc0609d9 100644 --- a/sound/soc/sdca/sdca_interrupts.c +++ b/sound/soc/sdca/sdca_interrupts.c @@ -592,7 +592,7 @@ void sdca_irq_cleanup(struct device *dev, EXPORT_SYMBOL_NS_GPL(sdca_irq_cleanup, "SND_SOC_SDCA"); /** - * sdca_irq_allocate - allocate an SDCA interrupt structure for a device + * devm_sdca_irq_allocate - allocate an SDCA interrupt structure for a device * @sdev: Device pointer against which things should be allocated. * @regmap: regmap to be used for accessing the SDCA IRQ registers. * @irq: The interrupt number. @@ -604,8 +604,8 @@ EXPORT_SYMBOL_NS_GPL(sdca_irq_cleanup, "SND_SOC_SDCA"); * Return: A pointer to the allocated sdca_interrupt_info struct, or an * error code. */ -struct sdca_interrupt_info *sdca_irq_allocate(struct device *sdev, - struct regmap *regmap, int irq) +struct sdca_interrupt_info *devm_sdca_irq_allocate(struct device *sdev, + struct regmap *regmap, int irq) { struct sdca_interrupt_info *info; int ret, i; @@ -634,7 +634,7 @@ struct sdca_interrupt_info *sdca_irq_allocate(struct device *sdev, return info; } -EXPORT_SYMBOL_NS_GPL(sdca_irq_allocate, "SND_SOC_SDCA"); +EXPORT_SYMBOL_NS_GPL(devm_sdca_irq_allocate, "SND_SOC_SDCA"); static void irq_enable_flags(struct sdca_function_data *function, struct sdca_interrupt_info *info, bool early) -- cgit v1.2.3 From f18e97fa7f125e0e7da130b4d0ff1edd48d7510a Mon Sep 17 00:00:00 2001 From: Charles Keepax Date: Tue, 21 Jul 2026 15:36:31 +0100 Subject: ASoC: SDCA: Add sdca_irq_cleanup_late() The SDCA IRQs are split into two groups, those registered at bus probe time (basically just FDL) and those registered at component time. There currently exists only a single cleanup function, if the FDL IRQ is freed at component time, then nothing would re-register it if the component is probed again. But the IRQs depending on a component need to be freed if the card is destroyed so they can't use stale components. Split the clean up into two functions one for the component level and one for the bus level. Signed-off-by: Charles Keepax Link: https://patch.msgid.link/20260721143636.361814-3-ckeepax@opensource.cirrus.com Signed-off-by: Mark Brown --- include/sound/sdca_interrupts.h | 5 ++++ sound/soc/sdca/sdca_class_function.c | 2 +- sound/soc/sdca/sdca_interrupts.c | 53 ++++++++++++++++++++++++++++-------- 3 files changed, 48 insertions(+), 12 deletions(-) (limited to 'include') diff --git a/include/sound/sdca_interrupts.h b/include/sound/sdca_interrupts.h index 28fd44eb9334..38c6c58c2cc7 100644 --- a/include/sound/sdca_interrupts.h +++ b/include/sound/sdca_interrupts.h @@ -33,6 +33,7 @@ struct sdca_function_data; * @priv: Pointer to private data for use by the handler. * @irq: IRQ number allocated to this interrupt, also used internally to track * the IRQ being assigned. + * @early_request: Flag to indicate this IRQ was requested at bus probe time. */ struct sdca_interrupt { const char *name; @@ -48,6 +49,7 @@ struct sdca_interrupt { void *priv; int irq; + bool early_request; }; /** @@ -86,6 +88,9 @@ int sdca_irq_populate(struct sdca_function_data *function, void sdca_irq_cleanup(struct device *dev, struct sdca_function_data *function, struct sdca_interrupt_info *info); +void sdca_irq_cleanup_late(struct device *dev, + struct sdca_function_data *function, + struct sdca_interrupt_info *info); struct sdca_interrupt_info *devm_sdca_irq_allocate(struct device *dev, struct regmap *regmap, int irq); diff --git a/sound/soc/sdca/sdca_class_function.c b/sound/soc/sdca/sdca_class_function.c index 1496a15f7d2a..5ae6c727c796 100644 --- a/sound/soc/sdca/sdca_class_function.c +++ b/sound/soc/sdca/sdca_class_function.c @@ -408,7 +408,7 @@ static void class_function_remove(struct auxiliary_device *auxdev) { struct class_function_drv *drv = auxiliary_get_drvdata(auxdev); - sdca_irq_cleanup(drv->dev, drv->function, drv->core->irq_info); + sdca_irq_cleanup_late(drv->dev, drv->function, drv->core->irq_info); } static int class_function_runtime_suspend(struct device *dev) diff --git a/sound/soc/sdca/sdca_interrupts.c b/sound/soc/sdca/sdca_interrupts.c index 1e4efc0609d9..d86884a89c40 100644 --- a/sound/soc/sdca/sdca_interrupts.c +++ b/sound/soc/sdca/sdca_interrupts.c @@ -456,6 +456,8 @@ int sdca_irq_populate_early(struct device *dev, struct regmap *regmap, if (ret) return ret; + interrupt->early_request = true; + ret = sdca_fdl_alloc_state(interrupt); if (ret) return ret; @@ -562,17 +564,10 @@ int sdca_irq_populate(struct sdca_function_data *function, } EXPORT_SYMBOL_NS_GPL(sdca_irq_populate, "SND_SOC_SDCA"); -/** - * sdca_irq_cleanup - Free all the individual IRQs for an SDCA Function - * @dev: Device pointer against which the sdca_interrupt_info was allocated. - * @function: Pointer to the SDCA Function. - * @info: Pointer to the SDCA interrupt info for this device. - * - * Typically this would be called from the driver for a single SDCA Function. - */ -void sdca_irq_cleanup(struct device *dev, - struct sdca_function_data *function, - struct sdca_interrupt_info *info) +static void sdca_irq_cleanup_flags(struct device *dev, + struct sdca_function_data *function, + struct sdca_interrupt_info *info, + bool late_cleanup) { int i; @@ -584,13 +579,49 @@ void sdca_irq_cleanup(struct device *dev, if (interrupt->function != function || !interrupt->irq) continue; + if (interrupt->early_request && !late_cleanup) + continue; + sdca_irq_free_locked(dev, info, i, interrupt->name, interrupt); kfree(interrupt->name); } } + +/** + * sdca_irq_cleanup - Free the regular IRQs for an SDCA Function + * @dev: Device pointer against which the sdca_interrupt_info was allocated. + * @function: Pointer to the SDCA Function. + * @info: Pointer to the SDCA interrupt info for this device. + * + * Typically this would be called from the driver for a single SDCA Function + * from component remove. + */ +void sdca_irq_cleanup(struct device *dev, + struct sdca_function_data *function, + struct sdca_interrupt_info *info) +{ + sdca_irq_cleanup_flags(dev, function, info, false); +} EXPORT_SYMBOL_NS_GPL(sdca_irq_cleanup, "SND_SOC_SDCA"); +/** + * sdca_irq_cleanup_late - Free the early IRQs for an SDCA Function + * @dev: Device pointer against which the sdca_interrupt_info was allocated. + * @function: Pointer to the SDCA Function. + * @info: Pointer to the SDCA interrupt info for this device. + * + * Typically this would be called from the driver for a single SDCA Function + * from bus remove. + */ +void sdca_irq_cleanup_late(struct device *dev, + struct sdca_function_data *function, + struct sdca_interrupt_info *info) +{ + sdca_irq_cleanup_flags(dev, function, info, true); +} +EXPORT_SYMBOL_NS_GPL(sdca_irq_cleanup_late, "SND_SOC_SDCA"); + /** * devm_sdca_irq_allocate - allocate an SDCA interrupt structure for a device * @sdev: Device pointer against which things should be allocated. -- cgit v1.2.3 From 0880082c27b6251cc3fea307dffa6899ad163e8b Mon Sep 17 00:00:00 2001 From: Charles Keepax Date: Tue, 21 Jul 2026 15:36:32 +0100 Subject: ASoC: SDCA: Remove devm from primary IRQ cleanup To provide greater flexibility on when the IRQs are requested for client drivers don't use devm for the primary IRQ request/cleanup helper functions. Signed-off-by: Charles Keepax Link: https://patch.msgid.link/20260721143636.361814-4-ckeepax@opensource.cirrus.com Signed-off-by: Mark Brown --- include/sound/sdca_fdl.h | 2 ++ include/sound/sdca_interrupts.h | 2 ++ include/sound/sdca_jack.h | 2 ++ sound/soc/sdca/sdca_fdl.c | 13 +++++++++++-- sound/soc/sdca/sdca_interrupts.c | 8 ++++++++ sound/soc/sdca/sdca_jack.c | 13 +++++++++++-- 6 files changed, 36 insertions(+), 4 deletions(-) (limited to 'include') diff --git a/include/sound/sdca_fdl.h b/include/sound/sdca_fdl.h index fbaf4b384c8a..dc33927b82bd 100644 --- a/include/sound/sdca_fdl.h +++ b/include/sound/sdca_fdl.h @@ -67,6 +67,8 @@ struct fdl_state { #if IS_ENABLED(CONFIG_SND_SOC_SDCA_FDL) int sdca_fdl_alloc_state(struct sdca_interrupt *interrupt); +void sdca_fdl_free_state(struct sdca_interrupt *interrupt); + int sdca_fdl_process(struct sdca_interrupt *interrupt); int sdca_fdl_sync(struct device *dev, struct sdca_function_data *function, struct sdca_interrupt_info *info); diff --git a/include/sound/sdca_interrupts.h b/include/sound/sdca_interrupts.h index 38c6c58c2cc7..8a44c19e917c 100644 --- a/include/sound/sdca_interrupts.h +++ b/include/sound/sdca_interrupts.h @@ -31,6 +31,7 @@ struct sdca_function_data; * @entity: Pointer to the Entity that the interrupt is associated with. * @control: Pointer to the Control that the interrupt is associated with. * @priv: Pointer to private data for use by the handler. + * @free_priv: Pointer to a function that can be used to free the priv data. * @irq: IRQ number allocated to this interrupt, also used internally to track * the IRQ being assigned. * @early_request: Flag to indicate this IRQ was requested at bus probe time. @@ -47,6 +48,7 @@ struct sdca_interrupt { struct sdca_control *control; void *priv; + void (*free_priv)(struct sdca_interrupt *interrupt); int irq; bool early_request; diff --git a/include/sound/sdca_jack.h b/include/sound/sdca_jack.h index 181541f0f4d8..59de40b7d7d0 100644 --- a/include/sound/sdca_jack.h +++ b/include/sound/sdca_jack.h @@ -28,6 +28,8 @@ struct jack_state { }; int sdca_jack_alloc_state(struct sdca_interrupt *interrupt); +void sdca_jack_free_state(struct sdca_interrupt *interrupt); + int sdca_jack_process(struct sdca_interrupt *interrupt); int sdca_jack_set_jack(struct sdca_interrupt_info *info, struct snd_soc_jack *jack); int sdca_jack_report(struct sdca_interrupt *interrupt); diff --git a/sound/soc/sdca/sdca_fdl.c b/sound/soc/sdca/sdca_fdl.c index 994821a6df61..82e09d960c12 100644 --- a/sound/soc/sdca/sdca_fdl.c +++ b/sound/soc/sdca/sdca_fdl.c @@ -481,10 +481,9 @@ EXPORT_SYMBOL_NS_GPL(sdca_fdl_process, "SND_SOC_SDCA"); */ int sdca_fdl_alloc_state(struct sdca_interrupt *interrupt) { - struct device *dev = interrupt->dev; struct fdl_state *fdl_state; - fdl_state = devm_kzalloc(dev, sizeof(*fdl_state), GFP_KERNEL); + fdl_state = kzalloc_obj(*fdl_state); if (!fdl_state) return -ENOMEM; @@ -499,3 +498,13 @@ int sdca_fdl_alloc_state(struct sdca_interrupt *interrupt) return 0; } EXPORT_SYMBOL_NS_GPL(sdca_fdl_alloc_state, "SND_SOC_SDCA"); + +/** + * sdca_fdl_free_state - free state for an FDL interrupt + * @interrupt: SDCA interrupt structure. + */ +void sdca_fdl_free_state(struct sdca_interrupt *interrupt) +{ + kfree(interrupt->priv); +} +EXPORT_SYMBOL_NS_GPL(sdca_fdl_free_state, "SND_SOC_SDCA"); diff --git a/sound/soc/sdca/sdca_interrupts.c b/sound/soc/sdca/sdca_interrupts.c index d86884a89c40..cd2c5d49bb95 100644 --- a/sound/soc/sdca/sdca_interrupts.c +++ b/sound/soc/sdca/sdca_interrupts.c @@ -457,6 +457,7 @@ int sdca_irq_populate_early(struct device *dev, struct regmap *regmap, return ret; interrupt->early_request = true; + interrupt->free_priv = sdca_fdl_free_state; ret = sdca_fdl_alloc_state(interrupt); if (ret) @@ -530,6 +531,8 @@ int sdca_irq_populate(struct sdca_function_data *function, handler = function_status_handler; break; case SDCA_CTL_TYPE_S(GE, DETECTED_MODE): + interrupt->free_priv = sdca_jack_free_state; + ret = sdca_jack_alloc_state(interrupt); if (ret) return ret; @@ -537,6 +540,8 @@ int sdca_irq_populate(struct sdca_function_data *function, handler = detected_mode_handler; break; case SDCA_CTL_TYPE_S(XU, FDL_CURRENTOWNER): + interrupt->free_priv = sdca_fdl_free_state; + ret = sdca_fdl_alloc_state(interrupt); if (ret) return ret; @@ -584,6 +589,9 @@ static void sdca_irq_cleanup_flags(struct device *dev, sdca_irq_free_locked(dev, info, i, interrupt->name, interrupt); + if (interrupt->free_priv) + interrupt->free_priv(interrupt); + kfree(interrupt->name); } } diff --git a/sound/soc/sdca/sdca_jack.c b/sound/soc/sdca/sdca_jack.c index ae9636622a84..ffa870964924 100644 --- a/sound/soc/sdca/sdca_jack.c +++ b/sound/soc/sdca/sdca_jack.c @@ -132,10 +132,9 @@ EXPORT_SYMBOL_NS_GPL(sdca_jack_process, "SND_SOC_SDCA"); */ int sdca_jack_alloc_state(struct sdca_interrupt *interrupt) { - struct device *dev = interrupt->dev; struct jack_state *jack_state; - jack_state = devm_kzalloc(dev, sizeof(*jack_state), GFP_KERNEL); + jack_state = kzalloc_obj(*jack_state); if (!jack_state) return -ENOMEM; @@ -145,6 +144,16 @@ int sdca_jack_alloc_state(struct sdca_interrupt *interrupt) } EXPORT_SYMBOL_NS_GPL(sdca_jack_alloc_state, "SND_SOC_SDCA"); +/** + * sdca_jack_free_state - free state for a jack interrupt + * @interrupt: SDCA interrupt structure. + */ +void sdca_jack_free_state(struct sdca_interrupt *interrupt) +{ + kfree(interrupt->priv); +} +EXPORT_SYMBOL_NS_GPL(sdca_jack_free_state, "SND_SOC_SDCA"); + static int type_get_mask(enum sdca_terminal_type type) { switch (type) { -- cgit v1.2.3 From 050406cbd676615ba71081bce69df710754d27e4 Mon Sep 17 00:00:00 2001 From: Charles Keepax Date: Tue, 21 Jul 2026 15:36:33 +0100 Subject: ASoC: SDCA: Populate IRQ data earlier Currently, the IRQ data (attached Entity/Control/etc) is populated as the IRQ is requested. However, this can cause issues as occasionally the setup process wants to access specifics of an IRQ before the IRQ is actually enabled. To facilitate this cache all the IRQ data during sdca_irq_populate_early() and make sdca_irq_populate() simply request the outstanding IRQs. This also has the advantage that sdca_irq_populate() can now just iterate through the IRQ array which is much smaller/faster than going through every Entity in the Function for Controls. Signed-off-by: Charles Keepax Link: https://patch.msgid.link/20260721143636.361814-5-ckeepax@opensource.cirrus.com Signed-off-by: Mark Brown --- include/sound/sdca_interrupts.h | 2 + sound/soc/sdca/sdca_interrupts.c | 115 +++++++++++++++------------------------ 2 files changed, 47 insertions(+), 70 deletions(-) (limited to 'include') diff --git a/include/sound/sdca_interrupts.h b/include/sound/sdca_interrupts.h index 8a44c19e917c..3b30146e21db 100644 --- a/include/sound/sdca_interrupts.h +++ b/include/sound/sdca_interrupts.h @@ -30,6 +30,7 @@ struct sdca_function_data; * @function: Pointer to the Function that the interrupt is associated with. * @entity: Pointer to the Entity that the interrupt is associated with. * @control: Pointer to the Control that the interrupt is associated with. + * @handler: Handler function to be called for the IRQ. * @priv: Pointer to private data for use by the handler. * @free_priv: Pointer to a function that can be used to free the priv data. * @irq: IRQ number allocated to this interrupt, also used internally to track @@ -46,6 +47,7 @@ struct sdca_interrupt { struct sdca_function_data *function; struct sdca_entity *entity; struct sdca_control *control; + irq_handler_t handler; void *priv; void (*free_priv)(struct sdca_interrupt *interrupt); diff --git a/sound/soc/sdca/sdca_interrupts.c b/sound/soc/sdca/sdca_interrupts.c index cd2c5d49bb95..6f0d8c0fe622 100644 --- a/sound/soc/sdca/sdca_interrupts.c +++ b/sound/soc/sdca/sdca_interrupts.c @@ -421,7 +421,8 @@ static struct sdca_interrupt *get_interrupt_data(struct device *dev, int irq, * * This is intended to be used as part of the Function boot process. It * can be called before the soundcard is registered (ie. doesn't depend - * on component) and will register the FDL interrupts. + * on component) and will populate all the required IRQ data, as well as + * registering the FDL interrupts to start booting the device. * * Return: Zero on success, and a negative error code on failure. */ @@ -448,24 +449,36 @@ int sdca_irq_populate_early(struct device *dev, struct regmap *regmap, else if (!interrupt) continue; + ret = sdca_irq_data_populate(dev, regmap, NULL, function, + entity, control, interrupt); + if (ret) + return ret; + switch (SDCA_CTL_TYPE(entity->type, control->sel)) { - case SDCA_CTL_TYPE_S(XU, FDL_CURRENTOWNER): - ret = sdca_irq_data_populate(dev, regmap, NULL, - function, entity, - control, interrupt); + case SDCA_CTL_TYPE_S(ENTITY_0, FUNCTION_STATUS): + interrupt->handler = function_status_handler; + break; + case SDCA_CTL_TYPE_S(GE, DETECTED_MODE): + interrupt->handler = detected_mode_handler; + interrupt->free_priv = sdca_jack_free_state; + + ret = sdca_jack_alloc_state(interrupt); if (ret) return ret; - - interrupt->early_request = true; + break; + case SDCA_CTL_TYPE_S(XU, FDL_CURRENTOWNER): + interrupt->handler = fdl_owner_handler; interrupt->free_priv = sdca_fdl_free_state; ret = sdca_fdl_alloc_state(interrupt); if (ret) return ret; + interrupt->early_request = true; + ret = sdca_irq_request_locked(dev, info, irq, interrupt->name, - fdl_owner_handler, + interrupt->handler, interrupt); if (ret) { dev_err(dev, "failed to request irq %s: %d\n", @@ -473,7 +486,11 @@ int sdca_irq_populate_early(struct device *dev, struct regmap *regmap, return ret; } break; + case SDCA_CTL_TYPE_S(HIDE, HIDTX_CURRENTOWNER): + interrupt->handler = hid_handler; + break; default: + interrupt->handler = base_handler; break; } } @@ -498,70 +515,26 @@ int sdca_irq_populate(struct sdca_function_data *function, struct sdca_interrupt_info *info) { struct device *dev = component->dev; - int i, j; + int i, ret; guard(mutex)(&info->irq_lock); - for (i = 0; i < function->num_entities; i++) { - struct sdca_entity *entity = &function->entities[i]; - - for (j = 0; j < entity->num_controls; j++) { - struct sdca_control *control = &entity->controls[j]; - int irq = control->interrupt_position; - struct sdca_interrupt *interrupt; - irq_handler_t handler; - int ret; - - interrupt = get_interrupt_data(dev, irq, info); - if (IS_ERR(interrupt)) - return PTR_ERR(interrupt); - else if (!interrupt) - continue; - - ret = sdca_irq_data_populate(dev, NULL, component, - function, entity, control, - interrupt); - if (ret) - return ret; - - handler = base_handler; - - switch (SDCA_CTL_TYPE(entity->type, control->sel)) { - case SDCA_CTL_TYPE_S(ENTITY_0, FUNCTION_STATUS): - handler = function_status_handler; - break; - case SDCA_CTL_TYPE_S(GE, DETECTED_MODE): - interrupt->free_priv = sdca_jack_free_state; - - ret = sdca_jack_alloc_state(interrupt); - if (ret) - return ret; - - handler = detected_mode_handler; - break; - case SDCA_CTL_TYPE_S(XU, FDL_CURRENTOWNER): - interrupt->free_priv = sdca_fdl_free_state; + for (i = 0; i < SDCA_MAX_INTERRUPTS; i++) { + struct sdca_interrupt *interrupt = &info->irqs[i]; + int irq; - ret = sdca_fdl_alloc_state(interrupt); - if (ret) - return ret; + if (interrupt->function != function || interrupt->irq) + continue; - handler = fdl_owner_handler; - break; - case SDCA_CTL_TYPE_S(HIDE, HIDTX_CURRENTOWNER): - handler = hid_handler; - break; - default: - break; - } + interrupt->component = component; - ret = sdca_irq_request_locked(dev, info, irq, interrupt->name, - handler, interrupt); - if (ret) { - dev_err(dev, "failed to request irq %s: %d\n", - interrupt->name, ret); - return ret; - } + irq = interrupt->control->interrupt_position; + ret = sdca_irq_request_locked(dev, info, irq, interrupt->name, + interrupt->handler, interrupt); + if (ret) { + dev_err(dev, "failed to request irq %s: %d\n", + interrupt->name, ret); + return ret; } } @@ -581,13 +554,15 @@ static void sdca_irq_cleanup_flags(struct device *dev, for (i = 0; i < SDCA_MAX_INTERRUPTS; i++) { struct sdca_interrupt *interrupt = &info->irqs[i]; - if (interrupt->function != function || !interrupt->irq) + if (interrupt->function != function || + (interrupt->early_request && !late_cleanup)) continue; - if (interrupt->early_request && !late_cleanup) - continue; + if (interrupt->irq) + sdca_irq_free_locked(dev, info, i, interrupt->name, interrupt); - sdca_irq_free_locked(dev, info, i, interrupt->name, interrupt); + if (!late_cleanup) + continue; if (interrupt->free_priv) interrupt->free_priv(interrupt); -- cgit v1.2.3 From 3e81e2fb216327a73510a7aa5318023c379d479a Mon Sep 17 00:00:00 2001 From: Charles Keepax Date: Tue, 21 Jul 2026 15:36:34 +0100 Subject: ASoC: Add a component fixup_controls callback A card level fixup_controls callback was added in: commit df4d27b19b89 ("ASoC: Introduce 'fixup_controls' card method") This allowed the machine driver to take actions after all the card controls have been added. However, there are times when a codec driver would also want to do things like obtain references to controls for later use, which require all the controls to be present. Add a component level fixup_controls callback, echoing the card level option. Signed-off-by: Charles Keepax Link: https://patch.msgid.link/20260721143636.361814-6-ckeepax@opensource.cirrus.com Signed-off-by: Mark Brown --- include/sound/soc-component.h | 2 ++ sound/soc/soc-component.c | 10 ++++++++++ sound/soc/soc-core.c | 5 +++++ 3 files changed, 17 insertions(+) (limited to 'include') diff --git a/include/sound/soc-component.h b/include/sound/soc-component.h index aa423865dbe7..4b7d7954953d 100644 --- a/include/sound/soc-component.h +++ b/include/sound/soc-component.h @@ -78,6 +78,7 @@ struct snd_soc_component_driver { unsigned int num_dapm_routes; int (*probe)(struct snd_soc_component *component); + int (*fixup_controls)(struct snd_soc_component *component); void (*remove)(struct snd_soc_component *component); int (*suspend)(struct snd_soc_component *component); int (*resume)(struct snd_soc_component *component); @@ -380,6 +381,7 @@ void snd_soc_component_suspend(struct snd_soc_component *component); void snd_soc_component_resume(struct snd_soc_component *component); int snd_soc_component_is_suspended(struct snd_soc_component *component); int snd_soc_component_probe(struct snd_soc_component *component); +int snd_soc_component_fixup_controls(struct snd_soc_component *component); void snd_soc_component_remove(struct snd_soc_component *component); int snd_soc_component_of_xlate_dai_id(struct snd_soc_component *component, struct device_node *ep); diff --git a/sound/soc/soc-component.c b/sound/soc/soc-component.c index 21492d15833f..2ce24513fac5 100644 --- a/sound/soc/soc-component.c +++ b/sound/soc/soc-component.c @@ -310,6 +310,16 @@ int snd_soc_component_probe(struct snd_soc_component *component) return soc_component_ret(component, ret); } +int snd_soc_component_fixup_controls(struct snd_soc_component *component) +{ + int ret = 0; + + if (component->driver->fixup_controls) + ret = component->driver->fixup_controls(component); + + return soc_component_ret(component, ret); +} + void snd_soc_component_remove(struct snd_soc_component *component) { if (component->driver->remove) diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index 7817beea5b3b..44f9bb4473f5 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -2162,6 +2162,11 @@ static int snd_soc_bind_card(struct snd_soc_card *card) goto probe_end; snd_soc_dapm_new_widgets(card); + for_each_card_components(card, component) { + ret = snd_soc_component_fixup_controls(component); + if (ret < 0) + goto probe_end; + } snd_soc_card_fixup_controls(card); ret = snd_card_register(card->snd_card); -- cgit v1.2.3 From bf1b7821f85383a8804441f8fd5165be148f3ec8 Mon Sep 17 00:00:00 2001 From: Charles Keepax Date: Tue, 21 Jul 2026 15:36:36 +0100 Subject: ASoC: SDCA: Move kcontrol search out of IRQ Now that the IRQs are always registered after all the ALSA controls are created it is possible to search for the control at the point the IRQ is requested. Move the control search out of the IRQ handler and do it at IRQ request time. This also fixes a potential issue when the card was torn down and reprobed without destroying the codec device, the kctl pointer stored by the IRQ handler would not be updated to the new control on the second probe. Signed-off-by: Charles Keepax Link: https://patch.msgid.link/20260721143636.361814-8-ckeepax@opensource.cirrus.com Signed-off-by: Mark Brown --- include/sound/sdca_jack.h | 1 + sound/soc/sdca/sdca_interrupts.c | 12 +++++++++++ sound/soc/sdca/sdca_jack.c | 43 ++++++++++++++++++++++++---------------- 3 files changed, 39 insertions(+), 17 deletions(-) (limited to 'include') diff --git a/include/sound/sdca_jack.h b/include/sound/sdca_jack.h index 59de40b7d7d0..871ba2d8146a 100644 --- a/include/sound/sdca_jack.h +++ b/include/sound/sdca_jack.h @@ -28,6 +28,7 @@ struct jack_state { }; int sdca_jack_alloc_state(struct sdca_interrupt *interrupt); +int sdca_jack_init_state(struct sdca_interrupt *interrupt); void sdca_jack_free_state(struct sdca_interrupt *interrupt); int sdca_jack_process(struct sdca_interrupt *interrupt); diff --git a/sound/soc/sdca/sdca_interrupts.c b/sound/soc/sdca/sdca_interrupts.c index 6f0d8c0fe622..42fbd3af8a75 100644 --- a/sound/soc/sdca/sdca_interrupts.c +++ b/sound/soc/sdca/sdca_interrupts.c @@ -521,6 +521,8 @@ int sdca_irq_populate(struct sdca_function_data *function, for (i = 0; i < SDCA_MAX_INTERRUPTS; i++) { struct sdca_interrupt *interrupt = &info->irqs[i]; + struct sdca_control *control = interrupt->control; + struct sdca_entity *entity = interrupt->entity; int irq; if (interrupt->function != function || interrupt->irq) @@ -528,6 +530,16 @@ int sdca_irq_populate(struct sdca_function_data *function, interrupt->component = component; + switch (SDCA_CTL_TYPE(entity->type, control->sel)) { + case SDCA_CTL_TYPE_S(GE, DETECTED_MODE): + ret = sdca_jack_init_state(interrupt); + if (ret) + return ret; + break; + default: + break; + } + irq = interrupt->control->interrupt_position; ret = sdca_irq_request_locked(dev, info, irq, interrupt->name, interrupt->handler, interrupt); diff --git a/sound/soc/sdca/sdca_jack.c b/sound/soc/sdca/sdca_jack.c index 3c84d17244a2..73f8067906a9 100644 --- a/sound/soc/sdca/sdca_jack.c +++ b/sound/soc/sdca/sdca_jack.c @@ -47,23 +47,6 @@ int sdca_jack_process(struct sdca_interrupt *interrupt) guard(rwsem_write)(rwsem); - if (!kctl) { - const char *name __free(kfree) = kasprintf(GFP_KERNEL, "%s %s", - interrupt->entity->label, - SDCA_CTL_SELECTED_MODE_NAME); - - if (!name) - return -ENOMEM; - - kctl = snd_soc_component_get_kcontrol(component, name); - if (!kctl) { - dev_err(dev, "control not found: %s\n", name); - return -ENODEV; - } - - state->kctl = kctl; - } - reg = SDW_SDCA_CTL(interrupt->function->desc->adr, interrupt->entity->id, interrupt->control->sel, 0); @@ -148,6 +131,32 @@ void sdca_jack_free_state(struct sdca_interrupt *interrupt) } EXPORT_SYMBOL_NS_GPL(sdca_jack_free_state, "SND_SOC_SDCA"); +/** + * sdca_jack_init_state - Initialise transient state for a jack interrupt + * @interrupt: SDCA interrupt structure. + * + * Return: Zero on success or a negative error code. + */ +int sdca_jack_init_state(struct sdca_interrupt *interrupt) +{ + struct jack_state *jack_state = interrupt->priv; + const char *name __free(kfree) = kasprintf(GFP_KERNEL, "%s %s", + interrupt->entity->label, + SDCA_CTL_SELECTED_MODE_NAME); + + if (!name) + return -ENOMEM; + + jack_state->kctl = snd_soc_component_get_kcontrol(interrupt->component, name); + if (!jack_state->kctl) { + dev_err(interrupt->dev, "control not found: %s\n", name); + return -ENODEV; + } + + return 0; +} +EXPORT_SYMBOL_NS_GPL(sdca_jack_init_state, "SND_SOC_SDCA"); + static int type_get_mask(enum sdca_terminal_type type) { switch (type) { -- cgit v1.2.3 From 97ac08560d236ca17f6606d9e671118e5eae5721 Mon Sep 17 00:00:00 2001 From: Eric Joyner Date: Wed, 22 Jul 2026 21:13:42 -0700 Subject: ethtool: Embed FEC hist ranges as buffer in struct When a driver's .get_fec_stats() handler is called and the driver supports FEC histogram stats, the driver supplies the histogram bin ranges via a pointer. This pointer is assigned while under the netdev ops lock in fec_prepare_data(), but the actual data is only read after the lock is released; so this allows the driver to change the ranges (e.g. from another .get_fec_stats() call) while the current call chain is reading them in fec_fill_reply(). Fix this by adding an ethtool core-owned buffer, ranges_buf, to struct ethtool_fec_hist. Drivers whose ranges are built dynamically (currently just mlx5) fill ranges_buf and then point the existing ranges pointer at it, giving ethtool a consistent copy that stays valid after the netdev ops lock is dropped and later in fec_fill_reply(). Drivers whose ranges are compile-time constants (bnxt, netdevsim) are unaffected by the potential race and keep setting the existing ranges pointer to their constant array, without making copies. Fixes: cc2f08129925 ("ethtool: add FEC bins histogram report") Signed-off-by: Eric Joyner Reviewed-by: Vadim Fedorenko Link: https://patch.msgid.link/20260723041342.39238-1-eric.joyner@amd.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/mellanox/mlx5/core/en.h | 1 - drivers/net/ethernet/mellanox/mlx5/core/en_main.c | 7 ------- drivers/net/ethernet/mellanox/mlx5/core/en_stats.c | 19 +++++++++---------- include/linux/ethtool.h | 1 + 4 files changed, 10 insertions(+), 18 deletions(-) (limited to 'include') diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en.h b/drivers/net/ethernet/mellanox/mlx5/core/en.h index d507289096c2..6867a5aed42c 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en.h +++ b/drivers/net/ethernet/mellanox/mlx5/core/en.h @@ -984,7 +984,6 @@ struct mlx5e_priv { struct mlx5e_mqprio_rl *mqprio_rl; struct dentry *dfs_root; struct mlx5_devcom_comp_dev *devcom; - struct ethtool_fec_hist_range *fec_ranges; }; static inline u16 mlx5e_stats_nch_read(const struct mlx5e_priv *priv) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_main.c b/drivers/net/ethernet/mellanox/mlx5/core/en_main.c index c1acb9012d3f..7d47a1da8b6b 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en_main.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/en_main.c @@ -6415,14 +6415,8 @@ int mlx5e_priv_init(struct mlx5e_priv *priv, if (!priv->channel_stats) goto err_free_tx_rates; - priv->fec_ranges = kzalloc_objs(*priv->fec_ranges, ETHTOOL_FEC_HIST_MAX); - if (!priv->fec_ranges) - goto err_free_channel_stats; - return 0; -err_free_channel_stats: - kfree(priv->channel_stats); err_free_tx_rates: kfree(priv->tx_rates); err_free_txq2sq_stats: @@ -6447,7 +6441,6 @@ void mlx5e_priv_cleanup(struct mlx5e_priv *priv) if (!priv->mdev) return; - kfree(priv->fec_ranges); for (i = 0; i < priv->stats_nch; i++) kvfree(priv->channel_stats[i]); kfree(priv->channel_stats); diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_stats.c b/drivers/net/ethernet/mellanox/mlx5/core/en_stats.c index de38b60806c2..e7e6db7f6bf1 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en_stats.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/en_stats.c @@ -1550,7 +1550,7 @@ static bool fec_rs_validate_hist_type(int mode, int hist_type) static u8 fec_rs_histogram_fill_ranges(struct mlx5e_priv *priv, int mode, - const struct ethtool_fec_hist_range **ranges) + struct ethtool_fec_hist_range *ranges) { struct mlx5_core_dev *mdev = priv->mdev; u32 out[MLX5_ST_SZ_DW(pphcr_reg)] = {0}; @@ -1558,8 +1558,6 @@ fec_rs_histogram_fill_ranges(struct mlx5e_priv *priv, int mode, int sz = MLX5_ST_SZ_BYTES(pphcr_reg); u8 hist_type, num_of_bins; - memset(priv->fec_ranges, 0, - ETHTOOL_FEC_HIST_MAX * sizeof(*priv->fec_ranges)); MLX5_SET(pphcr_reg, in, local_port, 1); if (mlx5_core_access_reg(mdev, in, sz, out, sz, MLX5_REG_PPHCR, 0, 0)) return 0; @@ -1575,12 +1573,11 @@ fec_rs_histogram_fill_ranges(struct mlx5e_priv *priv, int mode, for (int i = 0; i < num_of_bins; i++) { void *bin_range = MLX5_ADDR_OF(pphcr_reg, out, bin_range[i]); - priv->fec_ranges[i].high = MLX5_GET(bin_range_layout, bin_range, - high_val); - priv->fec_ranges[i].low = MLX5_GET(bin_range_layout, bin_range, - low_val); + ranges[i].high = MLX5_GET(bin_range_layout, bin_range, + high_val); + ranges[i].low = MLX5_GET(bin_range_layout, bin_range, + low_val); } - *ranges = priv->fec_ranges; return num_of_bins; } @@ -1622,10 +1619,12 @@ static void fec_set_histograms_stats(struct mlx5e_priv *priv, int mode, case MLX5E_FEC_LLRS_272_257_1: case MLX5E_FEC_RS_544_514_INTERLEAVED_QUAD: num_of_bins = - fec_rs_histogram_fill_ranges(priv, mode, &hist->ranges); - if (num_of_bins) + fec_rs_histogram_fill_ranges(priv, mode, hist->ranges_buf); + if (num_of_bins) { + hist->ranges = hist->ranges_buf; return fec_rs_histogram_fill_stats(priv, num_of_bins, hist); + } break; default: return; diff --git a/include/linux/ethtool.h b/include/linux/ethtool.h index 5d491a98265e..12683b5d125e 100644 --- a/include/linux/ethtool.h +++ b/include/linux/ethtool.h @@ -562,6 +562,7 @@ struct ethtool_fec_hist { u64 per_lane[ETHTOOL_MAX_LANES]; } values[ETHTOOL_FEC_HIST_MAX]; const struct ethtool_fec_hist_range *ranges; + struct ethtool_fec_hist_range ranges_buf[ETHTOOL_FEC_HIST_MAX]; }; /** * struct ethtool_fec_stats - statistics for IEEE 802.3 FEC -- cgit v1.2.3 From aec49a34d928a0ecdea85bd9f6b4114b668b68b8 Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Fri, 24 Jul 2026 18:52:09 -0700 Subject: ata: libata: avoid kernel-doc warnings Modify comments to prevent kernel-doc warnings: - use "/*" for a non-kernel-doc comment - add a Returns: section for ata_id_major_version() Warning: include/linux/ata.h:770 Cannot find identifier on line: * Warning: include/linux/ata.h:782 function parameter 'id' not described in 'ata_id_sct_data_tables' Warning: include/linux/ata.h:782 expecting prototype for Word(). Prototype was for ata_id_sct_data_tables() instead Warning: include/linux/ata.h:820 No description found for return value of 'ata_id_major_version' Signed-off-by: Randy Dunlap Signed-off-by: Damien Le Moal --- include/linux/ata.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'include') diff --git a/include/linux/ata.h b/include/linux/ata.h index 8fd48bcb2a46..7daad4cad985 100644 --- a/include/linux/ata.h +++ b/include/linux/ata.h @@ -762,8 +762,7 @@ static inline bool ata_id_sense_reporting_enabled(const u16 *id) return id[ATA_ID_COMMAND_SET_4] & BIT(6); } -/** - * +/* * Word: 206 - SCT Command Transport * 15:12 - Vendor Specific * 11:6 - Reserved @@ -810,8 +809,9 @@ static inline bool ata_id_sct_supported(const u16 *id) * * The practical impact of this is that ata_id_major_version cannot * reliably report on drives below ATA3. + * + * Returns: major version of ATA drive level or %0 if unknown */ - static inline unsigned int ata_id_major_version(const u16 *id) { unsigned int mver; -- cgit v1.2.3 From 1e024d2b41ee32bc06818f7f09a3562c58842cf9 Mon Sep 17 00:00:00 2001 From: Matt Vollrath Date: Fri, 24 Jul 2026 03:39:42 -0400 Subject: ata: libata-eh: Increase STANDBY IMMEDIATE timeout Correct a previous change (see Fixes) which reduced the standby timeout from 30 to 5 seconds. Increase it to 15 seconds. I was troubleshooting an error spotted during system suspend: [ 1217.152867] ata1.00: Entering standby power mode [ 1222.322948] ata1.00: qc timeout after 5000 msecs (cmd 0xe0) [ 1222.324010] ata1.00: STANDBY IMMEDIATE failed (err_mask=0x4) This drive is a Samsung 870 EVO SSD in good SMART standing, and I wasn't aware of any reason it should be taking so long to standby. The issue is intermittent, but I observed it sometimes taking 7 seconds to manually standby. I assume this was interruption of background maintenance after a power outage. As a desktop user, I would prefer to wait the extra 2 seconds at suspend to let the drive finish its business rather than drop the rails from under it. The change from 30 to 5 seconds was implicit when switching suspend from START STOP UNIT to an internal command with no timeout table entry. No reason was stated for the change. Fixes: aa3998dbeb3a ("ata: libata-scsi: Disable scsi device manage_system_start_stop") Cc: stable@vger.kernel.org Signed-off-by: Matt Vollrath Assisted-by: Claude:claude-5-fable Signed-off-by: Damien Le Moal --- drivers/ata/libata-eh.c | 8 ++++++++ include/linux/libata.h | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) (limited to 'include') diff --git a/drivers/ata/libata-eh.c b/drivers/ata/libata-eh.c index 05df7ea6954a..46947ed0c657 100644 --- a/drivers/ata/libata-eh.c +++ b/drivers/ata/libata-eh.c @@ -106,6 +106,12 @@ static const unsigned int ata_eh_flush_timeouts[] = { UINT_MAX, }; +static const unsigned int ata_eh_standby_timeouts[] = { + 15000, /* Some drives may be slow to standby */ + /* but don't hold up a suspend too long waiting for them */ + UINT_MAX, +}; + static const unsigned int ata_eh_other_timeouts[] = { 5000, /* same rationale as identify timeout */ 10000, /* ditto */ @@ -147,6 +153,8 @@ ata_eh_cmd_timeout_table[ATA_EH_CMD_TIMEOUT_TABLE_SIZE] = { .timeouts = ata_eh_other_timeouts, }, { .commands = CMDS(ATA_CMD_FLUSH, ATA_CMD_FLUSH_EXT), .timeouts = ata_eh_flush_timeouts }, + { .commands = CMDS(ATA_CMD_STANDBYNOW1), + .timeouts = ata_eh_standby_timeouts }, { .commands = CMDS(ATA_CMD_VERIFY), .timeouts = ata_eh_reset_timeouts }, }; diff --git a/include/linux/libata.h b/include/linux/libata.h index 96e626d6a7ca..6c0d385b2cde 100644 --- a/include/linux/libata.h +++ b/include/linux/libata.h @@ -425,7 +425,7 @@ enum { /* This should match the actual table size of * ata_eh_cmd_timeout_table in libata-eh.c. */ - ATA_EH_CMD_TIMEOUT_TABLE_SIZE = 8, + ATA_EH_CMD_TIMEOUT_TABLE_SIZE = 9, /* User visible DMA mask for DMA control. DO NOT renumber. */ ATA_DMA_MASK_ATA = (1 << 0), /* DMA on ATA Disk */ -- cgit v1.2.3 From 1d78d56c43ef3768183e8370e7367b162700e049 Mon Sep 17 00:00:00 2001 From: David Howells Date: Mon, 27 Jul 2026 14:07:15 +0100 Subject: netfs: Fix folio_queue ENOMEM in writeback by adding a mempool Fix the handling of folio_queue allocation failure in writeback by adding a mempool and passing in gfp_t flags to the rolling buffer functions that allocate memory, using the mempool if gfp != GFP_KERNEL. This is then extended upwards and the gfp to be used for a request is stored in the netfs_io_request struct and is then used for both requests and subrequests, eliminating the sleeping loops there. The failure caused: folio != NULL WARNING: fs/netfs/write_issue.c:603 at netfs_writepages+0x883/0xa10 fs/netfs/write_issue.c:603, CPU#3: syz.0.17/5919 Fixes: cd0277ed0c18 ("netfs: Use new folio_queue data type and iterator instead of xarray iter") Reported-by: syzbot+0da43efa72f88bd3a8af@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=0da43efa72f88bd3a8af Signed-off-by: David Howells Link: https://patch.msgid.link/20260727130716.1099906-5-dhowells@redhat.com Tested-by: syzbot+0da43efa72f88bd3a8af@syzkaller.appspotmail.com cc: Paulo Alcantara cc: Yun Zhou cc: Matthew Wilcox cc: Christoph Hellwig cc: netfs@lists.linux.dev cc: linux-fsdevel@vger.kernel.org Signed-off-by: Christian Brauner (Amutable) --- fs/netfs/buffered_read.c | 6 +++--- fs/netfs/internal.h | 1 + fs/netfs/main.c | 7 +++++++ fs/netfs/objects.c | 30 +++++++++++++++++------------- fs/netfs/read_pgpriv2.c | 2 +- fs/netfs/rolling_buffer.c | 22 +++++++++++++--------- fs/netfs/write_issue.c | 10 +++++----- include/linux/netfs.h | 1 + include/linux/rolling_buffer.h | 6 +++--- 9 files changed, 51 insertions(+), 34 deletions(-) (limited to 'include') diff --git a/fs/netfs/buffered_read.c b/fs/netfs/buffered_read.c index 3d86414ee40f..7fdfa4f27e34 100644 --- a/fs/netfs/buffered_read.c +++ b/fs/netfs/buffered_read.c @@ -361,7 +361,7 @@ void netfs_readahead(struct readahead_control *ractl) netfs_rreq_expand(rreq, ractl); rreq->submitted = rreq->start; - if (rolling_buffer_init(&rreq->buffer, rreq->debug_id, ITER_DEST) < 0) + if (rolling_buffer_init(&rreq->buffer, rreq->debug_id, ITER_DEST, rreq->gfp) < 0) goto cleanup_free; netfs_read_to_pagecache(rreq, ractl); @@ -380,10 +380,10 @@ static int netfs_create_singular_buffer(struct netfs_io_request *rreq, struct fo { ssize_t added; - if (rolling_buffer_init(&rreq->buffer, rreq->debug_id, ITER_DEST) < 0) + if (rolling_buffer_init(&rreq->buffer, rreq->debug_id, ITER_DEST, rreq->gfp) < 0) return -ENOMEM; - added = rolling_buffer_append(&rreq->buffer, folio, rollbuf_flags); + added = rolling_buffer_append(&rreq->buffer, folio, rollbuf_flags, rreq->gfp); if (added < 0) return added; rreq->submitted = rreq->start + added; diff --git a/fs/netfs/internal.h b/fs/netfs/internal.h index d889caa401dc..420ee7b26580 100644 --- a/fs/netfs/internal.h +++ b/fs/netfs/internal.h @@ -43,6 +43,7 @@ extern struct list_head netfs_io_requests; extern spinlock_t netfs_proc_lock; extern mempool_t netfs_request_pool; extern mempool_t netfs_subrequest_pool; +extern mempool_t netfs_folioq_pool; #ifdef CONFIG_PROC_FS static inline void netfs_proc_add_rreq(struct netfs_io_request *rreq) diff --git a/fs/netfs/main.c b/fs/netfs/main.c index 73da6c9f5777..927badf3989d 100644 --- a/fs/netfs/main.c +++ b/fs/netfs/main.c @@ -28,6 +28,7 @@ static struct kmem_cache *netfs_request_slab; static struct kmem_cache *netfs_subrequest_slab; mempool_t netfs_request_pool; mempool_t netfs_subrequest_pool; +mempool_t netfs_folioq_pool; #ifdef CONFIG_PROC_FS LIST_HEAD(netfs_io_requests); @@ -108,6 +109,9 @@ static int __init netfs_init(void) { int ret = -ENOMEM; + if (mempool_init_kmalloc_pool(&netfs_folioq_pool, 100, sizeof(struct folio_queue)) < 0) + goto error_folioq_pool; + netfs_request_slab = kmem_cache_create("netfs_request", sizeof(struct netfs_io_request), 0, SLAB_HWCACHE_ALIGN | SLAB_ACCOUNT, @@ -160,6 +164,8 @@ error_subreq: error_reqpool: kmem_cache_destroy(netfs_request_slab); error_req: + mempool_exit(&netfs_folioq_pool); +error_folioq_pool: return ret; } fs_initcall(netfs_init); @@ -172,5 +178,6 @@ static void __exit netfs_exit(void) kmem_cache_destroy(netfs_subrequest_slab); mempool_exit(&netfs_request_pool); kmem_cache_destroy(netfs_request_slab); + mempool_exit(&netfs_folioq_pool); } module_exit(netfs_exit); diff --git a/fs/netfs/objects.c b/fs/netfs/objects.c index b8c4918d3dcd..01461a74642d 100644 --- a/fs/netfs/objects.c +++ b/fs/netfs/objects.c @@ -7,7 +7,6 @@ #include #include -#include #include "internal.h" static void netfs_free_request(struct work_struct *work); @@ -26,17 +25,23 @@ struct netfs_io_request *netfs_alloc_request(struct address_space *mapping, struct netfs_io_request *rreq; mempool_t *mempool = ctx->ops->request_pool ?: &netfs_request_pool; struct kmem_cache *cache = mempool->pool_data; + gfp_t gfp = GFP_KERNEL; int ret; - for (;;) { - rreq = mempool_alloc(mempool, GFP_KERNEL); - if (rreq) - break; - msleep(10); + /* Writeback is part of memory reclaim and must not fail due to ENOMEM. */ + if (origin == NETFS_WRITEBACK || origin == NETFS_WRITEBACK_SINGLE) { + gfp = GFP_NOFS; /* Allows use of mempools. */ + + rreq = mempool_alloc(mempool, gfp); + } else { + rreq = mempool->alloc(gfp, mempool->pool_data); + if (!rreq) + return ERR_PTR(-ENOMEM); } memset(rreq, 0, kmem_cache_size(cache)); INIT_WORK(&rreq->cleanup_work, netfs_free_request); + rreq->gfp = gfp; rreq->start = start; rreq->len = len; rreq->origin = origin; @@ -200,13 +205,12 @@ struct netfs_io_subrequest *netfs_alloc_subrequest(struct netfs_io_request *rreq mempool_t *mempool = rreq->netfs_ops->subrequest_pool ?: &netfs_subrequest_pool; struct kmem_cache *cache = mempool->pool_data; - for (;;) { - subreq = mempool_alloc(rreq->netfs_ops->subrequest_pool ?: &netfs_subrequest_pool, - GFP_KERNEL); - if (subreq) - break; - msleep(10); - } + if (rreq->gfp == GFP_KERNEL) + subreq = mempool->alloc(rreq->gfp, mempool->pool_data); + else + subreq = mempool_alloc(mempool, rreq->gfp); + if (!subreq) + return NULL; memset(subreq, 0, kmem_cache_size(cache)); INIT_WORK(&subreq->work, NULL); diff --git a/fs/netfs/read_pgpriv2.c b/fs/netfs/read_pgpriv2.c index 7eacc58abadb..c31190993b76 100644 --- a/fs/netfs/read_pgpriv2.c +++ b/fs/netfs/read_pgpriv2.c @@ -53,7 +53,7 @@ static void netfs_pgpriv2_copy_folio(struct netfs_io_request *creq, struct folio trace_netfs_folio(folio, netfs_folio_trace_store_copy); /* Attach the folio to the rolling buffer. */ - if (rolling_buffer_append(&creq->buffer, folio, 0) < 0) { + if (rolling_buffer_append(&creq->buffer, folio, 0, creq->gfp) < 0) { folio_end_private_2(folio); clear_bit(NETFS_RREQ_FOLIO_COPY_TO_CACHE, &creq->flags); return; diff --git a/fs/netfs/rolling_buffer.c b/fs/netfs/rolling_buffer.c index a17fbf9853a4..8c0026836f9c 100644 --- a/fs/netfs/rolling_buffer.c +++ b/fs/netfs/rolling_buffer.c @@ -6,6 +6,7 @@ */ #include +#include #include #include #include @@ -27,7 +28,10 @@ struct folio_queue *netfs_folioq_alloc(unsigned int rreq_id, gfp_t gfp, { struct folio_queue *fq; - fq = kmalloc_obj(*fq, gfp); + if (gfp == GFP_KERNEL) + fq = netfs_folioq_pool.alloc(gfp, netfs_folioq_pool.pool_data); + else + fq = mempool_alloc(&netfs_folioq_pool, gfp); if (fq) { netfs_stat(&netfs_n_folioq); folioq_init(fq, rreq_id); @@ -50,7 +54,7 @@ void netfs_folioq_free(struct folio_queue *folioq, { trace_netfs_folioq(folioq, trace); netfs_stat_d(&netfs_n_folioq); - kfree(folioq); + mempool_free(folioq, &netfs_folioq_pool); } EXPORT_SYMBOL(netfs_folioq_free); @@ -60,11 +64,11 @@ EXPORT_SYMBOL(netfs_folioq_free); * consumer. */ int rolling_buffer_init(struct rolling_buffer *roll, unsigned int rreq_id, - unsigned int direction) + unsigned int direction, gfp_t gfp) { struct folio_queue *fq; - fq = netfs_folioq_alloc(rreq_id, GFP_NOFS, netfs_trace_folioq_rollbuf_init); + fq = netfs_folioq_alloc(rreq_id, gfp, netfs_trace_folioq_rollbuf_init); if (!fq) return -ENOMEM; @@ -77,14 +81,14 @@ int rolling_buffer_init(struct rolling_buffer *roll, unsigned int rreq_id, /* * Add another folio_queue to a rolling buffer if there's no space left. */ -int rolling_buffer_make_space(struct rolling_buffer *roll) +int rolling_buffer_make_space(struct rolling_buffer *roll, gfp_t gfp) { struct folio_queue *fq, *head = roll->head; if (!folioq_full(head)) return 0; - fq = netfs_folioq_alloc(head->rreq_id, GFP_NOFS, netfs_trace_folioq_make_space); + fq = netfs_folioq_alloc(head->rreq_id, gfp, netfs_trace_folioq_make_space); if (!fq) return -ENOMEM; fq->prev = head; @@ -122,7 +126,7 @@ ssize_t rolling_buffer_load_from_ra(struct rolling_buffer *roll, int nr, ix, to; ssize_t size = 0; - if (rolling_buffer_make_space(roll) < 0) + if (rolling_buffer_make_space(roll, GFP_KERNEL) < 0) return -ENOMEM; fq = roll->head; @@ -153,12 +157,12 @@ ssize_t rolling_buffer_load_from_ra(struct rolling_buffer *roll, * Append a folio to the rolling buffer. */ ssize_t rolling_buffer_append(struct rolling_buffer *roll, struct folio *folio, - unsigned int flags) + unsigned int flags, gfp_t gfp) { ssize_t size = folio_size(folio); int slot; - if (rolling_buffer_make_space(roll) < 0) + if (rolling_buffer_make_space(roll, gfp) < 0) return -ENOMEM; slot = folioq_append(roll->head, folio); diff --git a/fs/netfs/write_issue.c b/fs/netfs/write_issue.c index 14efe4cb9393..2d9cfcd43658 100644 --- a/fs/netfs/write_issue.c +++ b/fs/netfs/write_issue.c @@ -108,7 +108,7 @@ struct netfs_io_request *netfs_create_write_req(struct address_space *mapping, ictx = netfs_inode(wreq->inode); if (is_cacheable) fscache_begin_write_operation(&wreq->cache_resources, netfs_i_cookie(ictx)); - if (rolling_buffer_init(&wreq->buffer, wreq->debug_id, ITER_SOURCE) < 0) + if (rolling_buffer_init(&wreq->buffer, wreq->debug_id, ITER_SOURCE, wreq->gfp) < 0) goto nomem; wreq->cleaned_to = wreq->start; @@ -167,7 +167,7 @@ void netfs_prepare_write(struct netfs_io_request *wreq, */ if (iov_iter_is_folioq(wreq_iter) && wreq_iter->folioq_slot >= folioq_nr_slots(wreq_iter->folioq)) - rolling_buffer_make_space(&wreq->buffer); + rolling_buffer_make_space(&wreq->buffer, wreq->gfp); subreq = netfs_alloc_subrequest(wreq); subreq->source = stream->source; @@ -334,7 +334,7 @@ static int netfs_write_folio(struct netfs_io_request *wreq, _enter(""); - if (rolling_buffer_make_space(&wreq->buffer) < 0) + if (rolling_buffer_make_space(&wreq->buffer, wreq->gfp) < 0) return -ENOMEM; /* netfs_perform_write() may shift i_size around the page or from out @@ -436,7 +436,7 @@ static int netfs_write_folio(struct netfs_io_request *wreq, } /* Attach the folio to the rolling buffer. */ - rolling_buffer_append(&wreq->buffer, folio, 0); + rolling_buffer_append(&wreq->buffer, folio, 0, wreq->gfp); /* Move the submission point forward to allow for write-streaming data * not starting at the front of the page. We don't do write-streaming @@ -749,7 +749,7 @@ static int netfs_write_folio_single(struct netfs_io_request *wreq, /* Attach the folio to the rolling buffer. */ folio_get(folio); - ret = rolling_buffer_append(&wreq->buffer, folio, NETFS_ROLLBUF_PUT_MARK); + ret = rolling_buffer_append(&wreq->buffer, folio, NETFS_ROLLBUF_PUT_MARK, wreq->gfp); if (ret < 0) { folio_put(folio); return ret; diff --git a/include/linux/netfs.h b/include/linux/netfs.h index 1bc120d61c5b..d0b62d53eea9 100644 --- a/include/linux/netfs.h +++ b/include/linux/netfs.h @@ -255,6 +255,7 @@ struct netfs_io_request { unsigned long long cleaned_to; /* Position we've cleaned folios to */ unsigned long long abandon_to; /* Position to abandon folios to */ const struct folio *no_unlock_folio; /* Don't unlock this folio after read */ + gfp_t gfp; /* GFP flags to use */ unsigned int direct_bv_count; /* Number of elements in direct_bv[] */ unsigned int debug_id; unsigned int rsize; /* Maximum read size (0 for none) */ diff --git a/include/linux/rolling_buffer.h b/include/linux/rolling_buffer.h index ac15b1ffdd83..9e5dad29669c 100644 --- a/include/linux/rolling_buffer.h +++ b/include/linux/rolling_buffer.h @@ -43,13 +43,13 @@ struct rolling_buffer_snapshot { #define ROLLBUF_MARK_2 BIT(1) int rolling_buffer_init(struct rolling_buffer *roll, unsigned int rreq_id, - unsigned int direction); -int rolling_buffer_make_space(struct rolling_buffer *roll); + unsigned int direction, gfp_t gfp); +int rolling_buffer_make_space(struct rolling_buffer *roll, gfp_t gfp); ssize_t rolling_buffer_load_from_ra(struct rolling_buffer *roll, struct readahead_control *ractl, struct folio_batch *put_batch); ssize_t rolling_buffer_append(struct rolling_buffer *roll, struct folio *folio, - unsigned int flags); + unsigned int flags, gfp_t gfp); struct folio_queue *rolling_buffer_delete_spent(struct rolling_buffer *roll); void rolling_buffer_clear(struct rolling_buffer *roll); -- cgit v1.2.3 From af24e338bf5dafb80f42baa9a0b9e9b57b1c5d9c Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Fri, 24 Jul 2026 23:20:34 +0300 Subject: Bluetooth: ISO: fix race of kfree vs kref_get_unless_zero hci_conn::iso_data is accessed and modified without lock or RCU. This leads to a race [Task hdev->workqueue] [Task 2] iso_recv iso_conn_put(conn) conn = LOAD hcon->iso_data iso_conn_free(conn) iso_conn_hold_unless_zero(conn) hcon->iso_data = NULL kfree(conn) kref_get_unless_zero(&conn->ref) /* UAF */ and also to races in iso_conn_add() vs. iso_conn_free(). Fix by adding spinlock hci_conn::proto_lock and using it to guard hci_conn::iso_data. Fixes: dc26097bdb86 ("Bluetooth: ISO: Use kref to track lifetime of iso_conn") Signed-off-by: Pauli Virtanen Signed-off-by: Luiz Augusto von Dentz --- include/net/bluetooth/hci_core.h | 4 ++- net/bluetooth/hci_conn.c | 2 ++ net/bluetooth/iso.c | 64 +++++++++++++++++++++++++++++++++------- 3 files changed, 58 insertions(+), 12 deletions(-) (limited to 'include') diff --git a/include/net/bluetooth/hci_core.h b/include/net/bluetooth/hci_core.h index e7133ff87fbf..3df59849dcbe 100644 --- a/include/net/bluetooth/hci_core.h +++ b/include/net/bluetooth/hci_core.h @@ -767,9 +767,11 @@ struct hci_conn { struct dentry *debugfs; struct hci_dev *hdev; + + spinlock_t proto_lock; /* lock guarding protocol data */ void *l2cap_data; void *sco_data; - void *iso_data; + void *iso_data __guarded_by(&proto_lock); struct list_head link_list; struct hci_conn *parent; diff --git a/net/bluetooth/hci_conn.c b/net/bluetooth/hci_conn.c index 1966cd153d97..ebb04badf10c 100644 --- a/net/bluetooth/hci_conn.c +++ b/net/bluetooth/hci_conn.c @@ -1123,6 +1123,8 @@ static struct hci_conn *__hci_conn_add(struct hci_dev *hdev, int type, INIT_DELAYED_WORK(&conn->idle_work, hci_conn_idle); INIT_DELAYED_WORK(&conn->le_conn_timeout, le_conn_timeout); + spin_lock_init(&conn->proto_lock); + atomic_set(&conn->refcnt, 0); hci_dev_hold(hdev); diff --git a/net/bluetooth/iso.c b/net/bluetooth/iso.c index 30de99ba4b30..a461c8a4efed 100644 --- a/net/bluetooth/iso.c +++ b/net/bluetooth/iso.c @@ -109,9 +109,16 @@ static void iso_conn_free(struct kref *ref) BT_DBG("conn %p", conn); if (conn->hcon) { - conn->hcon->iso_data = NULL; - if (!test_and_set_bit(ISO_CONN_DROPPED, conn->flags)) - hci_conn_drop(conn->hcon); + spin_lock(&conn->hcon->proto_lock); + + /* Check we are not racing with iso_conn_add */ + if (conn->hcon->iso_data == conn) { + conn->hcon->iso_data = NULL; + if (!test_and_set_bit(ISO_CONN_DROPPED, conn->flags)) + hci_conn_drop(conn->hcon); + } + + spin_unlock(&conn->hcon->proto_lock); } kfree_skb(conn->rx_skb); @@ -126,7 +133,21 @@ static void iso_conn_put(struct iso_conn *conn) BT_DBG("conn %p refcnt %d", conn, kref_read(&conn->ref)); + /* The following race vs. iso_conn_del() is possible: + * + * 1. conn->hcon != NULL here + * 2. kref_put puts the last reference + * 3. concurrent iso_conn_del() gets iso_conn_hold_unless_zero() -> NULL + * and returns immediately, so conn->hcon is not cleared + * 4. iso_conn_free() dereferences conn->hcon + * + * To avoid UAF in step 4, take RCU before decrementing the refcount. + */ + rcu_read_lock(); + kref_put(&conn->ref, iso_conn_free); + + rcu_read_unlock(); } static struct iso_conn *iso_conn_hold_unless_zero(struct iso_conn *conn) @@ -205,22 +226,28 @@ static void iso_sock_disable_timer(struct sock *sk) /* ---- ISO connections ---- */ static struct iso_conn *iso_conn_add(struct hci_conn *hcon) + __must_hold(&hcon->hdev->lock) { - struct iso_conn *conn = hcon->iso_data; + struct iso_conn *conn; + + spin_lock(&hcon->proto_lock); - conn = iso_conn_hold_unless_zero(conn); + conn = iso_conn_hold_unless_zero(hcon->iso_data); if (conn) { if (!conn->hcon) { iso_conn_lock(conn); conn->hcon = hcon; iso_conn_unlock(conn); } + spin_unlock(&hcon->proto_lock); return conn; } - conn = kzalloc_obj(*conn); - if (!conn) + conn = kzalloc_obj(*conn, GFP_ATOMIC); + if (!conn) { + spin_unlock(&hcon->proto_lock); return NULL; + } kref_init(&conn->ref); spin_lock_init(&conn->lock); @@ -229,6 +256,8 @@ static struct iso_conn *iso_conn_add(struct hci_conn *hcon) conn->hcon = hcon; conn->tx_sn = 0; + spin_unlock(&hcon->proto_lock); + BT_DBG("hcon %p conn %p", hcon, conn); return conn; @@ -269,10 +298,12 @@ static void iso_chan_del(struct sock *sk, int err) static void iso_conn_del(struct hci_conn *hcon, int err) __must_hold(&hcon->hdev->lock) { - struct iso_conn *conn = hcon->iso_data; + struct iso_conn *conn; struct sock *sk; - conn = iso_conn_hold_unless_zero(conn); + spin_lock(&hcon->proto_lock); + conn = iso_conn_hold_unless_zero(hcon->iso_data); + spin_unlock(&hcon->proto_lock); if (!conn) return; @@ -296,10 +327,12 @@ static void iso_conn_del(struct hci_conn *hcon, int err) done: /* No sk access to conn->hcon any more (lock_sock + hdev->lock) */ + spin_lock(&hcon->proto_lock); iso_conn_lock(conn); conn->hcon = NULL; hcon->iso_data = NULL; iso_conn_unlock(conn); + spin_unlock(&hcon->proto_lock); iso_conn_put(conn); } @@ -421,6 +454,8 @@ static int iso_connect_bis(struct sock *sk) iso_pi(sk)->bc_sid = hcon->sid; } + lockdep_assert_held(&hcon->hdev->lock); + conn = iso_conn_add(hcon); if (!conn) { hci_conn_drop(hcon); @@ -524,6 +559,8 @@ static int iso_connect_cis(struct sock *sk) } } + lockdep_assert_held(&hcon->hdev->lock); + conn = iso_conn_add(hcon); if (!conn) { hci_conn_drop(hcon); @@ -855,8 +892,8 @@ static void iso_sock_disconn(struct sock *sk) */ if (bis_sk) { hcon->state = BT_OPEN; - hcon->iso_data = NULL; - iso_pi(sk)->conn->hcon = NULL; + set_bit(ISO_CONN_DROPPED, iso_pi(sk)->conn->flags); + iso_sock_clear_timer(sk); iso_chan_del(sk, bt_to_errno(hcon->abort_reason)); sock_put(bis_sk); @@ -1306,6 +1343,8 @@ static int iso_listen_bis(struct sock *sk) goto unlock; } + lockdep_assert_held(&hcon->hdev->lock); + conn = iso_conn_add(hcon); if (!conn) { hci_conn_drop(hcon); @@ -2591,7 +2630,10 @@ int iso_recv(struct hci_dev *hdev, u16 handle, struct sk_buff *skb, u16 flags) return -ENOENT; } + spin_lock(&hcon->proto_lock); conn = iso_conn_hold_unless_zero(hcon->iso_data); + spin_unlock(&hcon->proto_lock); + hcon = NULL; hci_dev_unlock(hdev); -- cgit v1.2.3 From 2e1d2e65e773d67dab163127f11a47dab0fbca9f Mon Sep 17 00:00:00 2001 From: Damien Le Moal Date: Thu, 9 Jul 2026 10:01:33 +0900 Subject: ata: libata-scsi: terminate deferred commands on time out If a command times out while we have deferred non-NCQ commands waiting to be issued, the SCSI EH task is not immediately woken up as the waiting deferred commands are never issued nor completed, thus leaving the SCSI host in a busy state (shost->host_failed != scsi_host_busy(shost)) which prevents the SCSI EH task from being woken up. Eventually, when the deferred commands also time out, the SCSI EH task is woken up and the timeout processing occurs. Avoid this unnecessary SCSI EH task wake-up additional time by scheduling a retry of all waiting deferred QCs, using the eh_timed_out SCSI host template operation. The function ata_scsi_eh_timed_out() is introduced to implement this operation. However, terminating deferred commands with DID_REQUEUE to force a retry by calling the function ata_scsi_requeue_deferred_qc() may still keep the SCSI host in a busy state because the block layer may immediately re-issue these commands. The solution to this is to schedule libata EH for the port which suffered the command timeout to prevent accepting any new command. ata_scsi_requeue_deferred_qc() is modified to add a call to ata_port_schedule_eh() for this purpose. In addition to this change, ata_scsi_requeue_deferred_qc() is also modified to take a new timedout_scmd scsi command argument which indicates the SCSI command that timed out. With this additional argument, ata_scsi_requeue_deferred_qc() can now also terminate with DID_TIME_OUT any timed out deferred qc, which simplifies ata_scsi_cmd_error_handler(). In this case, ata_scsi_requeue_deferred_qc() returns SCSI_EH_DONE, with this return value propagated back to the ata_scsi_eh_timed_out() operation to indicate to scsi_timeout() that the timed out command was handled and no further processing is needed. For non-timed out deferred qc that need to be retried, ata_scsi_requeue_deferred_qc() returns SCSI_EH_NOT_HANDLED, thus indicating to scsi_timeout() that the timed out command needs to go through the SCSI EH (and libata EH) processing by adding it to the EH work queue with scsi_eh_scmd_add(). One side effect of these changes is that the function atapi_qc_complete() needs to be modified to ensure that a deferred ATAPI command that needs to be retried is completed with DID_REQUEUE instead of the default SAM_STAT_GOOD status, and a command that timed out is completed with DID_TIME_OUT instead of SAM_STAT_CHECK_CONDITION. Fixes: 0ea84089dbf6 ("ata: libata-scsi: avoid Non-NCQ command starvation") Cc: stable@vger.kernel.org Signed-off-by: Damien Le Moal Reviewed-by: Igor Pylypiv Tested-by: Igor Pylypiv Reviewed-by: Niklas Cassel Reviewed-by: Martin K. Petersen --- drivers/ata/libata-eh.c | 29 +++--------- drivers/ata/libata-scsi.c | 112 +++++++++++++++++++++++++++++++++++++++++----- drivers/ata/libata.h | 3 +- include/linux/libata.h | 2 + 4 files changed, 111 insertions(+), 35 deletions(-) (limited to 'include') diff --git a/drivers/ata/libata-eh.c b/drivers/ata/libata-eh.c index 46947ed0c657..c154103d892c 100644 --- a/drivers/ata/libata-eh.c +++ b/drivers/ata/libata-eh.c @@ -658,29 +658,12 @@ int ata_scsi_cmd_error_handler(struct Scsi_Host *host, struct ata_port *ap, set_host_byte(scmd, DID_OK); ata_qc_for_each_raw(ap, qc, i) { - if (qc->scsicmd != scmd) - continue; - if ((qc->flags & ATA_QCFLAG_ACTIVE) || - qc == qc->dev->link->deferred_qc) + if (qc->scsicmd == scmd && + qc->flags & ATA_QCFLAG_ACTIVE) break; } - if (i < ATA_MAX_QUEUE && qc == qc->dev->link->deferred_qc) { - /* - * This is a deferred command that timed out while - * waiting for the command queue to drain. Since the qc - * is not active yet (deferred_qc is still set, so the - * deferred qc work has not issued the command yet), - * simply signal the timeout by finishing the SCSI - * command and clear the deferred qc to prevent the - * deferred qc work from issuing this qc. - */ - WARN_ON_ONCE(qc->flags & ATA_QCFLAG_ACTIVE); - qc->dev->link->deferred_qc = NULL; - cancel_work(&qc->dev->link->deferred_qc_work); - set_host_byte(scmd, DID_TIME_OUT); - scsi_eh_finish_cmd(scmd, &ap->eh_done_q); - } else if (i < ATA_MAX_QUEUE) { + if (i < ATA_MAX_QUEUE) { /* the scmd has an associated qc */ if (!(qc->flags & ATA_QCFLAG_EH)) { /* which hasn't failed yet, timeout */ @@ -956,10 +939,10 @@ static void ata_eh_set_pending(struct ata_port *ap, bool fastdrain) ap->pflags |= ATA_PFLAG_EH_PENDING; /* - * If we have a deferred qc, requeue it so that it is retried once EH - * completes. + * If we have deferred QCs, requeue them so that the SCSI EH task can + * run. */ - ata_scsi_requeue_deferred_qc(ap); + ata_scsi_requeue_deferred_qc(ap, NULL); if (!fastdrain) return; diff --git a/drivers/ata/libata-scsi.c b/drivers/ata/libata-scsi.c index 5868526301a2..02b04de925f9 100644 --- a/drivers/ata/libata-scsi.c +++ b/drivers/ata/libata-scsi.c @@ -1685,26 +1685,80 @@ void ata_scsi_deferred_qc_work(struct work_struct *work) spin_unlock_irqrestore(ap->lock, flags); } -void ata_scsi_requeue_deferred_qc(struct ata_port *ap) +enum scsi_timeout_action ata_scsi_requeue_deferred_qc(struct ata_port *ap, + struct scsi_cmnd *timedout_scmd) { + enum scsi_timeout_action action = SCSI_EH_NOT_HANDLED; + struct ata_queued_cmd *qc; struct ata_link *link; + u32 host_byte; lockdep_assert_held(ap->lock); /* - * If we have a deferred qc when a reset occurs or NCQ commands fail, - * do not try to be smart about what to do with this deferred command - * and simply requeue it by completing it with DID_REQUEUE. + * If we have deferred QCs when a reset, a timeout or an NCQ command + * fails, do not try to be smart about what to do with the deferred + * commands and simply terminate them and let the SCSI layer decide + * what to do. */ ata_for_each_link(link, ap, PMP_FIRST) { - struct ata_queued_cmd *qc = link->deferred_qc; + qc = link->deferred_qc; + if (!qc) + continue; + + /* + * Clear the deferred QC so that the deferred work does not try + * to issue it. + */ + link->deferred_qc = NULL; + cancel_work(&link->deferred_qc_work); + + /* + * We are going to complete some scsi command, either with + * DID_TIME_OUT if the command timed out while waiting for being + * issued, or with DID_REQUEUE if another command timed out or + * we had a failed command. However, the block layer may re-issue + * these commands immediately, keeping the scsi host busy and + * thus preventing the SCSI EH task from running. + * So schedule EH on the port to prevent accepting new commands + * until everything is sorted out with the error or timeout that + * got us here in the first place. Note that we set EH pending + * on the port before calling ata_port_schedule_eh() so that we + * do not reenter this function from ata_eh_set_pending() with + * timedout_scmd being NULL and erroneously retry deferred QCs + * that have timed out on other links. + */ + if (!ata_port_eh_scheduled(ap)) { + ap->pflags |= ATA_PFLAG_EH_PENDING; + ata_port_schedule_eh(ap); + } - if (qc) { - link->deferred_qc = NULL; - cancel_work(&link->deferred_qc_work); - ata_scsi_qc_done(qc, true, DID_REQUEUE << 16); + /* + * If we are being called from scsi_timeout(), then we have a + * non-NULL timedout_scmd. If the timed out command is for a + * deferred QC, terminate that deferred QC with DID_TIME_OUT and + * requeue all other deferred QCs. In this case we need to + * return SCSI_EH_DONE, because the timed out command was + * handled. + * If the timed out command is not for a deferred QC, we need to + * requeue all deferred QCs, and return SCSI_EH_NOT_HANDLED so + * that the timed out command gets added to the EH work queue + * with scsi_eh_scmd_add(), for later handling with libata EH + * ata_scsi_cmd_error_handler(). + * If timedout_scmd is NULL, we simply need to requeue all + * deferred QCs and the return value does not matter as we were + * not called from scsi_timeout(). + */ + if (timedout_scmd && qc->scsicmd == timedout_scmd) { + host_byte = DID_TIME_OUT; + action = SCSI_EH_DONE; + } else { + host_byte = DID_REQUEUE; } + ata_scsi_qc_done(qc, true, host_byte << 16); } + + return action; } static void ata_scsi_schedule_deferred_qc(struct ata_link *link) @@ -1723,13 +1777,41 @@ static void ata_scsi_schedule_deferred_qc(struct ata_link *link) return; if (ata_port_eh_scheduled(ap)) { - ata_scsi_requeue_deferred_qc(ap); + ata_scsi_requeue_deferred_qc(ap, NULL); return; } if (!ap->ops->qc_defer(qc)) queue_work(system_highpri_wq, &link->deferred_qc_work); } +static enum scsi_timeout_action +ata_scsi_retry_deferred_qc(struct ata_port *ap, struct scsi_cmnd *scmd) +{ + enum scsi_timeout_action action; + unsigned long flags; + + spin_lock_irqsave(ap->lock, flags); + action = ata_scsi_requeue_deferred_qc(ap, scmd); + spin_unlock_irqrestore(ap->lock, flags); + + return action; +} + +enum scsi_timeout_action ata_scsi_eh_timed_out(struct scsi_cmnd *scmd) +{ + struct ata_port *ap = ata_shost_to_port(scmd->device->host); + + /* + * ata_scsi_cmd_error_handler() takes care of commands that timed out + * while executing. However, if we have deferred QCs while a timeout + * triggers, we must requeue these commands for retry so that we do not + * unnecessarily delay starting the SCSI EH task until these deferred + * commands also time out. + */ + return ata_scsi_retry_deferred_qc(ap, scmd); +} +EXPORT_SYMBOL_GPL(ata_scsi_eh_timed_out); + static void ata_scsi_qc_complete(struct ata_queued_cmd *qc) { struct ata_link *link = qc->dev->link; @@ -2936,7 +3018,15 @@ static void atapi_qc_complete(struct ata_queued_cmd *qc) if (qc->cdb[0] == ALLOW_MEDIUM_REMOVAL && qc->dev->sdev) qc->dev->sdev->locked = 0; - ata_scsi_qc_done(qc, true, SAM_STAT_CHECK_CONDITION); + if (cmd->result) + ata_scsi_qc_done(qc, false, 0); + else + ata_scsi_qc_done(qc, true, SAM_STAT_CHECK_CONDITION); + return; + } + + if (cmd->result) { + ata_scsi_qc_done(qc, false, 0); return; } diff --git a/drivers/ata/libata.h b/drivers/ata/libata.h index 700627596ce1..8dacc1daabf0 100644 --- a/drivers/ata/libata.h +++ b/drivers/ata/libata.h @@ -180,7 +180,8 @@ enum scsi_qc_status __ata_scsi_queuecmd(struct scsi_cmnd *scmd, struct ata_port *ap) __must_hold(ap->lock); void ata_scsi_deferred_qc_work(struct work_struct *work); -void ata_scsi_requeue_deferred_qc(struct ata_port *ap); +enum scsi_timeout_action ata_scsi_requeue_deferred_qc(struct ata_port *ap, + struct scsi_cmnd *scmd); /* libata-eh.c */ extern unsigned int ata_internal_cmd_timeout(struct ata_device *dev, u8 cmd); diff --git a/include/linux/libata.h b/include/linux/libata.h index 6c0d385b2cde..f10b3287b507 100644 --- a/include/linux/libata.h +++ b/include/linux/libata.h @@ -1153,6 +1153,7 @@ extern int ata_scsi_ioctl(struct scsi_device *dev, unsigned int cmd, #endif extern enum scsi_qc_status ata_scsi_queuecmd(struct Scsi_Host *h, struct scsi_cmnd *cmd); +enum scsi_timeout_action ata_scsi_eh_timed_out(struct scsi_cmnd *cmd); #if IS_REACHABLE(CONFIG_ATA) bool ata_scsi_dma_need_drain(struct request *rq); #else @@ -1464,6 +1465,7 @@ extern const struct attribute_group *ata_common_sdev_groups[]; .ioctl = ata_scsi_ioctl, \ ATA_SCSI_COMPAT_IOCTL \ .queuecommand = ata_scsi_queuecmd, \ + .eh_timed_out = ata_scsi_eh_timed_out, \ .dma_need_drain = ata_scsi_dma_need_drain, \ .this_id = ATA_SHT_THIS_ID, \ .emulated = ATA_SHT_EMULATED, \ -- cgit v1.2.3 From de202d2251bc181c6019b1ad3c0ba8133e5ec68d Mon Sep 17 00:00:00 2001 From: Damien Le Moal Date: Thu, 9 Jul 2026 16:02:23 +0900 Subject: scsi: libsas: terminate deferred commands on time out If a command times out while we have deferred non-NCQ commands waiting to be issued, the SCSI EH task is not immediately woken up as the waiting deferred commands are never issued nor completed, thus leaving the SCSI host in a busy state (shost->host_failed != scsi_host_busy(shost)) which prevents the SCSI EH task from being woken up. Eventually, when the deferred commands also time out, the SCSI EH task is woken up and the timeout processing occurs. Avoid this unnecessary additional SCSI EH wake up time with the same method as implemented in libata-scsi, using the eh_timed_out SCSI host template operation. The function sas_eh_timed_out() implements this operation and executes the function ata_scsi_retry_deferred_qc() for SATA devices. Co-developed-by: Igor Pylypiv Signed-off-by: Igor Pylypiv Fixes: 0ea84089dbf6 ("ata: libata-scsi: avoid Non-NCQ command starvation") Cc: stable@vger.kernel.org Signed-off-by: Damien Le Moal Reviewed-by: John Garry Reviewed-by: Hannes Reinecke Tested-by: Igor Pylypiv Reviewed-by: Niklas Cassel Reviewed-by: Martin K. Petersen --- drivers/ata/libata-scsi.c | 5 +++-- drivers/scsi/libsas/sas_scsi_host.c | 15 +++++++++++++++ include/linux/libata.h | 2 ++ include/scsi/libsas.h | 2 ++ 4 files changed, 22 insertions(+), 2 deletions(-) (limited to 'include') diff --git a/drivers/ata/libata-scsi.c b/drivers/ata/libata-scsi.c index cd64787c4772..d2160ee7ca7d 100644 --- a/drivers/ata/libata-scsi.c +++ b/drivers/ata/libata-scsi.c @@ -1784,8 +1784,8 @@ static void ata_scsi_schedule_deferred_qc(struct ata_link *link) queue_work(system_highpri_wq, &link->deferred_qc_work); } -static enum scsi_timeout_action -ata_scsi_retry_deferred_qc(struct ata_port *ap, struct scsi_cmnd *scmd) +enum scsi_timeout_action ata_scsi_retry_deferred_qc(struct ata_port *ap, + struct scsi_cmnd *scmd) { enum scsi_timeout_action action; unsigned long flags; @@ -1796,6 +1796,7 @@ ata_scsi_retry_deferred_qc(struct ata_port *ap, struct scsi_cmnd *scmd) return action; } +EXPORT_SYMBOL_GPL(ata_scsi_retry_deferred_qc); enum scsi_timeout_action ata_scsi_eh_timed_out(struct scsi_cmnd *scmd) { diff --git a/drivers/scsi/libsas/sas_scsi_host.c b/drivers/scsi/libsas/sas_scsi_host.c index c83282733ec4..97ac3db5dccb 100644 --- a/drivers/scsi/libsas/sas_scsi_host.c +++ b/drivers/scsi/libsas/sas_scsi_host.c @@ -502,6 +502,21 @@ int sas_eh_target_reset_handler(struct scsi_cmnd *cmd) } EXPORT_SYMBOL_GPL(sas_eh_target_reset_handler); +/* + * Handle deferred QCs in case of a command timeout. + * See ata_scsi_eh_timed_out() for details. + */ +enum scsi_timeout_action sas_eh_timed_out(struct scsi_cmnd *cmd) +{ + struct domain_device *dev = cmd_to_domain_dev(cmd); + + if (dev_is_sata(dev)) + return ata_scsi_retry_deferred_qc(dev->sata_dev.ap, cmd); + + return SCSI_EH_NOT_HANDLED; +} +EXPORT_SYMBOL_GPL(sas_eh_timed_out); + /* Try to reset a device */ static int try_to_reset_cmd_device(struct scsi_cmnd *cmd) { diff --git a/include/linux/libata.h b/include/linux/libata.h index f10b3287b507..1827502b9cf2 100644 --- a/include/linux/libata.h +++ b/include/linux/libata.h @@ -1153,6 +1153,8 @@ extern int ata_scsi_ioctl(struct scsi_device *dev, unsigned int cmd, #endif extern enum scsi_qc_status ata_scsi_queuecmd(struct Scsi_Host *h, struct scsi_cmnd *cmd); +enum scsi_timeout_action ata_scsi_retry_deferred_qc(struct ata_port *ap, + struct scsi_cmnd *scmd); enum scsi_timeout_action ata_scsi_eh_timed_out(struct scsi_cmnd *cmd); #if IS_REACHABLE(CONFIG_ATA) bool ata_scsi_dma_need_drain(struct request *rq); diff --git a/include/scsi/libsas.h b/include/scsi/libsas.h index 163f23c92b41..c7017ae76c61 100644 --- a/include/scsi/libsas.h +++ b/include/scsi/libsas.h @@ -705,6 +705,7 @@ void sas_task_abort(struct sas_task *); int sas_eh_abort_handler(struct scsi_cmnd *cmd); int sas_eh_device_reset_handler(struct scsi_cmnd *cmd); int sas_eh_target_reset_handler(struct scsi_cmnd *cmd); +enum scsi_timeout_action sas_eh_timed_out(struct scsi_cmnd *cmd); extern void sas_target_destroy(struct scsi_target *); extern int sas_sdev_init(struct scsi_device *); @@ -743,6 +744,7 @@ void sas_notify_phy_event(struct asd_sas_phy *phy, enum phy_event event, .this_id = -1, \ .eh_device_reset_handler = sas_eh_device_reset_handler, \ .eh_target_reset_handler = sas_eh_target_reset_handler, \ + .eh_timed_out = sas_eh_timed_out, \ .target_destroy = sas_target_destroy, \ .ioctl = sas_ioctl, \ -- cgit v1.2.3 From 6bc85579c3bbb2f088cbac849c5dc2a134dda736 Mon Sep 17 00:00:00 2001 From: Simon Schippers Date: Tue, 28 Jul 2026 11:22:38 +0200 Subject: Revert "ptr_ring: move free-space check into separate helper" This reverts commit fba362c17d9d9211fc51f272156bb84fc23bdf98. __ptr_ring_check_produce() has no users left after reverting commit 1d6e569b7d0c ("tun/tap & vhost-net: avoid ptr_ring tail-drop when a qdisc is present"). Signed-off-by: Simon Schippers Acked-by: Michael S. Tsirkin Link: https://patch.msgid.link/20260728092240.250257-3-simon.schippers@tu-dortmund.de Signed-off-by: Jakub Kicinski --- include/linux/ptr_ring.h | 20 ++------------------ 1 file changed, 2 insertions(+), 18 deletions(-) (limited to 'include') diff --git a/include/linux/ptr_ring.h b/include/linux/ptr_ring.h index c95e891903f0..d2c3629bbe45 100644 --- a/include/linux/ptr_ring.h +++ b/include/linux/ptr_ring.h @@ -96,20 +96,6 @@ static inline bool ptr_ring_full_bh(struct ptr_ring *r) return ret; } -/* Note: callers invoking this in a loop must use a compiler barrier, - * for example cpu_relax(). Callers must hold producer_lock. - */ -static inline int __ptr_ring_check_produce(struct ptr_ring *r) -{ - if (unlikely(!r->size)) - return -EINVAL; - - if (data_race(r->queue[r->producer])) - return -ENOSPC; - - return 0; -} - /* Note: callers invoking this in a loop must use a compiler barrier, * for example cpu_relax(). Callers must hold producer_lock. * Callers are responsible for making sure pointer that is being queued @@ -117,10 +103,8 @@ static inline int __ptr_ring_check_produce(struct ptr_ring *r) */ static inline int __ptr_ring_produce(struct ptr_ring *r, void *ptr) { - int p = __ptr_ring_check_produce(r); - - if (p) - return p; + if (unlikely(!r->size) || data_race(r->queue[r->producer])) + return -ENOSPC; /* Make sure the pointer we are storing points to a valid data. */ /* Pairs with the dependency ordering in __ptr_ring_consume. */ -- cgit v1.2.3 From 8f83be72d9f5ef16c4a908450d0d993e8ec99d34 Mon Sep 17 00:00:00 2001 From: Simon Schippers Date: Tue, 28 Jul 2026 11:22:39 +0200 Subject: Revert "vhost-net: wake queue of tun/tap after ptr_ring consume" This reverts commit baf808fe4fcd35767ab732b4ab2ea80dabfd97a6. There is no netdev queue left to wake after reverting commit 1d6e569b7d0c ("tun/tap & vhost-net: avoid ptr_ring tail-drop when a qdisc is present"). Signed-off-by: Simon Schippers Acked-by: Michael S. Tsirkin Link: https://patch.msgid.link/20260728092240.250257-4-simon.schippers@tu-dortmund.de Signed-off-by: Jakub Kicinski --- drivers/net/tun.c | 23 ----------------------- drivers/vhost/net.c | 21 ++++++--------------- include/linux/if_tun.h | 3 --- 3 files changed, 6 insertions(+), 41 deletions(-) (limited to 'include') diff --git a/drivers/net/tun.c b/drivers/net/tun.c index ec5573f545af..39abc3078097 100644 --- a/drivers/net/tun.c +++ b/drivers/net/tun.c @@ -3787,29 +3787,6 @@ struct ptr_ring *tun_get_tx_ring(struct file *file) } EXPORT_SYMBOL_GPL(tun_get_tx_ring); -/* Callers must hold ring.consumer_lock */ -void tun_wake_queue(struct file *file, int consumed) -{ - struct tun_file *tfile; - struct tun_struct *tun; - - if (file->f_op != &tun_fops) - return; - - tfile = file->private_data; - if (!tfile) - return; - - rcu_read_lock(); - - tun = rcu_dereference(tfile->tun); - if (tun) - __tun_wake_queue(tun, tfile, consumed); - - rcu_read_unlock(); -} -EXPORT_SYMBOL_GPL(tun_wake_queue); - module_init(tun_init); module_exit(tun_cleanup); MODULE_DESCRIPTION(DRV_DESCRIPTION); diff --git a/drivers/vhost/net.c b/drivers/vhost/net.c index 3e72b9c6af0c..6949b704166d 100644 --- a/drivers/vhost/net.c +++ b/drivers/vhost/net.c @@ -176,21 +176,13 @@ static void *vhost_net_buf_consume(struct vhost_net_buf *rxq) return ret; } -static int vhost_net_buf_produce(struct sock *sk, - struct vhost_net_virtqueue *nvq) +static int vhost_net_buf_produce(struct vhost_net_virtqueue *nvq) { - struct file *file = sk->sk_socket->file; struct vhost_net_buf *rxq = &nvq->rxq; rxq->head = 0; - spin_lock(&nvq->rx_ring->consumer_lock); - rxq->tail = __ptr_ring_consume_batched(nvq->rx_ring, rxq->queue, - VHOST_NET_BATCH); - - if (rxq->tail) - tun_wake_queue(file, rxq->tail); - - spin_unlock(&nvq->rx_ring->consumer_lock); + rxq->tail = ptr_ring_consume_batched(nvq->rx_ring, rxq->queue, + VHOST_NET_BATCH); return rxq->tail; } @@ -217,15 +209,14 @@ static int vhost_net_buf_peek_len(void *ptr) return __skb_array_len_with_tag(ptr); } -static int vhost_net_buf_peek(struct sock *sk, - struct vhost_net_virtqueue *nvq) +static int vhost_net_buf_peek(struct vhost_net_virtqueue *nvq) { struct vhost_net_buf *rxq = &nvq->rxq; if (!vhost_net_buf_is_empty(rxq)) goto out; - if (!vhost_net_buf_produce(sk, nvq)) + if (!vhost_net_buf_produce(nvq)) return 0; out: @@ -1013,7 +1004,7 @@ static int peek_head_len(struct vhost_net_virtqueue *rvq, struct sock *sk) unsigned long flags; if (rvq->rx_ring) - return vhost_net_buf_peek(sk, rvq); + return vhost_net_buf_peek(rvq); spin_lock_irqsave(&sk->sk_receive_queue.lock, flags); head = skb_peek(&sk->sk_receive_queue); diff --git a/include/linux/if_tun.h b/include/linux/if_tun.h index 5f3e206c7a73..80166eb62f41 100644 --- a/include/linux/if_tun.h +++ b/include/linux/if_tun.h @@ -22,7 +22,6 @@ struct tun_msg_ctl { #if defined(CONFIG_TUN) || defined(CONFIG_TUN_MODULE) struct socket *tun_get_socket(struct file *); struct ptr_ring *tun_get_tx_ring(struct file *file); -void tun_wake_queue(struct file *file, int consumed); static inline bool tun_is_xdp_frame(void *ptr) { @@ -56,8 +55,6 @@ static inline struct ptr_ring *tun_get_tx_ring(struct file *f) return ERR_PTR(-EINVAL); } -static inline void tun_wake_queue(struct file *f, int consumed) {} - static inline bool tun_is_xdp_frame(void *ptr) { return false; -- cgit v1.2.3 From 0680cbbf39ca61c70be16141b5259f822e7cdb3b Mon Sep 17 00:00:00 2001 From: Boris Burkov Date: Mon, 27 Jul 2026 15:23:30 -0700 Subject: btrfs: trigger cow fixup via dirty_folio() The problem scenario: If we have a folio mmapped shared and then somebody does a dio read with that folio as the read destination, then it is possible that the dio will see a dirty destination page when it starts (and thus skip dirtying and just GUP pin it) but then while it is doing the read, btrfs finishes writing it back and by the endio, the folio is clean. In that case, the dio read must re-dirty the folio with aops->dirty_folio(): btrfs_check_read_bio() |- __iomap_dio_bio_end_io() from btrfs_bio_end_io() |- bio_check_pages_dirty() |- bio_dirty_fn() |- bio_release_pages(bio, true) |- __bio_release_pages(bio, mark_dirty == true) |- folio_lock() |- folio_mark_dirty() |- aops->dirty_folio() |- folio_unlock() A data block normally moves through writeback as follows: TASK folio_lock write clean -> dirty bit + delalloc folio_unlock WRITEBACK for-each-dirty-folio: folio_lock run_delalloc delalloc consumed -> dirty bit + OE submission dirty bit consumed -> writeback bit + OE folio_unlock ENDIO endio OE bytes accounted OE finish writeback -> clean; destroy OE Three critical invariants that this path maintains are: I1. Any dirty block is covered by delalloc xor an ordered extent I2. Any dirty block covered by an OE will be submitted into that OE I3. Any dirty block already submitted into an OE will not be submitted again into the same OE. These ensure that the block will be written exactly once. It is clear that not reserving delalloc for the re-dirty case violates I1. This situation, even without bs < folio_size, has long required btrfs to fixup such dirty pages during writeback with an asynchronous worker that is allowed to do this expensive work and writeback does not proceed for a folio while it is doing this work. Commit 247e743cbe6e ("Btrfs: Use async helpers to deal with pages that have been improperly dirtied") introduced the COW fixup to catch exactly this class at writeback, way back in 2008. Since then, there have been many advances to prevent most of the causes of such re-dirtying and we thought we could get away with removing the annoying cow-fixup in the hope of simplifying writeback for large folio support. Commit b2a9f217ad3f ("btrfs: remove the COW fixup mechanism") Commit 4927b141877c ("btrfs: remove folio ordered flag and subpage bitmap") Since it turns out this assumption was incorrect, as evidenced by the report and attendant reproducers, we must reintroduce the fixup concept. This is of course critically further complicated by bs < folio_size. In that case, rather than just a folio dirty bit, we have a bitmap for the dirty blocks in the folio. And the (also broken) invariant is: I4. folio dirty IFF at least one block bitmap dirty. The original report of a stall on a misinterpreted empty bitmap is exactly evidence of a violation of I4. It is exactly because of bs < folio_size we don't want to simply revert the removal patches. The original fixup was not properly bs < folio_size aware, which motivated removal in the first place. So we wish to build a bs < folio_size aware fixup. One other important detail from the old design, any normal write that happens after a re-dirty but before a fixup is racing with the cow fixup to do the delalloc reservation, therefore it must cancel the fixup state. If it arrives after the reservation exists, it will be a normal dirty overwrite. This critically informs the design in a pretty clear way. fixup requiring re-dirty has folio granularity, while cancellation has delalloc (block) granularity so while we only ever produce fixup in chunks of folios, we must be able to clear it in blocks. Therefore we must track the blocks needing fixup at block granularity. The obvious way to do this is with a new bitmap in btrfs_folio_state, but it is desirable to avoid that if possible. Unfortunately, I don't think it is possible and the reason is subtle and leans on a sort of extreme reproducer, but I think can be explained relatively succinctly. Consider a folio whose two halves will land in different ordered extents (can be accomplished with tricks using nodatasum) and a dio read is running with it as the shared mmap destination. 1. The front half: a. folio comes clean on a normal write b. dio read completes into the folio marking it fixup. c. a write comes for the previous folio for a range extending into this folio, this is a cancellation of the fixup which reserves space. d. writeback runs on the range *not* overlapping the folio. This half remains dirty but is now covered by an OE and is awaiting writeback running on its range to be submitted and finish the OE. 2. The back half: a. the folio is part of an OE that gets far enough along to clear writeback. b. dio read completes into the folio marking it fixup. After this, the folio's front half is dirty in the "normal" sense, it needs to be submitted to the OE waiting for it. It's a cancelled fixup. Meanwhile, the second half is a true fresh fixup. So at this point if we run writeback on this folio, we genuinely can't know what to do without block level information. If we submit it, we submit unreserved dirty from the back half. If we don't, we will never finish the OE waiting for it. So it's either a corruption or a deadlock. Thus, the full high level design picture: - btrfs_data_dirty_folio(): For out of band non-reserving dirties, mark still-clean blocks inside EOF dirty and set their fixup bits (the event carries no range, so every clean block is suspect). Already-dirty blocks are covered or pending and are left alone. - Writeback: skip fixup blocks and enqueue work for them - writepage_fixup(): for each fixup block do the fixup reservation in a worker, after which the blocks can be written back normally. - Typical reserving write paths cancel fixup state for the ranges they cover with btrfs_folio_cancel_fixup() Link: https://lore.kernel.org/linux-btrfs/20260721191152.101118-1-borntraeger@linux.ibm.com/ Assisted-by: LLM Reviewed-by: Qu Wenruo Signed-off-by: Boris Burkov Signed-off-by: David Sterba --- fs/btrfs/btrfs_inode.h | 1 + fs/btrfs/disk-io.c | 7 +- fs/btrfs/extent_io.c | 113 ++++++++++++++++++++++ fs/btrfs/fs.h | 12 +++ fs/btrfs/inode.c | 200 ++++++++++++++++++++++++++++++++++++++- fs/btrfs/subpage.c | 216 ++++++++++++++++++++++++++++++++++++++++++- fs/btrfs/subpage.h | 41 +++++++- include/trace/events/btrfs.h | 35 +++++++ 8 files changed, 613 insertions(+), 12 deletions(-) (limited to 'include') diff --git a/fs/btrfs/btrfs_inode.h b/fs/btrfs/btrfs_inode.h index 7fdc6c3fd066..1082fa92c145 100644 --- a/fs/btrfs/btrfs_inode.h +++ b/fs/btrfs/btrfs_inode.h @@ -600,6 +600,7 @@ int btrfs_prealloc_file_range_trans(struct inode *inode, loff_t actual_len, u64 *alloc_hint); int btrfs_run_delalloc_range(struct btrfs_inode *inode, struct folio *locked_folio, u64 start, u64 end, struct writeback_control *wbc); +void btrfs_queue_writepage_fixup(struct btrfs_inode *inode, struct folio *folio); int btrfs_encoded_io_compression_from_extent(struct btrfs_fs_info *fs_info, int compress_type); int btrfs_encoded_read_regular_fill_pages(struct btrfs_inode *inode, diff --git a/fs/btrfs/disk-io.c b/fs/btrfs/disk-io.c index 36332df9a0f1..6bb70c43a63f 100644 --- a/fs/btrfs/disk-io.c +++ b/fs/btrfs/disk-io.c @@ -1760,6 +1760,8 @@ static int read_backup_root(struct btrfs_fs_info *fs_info, u8 priority) /* helper to cleanup workers */ static void btrfs_stop_all_workers(struct btrfs_fs_info *fs_info) { + if (fs_info->fixup_workers) + destroy_workqueue(fs_info->fixup_workers); btrfs_destroy_workqueue(fs_info->delalloc_workers); btrfs_destroy_workqueue(fs_info->workers); if (fs_info->endio_workers) @@ -1967,6 +1969,9 @@ static int btrfs_init_workqueues(struct btrfs_fs_info *fs_info) fs_info->caching_workers = btrfs_alloc_workqueue(fs_info, "cache", flags, max_active, 0); + fs_info->fixup_workers = + alloc_ordered_workqueue("btrfs-fixup", ordered_flags); + fs_info->endio_workers = alloc_workqueue("btrfs-endio", flags, max_active); fs_info->endio_meta_workers = @@ -1992,7 +1997,7 @@ static int btrfs_init_workqueues(struct btrfs_fs_info *fs_info) fs_info->endio_workers && fs_info->endio_meta_workers && fs_info->endio_write_workers && fs_info->endio_freespace_worker && fs_info->rmw_workers && - fs_info->caching_workers && + fs_info->caching_workers && fs_info->fixup_workers && fs_info->delayed_workers && fs_info->qgroup_rescan_workers && fs_info->discard_ctl.discard_workers)) { return -ENOMEM; diff --git a/fs/btrfs/extent_io.c b/fs/btrfs/extent_io.c index de5785117a47..f032f0858f40 100644 --- a/fs/btrfs/extent_io.c +++ b/fs/btrfs/extent_io.c @@ -1440,6 +1440,115 @@ static bool find_next_delalloc_bitmap(struct folio *folio, return true; } +/* + * Debug checks for fixup selection logic to help ensure the invariants + * we expect for fixup marking hold in practice. + * + * - A dirty block without a fixup bit is covered by delalloc or a running + * ordered extent (it was dirtied by a reserving write path). + * - A block with a fixup bit is never covered by delalloc: every delalloc + * setter holds the folio lock and cancels the fixup state of the blocks + * it covers (btrfs_folio_set_dirty()) before releasing it. + */ +static void debug_check_writepage_fixup(struct btrfs_inode *inode, u64 start, + u32 len, bool needs_fixup) +{ + struct btrfs_ordered_extent *ordered; + bool delalloc; + + if (!IS_ENABLED(CONFIG_BTRFS_DEBUG)) + return; + + delalloc = btrfs_test_range_bit_exists(&inode->io_tree, start, + start + len - 1, EXTENT_DELALLOC); + if (needs_fixup) { + if (unlikely(delalloc)) + DEBUG_WARN("writeback: delalloc and fixup conflict. ino %llu start %llu", + btrfs_ino(inode), start); + } else { + if (delalloc) + return; + + ordered = btrfs_lookup_ordered_range(inode, start, len); + if (unlikely(!ordered)) + DEBUG_WARN("dirty block, no delalloc, fixup, ordered. ino %llu start %llu", + btrfs_ino(inode), start); + else + btrfs_put_ordered_extent(ordered); + } +} + +/* + * Handle folios dirtied without a delalloc reservation, e.g. + * O_DIRECT read into a MAP_SHARED mapping dirtying via set_page_dirty_lock(). + * + * btrfs_data_dirty_folio() records the affected blocks in the fixup bitmap + * and the folio fixup flag and we check them here in writeback. + * + * Don't submit such blocks and queue work for the fixup worker to reserve + * space for them so that they can be submitted properly by writeback. + * + * Return 1 if the folio needed fixup, 0 if not, and a negative error code + * on error. + */ +static noinline_for_stack int writepage_fixup(struct btrfs_inode *inode, + struct folio *folio, + struct btrfs_bio_ctrl *bio_ctrl) +{ + struct btrfs_fs_info *fs_info = inode_to_fs_info(&inode->vfs_inode); + const unsigned int blocks_per_folio = btrfs_blocks_per_folio(fs_info, folio); + const u32 sectorsize = fs_info->sectorsize; + const u64 page_start = folio_pos(folio); + bool found_fixup = false; + unsigned int bit; + + /* + * A folio was dirtied without calling aops->dirty_folio() which we + * explicitly assert is not allowed. + */ + if (unlikely(bitmap_empty(bio_ctrl->submit_bitmap, blocks_per_folio))) { + DEBUG_WARN(); + btrfs_err_rl(fs_info, + "root %lld ino %llu folio %llu is dirty with an empty dirty bitmap", + btrfs_root_id(inode->root), btrfs_ino(inode), + folio_pos(folio)); + return -EUCLEAN; + } + + /* Cheap check on the folio flag. Set iff the fixup bitmap is non-empty. */ + if (likely(!folio_test_fixup_pending(folio))) + return 0; + + for_each_set_bit(bit, bio_ctrl->submit_bitmap, blocks_per_folio) { + const u64 start = page_start + (bit << fs_info->sectorsize_bits); + const bool needs_fixup = btrfs_folio_test_fixup(fs_info, folio, + start, sectorsize); + + debug_check_writepage_fixup(inode, start, sectorsize, needs_fixup); + if (needs_fixup) { + bitmap_clear(bio_ctrl->submit_bitmap, bit, 1); + found_fixup = true; + } + } + if (likely(found_fixup)) { + btrfs_queue_writepage_fixup(inode, folio); + folio_redirty_for_writepage(bio_ctrl->wbc, folio); + if (bitmap_empty(bio_ctrl->submit_bitmap, blocks_per_folio)) { + folio_unlock(folio); + return 1; + } + return 0; + } + /* We should always find fixup if the folio fixup flag was set. */ + DEBUG_WARN(); + btrfs_err_rl(fs_info, + "root %lld ino %llu folio %llu is fixup with an empty fixup bitmap", + btrfs_root_id(inode->root), btrfs_ino(inode), + folio_pos(folio)); + + return -EUCLEAN; +} + /* * Do all of the delayed allocation setup. * @@ -1492,6 +1601,10 @@ static noinline_for_stack int writepage_delalloc(struct btrfs_inode *inode, /* Save the dirty bitmap as our submission bitmap will be a subset of it. */ btrfs_copy_subpage_dirty_bitmap(fs_info, folio, bio_ctrl->submit_bitmap); + ret = writepage_fixup(inode, folio, bio_ctrl); + if (ret) + return ret; + for_each_set_bitrange(start_bit, end_bit, bio_ctrl->submit_bitmap, blocks_per_folio) { u64 start = page_start + (start_bit << fs_info->sectorsize_bits); diff --git a/fs/btrfs/fs.h b/fs/btrfs/fs.h index 7ee9ec2b0efb..f7f343fbe732 100644 --- a/fs/btrfs/fs.h +++ b/fs/btrfs/fs.h @@ -713,6 +713,8 @@ struct btrfs_fs_info { struct btrfs_workqueue *endio_write_workers; struct btrfs_workqueue *endio_freespace_worker; struct btrfs_workqueue *caching_workers; + + struct workqueue_struct *fixup_workers; struct btrfs_workqueue *delayed_workers; struct task_struct *transaction_kthread; @@ -1200,6 +1202,16 @@ static inline void btrfs_wake_unfinished_drop(struct btrfs_fs_info *fs_info) clear_and_wake_up_bit(BTRFS_FS_UNFINISHED_DROPS, &fs_info->flags); } +/* + * We use the folio owner_2 flag to indicate the folio has blocks that were + * dirtied without a space reservation and need the writepage fixup before + * writeback. For bs < folio_size the fixup bitmap tracks the affected + * blocks. + */ +#define folio_test_fixup_pending(folio) folio_test_owner_2(folio) +#define folio_set_fixup_pending(folio) folio_set_owner_2(folio) +#define folio_clear_fixup_pending(folio) folio_clear_owner_2(folio) + #define BTRFS_FS_ERROR(fs_info) (READ_ONCE((fs_info)->fs_error)) #define BTRFS_FS_LOG_CLEANUP_ERROR(fs_info) \ diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c index 04ea10b61bbb..98b31a090626 100644 --- a/fs/btrfs/inode.c +++ b/fs/btrfs/inode.c @@ -2812,6 +2812,163 @@ int btrfs_set_extent_delalloc(struct btrfs_inode *inode, u64 start, u64 end, EXTENT_DELALLOC | extra_bits, cached_state); } +struct btrfs_writepage_fixup { + struct folio *folio; + struct btrfs_inode *inode; + struct work_struct work; +}; + +/* + * Do the real fixup work of reserving space for the blocks a folio's fixup + * state records. Queued by writepage_fixup() when writeback found the bits set. + * + * Since the fixup can be cancelled by a task dirtying with a reservation, we must + * re-check the state of fixup under the folio lock. + */ +static void btrfs_writepage_fixup_worker(struct work_struct *work) +{ + struct btrfs_writepage_fixup *fixup = + container_of(work, struct btrfs_writepage_fixup, work); + struct extent_state *cached_state = NULL; + struct extent_changeset *data_reserved = NULL; + unsigned long delalloc_bitmap[BITS_TO_LONGS(BTRFS_MAX_BLOCKS_PER_FOLIO)] = { 0 }; + struct folio *folio = fixup->folio; + struct btrfs_inode *inode = fixup->inode; + struct btrfs_fs_info *fs_info = inode->root->fs_info; + const unsigned int blocks_per_folio = btrfs_blocks_per_folio(fs_info, folio); + const u32 sectorsize = fs_info->sectorsize; + const u64 page_start = folio_pos(folio); + const u64 page_end = folio_next_pos(folio) - 1; + unsigned int start_bit; + unsigned int end_bit; + unsigned int bit; + bool reserved; + int ret; + + /* + * We would prefer to reserve under the folio lock when we know exactly + * which blocks need a reservation. Unfortunately, since the reservation + * can go into flushers which can go into writeback, which takes folio + * locks, that is not possible. Therefore, we have to reserve for the + * whole folio here, then release what we didn't end up needing once we + * figure it out. + * + * Also note the slightly strange error checking. If fixup is actually + * not set, we don't need to mark an error on the mapping. So hang on to + * ret until after we lock and find out if we actually care. + */ + ret = btrfs_delalloc_reserve_space(inode, &data_reserved, page_start, + folio_size(folio)); + reserved = (ret == 0); +again: + folio_lock(folio); + + if (!folio->mapping || !folio_test_fixup_pending(folio)) { + ret = 0; + goto out; + } + if (ret) + goto out; + + btrfs_lock_extent(&inode->io_tree, page_start, page_end, &cached_state); + + for (bit = 0; bit < blocks_per_folio; bit++) { + struct btrfs_ordered_extent *ordered; + const u64 start = page_start + (bit << fs_info->sectorsize_bits); + + if (test_bit(bit, delalloc_bitmap)) + continue; + if (!btrfs_folio_test_fixup(fs_info, folio, start, sectorsize)) + continue; + /* + * Any task that sets EXTENT_DELALLOC clears the fixup bits + * under the folio lock, so it should be impossible to observe + * both under the lock. Setting delalloc twice would wrongly + * double account the space. + */ + if (IS_ENABLED(CONFIG_BTRFS_DEBUG) && + unlikely(btrfs_test_range_bit_exists(&inode->io_tree, start, + start + sectorsize - 1, + EXTENT_DELALLOC))) { + DEBUG_WARN("fixup worker: delalloc and fixup conflict. ino %llu start %llu", + btrfs_ino(inode), start); + btrfs_folio_clear_fixup(fs_info, folio, start, sectorsize); + continue; + } + ordered = btrfs_lookup_ordered_range(inode, start, sectorsize); + if (ordered) { + trace_btrfs_writepage_fixup_defer(inode, ordered); + btrfs_unlock_extent(&inode->io_tree, page_start, + page_end, &cached_state); + folio_unlock(folio); + btrfs_start_ordered_extent(ordered); + btrfs_put_ordered_extent(ordered); + goto again; + } + ret = btrfs_set_extent_delalloc(inode, start, + start + sectorsize - 1, 0, + &cached_state); + if (ret) + break; + trace_btrfs_writepage_fixup_reserve(inode, start, sectorsize); + btrfs_folio_clear_fixup(fs_info, folio, start, sectorsize); + set_bit(bit, delalloc_bitmap); + } + + btrfs_unlock_extent(&inode->io_tree, page_start, page_end, &cached_state); +out: + if (ret < 0) { + /* Failure here is analogous to failure in writeback. */ + mapping_set_error(folio->mapping, ret); + btrfs_folio_clear_fixup_dirty(fs_info, folio, page_start, + folio_size(folio)); + } + if (reserved) { + btrfs_delalloc_release_extents(inode, folio_size(folio)); + for_each_clear_bitrange(start_bit, end_bit, delalloc_bitmap, + blocks_per_folio) + btrfs_delalloc_release_space(inode, data_reserved, + page_start + (start_bit << fs_info->sectorsize_bits), + (end_bit - start_bit) << fs_info->sectorsize_bits, + true); + } + folio_unlock(folio); + folio_put(folio); + kfree(fixup); + extent_changeset_free(data_reserved); + btrfs_add_delayed_iput(inode); +} + +/* + * Queue space reservation fixup work for blocks dirtied without a space reservation. + * + * Should be used by writeback while holding the folio locked. + * + * If we fail to queue fixup, then the folio state is unchanged and a future + * writeback pass will still see it. + */ +void btrfs_queue_writepage_fixup(struct btrfs_inode *inode, struct folio *folio) +{ + struct btrfs_fs_info *fs_info = inode->root->fs_info; + struct btrfs_writepage_fixup *fixup; + + fixup = kzalloc_obj(*fixup, GFP_NOFS); + if (!fixup) + return; + + /* + * This is called from within extent_write_cache_pages() which + * has successfully done an igrab(). But that will be released at the + * end of the writeback pass. We need to extend it for the worker as well. + */ + ihold(&inode->vfs_inode); + folio_get(folio); + INIT_WORK(&fixup->work, btrfs_writepage_fixup_worker); + fixup->folio = folio; + fixup->inode = inode; + queue_work(fs_info->fixup_workers, &fixup->work); +} + /* * Clear the old accounting flags and set EXTENT_DELALLOC for the range. * @@ -7507,6 +7664,12 @@ static void btrfs_invalidate_folio(struct folio *folio, size_t offset, folio_wait_writeback(folio); wait_subpage_spinlock(folio); + /* + * The invalidated blocks are going away; drop any fixup blocks among + * them, data included, as they have no space reservation. + */ + btrfs_folio_clear_fixup_dirty(fs_info, folio, page_start + offset, length); + /* * For subpage case, we have call sites like * btrfs_punch_hole_lock_range() which passes range not aligned to @@ -10548,6 +10711,41 @@ static const struct file_operations btrfs_dir_file_operations = { .setlease = generic_setlease, }; +/* + * The folio is going dirty without a btrfs delalloc space reservation. + * This requires a fixup before writeback which we might sleep so cannot + * run in this context, so we merely set state on the folio indicating it + * needs fixup before writeback. + * + * Note that there is no range in the input, so the whole folio is marked + * dirty and fixup. + * + * We believe that all callers of dirty_folio either: + * - take the folio lock (e.g. pinned folio release notification). + * - take the pte lock but must be running on a dirty pte which means + * page_mkwrite() ran on it and reserved the space. zap_pte_range() cannot + * race with writeback cleaning the folio because writeback runs + * folio_mkclean() which also uses the pte lock and revokes outstanding + * writable mappings. + * Therefore, an additional folio private lock (a la bfs->lock for all cases, + * not just subpage) is not necessary. + */ +static bool btrfs_data_dirty_folio(struct address_space *mapping, + struct folio *folio) +{ + struct btrfs_inode *inode = BTRFS_I(mapping->host); + struct btrfs_fs_info *fs_info = inode->root->fs_info; + const u64 page_start = folio_pos(folio); + const u64 range_end = min_t(u64, folio_next_pos(folio), + round_up(i_size_read(&inode->vfs_inode), + fs_info->sectorsize)); + + if (range_end > page_start) + btrfs_folio_set_fixup_dirty(fs_info, folio, page_start, + range_end - page_start); + return filemap_dirty_folio(mapping, folio); +} + /* * btrfs doesn't support the bmap operation because swapfiles * use bmap to make a mapping of extents in the file. They assume @@ -10568,7 +10766,7 @@ static const struct address_space_operations btrfs_aops = { .launder_folio = btrfs_launder_folio, .release_folio = btrfs_release_folio, .migrate_folio = btrfs_migrate_folio, - .dirty_folio = filemap_dirty_folio, + .dirty_folio = btrfs_data_dirty_folio, .error_remove_folio = generic_error_remove_folio, .swap_activate = btrfs_swap_activate, .swap_deactivate = btrfs_swap_deactivate, diff --git a/fs/btrfs/subpage.c b/fs/btrfs/subpage.c index 2a9397be8116..27dd677ca687 100644 --- a/fs/btrfs/subpage.c +++ b/fs/btrfs/subpage.c @@ -345,18 +345,57 @@ void btrfs_subpage_clear_uptodate(const struct btrfs_fs_info *fs_info, spin_unlock_irqrestore(&bfs->lock, flags); } +/* + * folio_mark_dirty() for a folio we are dirtying with a space reservation. + * + * Dirtiers without a reservation use btrfs_data_dirty_folio(). + */ +static void btrfs_folio_mark_dirty(struct folio *folio) +{ + struct address_space *mapping = folio_mapping(folio); + + if (!mapping || !mapping->host || !is_data_inode(BTRFS_I(mapping->host))) { + folio_mark_dirty(folio); + return; + } + if (folio_test_reclaim(folio)) + folio_clear_reclaim(folio); + filemap_dirty_folio(mapping, folio); +} + +/* + * The set helper of the dirty ops, so it only runs for folios without a + * fixup bitmap: for those the folio flag is the whole fixup state, and this + * reserving write covers the block, so retire it. Metadata never has the + * flag set and only pays the test. + */ +static void btrfs_folio_mark_dirty_reserved(struct folio *folio) +{ + if (folio_test_fixup_pending(folio)) + folio_clear_fixup_pending(folio); + btrfs_folio_mark_dirty(folio); +} + void btrfs_subpage_set_dirty(const struct btrfs_fs_info *fs_info, struct folio *folio, u64 start, u32 len) { struct btrfs_folio_state *bfs = folio_get_private(folio); - unsigned int start_bit = subpage_calc_start_bit(fs_info, folio, + unsigned int dirty_bit = subpage_calc_start_bit(fs_info, folio, dirty, start, len); + unsigned int fixup_bit = subpage_calc_start_bit(fs_info, folio, + fixup, start, len); + const unsigned int nbits = len >> fs_info->sectorsize_bits; unsigned long flags; spin_lock_irqsave(&bfs->lock, flags); - bitmap_set(bfs->bitmaps, start_bit, len >> fs_info->sectorsize_bits); + bitmap_set(bfs->bitmaps, dirty_bit, nbits); + /* Proper dirtying obviates the need for fixup. */ + bitmap_clear(bfs->bitmaps, fixup_bit, nbits); + if (folio_test_fixup_pending(folio) && + subpage_test_bitmap_all_zero(fs_info, folio, fixup)) + folio_clear_fixup_pending(folio); spin_unlock_irqrestore(&bfs->lock, flags); - folio_mark_dirty(folio); + btrfs_folio_mark_dirty(folio); } static void folio_clear_tags(struct folio *folio) @@ -457,6 +496,172 @@ void btrfs_subpage_clear_writeback(const struct btrfs_fs_info *fs_info, spin_unlock_irqrestore(&bfs->lock, flags); } +void btrfs_subpage_clear_fixup(const struct btrfs_fs_info *fs_info, + struct folio *folio, u64 start, u32 len) +{ + struct btrfs_folio_state *bfs = folio_get_private(folio); + unsigned int start_bit = subpage_calc_start_bit(fs_info, folio, + fixup, start, len); + unsigned long flags; + + spin_lock_irqsave(&bfs->lock, flags); + bitmap_clear(bfs->bitmaps, start_bit, len >> fs_info->sectorsize_bits); + if (subpage_test_bitmap_all_zero(fs_info, folio, fixup)) + folio_clear_fixup_pending(folio); + spin_unlock_irqrestore(&bfs->lock, flags); +} + +/* + * In one pass under bfs->lock, mark every block with a clear dirty bit in the + * range both dirty and needing fixup. + * + * Only called from the dirty_folio callback, which owns the folio-level + * dirty flag; calling folio_mark_dirty() here would recurse. + * + * The folio fixup flag and bits are both set under bfs->lock so that a + * writeback pass observing the new bits also observes the flag. + */ +static void btrfs_subpage_set_fixup_dirty(const struct btrfs_fs_info *fs_info, + struct folio *folio, u64 start, u32 len) +{ + struct btrfs_folio_state *bfs = folio_get_private(folio); + unsigned int dirty_bit = subpage_calc_start_bit(fs_info, folio, + dirty, start, len); + unsigned int fixup_bit = subpage_calc_start_bit(fs_info, folio, + fixup, start, len); + const unsigned int nbits = len >> fs_info->sectorsize_bits; + unsigned long flags; + bool marked = false; + + spin_lock_irqsave(&bfs->lock, flags); + for (unsigned int i = 0; i < nbits; i++) { + if (test_bit(dirty_bit + i, bfs->bitmaps)) + continue; + set_bit(dirty_bit + i, bfs->bitmaps); + set_bit(fixup_bit + i, bfs->bitmaps); + marked = true; + } + if (marked) + folio_set_fixup_pending(folio); + spin_unlock_irqrestore(&bfs->lock, flags); +} + +/* + * Mark the still-clean blocks of a folio dirty and needing fixup, for + * btrfs_data_dirty_folio(). + * + * A subpage block size folio that is not uptodate is left alone: its clean + * blocks may hold content that was never read in, which must not be marked + * dirty. + */ +void btrfs_folio_set_fixup_dirty(const struct btrfs_fs_info *fs_info, + struct folio *folio, u64 start, u32 len) +{ + if (!btrfs_is_subpage(fs_info, folio)) { + if (!folio_test_dirty(folio)) + folio_set_fixup_pending(folio); + return; + } + if (!folio_test_uptodate(folio)) + return; + btrfs_subpage_set_fixup_dirty(fs_info, folio, start, len); +} + +/* + * Drop the fixup blocks inside the range: clear both their fixup and dirty + * bits. + * + * Fixup blocks carry no space reservation, so their fixup and dirty bits + * must be dropped together. Clearing only the fixup bit would leave a + * dirty block without a reservation which is not a valid state. + * + * Returns true if the folio has no dirty blocks left. + */ +static bool btrfs_subpage_clear_fixup_dirty(const struct btrfs_fs_info *fs_info, + struct folio *folio, u64 start, u32 len) +{ + struct btrfs_folio_state *bfs = folio_get_private(folio); + unsigned int dirty_bit = subpage_calc_start_bit(fs_info, folio, + dirty, start, len); + unsigned int fixup_bit = subpage_calc_start_bit(fs_info, folio, + fixup, start, len); + const unsigned int nbits = len >> fs_info->sectorsize_bits; + unsigned long flags; + bool last; + + spin_lock_irqsave(&bfs->lock, flags); + for (unsigned int i = 0; i < nbits; i++) { + if (!test_bit(fixup_bit + i, bfs->bitmaps)) + continue; + clear_bit(fixup_bit + i, bfs->bitmaps); + clear_bit(dirty_bit + i, bfs->bitmaps); + } + if (subpage_test_bitmap_all_zero(fs_info, folio, fixup)) + folio_clear_fixup_pending(folio); + last = subpage_test_bitmap_all_zero(fs_info, folio, dirty); + spin_unlock_irqrestore(&bfs->lock, flags); + return last; +} + +/* + * Drop the fixup blocks inside the range, for callers discarding their data: + * btrfs_invalidate_folio() and the writepage fixup worker's error path. + * + * Callers that have just reserved space for a block want + * btrfs_folio_clear_fixup() instead - there the block stays dirty and gets + * written. + * + * The range can be byte-granular (an unaligned truncate through + * btrfs_invalidate_folio()); only blocks fully inside it are dropped, as a + * partially covered block still holds live data outside the range. For + * single-block folios the folio flag is the fixup state, so it is dropped + * only when the range covers the whole folio. + */ +void btrfs_folio_clear_fixup_dirty(const struct btrfs_fs_info *fs_info, + struct folio *folio, u64 start, u32 len) +{ + u64 aligned_start; + u64 aligned_end; + + /* The folio flag is set whenever any fixup bitmap bit is. */ + if (!folio_test_fixup_pending(folio)) + return; + if (!btrfs_is_subpage(fs_info, folio)) { + if (start <= folio_pos(folio) && + start + len >= folio_next_pos(folio)) { + folio_clear_fixup_pending(folio); + folio_clear_dirty_for_io(folio); + } + return; + } + btrfs_subpage_clamp_range(folio, &start, &len); + aligned_start = round_up(start, fs_info->sectorsize); + aligned_end = round_down(start + len, fs_info->sectorsize); + if (aligned_end <= aligned_start) + return; + if (btrfs_subpage_clear_fixup_dirty(fs_info, folio, aligned_start, + aligned_end - aligned_start)) + folio_clear_dirty_for_io(folio); +} + +bool btrfs_folio_test_fixup(const struct btrfs_fs_info *fs_info, + struct folio *folio, u64 start, u32 len) +{ + if (!btrfs_is_subpage(fs_info, folio)) + return folio_test_fixup_pending(folio); + return btrfs_subpage_test_fixup(fs_info, folio, start, len); +} + +void btrfs_folio_clear_fixup(const struct btrfs_fs_info *fs_info, + struct folio *folio, u64 start, u32 len) +{ + if (!btrfs_is_subpage(fs_info, folio)) { + folio_clear_fixup_pending(folio); + return; + } + btrfs_subpage_clear_fixup(fs_info, folio, start, len); +} + /* * Unlike set/clear which is dependent on each page status, for test all bits * are tested in the same way. @@ -480,6 +685,7 @@ bool btrfs_subpage_test_##name(const struct btrfs_fs_info *fs_info, \ IMPLEMENT_BTRFS_SUBPAGE_TEST_OP(uptodate); IMPLEMENT_BTRFS_SUBPAGE_TEST_OP(dirty); IMPLEMENT_BTRFS_SUBPAGE_TEST_OP(writeback); +IMPLEMENT_BTRFS_SUBPAGE_TEST_OP(fixup); /* * Note that, in selftests (extent-io-tests), we can have empty fs_info passed @@ -571,8 +777,8 @@ bool btrfs_meta_folio_test_##name(struct folio *folio, const struct extent_buffe } IMPLEMENT_BTRFS_PAGE_OPS(uptodate, folio_mark_uptodate, folio_clear_uptodate, folio_test_uptodate); -IMPLEMENT_BTRFS_PAGE_OPS(dirty, folio_mark_dirty, folio_clear_dirty_for_io, - folio_test_dirty); +IMPLEMENT_BTRFS_PAGE_OPS(dirty, btrfs_folio_mark_dirty_reserved, + folio_clear_dirty_for_io, folio_test_dirty); IMPLEMENT_BTRFS_PAGE_OPS(writeback, folio_start_writeback, folio_end_writeback, folio_test_writeback); diff --git a/fs/btrfs/subpage.h b/fs/btrfs/subpage.h index c6d7394e6418..9aceba93c818 100644 --- a/fs/btrfs/subpage.h +++ b/fs/btrfs/subpage.h @@ -14,15 +14,15 @@ struct folio; /* * Extra info for subpage bitmap. * - * For subpage we pack all uptodate/dirty/writeback bitmaps into + * For subpage we pack all uptodate/dirty/writeback/fixup bitmaps into * one larger bitmap. * * This structure records how they are organized in the bitmap: * - * /- uptodate /- dirty /- writeback - * | | | - * v v v - * |u|u|u|u|........|u|u|d|d|.......|d|d|w|w|.......|w|w| + * /- uptodate /- dirty /- writeback /- fixup + * | | | | + * v v v v + * |u|u|u|u|........|u|u|d|d|.......|d|d|w|w|.....|w|w|f|f|.....|f|f| * |< sectors_per_page >| * * Unlike regular macro-like enums, here we do not go upper-case names, as @@ -40,6 +40,14 @@ enum { */ btrfs_bitmap_nr_writeback, + /* + * Blocks dirtied by the dirty_folio callback instead of a reserving + * write path (e.g. set_page_dirty_lock() on a GUP pin). They have + * no space reservation and need the writepage fixup before they can + * be submitted. + */ + btrfs_bitmap_nr_fixup, + btrfs_bitmap_nr_max }; @@ -165,6 +173,29 @@ DECLARE_BTRFS_SUBPAGE_OPS(uptodate); DECLARE_BTRFS_SUBPAGE_OPS(dirty); DECLARE_BTRFS_SUBPAGE_OPS(writeback); +/* + * Fixup bit helpers. + * + * The fixup bit is data-only and has no plain set helper (setting happens + * together with dirtying in btrfs_subpage_set_fixup_dirty()), so it does not + * go through DECLARE_BTRFS_SUBPAGE_OPS(). For single-block folios the + * folio_*_fixup_pending() flag takes the place of the bitmap. + */ +void btrfs_subpage_clear_fixup(const struct btrfs_fs_info *fs_info, + struct folio *folio, u64 start, u32 len); +bool btrfs_subpage_test_fixup(const struct btrfs_fs_info *fs_info, + struct folio *folio, u64 start, u32 len); +bool btrfs_folio_test_fixup(const struct btrfs_fs_info *fs_info, + struct folio *folio, u64 start, u32 len); +void btrfs_folio_set_fixup_dirty(const struct btrfs_fs_info *fs_info, + struct folio *folio, u64 start, u32 len); +/* For a block that just got its space reserved; it stays dirty. */ +void btrfs_folio_clear_fixup(const struct btrfs_fs_info *fs_info, + struct folio *folio, u64 start, u32 len); +/* For callers discarding the data; clears the dirty bits too. */ +void btrfs_folio_clear_fixup_dirty(const struct btrfs_fs_info *fs_info, + struct folio *folio, u64 start, u32 len); + /* * Helper for error cleanup, where a folio will have its dirty flag cleared, * with writeback started and finished. diff --git a/include/trace/events/btrfs.h b/include/trace/events/btrfs.h index 4c5c47c5edb7..6c1438f6a4d3 100644 --- a/include/trace/events/btrfs.h +++ b/include/trace/events/btrfs.h @@ -689,6 +689,41 @@ DEFINE_EVENT(btrfs__ordered_extent, btrfs_ordered_extent_lookup_first, TP_ARGS(inode, ordered) ); +/* + * The writepage fixup worker deferred a block because this still-running + * ordered extent covers it. + */ +DEFINE_EVENT(btrfs__ordered_extent, btrfs_writepage_fixup_defer, + + TP_PROTO(const struct btrfs_inode *inode, + const struct btrfs_ordered_extent *ordered), + + TP_ARGS(inode, ordered) +); + +/* The writepage fixup worker reserved space for a block and set delalloc. */ +TRACE_EVENT(btrfs_writepage_fixup_reserve, + + TP_PROTO(const struct btrfs_inode *inode, u64 start, u32 len), + + TP_ARGS(inode, start, len), + + TP_STRUCT__entry_btrfs( + __field( u64, ino ) + __field( u64, start ) + __field( u32, len ) + ), + + TP_fast_assign_btrfs(inode->root->fs_info, + __entry->ino = btrfs_ino(inode); + __entry->start = start; + __entry->len = len; + ), + + TP_printk_btrfs("ino=%llu start=%llu len=%u", + __entry->ino, __entry->start, __entry->len) +); + DEFINE_EVENT(btrfs__ordered_extent, btrfs_ordered_extent_split, TP_PROTO(const struct btrfs_inode *inode, -- cgit v1.2.3 From b5be879519291f139fa7b365fd0dbc84710e4919 Mon Sep 17 00:00:00 2001 From: Mathieu Dubois-Briand Date: Fri, 24 Jul 2026 23:34:04 +0100 Subject: nvmem: layouts: Add fixed-layout driver Current implementation isn't working well when device tree nodes have a phandle on a fixed-layout nvmem node. As the fixed layout is handled in nvmem core, no driver is ever associated with the layout, and the device consumer driver probe is deferred indefinitely. Remove the specific handling of fixed-layout and add a layout driver. This makes the fixed-layout similar to all other layouts, fixing the whole issue. Fixes: fc29fd821d9a ("nvmem: core: Rework layouts to become regular devices") Cc: stable@vger.kernel.org Reviewed-by: Miquel Raynal Signed-off-by: Mathieu Dubois-Briand Signed-off-by: Srinivas Kandagatla Link: https://patch.msgid.link/20260724223404.629248-3-srini@kernel.org Signed-off-by: Greg Kroah-Hartman --- MAINTAINERS | 5 ++++ drivers/nvmem/core.c | 24 ++------------- drivers/nvmem/internals.h | 2 ++ drivers/nvmem/layouts.c | 11 ------- drivers/nvmem/layouts/Makefile | 1 + drivers/nvmem/layouts/fixed-layout.c | 58 ++++++++++++++++++++++++++++++++++++ include/linux/nvmem-provider.h | 6 ++++ 7 files changed, 74 insertions(+), 33 deletions(-) create mode 100644 drivers/nvmem/layouts/fixed-layout.c (limited to 'include') diff --git a/MAINTAINERS b/MAINTAINERS index 716acfc3d7c1..1b044ddc68f9 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -10134,6 +10134,11 @@ F: drivers/base/firmware_loader/ F: rust/kernel/firmware.rs F: include/linux/firmware.h +FIXED-LAYOUT NVMEM LAYOUT DRIVER +M: Mathieu Dubois-Briand +S: Maintained +F: drivers/nvmem/layouts/fixed-layout.c + FLEXTIMER FTM-QUADDEC DRIVER M: Patrick Havelange L: linux-iio@vger.kernel.org diff --git a/drivers/nvmem/core.c b/drivers/nvmem/core.c index e871181751f3..2f971b059704 100644 --- a/drivers/nvmem/core.c +++ b/drivers/nvmem/core.c @@ -786,7 +786,7 @@ static int nvmem_validate_keepouts(struct nvmem_device *nvmem) return 0; } -static int nvmem_add_cells_from_dt(struct nvmem_device *nvmem, struct device_node *np) +int nvmem_add_cells_from_dt(struct nvmem_device *nvmem, struct device_node *np) { struct device *dev = &nvmem->dev; const __be32 *addr; @@ -834,29 +834,13 @@ static int nvmem_add_cells_from_dt(struct nvmem_device *nvmem, struct device_nod return 0; } +EXPORT_SYMBOL_GPL(nvmem_add_cells_from_dt); static int nvmem_add_cells_from_legacy_of(struct nvmem_device *nvmem) { return nvmem_add_cells_from_dt(nvmem, nvmem->dev.of_node); } -static int nvmem_add_cells_from_fixed_layout(struct nvmem_device *nvmem) -{ - struct device_node *layout_np; - int err = 0; - - layout_np = of_nvmem_layout_get_container(nvmem); - if (!layout_np) - return 0; - - if (of_device_is_compatible(layout_np, "fixed-layout")) - err = nvmem_add_cells_from_dt(nvmem, layout_np); - - of_node_put(layout_np); - - return err; -} - int nvmem_layout_register(struct nvmem_layout *layout) { int ret; @@ -1005,10 +989,6 @@ struct nvmem_device *nvmem_register(const struct nvmem_config *config) goto err_remove_cells; } - rval = nvmem_add_cells_from_fixed_layout(nvmem); - if (rval) - goto err_remove_cells; - dev_dbg(&nvmem->dev, "Registering nvmem device %s\n", config->name); rval = device_add(&nvmem->dev); diff --git a/drivers/nvmem/internals.h b/drivers/nvmem/internals.h index 18fed57270e5..f6d452b6a28f 100644 --- a/drivers/nvmem/internals.h +++ b/drivers/nvmem/internals.h @@ -35,6 +35,8 @@ struct nvmem_device { bool sysfs_cells_populated; }; +int nvmem_add_cells_from_dt(struct nvmem_device *nvmem, struct device_node *np); + #if IS_ENABLED(CONFIG_OF) int nvmem_layout_bus_register(void); void nvmem_layout_bus_unregister(void); diff --git a/drivers/nvmem/layouts.c b/drivers/nvmem/layouts.c index b90584e1b99e..07a34be9669c 100644 --- a/drivers/nvmem/layouts.c +++ b/drivers/nvmem/layouts.c @@ -125,11 +125,6 @@ static int nvmem_layout_create_device(struct nvmem_device *nvmem, return 0; } -static const struct of_device_id of_nvmem_layout_skip_table[] = { - { .compatible = "fixed-layout", }, - {} -}; - static int nvmem_layout_bus_populate(struct nvmem_device *nvmem, struct device_node *layout_dn) { @@ -142,12 +137,6 @@ static int nvmem_layout_bus_populate(struct nvmem_device *nvmem, return 0; } - /* Fixed layouts are parsed manually somewhere else for now */ - if (of_match_node(of_nvmem_layout_skip_table, layout_dn)) { - pr_debug("%s() - skipping %pOF node\n", __func__, layout_dn); - return 0; - } - if (of_node_check_flag(layout_dn, OF_POPULATED_BUS)) { pr_debug("%s() - skipping %pOF, already populated\n", __func__, layout_dn); diff --git a/drivers/nvmem/layouts/Makefile b/drivers/nvmem/layouts/Makefile index 4940c9db0665..dd6c6c70b1a9 100644 --- a/drivers/nvmem/layouts/Makefile +++ b/drivers/nvmem/layouts/Makefile @@ -3,6 +3,7 @@ # Makefile for nvmem layouts. # +obj-$(CONFIG_NVMEM_LAYOUTS) += fixed-layout.o obj-$(CONFIG_NVMEM_LAYOUT_SL28_VPD) += sl28vpd.o obj-$(CONFIG_NVMEM_LAYOUT_ONIE_TLV) += onie-tlv.o obj-$(CONFIG_NVMEM_LAYOUT_U_BOOT_ENV) += u-boot-env.o diff --git a/drivers/nvmem/layouts/fixed-layout.c b/drivers/nvmem/layouts/fixed-layout.c new file mode 100644 index 000000000000..635d448b3dd2 --- /dev/null +++ b/drivers/nvmem/layouts/fixed-layout.c @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright 2026 Bootlin + * + * Authors: Mathieu Dubois-Briand + */ + +#include +#include + +#include "../internals.h" + +static int fixed_layout_add_cells(struct nvmem_layout *layout) +{ + struct device_node *np; + int ret; + + np = of_nvmem_layout_get_container(layout->nvmem); + if (!np) + return -ENOENT; + + ret = nvmem_add_cells_from_dt(layout->nvmem, np); + of_node_put(np); + + return ret; +} + +static int fixed_layout_probe(struct nvmem_layout *layout) +{ + layout->add_cells = fixed_layout_add_cells; + + return nvmem_layout_register(layout); +} + +static void fixed_layout_remove(struct nvmem_layout *layout) +{ + nvmem_layout_unregister(layout); +} + +static const struct of_device_id fixed_layout_of_match_table[] = { + { .compatible = "fixed-layout", }, + {}, +}; + +static struct nvmem_layout_driver fixed_layout_layout = { + .driver = { + .name = "fixed-layout", + .of_match_table = fixed_layout_of_match_table, + }, + .probe = fixed_layout_probe, + .remove = fixed_layout_remove, +}; +module_nvmem_layout_driver(fixed_layout_layout); + +MODULE_AUTHOR("Mathieu Dubois-Briand"); +MODULE_LICENSE("GPL"); +MODULE_DEVICE_TABLE(of, fixed_layout_of_match_table); +MODULE_DESCRIPTION("NVMEM fixed-layout driver"); diff --git a/include/linux/nvmem-provider.h b/include/linux/nvmem-provider.h index f3b13da78aac..6063fe5b7784 100644 --- a/include/linux/nvmem-provider.h +++ b/include/linux/nvmem-provider.h @@ -214,6 +214,12 @@ static inline int nvmem_layout_register(struct nvmem_layout *layout) static inline void nvmem_layout_unregister(struct nvmem_layout *layout) {} +static inline int nvmem_add_cells_from_dt(struct nvmem_device *nvmem, + struct device_node *np) +{ + return -EOPNOTSUPP; +} + #endif /* CONFIG_NVMEM */ #if IS_ENABLED(CONFIG_NVMEM) && IS_ENABLED(CONFIG_OF) -- cgit v1.2.3 From 992eb0dfbc4f594d60ae57974efa835bfac34d1e Mon Sep 17 00:00:00 2001 From: Bard Liao Date: Thu, 30 Jul 2026 09:25:18 +0800 Subject: ASoC/soundwire: Intel: reset the PCMSyCM registers in hda_sdw_bpt_close MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Resetting the PCMSyCM registers is required for Intel SoundWire stream. The same procedure is done in sdw_hda_dai_hw_params() for the normal SoundWire stream, too. Signed-off-by: Bard Liao Reviewed-by: Péter Ujfalusi Reviewed-by: Liam Girdwood Reviewed-by: Pierre-Louis Bossart Acked-by: Vinod Koul Link: https://patch.msgid.link/20260730012518.2180906-1-yung-chuan.liao@linux.intel.com Signed-off-by: Mark Brown --- drivers/soundwire/intel_ace2x.c | 4 +++- include/sound/hda-sdw-bpt.h | 5 +++-- sound/soc/sof/intel/hda-sdw-bpt.c | 31 ++++++++++++++++++++++++++++--- 3 files changed, 34 insertions(+), 6 deletions(-) (limited to 'include') diff --git a/drivers/soundwire/intel_ace2x.c b/drivers/soundwire/intel_ace2x.c index b37933efac5d..63f131d5682b 100644 --- a/drivers/soundwire/intel_ace2x.c +++ b/drivers/soundwire/intel_ace2x.c @@ -261,6 +261,7 @@ static int intel_ace2x_bpt_open_stream(struct sdw_intel *sdw, struct sdw_slave * __func__, str_read_write(command), ret); ret1 = hda_sdw_bpt_close(cdns->dev->parent, /* PCI device */ + sdw->instance, sdw->bpt_ctx.bpt_tx_stream, &sdw->bpt_ctx.dmab_tx_bdl, sdw->bpt_ctx.bpt_rx_stream, &sdw->bpt_ctx.dmab_rx_bdl); if (ret1 < 0) @@ -295,7 +296,8 @@ static void intel_ace2x_bpt_close_stream(struct sdw_intel *sdw, struct sdw_slave struct sdw_cdns *cdns = &sdw->cdns; int ret; - ret = hda_sdw_bpt_close(cdns->dev->parent /* PCI device */, sdw->bpt_ctx.bpt_tx_stream, + ret = hda_sdw_bpt_close(cdns->dev->parent /* PCI device */, sdw->instance, + sdw->bpt_ctx.bpt_tx_stream, &sdw->bpt_ctx.dmab_tx_bdl, sdw->bpt_ctx.bpt_rx_stream, &sdw->bpt_ctx.dmab_rx_bdl); if (ret < 0) diff --git a/include/sound/hda-sdw-bpt.h b/include/sound/hda-sdw-bpt.h index 9b654c31829a..e24a549f7d49 100644 --- a/include/sound/hda-sdw-bpt.h +++ b/include/sound/hda-sdw-bpt.h @@ -27,7 +27,7 @@ int hda_sdw_bpt_send_async(struct device *dev, struct hdac_ext_stream *bpt_tx_st int hda_sdw_bpt_wait(struct device *dev, struct hdac_ext_stream *bpt_tx_stream, struct hdac_ext_stream *bpt_rx_stream); -int hda_sdw_bpt_close(struct device *dev, struct hdac_ext_stream *bpt_tx_stream, +int hda_sdw_bpt_close(struct device *dev, int link_id, struct hdac_ext_stream *bpt_tx_stream, struct snd_dma_buffer *dmab_tx_bdl, struct hdac_ext_stream *bpt_rx_stream, struct snd_dma_buffer *dmab_rx_bdl); @@ -58,7 +58,8 @@ static inline int hda_sdw_bpt_wait(struct device *dev, struct hdac_ext_stream *b return -EOPNOTSUPP; } -static inline int hda_sdw_bpt_close(struct device *dev, struct hdac_ext_stream *bpt_tx_stream, +static inline int hda_sdw_bpt_close(struct device *dev, int link_id, + struct hdac_ext_stream *bpt_tx_stream, struct snd_dma_buffer *dmab_tx_bdl, struct hdac_ext_stream *bpt_rx_stream, struct snd_dma_buffer *dmab_rx_bdl) diff --git a/sound/soc/sof/intel/hda-sdw-bpt.c b/sound/soc/sof/intel/hda-sdw-bpt.c index 728ffe7ae54d..7351a0870725 100644 --- a/sound/soc/sof/intel/hda-sdw-bpt.c +++ b/sound/soc/sof/intel/hda-sdw-bpt.c @@ -322,7 +322,8 @@ int hda_sdw_bpt_open(struct device *dev, int link_id, struct hdac_ext_stream **b __func__, ret); close: - ret1 = hda_sdw_bpt_close(dev, *bpt_tx_stream, dmab_tx_bdl, *bpt_rx_stream, dmab_rx_bdl); + ret1 = hda_sdw_bpt_close(dev, link_id, *bpt_tx_stream, dmab_tx_bdl, + *bpt_rx_stream, dmab_rx_bdl); if (ret1 < 0) dev_err(dev, "%s: hda_sdw_bpt_close failed: %d\n", __func__, ret1); @@ -447,14 +448,38 @@ dma_disable: } EXPORT_SYMBOL_NS(hda_sdw_bpt_wait, "SND_SOC_SOF_INTEL_HDA_SDW_BPT"); -int hda_sdw_bpt_close(struct device *dev, struct hdac_ext_stream *bpt_tx_stream, +int hda_sdw_bpt_close(struct device *dev, int link_id, struct hdac_ext_stream *bpt_tx_stream, struct snd_dma_buffer *dmab_tx_bdl, struct hdac_ext_stream *bpt_rx_stream, struct snd_dma_buffer *dmab_rx_bdl) { + struct snd_sof_dev *sdev = dev_get_drvdata(dev); int ret; int ret1; - ret = hda_sdw_bpt_dma_deprepare(dev, bpt_rx_stream, dmab_rx_bdl); + /* + * In the case of SoundWire we need to reset the PCMSyCM registers. + * Need to continue depreparing the DMA buffers even if this fails. + */ + ret = hdac_bus_eml_sdw_map_stream_ch(sof_to_bus(sdev), link_id, + 0, /* PDI0 */ + 0, 0, SNDRV_PCM_STREAM_PLAYBACK); + if (ret < 0) + dev_err(dev, "%s: hdac_bus_eml_sdw_map_stream_ch failed %d for PDI0\n", + __func__, ret); + + ret1 = hdac_bus_eml_sdw_map_stream_ch(sof_to_bus(sdev), link_id, + 1, /* PDI1 */ + 0, 0, SNDRV_PCM_STREAM_CAPTURE); + if (ret1 < 0) { + dev_err(dev, "%s: hdac_bus_eml_sdw_map_stream_ch failed %d for PDI1\n", + __func__, ret1); + if (!ret) + ret = ret1; + } + + ret1 = hda_sdw_bpt_dma_deprepare(dev, bpt_rx_stream, dmab_rx_bdl); + if (!ret) + ret = ret1; ret1 = hda_sdw_bpt_dma_deprepare(dev, bpt_tx_stream, dmab_tx_bdl); if (!ret) -- cgit v1.2.3 From 712a6f545c359b427daa9a5a782e30d2f8331e25 Mon Sep 17 00:00:00 2001 From: Jozsef Kadlecsik Date: Thu, 30 Jul 2026 20:38:50 +0200 Subject: netfilter: ipset: switch ext_size to atomic64_t The hash types do not acquire set->lock, they use 'region locking' where only part of the hash table is locked. Parallel inserts and deletes are possible and CPUs can race on ->ext_size update. Switch to atomic64_t. This leaves another bug unresolved: there still can be a race on comment extension re-init. This will be handled in a later commit when converting to rhashtable backend. Fixes: f66ee0410b1c ("netfilter: ipset: Fix "INFO: rcu detected stall in hash_xxx" reports") Signed-off-by: Jozsef Kadlecsik Signed-off-by: Florian Westphal Signed-off-by: Pablo Neira Ayuso --- include/linux/netfilter/ipset/ip_set.h | 2 +- net/netfilter/ipset/ip_set_bitmap_gen.h | 4 ++-- net/netfilter/ipset/ip_set_core.c | 6 +++--- net/netfilter/ipset/ip_set_hash_gen.h | 2 +- net/netfilter/ipset/ip_set_list_set.c | 4 ++-- 5 files changed, 9 insertions(+), 9 deletions(-) (limited to 'include') diff --git a/include/linux/netfilter/ipset/ip_set.h b/include/linux/netfilter/ipset/ip_set.h index b98331572ad2..cadae9b2578f 100644 --- a/include/linux/netfilter/ipset/ip_set.h +++ b/include/linux/netfilter/ipset/ip_set.h @@ -273,7 +273,7 @@ struct ip_set { /* Number of elements (vs timeout) */ u32 elements; /* Size of the dynamic extensions (vs timeout) */ - size_t ext_size; + atomic64_t ext_size; /* Element data size */ size_t dsize; /* Offsets to extensions in elements */ diff --git a/net/netfilter/ipset/ip_set_bitmap_gen.h b/net/netfilter/ipset/ip_set_bitmap_gen.h index bb9b5bed10e1..226fdf17b683 100644 --- a/net/netfilter/ipset/ip_set_bitmap_gen.h +++ b/net/netfilter/ipset/ip_set_bitmap_gen.h @@ -77,7 +77,7 @@ mtype_flush(struct ip_set *set) mtype_ext_cleanup(set); bitmap_zero(map->members, map->elements); set->elements = 0; - set->ext_size = 0; + atomic64_set(&set->ext_size, 0); } /* Calculate the actual memory size of the set data */ @@ -93,7 +93,7 @@ mtype_head(struct ip_set *set, struct sk_buff *skb) { const struct mtype *map = set->data; struct nlattr *nested; - size_t memsize = mtype_memsize(map, set->dsize) + set->ext_size; + size_t memsize = mtype_memsize(map, set->dsize) + atomic64_read(&set->ext_size); nested = nla_nest_start(skb, IPSET_ATTR_DATA); if (!nested) diff --git a/net/netfilter/ipset/ip_set_core.c b/net/netfilter/ipset/ip_set_core.c index 6cfad152d7d1..822a53a7f502 100644 --- a/net/netfilter/ipset/ip_set_core.c +++ b/net/netfilter/ipset/ip_set_core.c @@ -350,7 +350,7 @@ ip_set_init_comment(struct ip_set *set, struct ip_set_comment *comment, size_t len = ext->comment ? strlen(ext->comment) : 0; if (unlikely(c)) { - set->ext_size -= sizeof(*c) + strlen(c->str) + 1; + atomic64_sub(sizeof(*c) + strlen(c->str) + 1, &set->ext_size); rcu_assign_pointer(comment->c, NULL); kfree_rcu(c, rcu); } @@ -362,7 +362,7 @@ ip_set_init_comment(struct ip_set *set, struct ip_set_comment *comment, if (unlikely(!c)) return; strscpy(c->str, ext->comment, len + 1); - set->ext_size += sizeof(*c) + strlen(c->str) + 1; + atomic64_add(sizeof(*c) + strlen(c->str) + 1, &set->ext_size); rcu_assign_pointer(comment->c, c); } EXPORT_SYMBOL_GPL(ip_set_init_comment); @@ -392,7 +392,7 @@ ip_set_comment_free(struct ip_set *set, void *ptr) c = rcu_dereference_protected(comment->c, 1); if (unlikely(!c)) return; - set->ext_size -= sizeof(*c) + strlen(c->str) + 1; + atomic64_sub(sizeof(*c) + strlen(c->str) + 1, &set->ext_size); rcu_assign_pointer(comment->c, NULL); kfree_rcu(c, rcu); } diff --git a/net/netfilter/ipset/ip_set_hash_gen.h b/net/netfilter/ipset/ip_set_hash_gen.h index dd31992c915c..8841daf28f01 100644 --- a/net/netfilter/ipset/ip_set_hash_gen.h +++ b/net/netfilter/ipset/ip_set_hash_gen.h @@ -1373,7 +1373,7 @@ mtype_head(struct ip_set *set, struct sk_buff *skb) rcu_read_lock_bh(); t = rcu_dereference_bh(h->table); mtype_ext_size(set, &elements, &ext_size); - memsize = mtype_ahash_memsize(h, t) + ext_size + set->ext_size; + memsize = mtype_ahash_memsize(h, t) + ext_size + atomic64_read(&set->ext_size); htable_bits = t->htable_bits; rcu_read_unlock_bh(); diff --git a/net/netfilter/ipset/ip_set_list_set.c b/net/netfilter/ipset/ip_set_list_set.c index 1cef84f15e8c..ca3ef9479e83 100644 --- a/net/netfilter/ipset/ip_set_list_set.c +++ b/net/netfilter/ipset/ip_set_list_set.c @@ -421,7 +421,7 @@ list_set_flush(struct ip_set *set) list_for_each_entry_safe(e, n, &map->members, list) list_set_del(set, e); set->elements = 0; - set->ext_size = 0; + atomic64_set(&set->ext_size, 0); } static void @@ -455,7 +455,7 @@ list_set_head(struct ip_set *set, struct sk_buff *skb) { const struct list_set *map = set->data; struct nlattr *nested; - size_t memsize = list_set_memsize(map, set->dsize) + set->ext_size; + size_t memsize = list_set_memsize(map, set->dsize) + atomic64_read(&set->ext_size); nested = nla_nest_start(skb, IPSET_ATTR_DATA); if (!nested) -- cgit v1.2.3 From 7defddefa95b3699b05b23bf3ceea94d872509bb Mon Sep 17 00:00:00 2001 From: Florian Westphal Date: Thu, 30 Jul 2026 20:38:53 +0200 Subject: netfilter: ipset: switch to rcu work In the initial ipset rhashtable conversion RFC series syzbot reported following splat: BUG: sleeping function [..] at kernel/irq_work.c:289 in_atomic(): 1, [..] irq_work_sync.. kernel/irq_work.c:289 rhashtable_free_and_destroy.. lib/rhashtable.c:1295 hash_netport4_destroy.. net/netfilter/ipset/ip_set_hash_gen.h:420 ip_set_destroy_set_rcu.. net/netfilter/ipset/ip_set_core.c:1169 rcu_core.. kernel/rcu/tree.c:2897 This is because post-rhashtable-conversion hash implementation needs to schedule in the destroy callback. At this time this isn't allowed. Replace existing call_rcu() based destruction with rcu_work api. Also allows to undo split of set destruction and gc work cancelling in a future patch. Signed-off-by: Florian Westphal Signed-off-by: Pablo Neira Ayuso --- include/linux/netfilter/ipset/ip_set.h | 4 +-- net/netfilter/ipset/ip_set_core.c | 46 +++++++++++++++++++++------------- 2 files changed, 31 insertions(+), 19 deletions(-) (limited to 'include') diff --git a/include/linux/netfilter/ipset/ip_set.h b/include/linux/netfilter/ipset/ip_set.h index cadae9b2578f..c46864cc6623 100644 --- a/include/linux/netfilter/ipset/ip_set.h +++ b/include/linux/netfilter/ipset/ip_set.h @@ -244,8 +244,8 @@ extern void ip_set_type_unregister(struct ip_set_type *set_type); /* A generic IP set */ struct ip_set { - /* For call_cru in destroy */ - struct rcu_head rcu; + /* for set destruction */ + struct rcu_work rwork; /* The name of the set */ char name[IPSET_MAXNAMELEN]; /* Lock protecting the set data */ diff --git a/net/netfilter/ipset/ip_set_core.c b/net/netfilter/ipset/ip_set_core.c index 822a53a7f502..543851a923d0 100644 --- a/net/netfilter/ipset/ip_set_core.c +++ b/net/netfilter/ipset/ip_set_core.c @@ -25,6 +25,7 @@ static LIST_HEAD(ip_set_type_list); /* all registered set types */ static DEFINE_MUTEX(ip_set_type_mutex); /* protects ip_set_type_list */ static DEFINE_RWLOCK(ip_set_ref_lock); /* protects the set refs */ +static struct workqueue_struct *ipset_destroy_wq; struct ip_set_net { struct ip_set * __rcu *ip_set_list; /* all individual sets */ @@ -1178,22 +1179,26 @@ ip_set_setname_policy[IPSET_ATTR_CMD_MAX + 1] = { .len = IPSET_MAXNAMELEN - 1 }, }; -/* In order to return quickly when destroying a single set, it is split - * into two stages: - * - Cancel garbage collector - * - Destroy the set itself via call_rcu() - */ - static void -ip_set_destroy_set_rcu(struct rcu_head *head) +destroy_and_free_set(struct ip_set *set) { - struct ip_set *set = container_of(head, struct ip_set, rcu); - set->variant->destroy(set); module_put(set->type->me); kfree(set); } +/* In order to return quickly when destroying a single set, + * destruction is done asynchronously via work queues. + */ +static void +ip_set_destroy_set_work(struct work_struct *work) +{ + struct ip_set *set = container_of(to_rcu_work(work), + struct ip_set, rwork); + + destroy_and_free_set(set); +} + static void _destroy_all_sets(struct ip_set_net *inst) { @@ -1283,7 +1288,8 @@ static int ip_set_destroy(struct sk_buff *skb, const struct nfnl_info *info, /* Must wait for flush to be really finished */ rcu_barrier(); } - call_rcu(&s->rcu, ip_set_destroy_set_rcu); + INIT_RCU_WORK(&s->rwork, ip_set_destroy_set_work); + queue_rcu_work(ipset_destroy_wq, &s->rwork); } return 0; out: @@ -2421,18 +2427,23 @@ static struct pernet_operations ip_set_net_ops = { static int __init ip_set_init(void) { - int ret = register_pernet_subsys(&ip_set_net_ops); + int ret; + + ipset_destroy_wq = alloc_ordered_workqueue("ipset_destroy_wq", 0); + if (!ipset_destroy_wq) + return -ENOMEM; + ret = register_pernet_subsys(&ip_set_net_ops); if (ret) { pr_err("ip_set: cannot register pernet_subsys.\n"); - return ret; + goto out_wq; } ret = nfnetlink_subsys_register(&ip_set_netlink_subsys); if (ret != 0) { pr_err("ip_set: cannot register with nfnetlink.\n"); unregister_pernet_subsys(&ip_set_net_ops); - return ret; + goto out_wq; } ret = nf_register_sockopt(&so_set); @@ -2440,10 +2451,13 @@ ip_set_init(void) pr_err("SO_SET registry failed: %d\n", ret); nfnetlink_subsys_unregister(&ip_set_netlink_subsys); unregister_pernet_subsys(&ip_set_net_ops); - return ret; + goto out_wq; } return 0; +out_wq: + destroy_workqueue(ipset_destroy_wq); + return ret; } static void __exit @@ -2453,9 +2467,7 @@ ip_set_fini(void) nfnetlink_subsys_unregister(&ip_set_netlink_subsys); unregister_pernet_subsys(&ip_set_net_ops); - /* Wait for call_rcu() in destroy */ - rcu_barrier(); - + destroy_workqueue(ipset_destroy_wq); pr_debug("these are the famous last words\n"); } -- cgit v1.2.3 From 646922a0379496154e8c8faca4f8e2fd9100cacc Mon Sep 17 00:00:00 2001 From: Julian Anastasov Date: Thu, 30 Jul 2026 21:35:05 +0300 Subject: ipvs: avoid out-of-bounds write in ip_vs_nat_icmp Sashiko warns that local attacker can modify the packet while it is processed by IPVS. Some places read the IP ihl field multiple times which can cause out-of-bounds access. One such place is ip_vs_nat_icmp where we can write after the validated area. Fix it by providing ciph argument just like it is done for IPv6 and use ciph->len as offset to the embedded transport header. Modify some IPv4 header checks by reading the ihl field only once. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Link: https://sashiko.dev/#/patchset/20260722101517.36313-1-ja%40ssi.bg Signed-off-by: Julian Anastasov Signed-off-by: Pablo Neira Ayuso --- include/net/ip_vs.h | 2 +- net/netfilter/ipvs/ip_vs_core.c | 67 +++++++++++++++++++++-------------------- net/netfilter/ipvs/ip_vs_xmit.c | 2 +- 3 files changed, 36 insertions(+), 35 deletions(-) (limited to 'include') diff --git a/include/net/ip_vs.h b/include/net/ip_vs.h index e6ca930a3507..1235f1934e94 100644 --- a/include/net/ip_vs.h +++ b/include/net/ip_vs.h @@ -2062,7 +2062,7 @@ static inline bool ip_vs_conn_use_hash2(struct ip_vs_conn *cp) void ip_vs_nat_icmp(struct sk_buff *skb, struct ip_vs_protocol *pp, struct ip_vs_conn *cp, int dir, unsigned int toff, - bool has_ports); + bool has_ports, struct ip_vs_iphdr *ciph); #ifdef CONFIG_IP_VS_IPV6 void ip_vs_nat_icmp_v6(struct sk_buff *skb, struct ip_vs_protocol *pp, diff --git a/net/netfilter/ipvs/ip_vs_core.c b/net/netfilter/ipvs/ip_vs_core.c index 6b79e0c4d9e2..0bdaeb4ed61e 100644 --- a/net/netfilter/ipvs/ip_vs_core.c +++ b/net/netfilter/ipvs/ip_vs_core.c @@ -925,28 +925,27 @@ static int ip_vs_route_me_harder(struct netns_ipvs *ipvs, int af, */ void ip_vs_nat_icmp(struct sk_buff *skb, struct ip_vs_protocol *pp, struct ip_vs_conn *cp, int inout, unsigned int toff, - bool has_ports) + bool has_ports, struct ip_vs_iphdr *ciph) { struct iphdr *iph = ip_hdr(skb); struct icmphdr *icmph = (struct icmphdr *)(skb->data + toff); - struct iphdr *ciph = (struct iphdr *)(icmph + 1); - unsigned int coff __maybe_unused = toff + sizeof(struct icmphdr); + struct iphdr *cih = (struct iphdr *)(icmph + 1); if (inout) { iph->saddr = cp->vaddr.ip; ip_send_check(iph); - ciph->daddr = cp->vaddr.ip; - ip_send_check(ciph); + cih->daddr = cp->vaddr.ip; + ip_send_check(cih); } else { iph->daddr = cp->daddr.ip; ip_send_check(iph); - ciph->saddr = cp->daddr.ip; - ip_send_check(ciph); + cih->saddr = cp->daddr.ip; + ip_send_check(cih); } /* the TCP/UDP/SCTP port */ if (has_ports) { - __be16 *ports = (void *)ciph + ciph->ihl*4; + __be16 *ports = (void *)(skb->data + ciph->len); if (inout) ports[1] = cp->vport; @@ -960,10 +959,10 @@ void ip_vs_nat_icmp(struct sk_buff *skb, struct ip_vs_protocol *pp, skb->ip_summed = CHECKSUM_UNNECESSARY; if (inout) - IP_VS_DBG_PKT(11, AF_INET, pp, skb, coff, + IP_VS_DBG_PKT(11, AF_INET, pp, skb, ciph->off, "Forwarding altered outgoing ICMP"); else - IP_VS_DBG_PKT(11, AF_INET, pp, skb, coff, + IP_VS_DBG_PKT(11, AF_INET, pp, skb, ciph->off, "Forwarding altered incoming ICMP"); } @@ -1056,7 +1055,7 @@ static int handle_response_icmp(int af, struct sk_buff *skb, ip_vs_nat_icmp_v6(skb, pp, cp, 1, toff, has_ports, ciph); else #endif - ip_vs_nat_icmp(skb, pp, cp, 1, toff, has_ports); + ip_vs_nat_icmp(skb, pp, cp, 1, toff, has_ports, ciph); if (ip_vs_route_me_harder(cp->ipvs, af, skb, hooknum)) goto out; @@ -1092,7 +1091,7 @@ static int ip_vs_out_icmp(struct netns_ipvs *ipvs, struct sk_buff *skb, struct ip_vs_iphdr ciph; struct ip_vs_conn *cp; struct ip_vs_protocol *pp; - unsigned int offset, ihl; + unsigned int offset; union nf_inet_addr snet; *related = 1; @@ -1105,7 +1104,6 @@ static int ip_vs_out_icmp(struct netns_ipvs *ipvs, struct sk_buff *skb, return NF_ACCEPT; } - ihl = ipvsh->len; offset = ipvsh->len; ic = skb_header_pointer(skb, offset, sizeof(_icmph), &_icmph); if (ic == NULL) @@ -1131,11 +1129,15 @@ static int ip_vs_out_icmp(struct netns_ipvs *ipvs, struct sk_buff *skb, /* Now find the contained IP header */ offset += sizeof(_icmph); + if (!ip_vs_fill_iph_skb_icmp(AF_INET, skb, offset, true, &ciph)) + return NF_ACCEPT; /* The packet looks wrong, ignore */ + cih = skb_header_pointer(skb, offset, sizeof(_ciph), &_ciph); - if (!(cih && cih->version == 4 && cih->ihl >= 5)) + if (!(cih && cih->version == 4 && + ciph.len - ciph.off >= sizeof(struct iphdr))) return NF_ACCEPT; /* The packet looks wrong, ignore */ - pp = ip_vs_proto_get(cih->protocol); + pp = ip_vs_proto_get(ciph.protocol); if (!pp) return NF_ACCEPT; @@ -1146,8 +1148,6 @@ static int ip_vs_out_icmp(struct netns_ipvs *ipvs, struct sk_buff *skb, IP_VS_DBG_PKT(11, AF_INET, pp, skb, offset, "Checking outgoing ICMP for"); - ip_vs_fill_iph_skb_icmp(AF_INET, skb, offset, true, &ciph); - /* The embedded headers contain source and dest in reverse order */ cp = INDIRECT_CALL_1(pp->conn_out_get, ip_vs_conn_out_get_proto, ipvs, AF_INET, skb, &ciph); @@ -1155,8 +1155,8 @@ static int ip_vs_out_icmp(struct netns_ipvs *ipvs, struct sk_buff *skb, return NF_ACCEPT; snet.ip = ipvsh->saddr.ip; - return handle_response_icmp(AF_INET, skb, &snet, cp, pp, &ciph, ihl, - hooknum); + return handle_response_icmp(AF_INET, skb, &snet, cp, pp, &ciph, + ipvsh->len, hooknum); } #ifdef CONFIG_IP_VS_IPV6 @@ -1803,10 +1803,12 @@ ip_vs_in_icmp(struct netns_ipvs *ipvs, struct sk_buff *skb, int *related, /* Now find the contained IP header */ offset += sizeof(_icmph); cih = skb_header_pointer(skb, offset, sizeof(_ciph), &_ciph); - if (!(cih && cih->version == 4 && cih->ihl >= 5)) + if (!cih) return NF_ACCEPT; /* The packet looks wrong, ignore */ - raddr = (union nf_inet_addr *)&cih->daddr; hlen_ipip = cih->ihl * 4; + if (!(cih->version == 4 && hlen_ipip >= sizeof(struct iphdr))) + return NF_ACCEPT; /* The packet looks wrong, ignore */ + raddr = (union nf_inet_addr *)&cih->daddr; /* Special case for errors for IPIP/UDP/GRE tunnel packets */ tunnel = false; @@ -1823,9 +1825,6 @@ ip_vs_in_icmp(struct netns_ipvs *ipvs, struct sk_buff *skb, int *related, if (!dest || dest->tun_type != IP_VS_CONN_F_TUNNEL_TYPE_IPIP) return NF_ACCEPT; offset += hlen_ipip; - cih = skb_header_pointer(skb, offset, sizeof(_ciph), &_ciph); - if (!(cih && cih->version == 4 && cih->ihl >= 5)) - return NF_ACCEPT; /* The packet looks wrong, ignore */ tunnel = true; } else if ((cih->protocol == IPPROTO_UDP || /* Can be UDP encap */ cih->protocol == IPPROTO_GRE) && /* Can be GRE encap */ @@ -1850,21 +1849,25 @@ ip_vs_in_icmp(struct netns_ipvs *ipvs, struct sk_buff *skb, int *related, /* Skip IP and UDP/GRE tunnel headers */ offset = offset2 + ulen; /* Now we should be at the original IP header */ - cih = skb_header_pointer(skb, offset, sizeof(_ciph), - &_ciph); - if (cih && cih->version == 4 && cih->ihl >= 5 && - iproto == IPPROTO_IPIP) + if (iproto == IPPROTO_IPIP) tunnel = true; else return NF_ACCEPT; } } - pd = ip_vs_proto_data_get(ipvs, cih->protocol); + if (!ip_vs_fill_iph_skb_icmp(AF_INET, skb, offset, !tunnel, &ciph)) + return NF_ACCEPT; + pd = ip_vs_proto_data_get(ipvs, ciph.protocol); if (!pd) return NF_ACCEPT; pp = pd->pp; + cih = skb_header_pointer(skb, offset, sizeof(_ciph), &_ciph); + if (!(cih && cih->version == 4 && + ciph.len - ciph.off >= sizeof(struct iphdr))) + return NF_ACCEPT; /* The packet looks wrong, ignore */ + /* Is the embedded protocol header present? */ if (unlikely(cih->frag_off & htons(IP_OFFSET) && !pp->dont_defrag)) return NF_ACCEPT; @@ -1872,9 +1875,6 @@ ip_vs_in_icmp(struct netns_ipvs *ipvs, struct sk_buff *skb, int *related, IP_VS_DBG_PKT(11, AF_INET, pp, skb, offset, "Checking incoming ICMP for"); - offset2 = offset; - ip_vs_fill_iph_skb_icmp(AF_INET, skb, offset, !tunnel, &ciph); - /* The embedded headers contain source and dest in reverse order. * For IPIP/UDP/GRE tunnel this is error for request, not for reply. */ @@ -1904,11 +1904,12 @@ ip_vs_in_icmp(struct netns_ipvs *ipvs, struct sk_buff *skb, int *related, } if (tunnel) { - unsigned int hlen_orig = cih->ihl * 4; + unsigned int hlen_orig = ciph.len - ciph.off; __be32 info = ic->un.gateway; __u8 type = ic->type; __u8 code = ic->code; + offset2 = offset; /* Update the MTU */ if (ic->type == ICMP_DEST_UNREACH && ic->code == ICMP_FRAG_NEEDED) { diff --git a/net/netfilter/ipvs/ip_vs_xmit.c b/net/netfilter/ipvs/ip_vs_xmit.c index 0b0c5304993a..c4508f3f43dd 100644 --- a/net/netfilter/ipvs/ip_vs_xmit.c +++ b/net/netfilter/ipvs/ip_vs_xmit.c @@ -1580,7 +1580,7 @@ ip_vs_icmp_xmit(struct sk_buff *skb, struct ip_vs_conn *cp, if (skb_cow(skb, rt->dst.dev->hard_header_len)) goto tx_error; - ip_vs_nat_icmp(skb, pp, cp, 0, toff, has_ports); + ip_vs_nat_icmp(skb, pp, cp, 0, toff, has_ports, ciph); /* Another hack: avoid icmp_send in ip_fragment */ skb->ignore_df = 1; -- cgit v1.2.3 From 99609cb0aa789c8d071050ce8579989551882cc6 Mon Sep 17 00:00:00 2001 From: Julian Anastasov Date: Thu, 30 Jul 2026 21:35:06 +0300 Subject: ipvs: return the csum validation for forward hook Sashiko notes that playing games with the skb dst and rt flags instead of providing hooknum is not a good idea when validating the checksums. Also, skipping checksum validation for FORWARD packets risk silent data corruption, even if the only user is the FTP-CMD packets coming from the real server. Sashiko also noticed that by using common checksum helper in the previous commit we actually fixed old bug where the TCP/UDP checksum for IPv6 on CHECKSUM_COMPLETE was not validated correctly. Fixes: e876b75b9020 ("ipvs: fix the checksum validations") Link: https://sashiko.dev/#/patchset/20260722211420.153933-1-pablo%40netfilter.org Link: https://sashiko.dev/#/patchset/20260727185024.67534-1-ja%40ssi.bg Link: https://sashiko.dev/#/patchset/20260728202520.59179-1-ja%40ssi.bg Signed-off-by: Julian Anastasov Signed-off-by: Pablo Neira Ayuso --- include/net/ip_vs.h | 19 +++++-------------- net/netfilter/ipvs/ip_vs_proto_sctp.c | 2 +- 2 files changed, 6 insertions(+), 15 deletions(-) (limited to 'include') diff --git a/include/net/ip_vs.h b/include/net/ip_vs.h index 1235f1934e94..d2813eb795be 100644 --- a/include/net/ip_vs.h +++ b/include/net/ip_vs.h @@ -25,9 +25,7 @@ #include /* for union nf_inet_addr */ #include #include /* for struct ipv6hdr */ -#include #include -#include #if IS_ENABLED(CONFIG_NF_CONNTRACK) #include #endif @@ -2095,30 +2093,23 @@ static inline __wsum ip_vs_check_diff2(__be16 old, __be16 new, __wsum oldsum) return csum_partial(diff, sizeof(diff), oldsum); } -static inline bool ip_vs_checksum_needed(struct sk_buff *skb, int af) +static inline bool ip_vs_checksum_needed(struct sk_buff *skb) { /* Checksum unnecessary or already validated? */ if (skb_csum_unnecessary(skb)) return false; - /* LOCAL_OUT ? */ - if (!skb->dev || skb->dev->flags & IFF_LOOPBACK) + /* Locally generated ? */ + if (!skb->dev) return false; - /* !LOCAL_IN (FORWARD) ? */ - if (af == AF_INET6) { - if (!(dst_rt6_info(skb_dst(skb))->rt6i_flags & RTF_LOCAL)) - return false; - } else { - if (!(skb_rtable(skb)->rt_flags & RTCF_LOCAL)) - return false; - } return true; } static inline bool ip_vs_checksum_common_check(struct sk_buff *skb, int offset, int proto, int af) { - if (!ip_vs_checksum_needed(skb, af)) + if (!ip_vs_checksum_needed(skb)) return true; + /* Validate csum even for FORWARD */ return !nf_checksum(skb, NF_INET_LOCAL_IN, offset, proto, af); } diff --git a/net/netfilter/ipvs/ip_vs_proto_sctp.c b/net/netfilter/ipvs/ip_vs_proto_sctp.c index 3dbd3096e163..c80567c73469 100644 --- a/net/netfilter/ipvs/ip_vs_proto_sctp.c +++ b/net/netfilter/ipvs/ip_vs_proto_sctp.c @@ -193,7 +193,7 @@ sctp_csum_check(int af, struct sk_buff *skb, struct ip_vs_protocol *pp, struct sctphdr *sh; __le32 cmp, val; - if (!ip_vs_checksum_needed(skb, af)) + if (!ip_vs_checksum_needed(skb)) return 1; sh = (struct sctphdr *)(skb->data + sctphoff); cmp = sh->checksum; -- cgit v1.2.3 From c89039b724cc4d5e63f295d45b754e0de0faa0ad Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Fri, 31 Jul 2026 15:00:36 +0200 Subject: Revert "thermal: hwmon: Use extra_groups for adding temperature attributes" Revert commit cfb5dc0f60fb ("thermal: hwmon: Use extra_groups for adding temperature attributes") because it is depended on by another one that turned out to be problematic. Signed-off-by: Rafael J. Wysocki Link: https://patch.msgid.link/1992232.tdWV9SEqCh@rafael.j.wysocki --- drivers/hwmon/hwmon.c | 6 +- drivers/thermal/thermal_hwmon.c | 122 +++++++++++++++++++++++++--------------- include/linux/hwmon.h | 3 +- 3 files changed, 80 insertions(+), 51 deletions(-) (limited to 'include') diff --git a/drivers/hwmon/hwmon.c b/drivers/hwmon/hwmon.c index 55a9a3ddd4aa..29dc90a2c3fe 100644 --- a/drivers/hwmon/hwmon.c +++ b/drivers/hwmon/hwmon.c @@ -1083,7 +1083,6 @@ EXPORT_SYMBOL_GPL(hwmon_device_register_with_info); * @dev: the parent device * @name: hwmon name attribute * @drvdata: driver data to attach to created device - * @extra_groups: pointer to list of additional non-standard attribute groups * * The use of this function is restricted. It is provided for legacy reasons * and must only be called from the thermal subsystem. @@ -1095,13 +1094,12 @@ EXPORT_SYMBOL_GPL(hwmon_device_register_with_info); */ struct device * hwmon_device_register_for_thermal(struct device *dev, const char *name, - void *drvdata, - const struct attribute_group **extra_groups) + void *drvdata) { if (!name || !dev) return ERR_PTR(-EINVAL); - return __hwmon_device_register(dev, name, drvdata, NULL, extra_groups); + return __hwmon_device_register(dev, name, drvdata, NULL, NULL); } EXPORT_SYMBOL_NS_GPL(hwmon_device_register_for_thermal, "HWMON_THERMAL"); diff --git a/drivers/thermal/thermal_hwmon.c b/drivers/thermal/thermal_hwmon.c index 386dfb9f559e..223ae1571655 100644 --- a/drivers/thermal/thermal_hwmon.c +++ b/drivers/thermal/thermal_hwmon.c @@ -25,13 +25,25 @@ */ #define THERMAL_HWMON_NAME_LENGTH (THERMAL_NAME_LENGTH + 11) +struct thermal_hwmon_attr { + struct device_attribute attr; +}; + +/* one temperature input for each thermal zone */ +struct thermal_hwmon_temp { + struct thermal_zone_device *tz; + struct thermal_hwmon_attr temp_input; /* hwmon sys attr */ + struct thermal_hwmon_attr temp_crit; /* hwmon sys attr */ + bool temp_crit_present; +}; + /* hwmon sys I/F */ /* thermal zone devices with the same type share one hwmon device */ struct thermal_hwmon_device { char name[THERMAL_HWMON_NAME_LENGTH]; struct device *device; struct list_head node; - struct thermal_zone_device *tz; + struct thermal_hwmon_temp tz_temp; }; static LIST_HEAD(thermal_hwmon_list); @@ -39,14 +51,19 @@ static LIST_HEAD(thermal_hwmon_list); static DEFINE_MUTEX(thermal_hwmon_list_lock); static ssize_t -temp1_input_show(struct device *dev, struct device_attribute *attr, char *buf) +temp_input_show(struct device *dev, struct device_attribute *attr, char *buf) { - struct thermal_hwmon_device *hwmon = dev_get_drvdata(dev); - struct thermal_zone_device *tz = hwmon->tz; int temperature; int ret; + struct thermal_hwmon_attr *hwmon_attr + = container_of(attr, struct thermal_hwmon_attr, attr); + struct thermal_hwmon_temp *temp + = container_of(hwmon_attr, struct thermal_hwmon_temp, + temp_input); + struct thermal_zone_device *tz = temp->tz; ret = thermal_zone_get_temp(tz, &temperature); + if (ret) return ret; @@ -54,10 +71,14 @@ temp1_input_show(struct device *dev, struct device_attribute *attr, char *buf) } static ssize_t -temp1_crit_show(struct device *dev, struct device_attribute *attr, char *buf) +temp_crit_show(struct device *dev, struct device_attribute *attr, char *buf) { - struct thermal_hwmon_device *hwmon = dev_get_drvdata(dev); - struct thermal_zone_device *tz = hwmon->tz; + struct thermal_hwmon_attr *hwmon_attr + = container_of(attr, struct thermal_hwmon_attr, attr); + struct thermal_hwmon_temp *temp + = container_of(hwmon_attr, struct thermal_hwmon_temp, + temp_crit); + struct thermal_zone_device *tz = temp->tz; int temperature; int ret; @@ -70,49 +91,22 @@ temp1_crit_show(struct device *dev, struct device_attribute *attr, char *buf) return sysfs_emit(buf, "%d\n", temperature); } -static DEVICE_ATTR_RO(temp1_input); -static DEVICE_ATTR_RO(temp1_crit); - -static struct attribute *thermal_hwmon_attrs[] = { - &dev_attr_temp1_input.attr, - &dev_attr_temp1_crit.attr, - NULL, -}; - -static umode_t thermal_hwmon_attr_is_visible(struct kobject *kobj, - struct attribute *a, int n) +static bool thermal_zone_crit_temp_valid(struct thermal_zone_device *tz) { - if (a == &dev_attr_temp1_input.attr) - return a->mode; - - if (a == &dev_attr_temp1_crit.attr) { - struct thermal_hwmon_device *hwmon = dev_get_drvdata(kobj_to_dev(kobj)); - struct thermal_zone_device *tz = hwmon->tz; - int dummy; - - if (tz->ops.get_crit_temp && !tz->ops.get_crit_temp(tz, &dummy)) - return a->mode; - } - - return 0; + int temp; + return tz->ops.get_crit_temp && !tz->ops.get_crit_temp(tz, &temp); } -static const struct attribute_group thermal_hwmon_group = { - .attrs = thermal_hwmon_attrs, - .is_visible = thermal_hwmon_attr_is_visible, -}; - -__ATTRIBUTE_GROUPS(thermal_hwmon); - int thermal_add_hwmon_sysfs(struct thermal_zone_device *tz) { struct thermal_hwmon_device *hwmon; + struct thermal_hwmon_temp *temp; + int result; hwmon = kzalloc_obj(*hwmon); if (!hwmon) return -ENOMEM; - hwmon->tz = tz; /* * Append the thermal zone ID preceded by an underline character to the * type to disambiguate the sensors command output. @@ -120,13 +114,35 @@ int thermal_add_hwmon_sysfs(struct thermal_zone_device *tz) scnprintf(hwmon->name, THERMAL_HWMON_NAME_LENGTH, "%s_%d", tz->type, tz->id); strreplace(hwmon->name, '-', '_'); hwmon->device = hwmon_device_register_for_thermal(&tz->device, - hwmon->name, hwmon, - thermal_hwmon_groups); + hwmon->name, hwmon); if (IS_ERR(hwmon->device)) { - int result = PTR_ERR(hwmon->device); + result = PTR_ERR(hwmon->device); + goto free_mem; + } - kfree(hwmon); - return result; + temp = &hwmon->tz_temp; + + temp->tz = tz; + + temp->temp_input.attr.attr.name = "temp1_input"; + temp->temp_input.attr.attr.mode = 0444; + temp->temp_input.attr.show = temp_input_show; + sysfs_attr_init(&temp->temp_input.attr.attr); + result = device_create_file(hwmon->device, &temp->temp_input.attr); + if (result) + goto unregister_name; + + if (thermal_zone_crit_temp_valid(tz)) { + temp->temp_crit.attr.attr.name = "temp1_crit"; + temp->temp_crit.attr.attr.mode = 0444; + temp->temp_crit.attr.show = temp_crit_show; + sysfs_attr_init(&temp->temp_crit.attr.attr); + result = device_create_file(hwmon->device, + &temp->temp_crit.attr); + if (result) + goto unregister_input; + + temp->temp_crit_present = true; } /* The list is needed for hwmon lookup during removal. */ @@ -135,6 +151,15 @@ int thermal_add_hwmon_sysfs(struct thermal_zone_device *tz) mutex_unlock(&thermal_hwmon_list_lock); return 0; + + unregister_input: + device_remove_file(hwmon->device, &temp->temp_input.attr); + unregister_name: + hwmon_device_unregister(hwmon->device); + free_mem: + kfree(hwmon); + + return result; } EXPORT_SYMBOL_GPL(thermal_add_hwmon_sysfs); @@ -144,7 +169,7 @@ thermal_hwmon_lookup(const struct thermal_zone_device *tz) struct thermal_hwmon_device *hwmon; list_for_each_entry(hwmon, &thermal_hwmon_list, node) { - if (hwmon->tz == tz) + if (hwmon->tz_temp.tz == tz) return hwmon; } return NULL; @@ -153,6 +178,7 @@ thermal_hwmon_lookup(const struct thermal_zone_device *tz) void thermal_remove_hwmon_sysfs(struct thermal_zone_device *tz) { struct thermal_hwmon_device *hwmon; + struct thermal_hwmon_temp *temp; scoped_guard(mutex, &thermal_hwmon_list_lock) { hwmon = thermal_hwmon_lookup(tz); @@ -162,6 +188,12 @@ void thermal_remove_hwmon_sysfs(struct thermal_zone_device *tz) list_del(&hwmon->node); } + temp = &hwmon->tz_temp; + + device_remove_file(hwmon->device, &temp->temp_input.attr); + if (temp->temp_crit_present) + device_remove_file(hwmon->device, &temp->temp_crit.attr); + hwmon_device_unregister(hwmon->device); kfree(hwmon); } diff --git a/include/linux/hwmon.h b/include/linux/hwmon.h index 77a6f2bffcba..dd713e193d0c 100644 --- a/include/linux/hwmon.h +++ b/include/linux/hwmon.h @@ -480,8 +480,7 @@ hwmon_device_register_with_info(struct device *dev, const struct attribute_group **extra_groups); struct device * hwmon_device_register_for_thermal(struct device *dev, const char *name, - void *drvdata, - const struct attribute_group **extra_groups); + void *drvdata); struct device * devm_hwmon_device_register_with_info(struct device *dev, const char *name, void *drvdata, -- cgit v1.2.3 From 4c77b45fa23015d41d7401f684bc22ca585b41fe Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Thu, 30 Jul 2026 22:06:25 -0700 Subject: fixp-arith: convert comments to kernel-doc format Insert a hyphen ('-') in 2 places to prevent kernel-doc warnings: Warning: include/linux/fixp-arith.h:42 This comment starts with '/**', but isn't a kernel-doc comment. * __fixp_sin32() returns the sin of an angle in degrees Warning: include/linux/fixp-arith.h:66 This comment starts with '/**', but isn't a kernel-doc comment. * fixp_sin32() returns the sin of an angle in degrees Signed-off-by: Randy Dunlap Link: https://patch.msgid.link/20260731050625.455556-1-rdunlap@infradead.org Signed-off-by: Dmitry Torokhov --- include/linux/fixp-arith.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'include') diff --git a/include/linux/fixp-arith.h b/include/linux/fixp-arith.h index e485fb0c1201..4d4f4a7d4749 100644 --- a/include/linux/fixp-arith.h +++ b/include/linux/fixp-arith.h @@ -40,7 +40,7 @@ static const s32 sin_table[] = { }; /** - * __fixp_sin32() returns the sin of an angle in degrees + * __fixp_sin32() - returns the sin of an angle in degrees * * @degrees: angle, in degrees, from 0 to 360. * @@ -64,7 +64,7 @@ static inline s32 __fixp_sin32(int degrees) } /** - * fixp_sin32() returns the sin of an angle in degrees + * fixp_sin32() - returns the sin of an angle in degrees * * @degrees: angle, in degrees. The angle can be positive or negative * -- cgit v1.2.3 From 152f174a13618bec1f842d2deb69245cb2ace51f Mon Sep 17 00:00:00 2001 From: Nikhil Solanke Date: Wed, 29 Jul 2026 01:21:57 +0530 Subject: usb: core: Add quirk for 255-bytes initial config read Certain third-party USB game controllers exposing (or spoofing) an Xbox 360-compatible interface (VID:PID 045e:028e) fail to enumerate under Linux. The device disconnects from the bus without responding to the initial GET_DESCRIPTOR(CONFIGURATION) request, and the kernel logs 'unable to read config index 0 descriptor/start: -71'. The device then falls back to a secondary Android HID mode (with a different VID:PID), losing XInput functionality including rumble support. The failure reproduces across multiple machines, host controller types, and kernel versions including current mainline and LTS. The device enumerates correctly and remains in XInput mode under Windows. Notably, the device enumerates correctly in Android mode when the same 9-byte request is issued for that mode's configuration descriptor, confirming the firmware bug is specific to the XInput mode. usbmon traces from Linux and Wireshark/USBPcap traces from Windows are identical up to the point of failure, with no visible protocol-level difference explaining the divergence. The root cause was identified when Michal Pecio discovered via a QEMU bus-level capture that Windows does not use wLength=9 for the initial config descriptor request; it uses wLength=255. Alan Stern subsequently confirmed this with a bus analyzer on a different USB 2.0 device, and Michal verified the behavior goes back to Windows 95 OSR2.1. So, add a new quirk flag USB_QUIRK_WINDOWS_CONFIG_REQ_SIZE which causes usb_get_configuration() to issue a 255 byte sized configuration request instead of USB_DT_CONFIG_SIZE (9) for the initial GET_DESCRIPTOR(CONFIGURATION) request, mimicking long-standing Windows behavior. This patch intentionally does not add any new VID:PID entries using this quirk. Some affected Xbox 360-compatible controllers spoof Microsoft's VID:PID, while genuine Microsoft controllers already enumerate correctly and do not require this quirk. Other affected clone devices use their own VID:PID pairs and can be added individually as they are identified. Suggested-by: Alan Stern Suggested-by: Michal Pecio Closes: https://lore.kernel.org/linux-usb/CAFgddh+JWdT4LLwMc5qjM8q_pBu-fRo2qADR5ovAKoGHWMQrRw@mail.gmail.com/ Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Cc: stable Acked-by: Alan Stern Signed-off-by: Nikhil Solanke Link: https://patch.msgid.link/20260728195158.65162-2-nikhilsolanke5@gmail.com Signed-off-by: Greg Kroah-Hartman --- Documentation/admin-guide/kernel-parameters.txt | 5 ++++ drivers/usb/core/config.c | 32 ++++++++++++++++++------- drivers/usb/core/quirks.c | 4 ++++ include/linux/usb/quirks.h | 3 +++ 4 files changed, 36 insertions(+), 8 deletions(-) (limited to 'include') diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt index b5493a7f8f22..3d35270dddef 100644 --- a/Documentation/admin-guide/kernel-parameters.txt +++ b/Documentation/admin-guide/kernel-parameters.txt @@ -8169,6 +8169,11 @@ Kernel parameters q = USB_QUIRK_FORCE_ONE_CONFIG (Device claims zero configurations, forcing to 1); + r = USB_QUIRK_WINDOWS_CONFIG_REQ_SIZE (Device + fails during initialization when asked for + 9-bytes configuration descriptor request. + Ask for 255-bytes request instead to mirror + Windows' behavior); Example: quirks=0781:5580:bk,0a5c:5834:gij usbhid.mousepoll= diff --git a/drivers/usb/core/config.c b/drivers/usb/core/config.c index 45e20c6d76c0..346a2faa9bb8 100644 --- a/drivers/usb/core/config.c +++ b/drivers/usb/core/config.c @@ -912,6 +912,18 @@ int usb_get_configuration(struct usb_device *dev) unsigned char *bigbuffer; struct usb_config_descriptor *desc; int result; + size_t usb_config_req_size; + + /* + * We usually start by grabbing the first 9-bytes descriptor so we know + * how long the whole configuration is. Some devices with quirky + * firmware will fail enumeration, so if the quirk is set, use 255 instead, + * mirroring the behavior of Windows. + */ + if (dev->quirks & USB_QUIRK_WINDOWS_CONFIG_REQ_SIZE) + usb_config_req_size = 255; + else + usb_config_req_size = USB_DT_CONFIG_SIZE; if (ncfg > USB_MAXCONFIG) { dev_notice(ddev, "too many configurations: %d, " @@ -938,15 +950,13 @@ int usb_get_configuration(struct usb_device *dev) if (!dev->rawdescriptors) return -ENOMEM; - desc = kmalloc(USB_DT_CONFIG_SIZE, GFP_KERNEL); + desc = kmalloc(usb_config_req_size, GFP_KERNEL); if (!desc) return -ENOMEM; for (cfgno = 0; cfgno < ncfg; cfgno++) { - /* We grab just the first descriptor so we know how long - * the whole configuration is */ result = usb_get_descriptor(dev, USB_DT_CONFIG, cfgno, - desc, USB_DT_CONFIG_SIZE); + desc, usb_config_req_size); if (result < 0) { dev_err(ddev, "unable to read config index %d " "descriptor/%s: %d\n", cfgno, "start", result); @@ -956,16 +966,14 @@ int usb_get_configuration(struct usb_device *dev) dev->descriptor.bNumConfigurations = cfgno; break; } else if (result < 4) { - dev_err(ddev, "config index %d descriptor too short " - "(expected %i, got %i)\n", cfgno, - USB_DT_CONFIG_SIZE, result); + dev_err(ddev, "config index %d descriptor too short (asked for %zu, got %i)\n", + cfgno, usb_config_req_size, result); result = -EINVAL; goto err; } length = max_t(int, le16_to_cpu(desc->wTotalLength), USB_DT_CONFIG_SIZE); - /* Now that we know the length, get the whole thing */ bigbuffer = kmalloc(length, GFP_KERNEL); if (!bigbuffer) { result = -ENOMEM; @@ -975,6 +983,13 @@ int usb_get_configuration(struct usb_device *dev) if (dev->quirks & USB_QUIRK_DELAY_INIT) msleep(200); + /* Skip the second read if we already got everything */ + if (result >= length) { + memcpy(bigbuffer, desc, length); + goto store_and_parse; + } + + /* Get the whole thing */ result = usb_get_descriptor(dev, USB_DT_CONFIG, cfgno, bigbuffer, length); if (result < 0) { @@ -989,6 +1004,7 @@ int usb_get_configuration(struct usb_device *dev) length = result; } +store_and_parse: dev->rawdescriptors[cfgno] = bigbuffer; result = usb_parse_configuration(dev, cfgno, diff --git a/drivers/usb/core/quirks.c b/drivers/usb/core/quirks.c index 87ee2d938bc0..f5a60ccf21d3 100644 --- a/drivers/usb/core/quirks.c +++ b/drivers/usb/core/quirks.c @@ -142,6 +142,10 @@ static int quirks_param_set(const char *value, const struct kernel_param *kp) break; case 'q': flags |= USB_QUIRK_FORCE_ONE_CONFIG; + break; + case 'r': + flags |= USB_QUIRK_WINDOWS_CONFIG_REQ_SIZE; + break; /* Ignore unrecognized flag characters */ } } diff --git a/include/linux/usb/quirks.h b/include/linux/usb/quirks.h index b3cc7beab4a3..a4043b33c2c2 100644 --- a/include/linux/usb/quirks.h +++ b/include/linux/usb/quirks.h @@ -81,4 +81,7 @@ /* Device claims zero configurations, forcing to 1 */ #define USB_QUIRK_FORCE_ONE_CONFIG BIT(18) +/* Use a 255 bytes config descriptor request mirroring windows behavior */ +#define USB_QUIRK_WINDOWS_CONFIG_REQ_SIZE BIT(19) + #endif /* __LINUX_USB_QUIRKS_H */ -- cgit v1.2.3 From 19366db6dfccac9b0867a151678cd7b89fb8fd99 Mon Sep 17 00:00:00 2001 From: Stanislav Fomichev Date: Mon, 27 Jul 2026 09:19:55 -0700 Subject: xsk: pass TX metadata pointer by reference Completion handling needs to know whether a timestamp was requested when the metadata was processed. Let xsk_tx_metadata_request() update the caller's metadata pointer so that decision can be carried forward without rereading user-controlled flags. This only changes the interface; behavior remains unchanged. Fixes: ca4419f15abd ("xsk: Add launch time hardware offload support to XDP Tx metadata") Cc: Cen Zhang (Microsoft) Signed-off-by: Stanislav Fomichev Link: https://patch.msgid.link/20260727161959.885642-3-sdf@fomichev.me Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/intel/igc/igc_main.c | 2 +- drivers/net/ethernet/mellanox/mlx5/core/en.h | 2 +- drivers/net/ethernet/mellanox/mlx5/core/en/xdp.c | 14 +++++++++----- drivers/net/ethernet/mellanox/mlx5/core/en/xdp.h | 4 ++-- drivers/net/ethernet/mellanox/mlx5/core/en/xsk/tx.c | 2 +- drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 2 +- include/net/libeth/xsk.h | 2 +- include/net/xdp_sock.h | 8 +++++--- 8 files changed, 21 insertions(+), 15 deletions(-) (limited to 'include') diff --git a/drivers/net/ethernet/intel/igc/igc_main.c b/drivers/net/ethernet/intel/igc/igc_main.c index b3883a5a7d7a..beaea1479485 100644 --- a/drivers/net/ethernet/intel/igc/igc_main.c +++ b/drivers/net/ethernet/intel/igc/igc_main.c @@ -3082,7 +3082,7 @@ static void igc_xdp_xmit_zc(struct igc_ring *ring) meta_req.tx_buffer = bi; meta_req.meta = meta; meta_req.used_desc = 0; - xsk_tx_metadata_request(meta, &igc_xsk_tx_metadata_ops, + xsk_tx_metadata_request(&meta, &igc_xsk_tx_metadata_ops, &meta_req); /* xsk_tx_metadata_request() may have updated next_to_use */ diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en.h b/drivers/net/ethernet/mellanox/mlx5/core/en.h index 6867a5aed42c..822859c360e7 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en.h +++ b/drivers/net/ethernet/mellanox/mlx5/core/en.h @@ -483,7 +483,7 @@ typedef int (*mlx5e_fp_xmit_xdp_frame_check)(struct mlx5e_xdpsq *); typedef bool (*mlx5e_fp_xmit_xdp_frame)(struct mlx5e_xdpsq *, struct mlx5e_xmit_data *, int, - struct xsk_tx_metadata *); + struct xsk_tx_metadata **); struct mlx5e_xdpsq { /* data path */ diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en/xdp.c b/drivers/net/ethernet/mellanox/mlx5/core/en/xdp.c index d8c7cb8837d7..dac5e9d4c8bd 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en/xdp.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/en/xdp.c @@ -452,11 +452,11 @@ INDIRECT_CALLABLE_SCOPE int mlx5e_xmit_xdp_frame_check_mpwqe(struct mlx5e_xdpsq INDIRECT_CALLABLE_SCOPE bool mlx5e_xmit_xdp_frame(struct mlx5e_xdpsq *sq, struct mlx5e_xmit_data *xdptxd, - int check_result, struct xsk_tx_metadata *meta); + int check_result, struct xsk_tx_metadata **meta); INDIRECT_CALLABLE_SCOPE bool mlx5e_xmit_xdp_frame_mpwqe(struct mlx5e_xdpsq *sq, struct mlx5e_xmit_data *xdptxd, - int check_result, struct xsk_tx_metadata *meta) + int check_result, struct xsk_tx_metadata **meta) { struct mlx5e_tx_mpwqe *session = &sq->mpwqe; struct mlx5e_xdpsq_stats *stats = sq->stats; @@ -504,7 +504,10 @@ mlx5e_xmit_xdp_frame_mpwqe(struct mlx5e_xdpsq *sq, struct mlx5e_xmit_data *xdptx * and it's safe to complete it at any time. */ mlx5e_xdp_mpwqe_session_start(sq); - xsk_tx_metadata_request(meta, &mlx5e_xsk_tx_metadata_ops, &session->wqe->eth); + if (meta) + xsk_tx_metadata_request(meta, + &mlx5e_xsk_tx_metadata_ops, + &session->wqe->eth); } mlx5e_xdp_mpwqe_add_dseg(sq, p, stats); @@ -535,7 +538,7 @@ INDIRECT_CALLABLE_SCOPE int mlx5e_xmit_xdp_frame_check(struct mlx5e_xdpsq *sq) INDIRECT_CALLABLE_SCOPE bool mlx5e_xmit_xdp_frame(struct mlx5e_xdpsq *sq, struct mlx5e_xmit_data *xdptxd, - int check_result, struct xsk_tx_metadata *meta) + int check_result, struct xsk_tx_metadata **meta) { struct mlx5e_xmit_data_frags *xdptxdf = container_of(xdptxd, struct mlx5e_xmit_data_frags, xd); @@ -649,7 +652,8 @@ mlx5e_xmit_xdp_frame(struct mlx5e_xdpsq *sq, struct mlx5e_xmit_data *xdptxd, sq->pc += num_wqebbs; - xsk_tx_metadata_request(meta, &mlx5e_xsk_tx_metadata_ops, eseg); + if (meta) + xsk_tx_metadata_request(meta, &mlx5e_xsk_tx_metadata_ops, eseg); sq->doorbell_cseg = cseg; diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en/xdp.h b/drivers/net/ethernet/mellanox/mlx5/core/en/xdp.h index 3c54f8962664..5b8f4094d553 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en/xdp.h +++ b/drivers/net/ethernet/mellanox/mlx5/core/en/xdp.h @@ -114,11 +114,11 @@ extern const struct xsk_tx_metadata_ops mlx5e_xsk_tx_metadata_ops; INDIRECT_CALLABLE_DECLARE(bool mlx5e_xmit_xdp_frame_mpwqe(struct mlx5e_xdpsq *sq, struct mlx5e_xmit_data *xdptxd, int check_result, - struct xsk_tx_metadata *meta)); + struct xsk_tx_metadata **meta)); INDIRECT_CALLABLE_DECLARE(bool mlx5e_xmit_xdp_frame(struct mlx5e_xdpsq *sq, struct mlx5e_xmit_data *xdptxd, int check_result, - struct xsk_tx_metadata *meta)); + struct xsk_tx_metadata **meta)); INDIRECT_CALLABLE_DECLARE(int mlx5e_xmit_xdp_frame_check_mpwqe(struct mlx5e_xdpsq *sq)); INDIRECT_CALLABLE_DECLARE(int mlx5e_xmit_xdp_frame_check(struct mlx5e_xdpsq *sq)); diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en/xsk/tx.c b/drivers/net/ethernet/mellanox/mlx5/core/en/xsk/tx.c index 8aeab4b21035..3d19dad8f868 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en/xsk/tx.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/en/xsk/tx.c @@ -105,7 +105,7 @@ bool mlx5e_xsk_tx(struct mlx5e_xdpsq *sq, unsigned int budget) ret = INDIRECT_CALL_2(sq->xmit_xdp_frame, mlx5e_xmit_xdp_frame_mpwqe, mlx5e_xmit_xdp_frame, sq, &xdptxd, - check_result, meta); + check_result, &meta); if (unlikely(!ret)) { if (sq->mpwqe.wqe) mlx5e_xdp_mpwqe_complete(sq); diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index 3801f9d45278..7c3a7c56a81a 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -2748,7 +2748,7 @@ static bool stmmac_xdp_xmit_zc(struct stmmac_priv *priv, u32 queue, u32 budget) meta_req.set_ic = &set_ic; meta_req.tbs = tx_q->tbs; meta_req.edesc = &tx_q->dma_entx[entry]; - xsk_tx_metadata_request(meta, &stmmac_xsk_tx_metadata_ops, + xsk_tx_metadata_request(&meta, &stmmac_xsk_tx_metadata_ops, &meta_req); if (set_ic) { tx_q->tx_count_frames = 0; diff --git a/include/net/libeth/xsk.h b/include/net/libeth/xsk.h index 82b5d21aae87..e2fa6bf6b1b3 100644 --- a/include/net/libeth/xsk.h +++ b/include/net/libeth/xsk.h @@ -205,7 +205,7 @@ __libeth_xsk_xmit_fill_buf_md(const struct xdp_desc *xdesc, BUILD_BUG_ON(!__builtin_constant_p(tmo == libeth_xsktmo)); tmo = tmo == libeth_xsktmo ? &__libeth_xsktmo : tmo; - xsk_tx_metadata_request(ctx.meta, tmo, &desc); + xsk_tx_metadata_request(&ctx.meta, tmo, &desc); return desc; } diff --git a/include/net/xdp_sock.h b/include/net/xdp_sock.h index 8b51876efbed..06c081feff42 100644 --- a/include/net/xdp_sock.h +++ b/include/net/xdp_sock.h @@ -153,17 +153,19 @@ static inline void xsk_tx_metadata_to_compl(struct xsk_tx_metadata *meta, /** * xsk_tx_metadata_request - Evaluate AF_XDP TX metadata at submission * and call appropriate xsk_tx_metadata_ops operation. - * @meta: pointer to AF_XDP metadata area + * @pmeta: pointer to pointer to AF_XDP metadata area * @ops: pointer to struct xsk_tx_metadata_ops * @priv: pointer to driver-private aread * * This function should be called by the networking device when * it prepares AF_XDP egress packet. */ -static inline void xsk_tx_metadata_request(const struct xsk_tx_metadata *meta, +static inline void xsk_tx_metadata_request(struct xsk_tx_metadata **pmeta, const struct xsk_tx_metadata_ops *ops, void *priv) { + const struct xsk_tx_metadata *meta = *pmeta; + if (!meta) return; @@ -231,7 +233,7 @@ static inline void xsk_tx_metadata_to_compl(struct xsk_tx_metadata *meta, { } -static inline void xsk_tx_metadata_request(struct xsk_tx_metadata *meta, +static inline void xsk_tx_metadata_request(struct xsk_tx_metadata **pmeta, const struct xsk_tx_metadata_ops *ops, void *priv) { -- cgit v1.2.3 From 9f60a67df8d3c862503bee62bada8e7089cba438 Mon Sep 17 00:00:00 2001 From: Stanislav Fomichev Date: Mon, 27 Jul 2026 09:19:56 -0700 Subject: xsk: clear metadata pointer when no timestamp is requested User space can change metadata flags after request processing. Rereading them during completion can therefore make the kernel write a timestamp that was not requested when the packet was submitted. Clear the metadata pointer during request processing unless timestamp completion is requested. Completion handling can then use the pointer itself instead of rereading the flags. On the mlx5 multi-packet WQE path metadata is evaluated per batch: xsk_tx_metadata_request() runs only for the descriptor that starts a session, just like the checksum offload that is applied once through the shared WQE. Only that descriptor's pointer is reset, so completion handling can record a timestamp for the other descriptors of the session regardless of their own XDP_TXMD_FLAGS_TIMESTAMP bit. The write stays inside the metadata area; the single-WQE, other zero-copy, and generic paths reset the pointer per descriptor and are unaffected. Fixes: ca4419f15abd ("xsk: Add launch time hardware offload support to XDP Tx metadata") Cc: Cen Zhang (Microsoft) Signed-off-by: Stanislav Fomichev Link: https://patch.msgid.link/20260727161959.885642-4-sdf@fomichev.me Signed-off-by: Jakub Kicinski --- include/net/xdp_sock.h | 14 ++++++++++---- net/xdp/xsk.c | 2 ++ 2 files changed, 12 insertions(+), 4 deletions(-) (limited to 'include') diff --git a/include/net/xdp_sock.h b/include/net/xdp_sock.h index 06c081feff42..2b2eb9b9d580 100644 --- a/include/net/xdp_sock.h +++ b/include/net/xdp_sock.h @@ -141,13 +141,16 @@ INDIRECT_CALLABLE_DECLARE(void xsk_destruct_skb(struct sk_buff *)); static inline void xsk_tx_metadata_to_compl(struct xsk_tx_metadata *meta, struct xsk_tx_metadata_compl *compl) { + compl->tx_timestamp = NULL; + if (!meta) return; - if (meta->flags & XDP_TXMD_FLAGS_TIMESTAMP) - compl->tx_timestamp = &meta->completion.tx_timestamp; - else - compl->tx_timestamp = NULL; + /* we can only arrive here if the completion timestamp has been + * requested via XDP_TXMD_FLAGS_TIMESTAMP, see xsk_tx_metadata_request + */ + + compl->tx_timestamp = &meta->completion.tx_timestamp; } /** @@ -182,6 +185,9 @@ static inline void xsk_tx_metadata_request(struct xsk_tx_metadata **pmeta, if (meta->flags & XDP_TXMD_FLAGS_CHECKSUM) ops->tmo_request_checksum(meta->request.csum_start, meta->request.csum_offset, priv); + + if (!(meta->flags & XDP_TXMD_FLAGS_TIMESTAMP)) + *pmeta = NULL; } /** diff --git a/net/xdp/xsk.c b/net/xdp/xsk.c index f906d51b6699..fcc6f17f3576 100644 --- a/net/xdp/xsk.c +++ b/net/xdp/xsk.c @@ -998,6 +998,8 @@ static int xsk_skb_metadata(struct sk_buff *skb, void *buffer, if (meta->flags & XDP_TXMD_FLAGS_LAUNCH_TIME) skb->skb_mstamp_ns = meta->request.launch_time; + if (!(meta->flags & XDP_TXMD_FLAGS_TIMESTAMP)) + meta = NULL; xsk_tx_metadata_to_compl(meta, &skb_shinfo(skb)->xsk_meta); return 0; -- cgit v1.2.3 From 439ce2dddf3d22129b9113a7881637256a35e936 Mon Sep 17 00:00:00 2001 From: Stanislav Fomichev Date: Mon, 27 Jul 2026 09:19:57 -0700 Subject: xsk: validate launch-time metadata size Launch-time metadata extends beyond the first 16 bytes of struct xsk_tx_metadata. Reject the request when the registered metadata area does not contain the complete field. Snapshot the validated flags for the generic transmit path and use that snapshot for request and completion processing, avoiding inconsistent decisions if user space changes the flags concurrently. Note that only xsk_skb_metadata is properly using the flags, __xsk_buff_get_metadata ignores them. Next commits address that. Fixes: ca4419f15abd ("xsk: Add launch time hardware offload support to XDP Tx metadata") Cc: Cen Zhang (Microsoft) Signed-off-by: Stanislav Fomichev Link: https://patch.msgid.link/20260727161959.885642-5-sdf@fomichev.me Signed-off-by: Jakub Kicinski --- include/net/xdp_sock_drv.h | 17 +++++++++++++---- net/xdp/xsk.c | 11 ++++++----- 2 files changed, 19 insertions(+), 9 deletions(-) (limited to 'include') diff --git a/include/net/xdp_sock_drv.h b/include/net/xdp_sock_drv.h index 46797645a0c2..68a787601357 100644 --- a/include/net/xdp_sock_drv.h +++ b/include/net/xdp_sock_drv.h @@ -260,21 +260,28 @@ xsk_buff_raw_get_ctx(const struct xsk_buff_pool *pool, u64 addr) 0) static inline bool -xsk_buff_valid_tx_metadata(const struct xsk_tx_metadata *meta) +xsk_buff_valid_tx_metadata(const struct xsk_buff_pool *pool, + const struct xsk_tx_metadata *meta, u64 *flags) { - return !(meta->flags & ~XDP_TXMD_FLAGS_VALID); + *flags = READ_ONCE(meta->flags); + if (*flags & XDP_TXMD_FLAGS_LAUNCH_TIME) + if (pool->tx_metadata_len < + offsetofend(struct xsk_tx_metadata, request.launch_time)) + return false; + return !(*flags & ~XDP_TXMD_FLAGS_VALID); } static inline struct xsk_tx_metadata * __xsk_buff_get_metadata(const struct xsk_buff_pool *pool, void *data) { struct xsk_tx_metadata *meta; + u64 flags; if (!pool->tx_metadata_len) return NULL; meta = data - pool->tx_metadata_len; - if (unlikely(!xsk_buff_valid_tx_metadata(meta))) + if (unlikely(!xsk_buff_valid_tx_metadata(pool, meta, &flags))) return NULL; /* no way to signal the error to the user */ return meta; @@ -469,7 +476,9 @@ xsk_buff_raw_get_ctx(const struct xsk_buff_pool *pool, u64 addr) return (struct xdp_desc_ctx){ }; } -static inline bool xsk_buff_valid_tx_metadata(struct xsk_tx_metadata *meta) +static inline bool +xsk_buff_valid_tx_metadata(const struct xsk_buff_pool *pool, + const struct xsk_tx_metadata *meta, u64 *flags) { return false; } diff --git a/net/xdp/xsk.c b/net/xdp/xsk.c index fcc6f17f3576..7855ee09c4b6 100644 --- a/net/xdp/xsk.c +++ b/net/xdp/xsk.c @@ -967,15 +967,16 @@ static int xsk_skb_metadata(struct sk_buff *skb, void *buffer, { struct xsk_tx_metadata *meta = NULL; u16 csum_start, csum_offset; + u64 flags; if (unlikely(pool->tx_metadata_len == 0)) return -EINVAL; meta = buffer - pool->tx_metadata_len; - if (unlikely(!xsk_buff_valid_tx_metadata(meta))) + if (unlikely(!xsk_buff_valid_tx_metadata(pool, meta, &flags))) return -EINVAL; - if (meta->flags & XDP_TXMD_FLAGS_CHECKSUM) { + if (flags & XDP_TXMD_FLAGS_CHECKSUM) { csum_start = READ_ONCE(meta->request.csum_start); csum_offset = READ_ONCE(meta->request.csum_offset); @@ -996,9 +997,9 @@ static int xsk_skb_metadata(struct sk_buff *skb, void *buffer, } } - if (meta->flags & XDP_TXMD_FLAGS_LAUNCH_TIME) - skb->skb_mstamp_ns = meta->request.launch_time; - if (!(meta->flags & XDP_TXMD_FLAGS_TIMESTAMP)) + if (flags & XDP_TXMD_FLAGS_LAUNCH_TIME) + skb->skb_mstamp_ns = READ_ONCE(meta->request.launch_time); + if (!(flags & XDP_TXMD_FLAGS_TIMESTAMP)) meta = NULL; xsk_tx_metadata_to_compl(meta, &skb_shinfo(skb)->xsk_meta); -- cgit v1.2.3 From ddd0d6c5bfe2fef7c7cf31f62265f29b7b9eb9ef Mon Sep 17 00:00:00 2001 From: Stanislav Fomichev Date: Mon, 27 Jul 2026 09:19:58 -0700 Subject: xsk: move xsk_tx_metadata_request() to xdp_sock_drv.h xsk_tx_metadata_request() must validate metadata with xsk_buff_valid_tx_metadata(), which is defined in xdp_sock_drv.h. Move the helper there before adding that dependency. All callers already include the destination header, so this has no functional effect. Fixes: ca4419f15abd ("xsk: Add launch time hardware offload support to XDP Tx metadata") Cc: Cen Zhang (Microsoft) Signed-off-by: Stanislav Fomichev Link: https://patch.msgid.link/20260727161959.885642-6-sdf@fomichev.me Signed-off-by: Jakub Kicinski --- include/net/xdp_sock.h | 43 ------------------------------------------- include/net/xdp_sock_drv.h | 43 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 43 deletions(-) (limited to 'include') diff --git a/include/net/xdp_sock.h b/include/net/xdp_sock.h index 2b2eb9b9d580..6e70b320b399 100644 --- a/include/net/xdp_sock.h +++ b/include/net/xdp_sock.h @@ -153,43 +153,6 @@ static inline void xsk_tx_metadata_to_compl(struct xsk_tx_metadata *meta, compl->tx_timestamp = &meta->completion.tx_timestamp; } -/** - * xsk_tx_metadata_request - Evaluate AF_XDP TX metadata at submission - * and call appropriate xsk_tx_metadata_ops operation. - * @pmeta: pointer to pointer to AF_XDP metadata area - * @ops: pointer to struct xsk_tx_metadata_ops - * @priv: pointer to driver-private aread - * - * This function should be called by the networking device when - * it prepares AF_XDP egress packet. - */ -static inline void xsk_tx_metadata_request(struct xsk_tx_metadata **pmeta, - const struct xsk_tx_metadata_ops *ops, - void *priv) -{ - const struct xsk_tx_metadata *meta = *pmeta; - - if (!meta) - return; - - if (ops->tmo_request_launch_time) - if (meta->flags & XDP_TXMD_FLAGS_LAUNCH_TIME) - ops->tmo_request_launch_time(meta->request.launch_time, - priv); - - if (ops->tmo_request_timestamp) - if (meta->flags & XDP_TXMD_FLAGS_TIMESTAMP) - ops->tmo_request_timestamp(priv); - - if (ops->tmo_request_checksum) - if (meta->flags & XDP_TXMD_FLAGS_CHECKSUM) - ops->tmo_request_checksum(meta->request.csum_start, - meta->request.csum_offset, priv); - - if (!(meta->flags & XDP_TXMD_FLAGS_TIMESTAMP)) - *pmeta = NULL; -} - /** * xsk_tx_metadata_complete - Evaluate AF_XDP TX metadata at completion * and call appropriate xsk_tx_metadata_ops operation. @@ -239,12 +202,6 @@ static inline void xsk_tx_metadata_to_compl(struct xsk_tx_metadata *meta, { } -static inline void xsk_tx_metadata_request(struct xsk_tx_metadata **pmeta, - const struct xsk_tx_metadata_ops *ops, - void *priv) -{ -} - static inline void xsk_tx_metadata_complete(struct xsk_tx_metadata_compl *compl, const struct xsk_tx_metadata_ops *ops, void *priv) diff --git a/include/net/xdp_sock_drv.h b/include/net/xdp_sock_drv.h index 68a787601357..f87c4215673e 100644 --- a/include/net/xdp_sock_drv.h +++ b/include/net/xdp_sock_drv.h @@ -271,6 +271,43 @@ xsk_buff_valid_tx_metadata(const struct xsk_buff_pool *pool, return !(*flags & ~XDP_TXMD_FLAGS_VALID); } +/** + * xsk_tx_metadata_request - Evaluate AF_XDP TX metadata at submission + * and call appropriate xsk_tx_metadata_ops operation. + * @pmeta: pointer to pointer to AF_XDP metadata area + * @ops: pointer to struct xsk_tx_metadata_ops + * @priv: pointer to driver-private aread + * + * This function should be called by the networking device when + * it prepares AF_XDP egress packet. + */ +static inline void xsk_tx_metadata_request(struct xsk_tx_metadata **pmeta, + const struct xsk_tx_metadata_ops *ops, + void *priv) +{ + const struct xsk_tx_metadata *meta = *pmeta; + + if (!meta) + return; + + if (ops->tmo_request_launch_time) + if (meta->flags & XDP_TXMD_FLAGS_LAUNCH_TIME) + ops->tmo_request_launch_time(meta->request.launch_time, + priv); + + if (ops->tmo_request_timestamp) + if (meta->flags & XDP_TXMD_FLAGS_TIMESTAMP) + ops->tmo_request_timestamp(priv); + + if (ops->tmo_request_checksum) + if (meta->flags & XDP_TXMD_FLAGS_CHECKSUM) + ops->tmo_request_checksum(meta->request.csum_start, + meta->request.csum_offset, priv); + + if (!(meta->flags & XDP_TXMD_FLAGS_TIMESTAMP)) + *pmeta = NULL; +} + static inline struct xsk_tx_metadata * __xsk_buff_get_metadata(const struct xsk_buff_pool *pool, void *data) { @@ -483,6 +520,12 @@ xsk_buff_valid_tx_metadata(const struct xsk_buff_pool *pool, return false; } +static inline void xsk_tx_metadata_request(struct xsk_tx_metadata **pmeta, + const struct xsk_tx_metadata_ops *ops, + void *priv) +{ +} + static inline struct xsk_tx_metadata * __xsk_buff_get_metadata(const struct xsk_buff_pool *pool, void *data) { -- cgit v1.2.3 From 849b1664dbda1cf6c63e0fd4f9dec23782b8c851 Mon Sep 17 00:00:00 2001 From: Stanislav Fomichev Date: Mon, 27 Jul 2026 09:19:59 -0700 Subject: xsk: validate metadata when processing requests The zero-copy path validates TX metadata while obtaining the descriptor context, then reads it again later when preparing the hardware request. User space can change the metadata between those operations and bypass the original validation. Validate the metadata in xsk_tx_metadata_request() and use the resulting flags snapshot for every feature check. Read request fields once so all zero-copy drivers process only values observed after successful validation. Fixes: ca4419f15abd ("xsk: Add launch time hardware offload support to XDP Tx metadata") Cc: Cen Zhang (Microsoft) Signed-off-by: Stanislav Fomichev Link: https://patch.msgid.link/20260727161959.885642-7-sdf@fomichev.me Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/intel/igc/igc_main.c | 2 +- drivers/net/ethernet/mellanox/mlx5/core/en/xdp.c | 5 ++- drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 4 +- include/net/libeth/xsk.h | 2 +- include/net/xdp_sock_drv.h | 51 ++++++++++++----------- net/xdp/xsk_buff_pool.c | 6 +-- 6 files changed, 37 insertions(+), 33 deletions(-) (limited to 'include') diff --git a/drivers/net/ethernet/intel/igc/igc_main.c b/drivers/net/ethernet/intel/igc/igc_main.c index beaea1479485..16fcc8cf6191 100644 --- a/drivers/net/ethernet/intel/igc/igc_main.c +++ b/drivers/net/ethernet/intel/igc/igc_main.c @@ -3082,7 +3082,7 @@ static void igc_xdp_xmit_zc(struct igc_ring *ring) meta_req.tx_buffer = bi; meta_req.meta = meta; meta_req.used_desc = 0; - xsk_tx_metadata_request(&meta, &igc_xsk_tx_metadata_ops, + xsk_tx_metadata_request(pool, &meta, &igc_xsk_tx_metadata_ops, &meta_req); /* xsk_tx_metadata_request() may have updated next_to_use */ diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en/xdp.c b/drivers/net/ethernet/mellanox/mlx5/core/en/xdp.c index dac5e9d4c8bd..77ea51bfbaae 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en/xdp.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/en/xdp.c @@ -505,7 +505,7 @@ mlx5e_xmit_xdp_frame_mpwqe(struct mlx5e_xdpsq *sq, struct mlx5e_xmit_data *xdptx */ mlx5e_xdp_mpwqe_session_start(sq); if (meta) - xsk_tx_metadata_request(meta, + xsk_tx_metadata_request(sq->xsk_pool, meta, &mlx5e_xsk_tx_metadata_ops, &session->wqe->eth); } @@ -653,7 +653,8 @@ mlx5e_xmit_xdp_frame(struct mlx5e_xdpsq *sq, struct mlx5e_xmit_data *xdptxd, sq->pc += num_wqebbs; if (meta) - xsk_tx_metadata_request(meta, &mlx5e_xsk_tx_metadata_ops, eseg); + xsk_tx_metadata_request(sq->xsk_pool, meta, + &mlx5e_xsk_tx_metadata_ops, eseg); sq->doorbell_cseg = cseg; diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index 7c3a7c56a81a..97b28cfa14c2 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -2748,8 +2748,8 @@ static bool stmmac_xdp_xmit_zc(struct stmmac_priv *priv, u32 queue, u32 budget) meta_req.set_ic = &set_ic; meta_req.tbs = tx_q->tbs; meta_req.edesc = &tx_q->dma_entx[entry]; - xsk_tx_metadata_request(&meta, &stmmac_xsk_tx_metadata_ops, - &meta_req); + xsk_tx_metadata_request(pool, &meta, + &stmmac_xsk_tx_metadata_ops, &meta_req); if (set_ic) { tx_q->tx_count_frames = 0; stmmac_set_tx_ic(priv, tx_desc); diff --git a/include/net/libeth/xsk.h b/include/net/libeth/xsk.h index e2fa6bf6b1b3..5dcc0d7f65b7 100644 --- a/include/net/libeth/xsk.h +++ b/include/net/libeth/xsk.h @@ -205,7 +205,7 @@ __libeth_xsk_xmit_fill_buf_md(const struct xdp_desc *xdesc, BUILD_BUG_ON(!__builtin_constant_p(tmo == libeth_xsktmo)); tmo = tmo == libeth_xsktmo ? &__libeth_xsktmo : tmo; - xsk_tx_metadata_request(&ctx.meta, tmo, &desc); + xsk_tx_metadata_request(sq->pool, &ctx.meta, tmo, &desc); return desc; } diff --git a/include/net/xdp_sock_drv.h b/include/net/xdp_sock_drv.h index f87c4215673e..b344789f5df8 100644 --- a/include/net/xdp_sock_drv.h +++ b/include/net/xdp_sock_drv.h @@ -245,7 +245,7 @@ static inline void *xsk_buff_raw_get_data(struct xsk_buff_pool *pool, u64 addr) * details. * * Return: new &xdp_desc_ctx struct containing desc's DMA address and metadata - * pointer, if it is present and valid (initialized to %NULL otherwise). + * pointer, if it is present (initialized to %NULL otherwise). */ static inline struct xdp_desc_ctx xsk_buff_raw_get_ctx(const struct xsk_buff_pool *pool, u64 addr) @@ -274,54 +274,56 @@ xsk_buff_valid_tx_metadata(const struct xsk_buff_pool *pool, /** * xsk_tx_metadata_request - Evaluate AF_XDP TX metadata at submission * and call appropriate xsk_tx_metadata_ops operation. + * @pool: pointer to AF_XDP buffer pool, used to validate the metadata * @pmeta: pointer to pointer to AF_XDP metadata area * @ops: pointer to struct xsk_tx_metadata_ops - * @priv: pointer to driver-private aread + * @priv: pointer to driver-private area * * This function should be called by the networking device when * it prepares AF_XDP egress packet. */ -static inline void xsk_tx_metadata_request(struct xsk_tx_metadata **pmeta, - const struct xsk_tx_metadata_ops *ops, - void *priv) +static inline void +xsk_tx_metadata_request(const struct xsk_buff_pool *pool, + struct xsk_tx_metadata **pmeta, + const struct xsk_tx_metadata_ops *ops, void *priv) { const struct xsk_tx_metadata *meta = *pmeta; + u64 flags; if (!meta) return; + if (unlikely(!xsk_buff_valid_tx_metadata(pool, meta, &flags))) { + *pmeta = NULL; + return; /* no way to signal the error to the user */ + } + if (ops->tmo_request_launch_time) - if (meta->flags & XDP_TXMD_FLAGS_LAUNCH_TIME) - ops->tmo_request_launch_time(meta->request.launch_time, - priv); + if (flags & XDP_TXMD_FLAGS_LAUNCH_TIME) + ops->tmo_request_launch_time( + READ_ONCE(meta->request.launch_time), priv); if (ops->tmo_request_timestamp) - if (meta->flags & XDP_TXMD_FLAGS_TIMESTAMP) + if (flags & XDP_TXMD_FLAGS_TIMESTAMP) ops->tmo_request_timestamp(priv); if (ops->tmo_request_checksum) - if (meta->flags & XDP_TXMD_FLAGS_CHECKSUM) - ops->tmo_request_checksum(meta->request.csum_start, - meta->request.csum_offset, priv); + if (flags & XDP_TXMD_FLAGS_CHECKSUM) + ops->tmo_request_checksum( + READ_ONCE(meta->request.csum_start), + READ_ONCE(meta->request.csum_offset), priv); - if (!(meta->flags & XDP_TXMD_FLAGS_TIMESTAMP)) + if (!(flags & XDP_TXMD_FLAGS_TIMESTAMP)) *pmeta = NULL; } static inline struct xsk_tx_metadata * __xsk_buff_get_metadata(const struct xsk_buff_pool *pool, void *data) { - struct xsk_tx_metadata *meta; - u64 flags; - if (!pool->tx_metadata_len) return NULL; - meta = data - pool->tx_metadata_len; - if (unlikely(!xsk_buff_valid_tx_metadata(pool, meta, &flags))) - return NULL; /* no way to signal the error to the user */ - - return meta; + return data - pool->tx_metadata_len; } static inline struct xsk_tx_metadata * @@ -520,9 +522,10 @@ xsk_buff_valid_tx_metadata(const struct xsk_buff_pool *pool, return false; } -static inline void xsk_tx_metadata_request(struct xsk_tx_metadata **pmeta, - const struct xsk_tx_metadata_ops *ops, - void *priv) +static inline void +xsk_tx_metadata_request(const struct xsk_buff_pool *pool, + struct xsk_tx_metadata **pmeta, + const struct xsk_tx_metadata_ops *ops, void *priv) { } diff --git a/net/xdp/xsk_buff_pool.c b/net/xdp/xsk_buff_pool.c index a4089480b22b..78c14f106395 100644 --- a/net/xdp/xsk_buff_pool.c +++ b/net/xdp/xsk_buff_pool.c @@ -765,11 +765,11 @@ EXPORT_SYMBOL(xp_raw_get_dma); * @addr: desc address (from userspace) * * Helper for getting desc's DMA address and metadata pointer, if present. - * Saves one call on hotpath, double calculation of the actual address, - * and inline checks for metadata presence and sanity. + * Saves one call on hotpath and double calculation of the actual address. + * Metadata is validated later by xsk_tx_metadata_request(). * * Return: new &xdp_desc_ctx struct containing desc's DMA address and metadata - * pointer, if it is present and valid (initialized to %NULL otherwise). + * pointer, if it is present (initialized to %NULL otherwise). */ struct xdp_desc_ctx xp_raw_get_ctx(const struct xsk_buff_pool *pool, u64 addr) { -- cgit v1.2.3 From 0e243671bc7b8eaf00f83dd2f4367436dc0cff98 Mon Sep 17 00:00:00 2001 From: Kyle Zeng Date: Mon, 3 Aug 2026 12:27:57 +0000 Subject: ipv6: prevent in6_dev_get() from resurrecting inet6_dev in6_dev_get() reads dev->ip6_ptr under RCU and then unconditionally increments its refcount. Device teardown can clear the pointer and drop the last reference between these operations. The increment then resurrects an object whose RCU free has already been queued, so callers can use it after it is freed. Use refcount_inc_not_zero() and return NULL when the object has already reached zero. RCU keeps the memory accessible through the attempted reference acquisition, and a successful increment pins the object for the caller. An independent run on the exact unpatched 6f5156d7a31a (v7.2-rc3) kernel reproduced the invalid reference acquisition as UID 1000: refcount_t: addition on 0; use-after-free. ip6_mc_source+0xef4/0x17e0 It was followed by the corresponding reference underflow in ip6_mc_source(). The supplied trace from the same unpatched revision additionally shows the access after the RCU read-side section ends: BUG: KASAN: slab-use-after-free in mutex_lock+0x76/0xe0 Write of size 8 at addr ffff888015b50240 by task poc/1219 Bug found and triaged by OpenAI Security Research and validated by Trail of Bits. Fixes: 8814c4b53381 ("[IPV6] ADDRCONF: Convert addrconf_lock to RCU.") Cc: stable@vger.kernel.org Signed-off-by: Kyle Zeng Co-developed-by: David Lee Signed-off-by: David Lee Reviewed-by: Ido Schimmel Link: https://patch.msgid.link/20260803122758.666112-1-david.lee@trailofbits.com Signed-off-by: Jakub Kicinski --- include/net/addrconf.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'include') diff --git a/include/net/addrconf.h b/include/net/addrconf.h index 8ced27a8229b..e6764245995f 100644 --- a/include/net/addrconf.h +++ b/include/net/addrconf.h @@ -405,8 +405,8 @@ static inline struct inet6_dev *in6_dev_get(const struct net_device *dev) rcu_read_lock(); idev = rcu_dereference(dev->ip6_ptr); - if (idev) - refcount_inc(&idev->refcnt); + if (idev && !refcount_inc_not_zero(&idev->refcnt)) + idev = NULL; rcu_read_unlock(); return idev; } -- cgit v1.2.3 From 26444eb71465c9934d9d418ef69c43f61185329b Mon Sep 17 00:00:00 2001 From: "Lorenzo Stoakes (ARM)" Date: Thu, 23 Jul 2026 16:16:31 +0100 Subject: mm/vmalloc: acquire init_mm lock on huge vmap to avoid ptdump UAF Patch series "mm: fix UAF caused by race between ptdump and vmap pgtable freeing", v6. Kernel page table walkers fall into two broad categories - those ranges where no exclusion is required via walk_kernel_page_table_range_lockless() and those where exclusion is required via walk_kernel_page_table_range() or walk_page_range_debug(). The former category is used only by arm64 arch code operating on ranges it both wholly owns and does not concurrently write. The latter category consists of kernel page table walkers operating on ranges that are wholly owned (but which need exclusion against concurrent writers). The lock used for exclusion is the mmap lock, and for kernel ranges this is the mmap lock on init_mm. ptdump is a special case being both the only user of walk_page_range_debug(), and the only case in which it walks ranges it does not own. This presents a problem, as page tables may be freed under ptdump. And indeed there is a use-after-free bug in the kernel as a result, which this series addresses. vmap promotes page tables to huge leaf entries where possible, freeing the lower page table when it does. It does this with no meaningful locks held against concurrent ptdump walks. As a result, use-after-free can currently occur. This series addresses the issue by having the vmap huge promotion logic acquire the mmap read lock while both setting the huge page table entry and freeing the prior leaf page table. The ptdump code already acquires the mmap write lock, so by doing so we ensure that the ptdump walker only ever observes either the huge page table entry or the existing page table entry, and nothing is freed underneath it. A mitigation for this issue was already applied for arm64 in commit fa93b45fd397 ("arm64: Enable vmalloc-huge with ptdump"), which this series has to deal with carefully. This mitigation resolves the issue by acquiring the mmap read lock on init_mm on vmap page table free if a ptdump is in progress. However the fix in this series would cause a deadlock if we were to simply apply it for arm64 without also reverting the change. This is because vmap may acquire the read lock before ptdump attempts to acquire the write lock, which then gets queued, and rwsem starvation rules mean that the (unacknowledged) nested mmap read lock in the arm64 code would also block, meaning the original read lock is never released and thus deadlock. This series works around this by #ifndef CONFIG_ARM64'ing the mmap read lock in vmap logic, then partially reverting commit fa93b45fd397 ("arm64: Enable vmalloc-huge with ptdump"), keeping the enablement of huge vmap support, and removing the ifdeffery with the partial revert patch. There are related issues that are also addressed in this series: * x86 page attribute logic, specifically Change Page Attributes (CPA), implements a feature whereby huge ranges can be collapsed into huge leaf entries. This can similarly cause a UAF when done in parallel with a ptdump walk, so similarly acquire the init_mm mmap lock to avoid this. * The CPA logic allows concurrent page table manipulation and CPA collapse, meaning the former risks accessing a page table the latter frees. Fix this by acquiring mmap write lock on init_mm across the whole CPA collapse operation and read lock on the page table manipulation. * x86 and arm64 permit walks of non-kernel mm's (both allowing efi mm walks, and in x86's case arbitrary mm's), so we ensure kernel mappings remain stable by locking the init_mm as well as the mm being walked. The ordering of patches is established for both strict dependencies (the arm64 partial revert in particular has to be done after the vmap changes) and logical ones (the non-kernel mm fix only makes sense once the vmap/CPA fixes are in place). This patch (of 3): Currently there is a nasty race between ptdump and vmap when attempting to map a huge P4D, PUD or PMD entry: * ptdump walks kernel page table ranges it doesn't own. * When vmap maps ranges it tries to promotes existing ones to huge page tables in vmap_try_huge_[p4d,pud,pmd]() at P4D, PUD and PMD level, freeing the lower page table in [p4d,pud,pmd]_free_[pud,pmd,pte]_page() when it succeeds. Both of these things can happen at the same time and as a result ptdump can access a freed page table, resulting in a use-after-free and memory corruption. This is possible because while ptdump_walk_pgd() holds both the mem hotplug lock and the mmap write lock before invoking walk_page_range_debug(), vmap takes no relevant locks at all. Fix this by holding the mmap read lock in vmap_try_huge_*() when freeing page tables. The read lock is sufficient: ptdump is the only walker that must be excluded and it holds the mmap write lock. Other holders of the read lock may run concurrently, but each exclusively owns the range it operates on and cannot reach the page tables freed here. We also hold the lock while assigning the huge page table entry, which means page table walkers observe only the huge or non-huge page table entry. We use a trylock to prevent ptdump from blocking vmap making forward progress. This is fine because it's an optimisation in any case, and thus the vmap can safely proceed regardless. All other kernel page table walkers that touch vmalloc ranges either exclusively own the memory walked or acquire the mmap lock, so this correctly excludes those walkers. One wrinkle here is commit fa93b45fd397 ("arm64: Enable vmalloc-huge with ptdump"), which addresses the issue for arm64 only by explicitly acquiring the mmap read lock on kernel page table freeing should a concurrent ptdump be in progress. This is problematic as vmap may acquire the mmap read lock prior to ptdump attempting to acquire an mmap write lock, leading to a deadlock when the mmap read lock is slept upon on page table freeing due to rwsem anti-starvation. We work around this by predicating the mmap lock being taken on !CONFIG_ARM64 for the time being. With this patch applied, a follow up will partially revert commit fa93b45fd397 ("arm64: Enable vmalloc-huge with ptdump") and at that stage remove the arm64 ifdeffery. We also update walk_page_range_debug() to assert the mmap write lock unconditionally and update the comment here to reflect this change. The issue has existed as long as ptdump was available and vmap freed page tables when promoting to a huge leaf entry, that is, since commit b6bdb7517c3d ("mm/vmalloc: add interfaces to free unmapped page table") for huge ioremap, and commit 121e6f3258fe ("mm/vmalloc: hugepage vmalloc mappings") for huge vmalloc. Since the former is the earlier of the two we choose that for our Fixes tag. We also define a guard class for mmap_read_trylock() so we can use cleanup.h to make the scope handling cleaner in the implementation. This patch is based on work by David Carlier (linked), with gratitude! Link: https://lore.kernel.org/20260723-series-vmap-race-fix-v6-0-8cc77dcc0018@kernel.org Link: https://lore.kernel.org/20260723-series-vmap-race-fix-v6-1-8cc77dcc0018@kernel.org Fixes: b6bdb7517c3d ("mm/vmalloc: add interfaces to free unmapped page table") Signed-off-by: Lorenzo Stoakes (ARM) Reported-by: syzbot+fd95a72470f5a44e464c@syzkaller.appspotmail.com Closes: https://lore.kernel.org/all/6a287988.39669fcc.33b062.00a0.GAE@google.com/T/ Link: https://lore.kernel.org/linux-mm/20260706203128.162335-1-devnexen@gmail.com/ Reviewed-by: Mike Rapoport (Microsoft) Reviewed-by: Dev Jain Acked-by: David Hildenbrand (Arm) Reviewed-by: Kiryl Shutsemau Cc: Cc: Andy Lutomirski Cc: "Borah, Chaitanya Kumar" Cc: "Borislav Petkov (AMD)" Cc: Catalin Marinas Cc: Dave Hansen Cc: "H. Peter Anvin" Cc: Ingo Molnar Cc: Liam R. Howlett Cc: Michal Hocko Cc: Peter Zijlstra Cc: Ryan Roberts Cc: Shakeel Butt Cc: Suren Baghdasaryan Cc: Toshi Kani Cc: "Uladzislau Rezki (Sony)" Cc: Vlastimil Babka Cc: Will Deacon Signed-off-by: Andrew Morton --- include/linux/mmap_lock.h | 1 + mm/pagewalk.c | 22 +++++++++++---------- mm/vmalloc.c | 49 ++++++++++++++++++++++++++++++++++++++--------- 3 files changed, 53 insertions(+), 19 deletions(-) (limited to 'include') diff --git a/include/linux/mmap_lock.h b/include/linux/mmap_lock.h index 04b8f61ece5d..6b5c2390cc30 100644 --- a/include/linux/mmap_lock.h +++ b/include/linux/mmap_lock.h @@ -621,6 +621,7 @@ static inline void mmap_read_unlock(struct mm_struct *mm) DEFINE_GUARD(mmap_read_lock, struct mm_struct *, mmap_read_lock(_T), mmap_read_unlock(_T)) +DEFINE_GUARD_COND(mmap_read_lock, _try, mmap_read_trylock(_T)) static inline void mmap_read_unlock_non_owner(struct mm_struct *mm) { diff --git a/mm/pagewalk.c b/mm/pagewalk.c index 3ae2586ff45b..bbcfd68d0907 100644 --- a/mm/pagewalk.c +++ b/mm/pagewalk.c @@ -678,6 +678,8 @@ int walk_kernel_page_table_range_lockless(unsigned long start, unsigned long end * will also not lock the PTEs for the pte_entry() callback. * * This is for debugging purposes ONLY. + * + * The mmap write lock must be held. */ int walk_page_range_debug(struct mm_struct *mm, unsigned long start, unsigned long end, const struct mm_walk_ops *ops, @@ -691,6 +693,16 @@ int walk_page_range_debug(struct mm_struct *mm, unsigned long start, .no_vma = true }; + /* + * When walking userland page tables, an mmap write lock must be held to + * account for munmap() downgrading to an mmap read lock when tearing + * down page tables. + * + * When walking kernel page tables, an mmap write lock must also be held + * to account for page table freeing on vmap huge page mapping. + */ + mmap_assert_write_locked(mm); + /* For convenience, we allow traversal of kernel mappings. */ if (mm == &init_mm) return walk_kernel_page_table_range(start, end, ops, @@ -700,16 +712,6 @@ int walk_page_range_debug(struct mm_struct *mm, unsigned long start, if (!check_ops_safe(ops)) return -EINVAL; - /* - * The mmap lock protects the page walker from changes to the page - * tables during the walk. However a read lock is insufficient to - * protect those areas which don't have a VMA as munmap() detaches - * the VMAs before downgrading to a read lock and actually tearing - * down PTEs/page tables. In which case, the mmap write lock should - * be held. - */ - mmap_assert_write_locked(mm); - return walk_pgd_range(start, end, &walk); } diff --git a/mm/vmalloc.c b/mm/vmalloc.c index 1afca3568b9b..d5c4d2bb770b 100644 --- a/mm/vmalloc.c +++ b/mm/vmalloc.c @@ -43,6 +43,7 @@ #include #include #include +#include #define CREATE_TRACE_POINTS #include @@ -158,10 +159,24 @@ static int vmap_try_huge_pmd(pmd_t *pmd, unsigned long addr, unsigned long end, if (!IS_ALIGNED(phys_addr, PMD_SIZE)) return 0; - if (pmd_present(*pmd) && !pmd_free_pte_page(pmd, addr)) - return 0; + if (!pmd_present(*pmd)) + return pmd_set_huge(pmd, phys_addr, prot); - return pmd_set_huge(pmd, phys_addr, prot); + /* + * Acquire the mmap read lock to exclude ptdump, which walks + * kernel page tables it does not own under the mmap write lock. + * + * Concurrent read lock holders are safe: each exclusively owns + * the range it operates on and cannot reach this page table. + */ +#ifndef CONFIG_ARM64 + scoped_cond_guard(mmap_read_lock_try, return 0, &init_mm) +#endif + { + if (!pmd_free_pte_page(pmd, addr)) + return 0; + return pmd_set_huge(pmd, phys_addr, prot); + } } static int vmap_pmd_range(pud_t *pud, unsigned long addr, unsigned long end, @@ -210,10 +225,18 @@ static int vmap_try_huge_pud(pud_t *pud, unsigned long addr, unsigned long end, if (!IS_ALIGNED(phys_addr, PUD_SIZE)) return 0; - if (pud_present(*pud) && !pud_free_pmd_page(pud, addr)) - return 0; + if (!pud_present(*pud)) + return pud_set_huge(pud, phys_addr, prot); - return pud_set_huge(pud, phys_addr, prot); + /* See comment in vmap_try_huge_pmd(). */ +#ifndef CONFIG_ARM64 + scoped_cond_guard(mmap_read_lock_try, return 0, &init_mm) +#endif + { + if (!pud_free_pmd_page(pud, addr)) + return 0; + return pud_set_huge(pud, phys_addr, prot); + } } static int vmap_pud_range(p4d_t *p4d, unsigned long addr, unsigned long end, @@ -262,10 +285,18 @@ static int vmap_try_huge_p4d(p4d_t *p4d, unsigned long addr, unsigned long end, if (!IS_ALIGNED(phys_addr, P4D_SIZE)) return 0; - if (p4d_present(*p4d) && !p4d_free_pud_page(p4d, addr)) - return 0; + if (!p4d_present(*p4d)) + return p4d_set_huge(p4d, phys_addr, prot); - return p4d_set_huge(p4d, phys_addr, prot); + /* See comment in vmap_try_huge_pmd(). */ +#ifndef CONFIG_ARM64 + scoped_cond_guard(mmap_read_lock_try, return 0, &init_mm) +#endif + { + if (!p4d_free_pud_page(p4d, addr)) + return 0; + return p4d_set_huge(p4d, phys_addr, prot); + } } static int vmap_p4d_range(pgd_t *pgd, unsigned long addr, unsigned long end, -- cgit v1.2.3 From c27e360545373b7aee9862a5beef3b9fb3df0c25 Mon Sep 17 00:00:00 2001 From: Hidayath Khan Date: Thu, 30 Jul 2026 14:42:27 +0200 Subject: dibs: initialise dibs->lock in dibs_dev_alloc() dibs->lock is initialised by dibs_dev_add(), but a dibs device can already take interrupts before that call: ism_probe() runs ism_dev_init(), and hence request_irq(), before it calls dibs_dev_add(). No client can have registered a dmb at that point, so no dmb interrupt can occur, but a GID event interrupt can, and ism_handle_irq() takes dibs->lock unconditionally on entry, before it inspects anything else. Initialise the lock in dibs_dev_alloc() instead, so that it is valid as soon as a driver can publish the device to its interrupt handler. Fixes: cc21191b584c ("dibs: Move data path to dibs layer") Cc: stable@vger.kernel.org Reviewed-by: Alexandra Winter Signed-off-by: Hidayath Khan Link: https://patch.msgid.link/20260730124227.167829-1-hidayath@linux.ibm.com Signed-off-by: Jakub Kicinski --- drivers/dibs/dibs_main.c | 2 +- include/linux/dibs.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'include') diff --git a/drivers/dibs/dibs_main.c b/drivers/dibs/dibs_main.c index 14c3e2d84902..4c26fd06973f 100644 --- a/drivers/dibs/dibs_main.c +++ b/drivers/dibs/dibs_main.c @@ -138,6 +138,7 @@ struct dibs_dev *dibs_dev_alloc(void) dibs = kzalloc_obj(*dibs); if (!dibs) return dibs; + spin_lock_init(&dibs->lock); dibs->dev.release = dibs_dev_release; dibs->dev.class = &dibs_class; device_initialize(&dibs->dev); @@ -186,7 +187,6 @@ int dibs_dev_add(struct dibs_dev *dibs) int i, ret; max_dmbs = dibs->ops->max_dmbs(); - spin_lock_init(&dibs->lock); dibs->dmb_clientid_arr = kzalloc(max_dmbs, GFP_KERNEL); if (!dibs->dmb_clientid_arr) return -ENOMEM; diff --git a/include/linux/dibs.h b/include/linux/dibs.h index c75607f8a5cf..d3e0777f25ae 100644 --- a/include/linux/dibs.h +++ b/include/linux/dibs.h @@ -439,7 +439,7 @@ static inline void *dibs_get_priv(struct dibs_dev *dev, /** * dibs_dev_alloc() - allocate and reference device structure * - * The following fields will be valid upon successful return: dev + * The following fields will be valid upon successful return: dev, lock * NOTE: Use put_device(dibs_get_dev(@dibs)) to give up your reference instead * of freeing @dibs @dev directly once you have successfully called this * function. -- cgit v1.2.3 From dedd34b0f2310e28c5f6d4875cfbf4b7ed821c01 Mon Sep 17 00:00:00 2001 From: Zijie Huang Date: Sat, 1 Aug 2026 21:42:33 +0800 Subject: net/sched: reject overly deep qdisc hierarchies Deep qdisc hierarchies can lead to excessive recursion in qdisc tree walkers and exhaust the kernel stack. The existing loop check does not cover the create-and-graft path, so a hierarchy can still be extended by creating a new child qdisc below an already deep parent. Store the hierarchy depth in struct Qdisc and update it when qdiscs are grafted. Reject new child qdiscs once the parent is already at the maximum allowed depth. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Cc: stable@vger.kernel.org Suggested-by: Jamal Hadi Salim Reported-by: Vega Assisted-by: Codex:gpt-5.4 Signed-off-by: Zijie Huang Signed-off-by: Ren Wei Reviewed-by: Victor Nogueira Link: https://patch.msgid.link/1e9ab39597423fd5d13cfaaf52279b8ee3d9fc3c.1785434373.git.milkory@outlook.com Acked-by: Jamal Hadi Salim Signed-off-by: Paolo Abeni --- include/net/sch_generic.h | 1 + net/sched/sch_api.c | 9 +++++++++ 2 files changed, 10 insertions(+) (limited to 'include') diff --git a/include/net/sch_generic.h b/include/net/sch_generic.h index 45a1e8c78222..cbc248776511 100644 --- a/include/net/sch_generic.h +++ b/include/net/sch_generic.h @@ -99,6 +99,7 @@ struct Qdisc { struct hlist_node hash; u32 handle; u32 parent; + int depth; struct netdev_queue *dev_queue; diff --git a/net/sched/sch_api.c b/net/sched/sch_api.c index 668bcd60d183..65b35528d125 100644 --- a/net/sched/sch_api.c +++ b/net/sched/sch_api.c @@ -1114,6 +1114,9 @@ static int qdisc_graft(struct net_device *dev, struct Qdisc *parent, unsigned int i, num_q, ingress; struct netdev_queue *dev_queue; + if (new) + new->depth = 0; + ingress = 0; num_q = dev->num_tx_queues; if ((q && q->flags & TCQ_F_INGRESS) || @@ -1211,9 +1214,15 @@ skip: NL_SET_ERR_MSG(extack, "STAB not supported on a non root"); return -EINVAL; } + if (new && parent->depth >= 7) { + NL_SET_ERR_MSG(extack, "Qdisc hierarchy is too deep"); + return -E2BIG; + } err = cops->graft(parent, cl, new, &old, extack); if (err) return err; + if (new) + new->depth = parent->depth + 1; notify_and_destroy(net, skb, n, classid, old, new, extack); } return 0; -- cgit v1.2.3 From 3b9a324e646d3657a8d9806dfbfe4f3e4066e882 Mon Sep 17 00:00:00 2001 From: Qihang Tang Date: Wed, 5 Aug 2026 20:57:27 +0800 Subject: net: remove CAP_SYS_RAWIO zero-padding in dev_validate_header dev_validate_header() reads dev->hard_header_len directly when zero-padding short link layer headers for CAP_SYS_RAWIO holders: if (capable(CAP_SYS_RAWIO)) { memset(ll_header + len, 0, dev->hard_header_len - len); return true; } Packet send paths call dev_validate_header() on skbs whose headroom was allocated from an earlier hard_header_len read. If the device is reconfigured so that dev->hard_header_len increases before validation, the memset writes past the reserved buffer, an out-of-bounds write. This out-of-bounds write is masked in some SOCK_RAW paths today because the same concurrent increase can first make skb_push() exceed the reserved headroom and trigger skb_under_panic(). Remove the zero-padding branch before making those hard_header_len reads consistent, so the snapshot fixes do not turn a loud panic into a silent overwrite. This path is only reached for variable length L2 protocols, where len < hard_header_len but len >= min_header_len. No remaining in-tree variable length L2 protocol implements header_ops->validate, and the CAP_SYS_RAWIO bypass that zero-pads and accepts short headers has no real value beyond allowing testing of intentionally malformed input. Drop the CAP_SYS_RAWIO branch. The remaining reads of dev->hard_header_len in dev_validate_header() are comparisons only and have no memory safety impact. Suggested-by: Willem de Bruijn Fixes: 2793a23aacbd ("net: validate variable length ll headers") Cc: stable@vger.kernel.org Signed-off-by: Qihang Tang Reviewed-by: Willem de Bruijn Link: https://patch.msgid.link/20260805125729.19220-2-q.h.hack.winter@gmail.com Signed-off-by: Jakub Kicinski --- include/linux/netdevice.h | 5 ----- 1 file changed, 5 deletions(-) (limited to 'include') diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h index 9981d637f8b5..9a770eb823ce 100644 --- a/include/linux/netdevice.h +++ b/include/linux/netdevice.h @@ -3531,11 +3531,6 @@ static inline bool dev_validate_header(const struct net_device *dev, if (len < dev->min_header_len) return false; - if (capable(CAP_SYS_RAWIO)) { - memset(ll_header + len, 0, dev->hard_header_len - len); - return true; - } - if (dev->header_ops && dev->header_ops->validate) return dev->header_ops->validate(ll_header, len); -- cgit v1.2.3 From 03390aa32e669cc4ecd7d34108e2e1afc13d689d Mon Sep 17 00:00:00 2001 From: Qihang Tang Date: Wed, 5 Aug 2026 20:57:28 +0800 Subject: packet: use consistent hard_header_len in non-ring send paths packet_snd() reads dev->hard_header_len multiple times while allocating and constructing an skb. Device reconfiguration can change this value concurrently, for example through bonding device type changes. For SOCK_RAW, packet_snd() can save a larger value in reserve and later allocate headroom using a smaller value. Moving skb->data back by reserve then places it before skb->head, and the following copy from userspace can attempt an out-of-bounds write. packet_sendmsg_spkt() has the same issue because it calculates its reservation and header offset from separate reads before dropping the RCU read lock to allocate the skb. Add LL_RESERVED_SPACE_EX() for callers that already saved a header length. Read hard_header_len once in packet_snd() and use it for allocation and construction. In packet_sendmsg_spkt(), preserve the allocation-time value through the device lookup retry. The separate SOCK_DGRAM consistency problem between hard_header_len and header_ops->create is not addressed here. Fixes: b84bbaf7a6c8 ("packet: in packet_snd start writing at link layer allocation") Cc: stable@vger.kernel.org Signed-off-by: Qihang Tang Reviewed-by: Willem de Bruijn Link: https://patch.msgid.link/20260805125729.19220-3-q.h.hack.winter@gmail.com Signed-off-by: Jakub Kicinski --- include/linux/netdevice.h | 6 ++++-- net/packet/af_packet.c | 26 ++++++++++++++++---------- 2 files changed, 20 insertions(+), 12 deletions(-) (limited to 'include') diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h index 9a770eb823ce..8840b126979f 100644 --- a/include/linux/netdevice.h +++ b/include/linux/netdevice.h @@ -300,9 +300,11 @@ struct hh_cache { * We could use other alignment values, but we must maintain the * relationship HH alignment <= LL alignment. */ -#define LL_RESERVED_SPACE(dev) \ - ((((dev)->hard_header_len + READ_ONCE((dev)->needed_headroom)) \ +#define LL_RESERVED_SPACE_EX(dev, hlen) \ + ((((hlen) + READ_ONCE((dev)->needed_headroom)) \ & ~(HH_DATA_MOD - 1)) + HH_DATA_MOD) +#define LL_RESERVED_SPACE(dev) \ + LL_RESERVED_SPACE_EX(dev, (dev)->hard_header_len) #define LL_RESERVED_SPACE_EXTRA(dev,extra) \ ((((dev)->hard_header_len + READ_ONCE((dev)->needed_headroom) + (extra)) \ & ~(HH_DATA_MOD - 1)) + HH_DATA_MOD) diff --git a/net/packet/af_packet.c b/net/packet/af_packet.c index 0e1355be89f6..b7af45c809e4 100644 --- a/net/packet/af_packet.c +++ b/net/packet/af_packet.c @@ -1966,8 +1966,9 @@ static int packet_sendmsg_spkt(struct socket *sock, struct msghdr *msg, struct net_device *dev; struct sockcm_cookie sockc; __be16 proto = 0; - int err; + int hard_header_len; int extra_len = 0; + int err; /* * Get and verify the address. @@ -2010,14 +2011,18 @@ retry: extra_len = 4; /* We're doing our own CRC */ } + /* Keep the allocation-time header length across retry. */ + if (!skb) + hard_header_len = READ_ONCE(dev->hard_header_len); + err = -EMSGSIZE; - if (len > dev->mtu + dev->hard_header_len + VLAN_HLEN + extra_len) + if (len > dev->mtu + hard_header_len + VLAN_HLEN + extra_len) goto out_unlock; if (!skb) { - size_t reserved = LL_RESERVED_SPACE(dev); + size_t reserved = LL_RESERVED_SPACE_EX(dev, hard_header_len); int tlen = dev->needed_tailroom; - unsigned int hhlen = dev->header_ops ? dev->hard_header_len : 0; + unsigned int hhlen = dev->header_ops ? hard_header_len : 0; rcu_read_unlock(); skb = sock_wmalloc(sk, len + reserved + tlen, 0, GFP_KERNEL); @@ -2047,7 +2052,7 @@ retry: err = -EINVAL; goto out_unlock; } - if (len > (dev->mtu + dev->hard_header_len + extra_len) && + if (len > (dev->mtu + hard_header_len + extra_len) && !packet_extra_vlan_len_allowed(dev, skb)) { err = -EMSGSIZE; goto out_unlock; @@ -2969,7 +2974,7 @@ static int packet_snd(struct socket *sock, struct msghdr *msg, size_t len) int offset = 0; struct packet_sock *po = pkt_sk(sk); int vnet_hdr_sz = READ_ONCE(po->vnet_hdr_sz); - int hlen, tlen, linear; + int hard_header_len, hlen, tlen, linear; int extra_len = 0; /* @@ -3009,8 +3014,9 @@ static int packet_snd(struct socket *sock, struct msghdr *msg, size_t len) goto out_unlock; } + hard_header_len = READ_ONCE(dev->hard_header_len); if (sock->type == SOCK_RAW) - reserve = dev->hard_header_len; + reserve = hard_header_len; if (vnet_hdr_sz) { err = packet_snd_vnet_parse(msg, &len, &vnet_hdr, vnet_hdr_sz); if (err) @@ -3031,10 +3037,10 @@ static int packet_snd(struct socket *sock, struct msghdr *msg, size_t len) goto out_unlock; err = -ENOBUFS; - hlen = LL_RESERVED_SPACE(dev); + hlen = LL_RESERVED_SPACE_EX(dev, hard_header_len); tlen = dev->needed_tailroom; linear = __virtio16_to_cpu(vio_le(), vnet_hdr.hdr_len); - linear = max(linear, min_t(int, len, dev->hard_header_len)); + linear = max(linear, min_t(int, len, hard_header_len)); skb = packet_alloc_skb(sk, hlen + tlen, hlen, len, linear, msg->msg_flags & MSG_DONTWAIT, &err); if (skb == NULL) @@ -3050,7 +3056,7 @@ static int packet_snd(struct socket *sock, struct msghdr *msg, size_t len) } else if (reserve) { skb_reserve(skb, -reserve); if (len < reserve + sizeof(struct ipv6hdr) && - dev->min_header_len != dev->hard_header_len) + dev->min_header_len != hard_header_len) skb_reset_network_header(skb); } -- cgit v1.2.3