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 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