From: Greg Kroah-Hartman Date: Thu, 30 Jul 2026 13:14:40 +0000 (+0200) Subject: 6.6-stable patches X-Git-Tag: v6.6.148~29 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=211ccbc3568d3b061851fa20f220b792deb81a5c;p=thirdparty%2Fkernel%2Fstable-queue.git 6.6-stable patches added patches: afs-annotate-struct-afs_addr_list-with-__counted_by.patch afs-turn-the-afs_addr_list-address-array-into-an-array-of-structs.patch alsa-hda-conexant-remove-mic-bias-threshold-override.patch alsa-hda-fix-cached-processing-coefficient-verbs.patch asoc-mediatek-mt8183-check-runtime-resume-during-probe.patch asoc-mediatek-mt8192-afe-pcm-convert-to-devm_pm_runtime_enable.patch asoc-mediatek-mt8192-afe-pcm-simplify-probe-with-local-dev-variable.patch asoc-mediatek-mt8192-afe-pcm-simplify-with-dev_err_probe.patch asoc-mediatek-mt8192-check-runtime-resume-during-probe.patch asoc-mediatek-mt8195-remove-afe-dai-component-and-rework-codec-link.patch asoc-mediatek-use-common-mtk_afe_pcm_platform-with-common-probe-cb.patch ata-libata-core-reject-an-invalid-concurrent-positioning-ranges-count.patch audit-fix-recursive-locking-deadlock-in-audit_dupe_exe.patch audit-use-unsigned-int-instead-of-unsigned.patch audit-widen-ino-fields-to-u64.patch bootconfig-do-not-put-quotes-on-cmdline-items-unless-necessary.patch bootconfig-fix-null-pointer-arithmetic-in-xbc_snprint_cmdline.patch bootconfig-move-xbc_snprint_cmdline-to-lib-bootconfig.c.patch bpf-reject-bpf_map_type_inode_storage-creation-if-bpf-lsm-is-uninitialized.patch dm-verity-avoid-double-increment-of-use_bh_wq_enabled.patch dm-verity-make-error-counter-atomic.patch dma-buf-udmabuf-skip-redundant-cpu-sync-to-fix-cacheline-eexist-warning.patch dma-dw-edma-fix-build-warning-in-dw_edma_pcie_probe.patch dmaengine-dw-edma-fix-confusing-cleanup.h-syntax.patch dmaengine-dw-edma-pcie-reject-devices-without-driver-data.patch fbcon-rename-struct-fbcon_ops-to-struct-fbcon_par.patch fbcon-use-correct-type-for-vc_resize-return-value.patch fbdev-efifb-fix-memory-leak-in-efifb_probe.patch fbdev-efifb-replace-references-to-global-screen_info-by-local-pointer.patch firmware_loader-introduce-__free-cleanup-hanler.patch fpga-dfl-afu-validate-dma-mapping-length-in-afu_dma_map_region.patch fs-resctrl-fix-double-add-of-pseudo-locked-region-s-rmid-to-free-list.patch gpio-mt7621-avoid-corruption-of-shared-interrupt-trigger-state.patch gpio-tegra-do-not-call-pinctrl-for-gpio-direction.patch i2c-davinci-unregister-cpufreq-notifier-on-probe-failure.patch i2c-i801-fix-hardware-state-machine-corruption-in-error-path.patch i2c-imx-fix-locked-bus-on-smbus-block-read-of-0-atomic.patch i2c-imx-separate-atomic-dma-and-non-dma-use-case.patch input-ims-pcu-fix-firmware-leak-in-async-update.patch iommu-amd-don-t-split-flush-for-amd_iommu_domain_flush_all.patch ipmi-fix-refcount-leak-in-i_ipmi_request.patch kvm-introduce-vcpu-wants_to_run.patch kvm-x86-only-reset-tsc-deadline-timer-in-apic_timer_expired-on-kvm_run.patch locking-rt-fix-the-incorrect-rcu-protection-in-rt_spin_unlock.patch lsm-infrastructure-management-of-the-sock-security.patch mm-mm_init-fix-pageblock-migratetype-for-zone_device-compound-pages.patch mmc-vub300-fix-use-after-free-on-disconnect.patch mmc-vub300-fix-use-after-free-on-probe-failure.patch mmc-vub300-rename-probe-error-labels.patch mtd-maps-vmu-flash-fix-fault-in-unaligned-fixup.patch mtd-rawnand-add-a-helper-for-calculating-a-page-index.patch mtd-rawnand-ensure-all-continuous-terms-are-always-in-sync.patch mtd-rawnand-pause-continuous-reads-at-block-boundaries.patch net-ip6_tunnel-require-cap_net_admin-in-the-device-netns-for-changelink.patch net-ipa-fix-smem-state-handle-leaks-in-smp2p-init.patch net-macb-drop-in-flight-tx-skbs-on-close.patch net-mana-validate-the-packet-length-reported-by-the-nic.patch netfilter-nf_conntrack_sip-remove-net-variable-shadowing.patch netfilter-nf_conntrack_sip-validate-skb_dst-before-accessing-it.patch netfilter-nf_tables-remove-register-tracking-infrastructure.patch netfilter-nf_tables-remove-unused-nft_reduce_is_readonly.patch netfilter-nft_fib-reject-fib-expression-on-the-netdev-egress-hook.patch netfilter-nft_set_pipapo-don-t-leak-bad-clone-into-future-transaction.patch netfilter-nft_set_pipapo-make-pipapo_clone-helper-return-null.patch netfilter-nft_set_pipapo-merge-deactivate-helper-into-caller.patch netfilter-nft_set_pipapo-move-cloning-of-match-info-to-insert-removal-path.patch netfilter-nft_set_pipapo-move-prove_locking-helper-around.patch netfilter-nft_set_pipapo-prepare-pipapo_get-helper-for-on-demand-clone.patch netfilter-nft_set_pipapo-prepare-walk-function-for-on-demand-clone.patch netfilter-nft_set_pipapo-use-gfp_kernel-for-insertions.patch nfs-charge-unstable-writes-by-request-size-not-folio-size.patch nfs-remove-dead-code-for-the-old-swap-over-nfs-implementation.patch nfsd-pass-nfsd_file-to-nfsd_iter_read.patch octeontx2-af-cn10k-restrict-vf-lmtline-sharing-to-its-own-pf.patch octeontx2-annotate-mmio-regions-as-__iomem.patch octeontx2-pf-clear-stale-mailbox-irq-state-before-request_irq.patch octeontx2-pf-fix-sqb-pointer-leak-on-init-failure.patch octeontx2-vf-clear-stale-mailbox-irq-state-before-request_irq.patch ovl-use-linked-upper-dentry-in-copy-up-tmpfile.patch pinctrl-remove-pinctrl_gpio_direction_output.patch platform-x86-dell-laptop-fix-missing-cleanups-in-init-error-path.patch platform-x86-dell-smbios-move-request-functions-for-reuse.patch rxrpc-don-t-need-barrier-for-tx_bottom-and-acks_hard_ack.patch rxrpc-fix-notification-vs-call-release-vs-recvmsg.patch rxrpc-fix-socket-notification-race.patch rxrpc-pull-out-certain-app-callback-funcs-into-an-ops-table.patch rxrpc-serialize-kernel-accept-preallocation-with-socket-teardown.patch rxrpc-use-irq-disabling-spinlocks-between-app-and-i-o-thread.patch selinux-avoid-sk_socket-dereference-in-selinux_sctp_bind_connect.patch serial-max310x-implement-gpio_chip-get_direction.patch serial-max310x-replace-bare-use-of-unsigned-with-unsigned-int-checkpatch.patch sunrpc-add-helpers-to-convert-xdr_buf-byte-ranges-to-scatterlists.patch sunrpc-allocate-a-separate-bvec-array-for-socket-sends.patch sunrpc-return-an-error-from-xdr_buf_to_bvec-on-overflow.patch taskstats-fill_stats_for_tgid-use-for_each_thread.patch taskstats-retain-dead-thread-stats-in-tgid-queries.patch thunderbolt-handle-lane-bonding-of-gen-4-xdomain-links-properly.patch thunderbolt-keep-xdomain-reference-during-the-lifetime-of-a-service.patch thunderbolt-prevent-xdomain-delayed-work-use-after-free-on-disconnect.patch thunderbolt-remove-service-debugfs-entries-during-unregister.patch thunderbolt-remove-usage-of-the-deprecated-ida_simple_xx-api.patch thunderbolt-remove-xdomain-from-the-bus-without-holding-tb-lock.patch thunderbolt-update-property.c-function-documentation.patch tipc-restrict-socket-queue-dumps-in-enqueue-tracepoints.patch tracing-user_events-fix-use-after-free-in-user_event_mm_dup.patch udmabuf-do-not-create-malformed-scatterlists.patch vduse-avoid-leaking-information-to-userspace.patch vduse-remove-unused-vaddr-parameter-of-vduse_domain_free_coherent.patch vduse-take-out-allocations-from-vduse_dev_alloc_coherent.patch vduse-use-fixed-4kb-bounce-pages-for-non-4kb-page-size.patch vfs-audit-introduce-kern_path_parent-for-audit.patch wifi-libertas_tf-fix-use-after-free-in-lbtf_free_adapter.patch workqueue-add-system_percpu_wq-and-system_dfl_wq.patch workqueue-factor-out-init_cpu_worker_pool.patch --- diff --git a/queue-6.6/afs-annotate-struct-afs_addr_list-with-__counted_by.patch b/queue-6.6/afs-annotate-struct-afs_addr_list-with-__counted_by.patch new file mode 100644 index 0000000000..f7a49883a2 --- /dev/null +++ b/queue-6.6/afs-annotate-struct-afs_addr_list-with-__counted_by.patch @@ -0,0 +1,46 @@ +From stable+bounces-287896-greg=kroah.com@vger.kernel.org Wed Jul 22 17:03:27 2026 +From: Sasha Levin +Date: Wed, 22 Jul 2026 10:48:33 -0400 +Subject: afs: Annotate struct afs_addr_list with __counted_by +To: stable@vger.kernel.org +Cc: Kees Cook , David Howells , Marc Dionne , linux-afs@lists.infradead.org, "Gustavo A. R. Silva" , Sasha Levin +Message-ID: <20260722144836.1601242-1-sashal@kernel.org> + +From: Kees Cook + +[ Upstream commit 2d26302bdff1300029a67931739f5770eac22493 ] + +Prepare for the coming implementation by GCC and Clang of the __counted_by +attribute. Flexible array members annotated with __counted_by can have +their accesses bounds-checked at run-time checking via CONFIG_UBSAN_BOUNDS +(for array indexing) and CONFIG_FORTIFY_SOURCE (for strcpy/memcpy-family +functions). + +As found with Coccinelle[1], add __counted_by for struct afs_addr_list. + +[1] https://github.com/kees/kernel-tools/blob/trunk/coccinelle/examples/counted_by.cocci + +Cc: David Howells +Cc: Marc Dionne +Cc: linux-afs@lists.infradead.org +Reviewed-by: "Gustavo A. R. Silva" +Link: https://lore.kernel.org/r/20230915201449.never.649-kees@kernel.org +Signed-off-by: Kees Cook +Stable-dep-of: dc175389b18c ("rxrpc: serialize kernel accept preallocation with socket teardown") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + fs/afs/internal.h | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +--- a/fs/afs/internal.h ++++ b/fs/afs/internal.h +@@ -87,7 +87,7 @@ struct afs_addr_list { + enum dns_lookup_status status:8; + unsigned long failed; /* Mask of addrs that failed locally/ICMP */ + unsigned long responded; /* Mask of addrs that responded */ +- struct sockaddr_rxrpc addrs[]; ++ struct sockaddr_rxrpc addrs[] __counted_by(max_addrs); + #define AFS_MAX_ADDRESSES ((unsigned int)(sizeof(unsigned long) * 8)) + }; + diff --git a/queue-6.6/afs-turn-the-afs_addr_list-address-array-into-an-array-of-structs.patch b/queue-6.6/afs-turn-the-afs_addr_list-address-array-into-an-array-of-structs.patch new file mode 100644 index 0000000000..692a53d2fd --- /dev/null +++ b/queue-6.6/afs-turn-the-afs_addr_list-address-array-into-an-array-of-structs.patch @@ -0,0 +1,250 @@ +From stable+bounces-287897-greg=kroah.com@vger.kernel.org Wed Jul 22 16:50:51 2026 +From: Sasha Levin +Date: Wed, 22 Jul 2026 10:48:34 -0400 +Subject: afs: Turn the afs_addr_list address array into an array of structs +To: stable@vger.kernel.org +Cc: David Howells , Marc Dionne , linux-afs@lists.infradead.org, Sasha Levin +Message-ID: <20260722144836.1601242-2-sashal@kernel.org> + +From: David Howells + +[ Upstream commit 07f3502b33a260f873e35708d2fa693eb52225cb ] + +Turn the afs_addr_list address array into an array of structs, thereby +allowing per-address (such as RTT) info to be added. + +Signed-off-by: David Howells +cc: Marc Dionne +cc: linux-afs@lists.infradead.org +Stable-dep-of: dc175389b18c ("rxrpc: serialize kernel accept preallocation with socket teardown") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + fs/afs/addr_list.c | 10 +++++----- + fs/afs/fs_probe.c | 6 +++--- + fs/afs/internal.h | 6 +++++- + fs/afs/proc.c | 4 ++-- + fs/afs/rotate.c | 2 +- + fs/afs/rxrpc.c | 4 ++-- + fs/afs/server.c | 4 ++-- + fs/afs/vl_alias.c | 4 ++-- + fs/afs/vl_probe.c | 6 +++--- + fs/afs/vl_rotate.c | 2 +- + 10 files changed, 26 insertions(+), 22 deletions(-) + +--- a/fs/afs/addr_list.c ++++ b/fs/afs/addr_list.c +@@ -45,7 +45,7 @@ struct afs_addr_list *afs_alloc_addrlist + alist->max_addrs = nr; + + for (i = 0; i < nr; i++) { +- struct sockaddr_rxrpc *srx = &alist->addrs[i]; ++ struct sockaddr_rxrpc *srx = &alist->addrs[i].srx; + srx->srx_family = AF_RXRPC; + srx->srx_service = service; + srx->transport_type = SOCK_DGRAM; +@@ -281,7 +281,7 @@ void afs_merge_fs_addr4(struct afs_addr_ + return; + + for (i = 0; i < alist->nr_ipv4; i++) { +- struct sockaddr_in *a = &alist->addrs[i].transport.sin; ++ struct sockaddr_in *a = &alist->addrs[i].srx.transport.sin; + u32 a_addr = ntohl(a->sin_addr.s_addr); + u16 a_port = ntohs(a->sin_port); + +@@ -298,7 +298,7 @@ void afs_merge_fs_addr4(struct afs_addr_ + alist->addrs + i, + sizeof(alist->addrs[0]) * (alist->nr_addrs - i)); + +- srx = &alist->addrs[i]; ++ srx = &alist->addrs[i].srx; + srx->srx_family = AF_RXRPC; + srx->transport_type = SOCK_DGRAM; + srx->transport_len = sizeof(srx->transport.sin); +@@ -321,7 +321,7 @@ void afs_merge_fs_addr6(struct afs_addr_ + return; + + for (i = alist->nr_ipv4; i < alist->nr_addrs; i++) { +- struct sockaddr_in6 *a = &alist->addrs[i].transport.sin6; ++ struct sockaddr_in6 *a = &alist->addrs[i].srx.transport.sin6; + u16 a_port = ntohs(a->sin6_port); + + diff = memcmp(xdr, &a->sin6_addr, 16); +@@ -338,7 +338,7 @@ void afs_merge_fs_addr6(struct afs_addr_ + alist->addrs + i, + sizeof(alist->addrs[0]) * (alist->nr_addrs - i)); + +- srx = &alist->addrs[i]; ++ srx = &alist->addrs[i].srx; + srx->srx_family = AF_RXRPC; + srx->transport_type = SOCK_DGRAM; + srx->transport_len = sizeof(srx->transport.sin6); +--- a/fs/afs/fs_probe.c ++++ b/fs/afs/fs_probe.c +@@ -153,12 +153,12 @@ responded: + if (call->service_id == YFS_FS_SERVICE) { + server->probe.is_yfs = true; + set_bit(AFS_SERVER_FL_IS_YFS, &server->flags); +- alist->addrs[index].srx_service = call->service_id; ++ alist->addrs[index].srx.srx_service = call->service_id; + } else { + server->probe.not_yfs = true; + if (!server->probe.is_yfs) { + clear_bit(AFS_SERVER_FL_IS_YFS, &server->flags); +- alist->addrs[index].srx_service = call->service_id; ++ alist->addrs[index].srx.srx_service = call->service_id; + } + cap0 = ntohl(call->tmp); + if (cap0 & AFS3_VICED_CAPABILITY_64BITFILES) +@@ -182,7 +182,7 @@ out: + spin_unlock(&server->probe_lock); + + _debug("probe %pU [%u] %pISpc rtt=%u ret=%d", +- &server->uuid, index, &alist->addrs[index].transport, ++ &server->uuid, index, &alist->addrs[index].srx.transport, + rtt_us, ret); + + return afs_done_one_fs_probe(call->net, server); +--- a/fs/afs/internal.h ++++ b/fs/afs/internal.h +@@ -87,7 +87,9 @@ struct afs_addr_list { + enum dns_lookup_status status:8; + unsigned long failed; /* Mask of addrs that failed locally/ICMP */ + unsigned long responded; /* Mask of addrs that responded */ +- struct sockaddr_rxrpc addrs[] __counted_by(max_addrs); ++ struct { ++ struct sockaddr_rxrpc srx; ++ } addrs[] __counted_by(max_addrs); + #define AFS_MAX_ADDRESSES ((unsigned int)(sizeof(unsigned long) * 8)) + }; + +@@ -972,6 +974,8 @@ extern void afs_put_addrlist(struct afs_ + extern struct afs_vlserver_list *afs_parse_text_addrs(struct afs_net *, + const char *, size_t, char, + unsigned short, unsigned short); ++bool afs_addr_list_same(const struct afs_addr_list *a, ++ const struct afs_addr_list *b); + extern struct afs_vlserver_list *afs_dns_query(struct afs_cell *, time64_t *); + extern bool afs_iterate_addresses(struct afs_addr_cursor *); + extern int afs_end_cursor(struct afs_addr_cursor *); +--- a/fs/afs/proc.c ++++ b/fs/afs/proc.c +@@ -307,7 +307,7 @@ static int afs_proc_cell_vlservers_show( + for (i = 0; i < alist->nr_addrs; i++) + seq_printf(m, " %c %pISpc\n", + alist->preferred == i ? '>' : '-', +- &alist->addrs[i].transport); ++ &alist->addrs[i].srx.transport); + } + seq_printf(m, " info: fl=%lx rtt=%d\n", vlserver->flags, vlserver->rtt); + seq_printf(m, " probe: fl=%x e=%d ac=%d out=%d\n", +@@ -399,7 +399,7 @@ static int afs_proc_servers_show(struct + alist->version, alist->responded, alist->failed); + for (i = 0; i < alist->nr_addrs; i++) + seq_printf(m, " [%x] %pISpc%s\n", +- i, &alist->addrs[i].transport, ++ i, &alist->addrs[i].srx.transport, + alist->preferred == i ? "*" : ""); + return 0; + } +--- a/fs/afs/rotate.c ++++ b/fs/afs/rotate.c +@@ -409,7 +409,7 @@ iterate_address: + + _debug("address [%u] %u/%u %pISp", + op->index, op->ac.index, op->ac.alist->nr_addrs, +- &op->ac.alist->addrs[op->ac.index].transport); ++ &op->ac.alist->addrs[op->ac.index].srx.transport); + + _leave(" = t"); + return true; +--- a/fs/afs/rxrpc.c ++++ b/fs/afs/rxrpc.c +@@ -302,7 +302,7 @@ static void afs_notify_end_request_tx(st + */ + void afs_make_call(struct afs_addr_cursor *ac, struct afs_call *call, gfp_t gfp) + { +- struct sockaddr_rxrpc *srx = &ac->alist->addrs[ac->index]; ++ struct sockaddr_rxrpc *srx = &ac->alist->addrs[ac->index].srx; + struct rxrpc_call *rxcall; + struct msghdr msg; + struct kvec iov[1]; +@@ -474,7 +474,7 @@ static void afs_log_error(struct afs_cal + max = m + 1; + pr_notice("kAFS: Peer reported %s failure on %s [%pISp]\n", + msg, call->type->name, +- &call->alist->addrs[call->addr_ix].transport); ++ &call->alist->addrs[call->addr_ix].srx.transport); + } + } + +--- a/fs/afs/server.c ++++ b/fs/afs/server.c +@@ -43,7 +43,7 @@ struct afs_server *afs_find_server(struc + hlist_for_each_entry_rcu(server, &net->fs_addresses6, addr6_link) { + alist = rcu_dereference(server->addresses); + for (i = alist->nr_ipv4; i < alist->nr_addrs; i++) { +- b = &alist->addrs[i].transport.sin6; ++ b = &alist->addrs[i].srx.transport.sin6; + diff = ((u16 __force)a->sin6_port - + (u16 __force)b->sin6_port); + if (diff == 0) +@@ -59,7 +59,7 @@ struct afs_server *afs_find_server(struc + hlist_for_each_entry_rcu(server, &net->fs_addresses4, addr4_link) { + alist = rcu_dereference(server->addresses); + for (i = 0; i < alist->nr_ipv4; i++) { +- b = &alist->addrs[i].transport.sin; ++ b = &alist->addrs[i].srx.transport.sin; + diff = ((u16 __force)a->sin_port - + (u16 __force)b->sin_port); + if (diff == 0) +--- a/fs/afs/vl_alias.c ++++ b/fs/afs/vl_alias.c +@@ -94,8 +94,8 @@ static int afs_compare_fs_alists(const s + lb = rcu_dereference(server_b->addresses); + + while (a < la->nr_addrs && b < lb->nr_addrs) { +- const struct sockaddr_rxrpc *srx_a = &la->addrs[a]; +- const struct sockaddr_rxrpc *srx_b = &lb->addrs[b]; ++ const struct sockaddr_rxrpc *srx_a = &la->addrs[a].srx; ++ const struct sockaddr_rxrpc *srx_b = &lb->addrs[b].srx; + int diff = afs_compare_addrs(srx_a, srx_b); + + if (diff < 0) { +--- a/fs/afs/vl_probe.c ++++ b/fs/afs/vl_probe.c +@@ -106,12 +106,12 @@ responded: + if (call->service_id == YFS_VL_SERVICE) { + server->probe.flags |= AFS_VLSERVER_PROBE_IS_YFS; + set_bit(AFS_VLSERVER_FL_IS_YFS, &server->flags); +- alist->addrs[index].srx_service = call->service_id; ++ alist->addrs[index].srx.srx_service = call->service_id; + } else { + server->probe.flags |= AFS_VLSERVER_PROBE_NOT_YFS; + if (!(server->probe.flags & AFS_VLSERVER_PROBE_IS_YFS)) { + clear_bit(AFS_VLSERVER_FL_IS_YFS, &server->flags); +- alist->addrs[index].srx_service = call->service_id; ++ alist->addrs[index].srx.srx_service = call->service_id; + } + } + +@@ -131,7 +131,7 @@ out: + spin_unlock(&server->probe_lock); + + _debug("probe [%u][%u] %pISpc rtt=%u ret=%d", +- server_index, index, &alist->addrs[index].transport, rtt_us, ret); ++ server_index, index, &alist->addrs[index].srx.transport, rtt_us, ret); + + afs_done_one_vl_probe(server, have_result); + } +--- a/fs/afs/vl_rotate.c ++++ b/fs/afs/vl_rotate.c +@@ -249,7 +249,7 @@ iterate_address: + + _debug("VL address %d/%d", vc->ac.index, vc->ac.alist->nr_addrs); + +- _leave(" = t %pISpc", &vc->ac.alist->addrs[vc->ac.index].transport); ++ _leave(" = t %pISpc", &vc->ac.alist->addrs[vc->ac.index].srx.transport); + return true; + + next_server: diff --git a/queue-6.6/alsa-hda-conexant-remove-mic-bias-threshold-override.patch b/queue-6.6/alsa-hda-conexant-remove-mic-bias-threshold-override.patch new file mode 100644 index 0000000000..c771488a2a --- /dev/null +++ b/queue-6.6/alsa-hda-conexant-remove-mic-bias-threshold-override.patch @@ -0,0 +1,47 @@ +From stable+bounces-287801-greg=kroah.com@vger.kernel.org Wed Jul 22 13:02:56 2026 +From: Sasha Levin +Date: Wed, 22 Jul 2026 07:01:17 -0400 +Subject: ALSA: hda: conexant: Remove mic bias threshold override +To: stable@vger.kernel.org +Cc: Zhang Heng , Takashi Iwai , Sasha Levin +Message-ID: <20260722110117.1006143-1-sashal@kernel.org> + +From: Zhang Heng + +[ Upstream commit f52524da7084c1a54683ae9fbc73e93fff19dd64 ] + +Remove the mic bias current comparator threshold override (NID 0x1c, +verb 0x320, value 0x010) from Conexant codec driver. + +This override was originally intended to support volume up/down controls on +headsets with inline remote controls, but it causes microphone detection +failures on some headsets with impedance less than 1k ohm. + +After consulting with the vendor's engineers, it was confirmed that this +setting is board-specific and should be handled by BIOS/firmware rather +than the generic codec driver, especially since inline remote support +is not currently implemented. + +Fixes: 7aeb25908648 ("ALSA: hda/conexant: Fix headset auto detect fail in cx8070 and SN6140") +Cc: stable@vger.kernel.org +Signed-off-by: Zhang Heng +Link: https://patch.msgid.link/20260713100329.306892-1-zhangheng@kylinos.cn +Signed-off-by: Takashi Iwai +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + sound/pci/hda/patch_conexant.c | 3 --- + 1 file changed, 3 deletions(-) + +--- a/sound/pci/hda/patch_conexant.c ++++ b/sound/pci/hda/patch_conexant.c +@@ -168,9 +168,6 @@ static void cx_fixup_headset_recog(struc + { + unsigned int mic_present; + +- /* fix some headset type recognize fail issue, such as EDIFIER headset */ +- /* set micbias output current comparator threshold from 66% to 55%. */ +- snd_hda_codec_write(codec, 0x1c, 0, 0x320, 0x010); + /* set OFF voltage for DFET from -1.2V to -0.8V, set headset micbias register + * value adjustment trim from 2.2K ohms to 2.0K ohms. + */ diff --git a/queue-6.6/alsa-hda-fix-cached-processing-coefficient-verbs.patch b/queue-6.6/alsa-hda-fix-cached-processing-coefficient-verbs.patch new file mode 100644 index 0000000000..09938d6836 --- /dev/null +++ b/queue-6.6/alsa-hda-fix-cached-processing-coefficient-verbs.patch @@ -0,0 +1,62 @@ +From stable+bounces-287811-greg=kroah.com@vger.kernel.org Wed Jul 22 13:31:18 2026 +From: Sasha Levin +Date: Wed, 22 Jul 2026 07:24:21 -0400 +Subject: ALSA: hda: Fix cached processing coefficient verbs +To: stable@vger.kernel.org +Cc: Xu Rao , Takashi Iwai , Sasha Levin +Message-ID: <20260722112421.1114485-1-sashal@kernel.org> + +From: Xu Rao + +[ Upstream commit f67be28fdf8b5d31ac1cc1152bb17250f9f8f513 ] + +Intel HD Audio defines Coefficient Index and Processing Coefficient as +separate audio widget controls in the Audio Widget Verb Definitions: +Coefficient Index selects the coefficient slot, while Processing +Coefficient accesses the value at the selected slot. + +hda_reg_read_coef() selects the slot with AC_VERB_SET_COEF_INDEX, but +then uses AC_VERB_GET_COEF_INDEX for the value read. That reads back the +selected index instead of the coefficient value. hda_reg_write_coef() +has the same issue and builds the value write from AC_VERB_GET_COEF_INDEX +instead of AC_VERB_SET_PROC_COEF. + +This only affects the regmap coefficient cache path used by codecs that +set codec->cache_coef. Direct coefficient helpers already use the normal +SET_COEF_INDEX followed by GET_PROC_COEF or SET_PROC_COEF sequence, which +is likely why this has not been noticed widely. + +Use AC_VERB_GET_PROC_COEF for cached coefficient reads and +AC_VERB_SET_PROC_COEF for cached coefficient writes. + +Fixes: 40ba66a702b8 ("ALSA: hda - Add cache support for COEF read/write") +Cc: stable@vger.kernel.org +Signed-off-by: Xu Rao +Link: https://patch.msgid.link/DB9023BF2920BA99+20260707132419.1731342-1-raoxu@uniontech.com +Signed-off-by: Takashi Iwai +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + sound/hda/hdac_regmap.c | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +--- a/sound/hda/hdac_regmap.c ++++ b/sound/hda/hdac_regmap.c +@@ -214,7 +214,7 @@ static int hda_reg_read_coef(struct hdac + err = snd_hdac_exec_verb(codec, verb, 0, NULL); + if (err < 0) + return err; +- verb = (reg & ~0xfffff) | (AC_VERB_GET_COEF_INDEX << 8); ++ verb = (reg & ~0xfffff) | (AC_VERB_GET_PROC_COEF << 8); + return snd_hdac_exec_verb(codec, verb, 0, val); + } + +@@ -232,7 +232,7 @@ static int hda_reg_write_coef(struct hda + err = snd_hdac_exec_verb(codec, verb, 0, NULL); + if (err < 0) + return err; +- verb = (reg & ~0xfffff) | (AC_VERB_GET_COEF_INDEX << 8) | ++ verb = (reg & ~0xfffff) | (AC_VERB_SET_PROC_COEF << 8) | + (val & 0xffff); + return snd_hdac_exec_verb(codec, verb, 0, NULL); + } diff --git a/queue-6.6/asoc-mediatek-mt8183-check-runtime-resume-during-probe.patch b/queue-6.6/asoc-mediatek-mt8183-check-runtime-resume-during-probe.patch new file mode 100644 index 0000000000..9c48edb84f --- /dev/null +++ b/queue-6.6/asoc-mediatek-mt8183-check-runtime-resume-during-probe.patch @@ -0,0 +1,60 @@ +From stable+bounces-288180-greg=kroah.com@vger.kernel.org Thu Jul 23 04:53:11 2026 +From: Sasha Levin +Date: Wed, 22 Jul 2026 22:53:02 -0400 +Subject: ASoC: mediatek: mt8183: Check runtime resume during probe +To: stable@vger.kernel.org +Cc: "Cássio Gabriel" , "Mark Brown" , "Sasha Levin" +Message-ID: <20260723025302.2872512-1-sashal@kernel.org> + +From: Cássio Gabriel + +[ Upstream commit f0334fbfd107682d0c95f3f71e25f6127038e2b9 ] + +The MT8183 AFE probe uses pm_runtime_get_sync() before reading hardware +defaults into the regmap cache, but does not check whether runtime resume +failed. If regmap_reinit_cache() then fails, the temporary runtime PM +usage count is also not released. + +Use pm_runtime_resume_and_get() so resume failures abort probe without +leaking a usage count, and release the temporary reference before +handling the regmap cache result. + +Fixes: a94aec035a12 ("ASoC: mediatek: mt8183: add platform driver") +Cc: stable@vger.kernel.org +Signed-off-by: Cássio Gabriel +Link: https://patch.msgid.link/20260527-asoc-mt8183-probe-cleanup-v1-2-4f4f5593c8d1@gmail.com +Signed-off-by: Mark Brown +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + sound/soc/mediatek/mt8183/mt8183-afe-pcm.c | 12 ++++++++---- + 1 file changed, 8 insertions(+), 4 deletions(-) + +--- a/sound/soc/mediatek/mt8183/mt8183-afe-pcm.c ++++ b/sound/soc/mediatek/mt8183/mt8183-afe-pcm.c +@@ -1149,17 +1149,21 @@ static int mt8183_afe_pcm_dev_probe(stru + + /* enable clock for regcache get default value from hw */ + afe_priv->pm_runtime_bypass_reg_ctl = true; +- pm_runtime_get_sync(&pdev->dev); ++ ret = pm_runtime_resume_and_get(dev); ++ if (ret) { ++ afe_priv->pm_runtime_bypass_reg_ctl = false; ++ goto err_pm_disable; ++ } + + ret = regmap_reinit_cache(afe->regmap, &mt8183_afe_regmap_config); ++ pm_runtime_put_sync(dev); ++ afe_priv->pm_runtime_bypass_reg_ctl = false; ++ + if (ret) { + dev_err(dev, "regmap_reinit_cache fail, ret %d\n", ret); + goto err_pm_disable; + } + +- pm_runtime_put_sync(&pdev->dev); +- afe_priv->pm_runtime_bypass_reg_ctl = false; +- + regcache_cache_only(afe->regmap, true); + regcache_mark_dirty(afe->regmap); + diff --git a/queue-6.6/asoc-mediatek-mt8192-afe-pcm-convert-to-devm_pm_runtime_enable.patch b/queue-6.6/asoc-mediatek-mt8192-afe-pcm-convert-to-devm_pm_runtime_enable.patch new file mode 100644 index 0000000000..2eabd33c13 --- /dev/null +++ b/queue-6.6/asoc-mediatek-mt8192-afe-pcm-convert-to-devm_pm_runtime_enable.patch @@ -0,0 +1,153 @@ +From stable+bounces-288169-greg=kroah.com@vger.kernel.org Thu Jul 23 04:29:25 2026 +From: Sasha Levin +Date: Wed, 22 Jul 2026 22:29:11 -0400 +Subject: ASoC: mediatek: mt8192-afe-pcm: Convert to devm_pm_runtime_enable() +To: stable@vger.kernel.org +Cc: AngeloGioacchino Del Regno , Chen-Yu Tsai , Mark Brown , Sasha Levin +Message-ID: <20260723022915.2845450-2-sashal@kernel.org> + +From: AngeloGioacchino Del Regno + +[ Upstream commit 7aaaa22de56ce0dae15fd9f42a69a1d1a7a6e078 ] + +Switch from pm_runtime_enable() to devm_pm_runtime_enable(), allowing +to remove all gotos from the probe function. + +Signed-off-by: AngeloGioacchino Del Regno +Reviewed-by: Chen-Yu Tsai +Link: https://msgid.link/r/20240313110147.1267793-2-angelogioacchino.delregno@collabora.com +Signed-off-by: Mark Brown +Stable-dep-of: e24d5dde56a5 ("ASoC: mediatek: mt8192: Check runtime resume during probe") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + sound/soc/mediatek/mt8192/mt8192-afe-pcm.c | 46 ++++++++++------------------- + 1 file changed, 17 insertions(+), 29 deletions(-) + +--- a/sound/soc/mediatek/mt8192/mt8192-afe-pcm.c ++++ b/sound/soc/mediatek/mt8192/mt8192-afe-pcm.c +@@ -2233,21 +2233,20 @@ static int mt8192_afe_pcm_dev_probe(stru + return ret; + } + +- pm_runtime_enable(&pdev->dev); +- if (!pm_runtime_enabled(&pdev->dev)) +- goto err_pm_disable; ++ ret = devm_pm_runtime_enable(&pdev->dev); ++ if (ret) ++ return ret; + + /* regmap init */ + afe->regmap = syscon_node_to_regmap(dev->parent->of_node); + if (IS_ERR(afe->regmap)) { + dev_err(dev, "could not get regmap from parent\n"); +- ret = PTR_ERR(afe->regmap); +- goto err_pm_disable; ++ return PTR_ERR(afe->regmap); + } + ret = regmap_attach_dev(dev, afe->regmap, &mt8192_afe_regmap_config); + if (ret) { + dev_warn(dev, "regmap_attach_dev fail, ret %d\n", ret); +- goto err_pm_disable; ++ return ret; + } + + /* enable clock for regcache get default value from hw */ +@@ -2257,7 +2256,7 @@ static int mt8192_afe_pcm_dev_probe(stru + ret = regmap_reinit_cache(afe->regmap, &mt8192_afe_regmap_config); + if (ret) { + dev_err(dev, "regmap_reinit_cache fail, ret %d\n", ret); +- goto err_pm_disable; ++ return ret; + } + + pm_runtime_put_sync(&pdev->dev); +@@ -2270,10 +2269,8 @@ static int mt8192_afe_pcm_dev_probe(stru + afe->memif_size = MT8192_MEMIF_NUM; + afe->memif = devm_kcalloc(dev, afe->memif_size, sizeof(*afe->memif), + GFP_KERNEL); +- if (!afe->memif) { +- ret = -ENOMEM; +- goto err_pm_disable; +- } ++ if (!afe->memif) ++ return -ENOMEM; + + for (i = 0; i < afe->memif_size; i++) { + afe->memif[i].data = &memif_data[i]; +@@ -2287,26 +2284,22 @@ static int mt8192_afe_pcm_dev_probe(stru + afe->irqs_size = MT8192_IRQ_NUM; + afe->irqs = devm_kcalloc(dev, afe->irqs_size, sizeof(*afe->irqs), + GFP_KERNEL); +- if (!afe->irqs) { +- ret = -ENOMEM; +- goto err_pm_disable; +- } ++ if (!afe->irqs) ++ return -ENOMEM; + + for (i = 0; i < afe->irqs_size; i++) + afe->irqs[i].irq_data = &irq_data[i]; + + /* request irq */ + irq_id = platform_get_irq(pdev, 0); +- if (irq_id < 0) { +- ret = irq_id; +- goto err_pm_disable; +- } ++ if (irq_id < 0) ++ return irq_id; + + ret = devm_request_irq(dev, irq_id, mt8192_afe_irq_handler, + IRQF_TRIGGER_NONE, "asys-isr", (void *)afe); + if (ret) { + dev_err(dev, "could not request_irq for Afe_ISR_Handle\n"); +- goto err_pm_disable; ++ return ret; + } + + /* init sub_dais */ +@@ -2317,7 +2310,7 @@ static int mt8192_afe_pcm_dev_probe(stru + if (ret) { + dev_warn(afe->dev, "dai register i %d fail, ret %d\n", + i, ret); +- goto err_pm_disable; ++ return ret; + } + } + +@@ -2326,7 +2319,7 @@ static int mt8192_afe_pcm_dev_probe(stru + if (ret) { + dev_warn(afe->dev, "mtk_afe_combine_sub_dai fail, ret %d\n", + ret); +- goto err_pm_disable; ++ return ret; + } + + /* others */ +@@ -2345,7 +2338,7 @@ static int mt8192_afe_pcm_dev_probe(stru + &mt8192_afe_component, NULL, 0); + if (ret) { + dev_warn(dev, "err_platform\n"); +- goto err_pm_disable; ++ return ret; + } + + ret = devm_snd_soc_register_component(&pdev->dev, +@@ -2354,15 +2347,10 @@ static int mt8192_afe_pcm_dev_probe(stru + afe->num_dai_drivers); + if (ret) { + dev_warn(dev, "err_dai_component\n"); +- goto err_pm_disable; ++ return ret; + } + + return 0; +- +-err_pm_disable: +- pm_runtime_disable(&pdev->dev); +- +- return ret; + } + + static void mt8192_afe_pcm_dev_remove(struct platform_device *pdev) diff --git a/queue-6.6/asoc-mediatek-mt8192-afe-pcm-simplify-probe-with-local-dev-variable.patch b/queue-6.6/asoc-mediatek-mt8192-afe-pcm-simplify-probe-with-local-dev-variable.patch new file mode 100644 index 0000000000..3a9354bf64 --- /dev/null +++ b/queue-6.6/asoc-mediatek-mt8192-afe-pcm-simplify-probe-with-local-dev-variable.patch @@ -0,0 +1,94 @@ +From stable+bounces-288172-greg=kroah.com@vger.kernel.org Thu Jul 23 04:29:46 2026 +From: Sasha Levin +Date: Wed, 22 Jul 2026 22:29:14 -0400 +Subject: ASoC: mediatek: mt8192-afe-pcm: Simplify probe() with local dev variable +To: stable@vger.kernel.org +Cc: Tang Bin , Mark Brown , Sasha Levin +Message-ID: <20260723022915.2845450-5-sashal@kernel.org> + +From: Tang Bin + +[ Upstream commit 01981565c764c554cc96e2d30a71c42975171416 ] + +Simplify the function mt8192_afe_pcm_dev_probe() by +using local 'dev' instead of '&pdev->dev'. + +Signed-off-by: Tang Bin +Link: https://patch.msgid.link/20241025080026.2393-1-tangbin@cmss.chinamobile.com +Signed-off-by: Mark Brown +Stable-dep-of: e24d5dde56a5 ("ASoC: mediatek: mt8192: Check runtime resume during probe") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + sound/soc/mediatek/mt8192/mt8192-afe-pcm.c | 19 +++++++++---------- + 1 file changed, 9 insertions(+), 10 deletions(-) + +--- a/sound/soc/mediatek/mt8192/mt8192-afe-pcm.c ++++ b/sound/soc/mediatek/mt8192/mt8192-afe-pcm.c +@@ -2164,27 +2164,26 @@ static int mt8192_afe_pcm_dev_probe(stru + { + struct mtk_base_afe *afe; + struct mt8192_afe_private *afe_priv; +- struct device *dev; ++ struct device *dev = &pdev->dev; + struct reset_control *rstc; + int i, ret, irq_id; + +- ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(34)); ++ ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(34)); + if (ret) + return ret; + +- afe = devm_kzalloc(&pdev->dev, sizeof(*afe), GFP_KERNEL); ++ afe = devm_kzalloc(dev, sizeof(*afe), GFP_KERNEL); + if (!afe) + return -ENOMEM; + platform_set_drvdata(pdev, afe); + +- afe->platform_priv = devm_kzalloc(&pdev->dev, sizeof(*afe_priv), ++ afe->platform_priv = devm_kzalloc(dev, sizeof(*afe_priv), + GFP_KERNEL); + if (!afe->platform_priv) + return -ENOMEM; + afe_priv = afe->platform_priv; + +- afe->dev = &pdev->dev; +- dev = afe->dev; ++ afe->dev = dev; + + ret = of_reserved_mem_device_init(dev); + if (ret) { +@@ -2212,7 +2211,7 @@ static int mt8192_afe_pcm_dev_probe(stru + if (ret) + return dev_err_probe(dev, ret, "failed to trigger audio reset\n"); + +- ret = devm_pm_runtime_enable(&pdev->dev); ++ ret = devm_pm_runtime_enable(dev); + if (ret) + return ret; + +@@ -2228,13 +2227,13 @@ static int mt8192_afe_pcm_dev_probe(stru + + /* enable clock for regcache get default value from hw */ + afe_priv->pm_runtime_bypass_reg_ctl = true; +- pm_runtime_get_sync(&pdev->dev); ++ pm_runtime_get_sync(dev); + + ret = regmap_reinit_cache(afe->regmap, &mt8192_afe_regmap_config); + if (ret) + return dev_err_probe(dev, ret, "regmap_reinit_cache fail\n"); + +- pm_runtime_put_sync(&pdev->dev); ++ pm_runtime_put_sync(dev); + afe_priv->pm_runtime_bypass_reg_ctl = false; + + regcache_cache_only(afe->regmap, true); +@@ -2301,7 +2300,7 @@ static int mt8192_afe_pcm_dev_probe(stru + afe->runtime_suspend = mt8192_afe_runtime_suspend; + + /* register platform */ +- ret = devm_snd_soc_register_component(&pdev->dev, ++ ret = devm_snd_soc_register_component(dev, + &mtk_afe_pcm_platform, + afe->dai_drivers, + afe->num_dai_drivers); diff --git a/queue-6.6/asoc-mediatek-mt8192-afe-pcm-simplify-with-dev_err_probe.patch b/queue-6.6/asoc-mediatek-mt8192-afe-pcm-simplify-with-dev_err_probe.patch new file mode 100644 index 0000000000..b8cb42eb48 --- /dev/null +++ b/queue-6.6/asoc-mediatek-mt8192-afe-pcm-simplify-with-dev_err_probe.patch @@ -0,0 +1,147 @@ +From stable+bounces-288170-greg=kroah.com@vger.kernel.org Thu Jul 23 04:29:23 2026 +From: Sasha Levin +Date: Wed, 22 Jul 2026 22:29:12 -0400 +Subject: ASoC: mediatek: mt8192-afe-pcm: Simplify with dev_err_probe() +To: stable@vger.kernel.org +Cc: AngeloGioacchino Del Regno , Chen-Yu Tsai , Mark Brown , Sasha Levin +Message-ID: <20260723022915.2845450-3-sashal@kernel.org> + +From: AngeloGioacchino Del Regno + +[ Upstream commit 324c603a4efca7d1045e0bf3477ca54970eac72c ] + +Simplify the probe function by switching error prints to return +dev_err_probe(), lowering the lines count; while at it, also +beautify some messages and change some others' level from warn +to error. + +Signed-off-by: AngeloGioacchino Del Regno +Reviewed-by: Chen-Yu Tsai +Link: https://msgid.link/r/20240313110147.1267793-3-angelogioacchino.delregno@collabora.com +Signed-off-by: Mark Brown +Stable-dep-of: e24d5dde56a5 ("ASoC: mediatek: mt8192: Check runtime resume during probe") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + sound/soc/mediatek/mt8192/mt8192-afe-pcm.c | 65 +++++++++-------------------- + 1 file changed, 22 insertions(+), 43 deletions(-) + +--- a/sound/soc/mediatek/mt8192/mt8192-afe-pcm.c ++++ b/sound/soc/mediatek/mt8192/mt8192-afe-pcm.c +@@ -2221,17 +2221,12 @@ static int mt8192_afe_pcm_dev_probe(stru + + /* reset controller to reset audio regs before regmap cache */ + rstc = devm_reset_control_get_exclusive(dev, "audiosys"); +- if (IS_ERR(rstc)) { +- ret = PTR_ERR(rstc); +- dev_err(dev, "could not get audiosys reset:%d\n", ret); +- return ret; +- } ++ if (IS_ERR(rstc)) ++ return dev_err_probe(dev, PTR_ERR(rstc), "could not get audiosys reset\n"); + + ret = reset_control_reset(rstc); +- if (ret) { +- dev_err(dev, "failed to trigger audio reset:%d\n", ret); +- return ret; +- } ++ if (ret) ++ return dev_err_probe(dev, ret, "failed to trigger audio reset\n"); + + ret = devm_pm_runtime_enable(&pdev->dev); + if (ret) +@@ -2239,25 +2234,21 @@ static int mt8192_afe_pcm_dev_probe(stru + + /* regmap init */ + afe->regmap = syscon_node_to_regmap(dev->parent->of_node); +- if (IS_ERR(afe->regmap)) { +- dev_err(dev, "could not get regmap from parent\n"); +- return PTR_ERR(afe->regmap); +- } ++ if (IS_ERR(afe->regmap)) ++ return dev_err_probe(dev, PTR_ERR(afe->regmap), ++ "could not get regmap from parent"); ++ + ret = regmap_attach_dev(dev, afe->regmap, &mt8192_afe_regmap_config); +- if (ret) { +- dev_warn(dev, "regmap_attach_dev fail, ret %d\n", ret); +- return ret; +- } ++ if (ret) ++ return dev_err_probe(dev, ret, "regmap_attach_dev fail\n"); + + /* enable clock for regcache get default value from hw */ + afe_priv->pm_runtime_bypass_reg_ctl = true; + pm_runtime_get_sync(&pdev->dev); + + ret = regmap_reinit_cache(afe->regmap, &mt8192_afe_regmap_config); +- if (ret) { +- dev_err(dev, "regmap_reinit_cache fail, ret %d\n", ret); +- return ret; +- } ++ if (ret) ++ return dev_err_probe(dev, ret, "regmap_reinit_cache fail\n"); + + pm_runtime_put_sync(&pdev->dev); + afe_priv->pm_runtime_bypass_reg_ctl = false; +@@ -2297,30 +2288,22 @@ static int mt8192_afe_pcm_dev_probe(stru + + ret = devm_request_irq(dev, irq_id, mt8192_afe_irq_handler, + IRQF_TRIGGER_NONE, "asys-isr", (void *)afe); +- if (ret) { +- dev_err(dev, "could not request_irq for Afe_ISR_Handle\n"); +- return ret; +- } ++ if (ret) ++ return dev_err_probe(dev, ret, "could not request_irq for Afe_ISR_Handle\n"); + + /* init sub_dais */ + INIT_LIST_HEAD(&afe->sub_dais); + + for (i = 0; i < ARRAY_SIZE(dai_register_cbs); i++) { + ret = dai_register_cbs[i](afe); +- if (ret) { +- dev_warn(afe->dev, "dai register i %d fail, ret %d\n", +- i, ret); +- return ret; +- } ++ if (ret) ++ return dev_err_probe(afe->dev, ret, "dai %d register fail", i); + } + + /* init dai_driver and component_driver */ + ret = mtk_afe_combine_sub_dai(afe); +- if (ret) { +- dev_warn(afe->dev, "mtk_afe_combine_sub_dai fail, ret %d\n", +- ret); +- return ret; +- } ++ if (ret) ++ return dev_err_probe(afe->dev, ret, "mtk_afe_combine_sub_dai fail\n"); + + /* others */ + afe->mtk_afe_hardware = &mt8192_afe_hardware; +@@ -2336,19 +2319,15 @@ static int mt8192_afe_pcm_dev_probe(stru + /* register platform */ + ret = devm_snd_soc_register_component(&pdev->dev, + &mt8192_afe_component, NULL, 0); +- if (ret) { +- dev_warn(dev, "err_platform\n"); +- return ret; +- } ++ if (ret) ++ return dev_err_probe(dev, ret, "Couldn't register AFE component\n"); + + ret = devm_snd_soc_register_component(&pdev->dev, + &mt8192_afe_pcm_component, + afe->dai_drivers, + afe->num_dai_drivers); +- if (ret) { +- dev_warn(dev, "err_dai_component\n"); +- return ret; +- } ++ if (ret) ++ return dev_err_probe(dev, ret, "Couldn't register AFE-PCM component\n"); + + return 0; + } diff --git a/queue-6.6/asoc-mediatek-mt8192-check-runtime-resume-during-probe.patch b/queue-6.6/asoc-mediatek-mt8192-check-runtime-resume-during-probe.patch new file mode 100644 index 0000000000..ad4f7b6b92 --- /dev/null +++ b/queue-6.6/asoc-mediatek-mt8192-check-runtime-resume-during-probe.patch @@ -0,0 +1,62 @@ +From stable+bounces-288173-greg=kroah.com@vger.kernel.org Thu Jul 23 04:29:56 2026 +From: Sasha Levin +Date: Wed, 22 Jul 2026 22:29:15 -0400 +Subject: ASoC: mediatek: mt8192: Check runtime resume during probe +To: stable@vger.kernel.org +Cc: "Cássio Gabriel" , "Mark Brown" , "Sasha Levin" +Message-ID: <20260723022915.2845450-6-sashal@kernel.org> + +From: Cássio Gabriel + +[ Upstream commit e24d5dde56a50946020b134fa8448869093db76a ] + +The MT8192 AFE probe enables runtime PM temporarily while reinitializing +the regmap cache from hardware, but it uses pm_runtime_get_sync() +without checking the return value. If runtime resume fails, probe keeps +going without the device necessarily being accessible, and +pm_runtime_get_sync() may leave the PM usage count incremented. + +The regmap_reinit_cache() failure path also returns before dropping the +temporary PM reference and before clearing pm_runtime_bypass_reg_ctl. + +Use pm_runtime_resume_and_get() so resume failures do not leak a usage +count, and clear the temporary bypass flag after dropping the probe PM +reference on all regmap_reinit_cache() outcomes. + +Fixes: 125ab5d588b0 ("ASoC: mediatek: mt8192: add platform driver") +Cc: stable@vger.kernel.org +Signed-off-by: Cássio Gabriel +Link: https://patch.msgid.link/20260527-asoc-mt8192-probe-cleanup-v1-2-1bb834d05b72@gmail.com +Signed-off-by: Mark Brown +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + sound/soc/mediatek/mt8192/mt8192-afe-pcm.c | 12 ++++++++---- + 1 file changed, 8 insertions(+), 4 deletions(-) + +--- a/sound/soc/mediatek/mt8192/mt8192-afe-pcm.c ++++ b/sound/soc/mediatek/mt8192/mt8192-afe-pcm.c +@@ -2227,15 +2227,19 @@ static int mt8192_afe_pcm_dev_probe(stru + + /* enable clock for regcache get default value from hw */ + afe_priv->pm_runtime_bypass_reg_ctl = true; +- pm_runtime_get_sync(dev); ++ ret = pm_runtime_resume_and_get(dev); ++ if (ret) { ++ afe_priv->pm_runtime_bypass_reg_ctl = false; ++ return dev_err_probe(dev, ret, "failed to resume device\n"); ++ } + + ret = regmap_reinit_cache(afe->regmap, &mt8192_afe_regmap_config); +- if (ret) +- return dev_err_probe(dev, ret, "regmap_reinit_cache fail\n"); +- + pm_runtime_put_sync(dev); + afe_priv->pm_runtime_bypass_reg_ctl = false; + ++ if (ret) ++ return dev_err_probe(dev, ret, "regmap_reinit_cache fail\n"); ++ + regcache_cache_only(afe->regmap, true); + regcache_mark_dirty(afe->regmap); + diff --git a/queue-6.6/asoc-mediatek-mt8195-remove-afe-dai-component-and-rework-codec-link.patch b/queue-6.6/asoc-mediatek-mt8195-remove-afe-dai-component-and-rework-codec-link.patch new file mode 100644 index 0000000000..0a45d2dc01 --- /dev/null +++ b/queue-6.6/asoc-mediatek-mt8195-remove-afe-dai-component-and-rework-codec-link.patch @@ -0,0 +1,194 @@ +From stable+bounces-288168-greg=kroah.com@vger.kernel.org Thu Jul 23 04:29:22 2026 +From: Sasha Levin +Date: Wed, 22 Jul 2026 22:29:10 -0400 +Subject: ASoC: mediatek: mt8195: Remove afe-dai component and rework codec link +To: stable@vger.kernel.org +Cc: AngeloGioacchino Del Regno , Mark Brown , Sasha Levin +Message-ID: <20260723022915.2845450-1-sashal@kernel.org> + +From: AngeloGioacchino Del Regno + +[ Upstream commit e70b8dd26711704b1ff1f1b4eb3d048ba69e29da ] + +Remove the extra 'mt8195-afe-pcm-dai' component, register the DAI +drivers to the main AFE component, and rework the DAI linking between +the headset codec (RT5682/RT5682S) and the TDM interface in the probe +function to stop assigning name, relying on the of_node of the codec. +Also replace the COMP_DUMMY codec entry with a COMP_EMPTY for the +ETDM2_IN and remove it entirely from ETDM1_OUT to fix the registration +flow for this sound card. + +While at it, since we also need to swap the codec init function from +ETDM2_IN to ETDM1_OUT, remove the static assignment of both `ops` and +`init` for both, as we now assign these dynamically during probe. + +Fixes: 13f58267cda3 ("ASoC: soc.h: don't create dummy Component via COMP_DUMMY()") +Signed-off-by: AngeloGioacchino Del Regno +Link: https://msgid.link/r/20240110105757.539089-1-angelogioacchino.delregno@collabora.com +Signed-off-by: Mark Brown +Stable-dep-of: e24d5dde56a5 ("ASoC: mediatek: mt8192: Check runtime resume during probe") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + sound/soc/mediatek/mt8195/mt8195-afe-pcm.c | 33 ----------------------- + sound/soc/mediatek/mt8195/mt8195-mt6359.c | 41 +++++++++++++++++++---------- + 2 files changed, 29 insertions(+), 45 deletions(-) + +--- a/sound/soc/mediatek/mt8195/mt8195-afe-pcm.c ++++ b/sound/soc/mediatek/mt8195/mt8195-afe-pcm.c +@@ -1795,10 +1795,6 @@ static const struct snd_kcontrol_new mt8 + MT8195_AFE_IRQ_28), + }; + +-static const struct snd_soc_component_driver mt8195_afe_pcm_dai_component = { +- .name = "mt8195-afe-pcm-dai", +-}; +- + static const struct mtk_base_memif_data memif_data[MT8195_AFE_MEMIF_NUM] = { + [MT8195_AFE_MEMIF_DL2] = { + .name = "DL2", +@@ -3037,7 +3033,6 @@ static int mt8195_afe_pcm_dev_probe(stru + struct device *dev = &pdev->dev; + struct reset_control *rstc; + int i, irq_id, ret; +- struct snd_soc_component *component; + + ret = of_reserved_mem_device_init(dev); + if (ret) +@@ -3170,36 +3165,12 @@ static int mt8195_afe_pcm_dev_probe(stru + + /* register component */ + ret = devm_snd_soc_register_component(dev, &mt8195_afe_component, +- NULL, 0); ++ afe->dai_drivers, afe->num_dai_drivers); + if (ret) { + dev_warn(dev, "err_platform\n"); + goto err_pm_put; + } + +- component = devm_kzalloc(dev, sizeof(*component), GFP_KERNEL); +- if (!component) { +- ret = -ENOMEM; +- goto err_pm_put; +- } +- +- ret = snd_soc_component_initialize(component, +- &mt8195_afe_pcm_dai_component, +- dev); +- if (ret) +- goto err_pm_put; +- +-#ifdef CONFIG_DEBUG_FS +- component->debugfs_prefix = "pcm"; +-#endif +- +- ret = snd_soc_add_component(component, +- afe->dai_drivers, +- afe->num_dai_drivers); +- if (ret) { +- dev_warn(dev, "err_dai_component\n"); +- goto err_pm_put; +- } +- + ret = regmap_multi_reg_write(afe->regmap, mt8195_afe_reg_defaults, + ARRAY_SIZE(mt8195_afe_reg_defaults)); + if (ret) +@@ -3224,8 +3195,6 @@ err_pm_put: + + static void mt8195_afe_pcm_dev_remove(struct platform_device *pdev) + { +- snd_soc_unregister_component(&pdev->dev); +- + pm_runtime_disable(&pdev->dev); + if (!pm_runtime_status_suspended(&pdev->dev)) + mt8195_afe_runtime_suspend(&pdev->dev); +--- a/sound/soc/mediatek/mt8195/mt8195-mt6359.c ++++ b/sound/soc/mediatek/mt8195/mt8195-mt6359.c +@@ -934,12 +934,11 @@ SND_SOC_DAILINK_DEFS(ETDM1_IN_BE, + + SND_SOC_DAILINK_DEFS(ETDM2_IN_BE, + DAILINK_COMP_ARRAY(COMP_CPU("ETDM2_IN")), +- DAILINK_COMP_ARRAY(COMP_DUMMY()), ++ DAILINK_COMP_ARRAY(COMP_EMPTY()), + DAILINK_COMP_ARRAY(COMP_EMPTY())); + + SND_SOC_DAILINK_DEFS(ETDM1_OUT_BE, + DAILINK_COMP_ARRAY(COMP_CPU("ETDM1_OUT")), +- DAILINK_COMP_ARRAY(COMP_DUMMY()), + DAILINK_COMP_ARRAY(COMP_EMPTY())); + + SND_SOC_DAILINK_DEFS(ETDM2_OUT_BE, +@@ -1237,8 +1236,6 @@ static struct snd_soc_dai_link mt8195_mt + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS, + .dpcm_capture = 1, +- .init = mt8195_rt5682_init, +- .ops = &mt8195_rt5682_etdm_ops, + .be_hw_params_fixup = mt8195_etdm_hw_params_fixup, + SND_SOC_DAILINK_REG(ETDM2_IN_BE), + }, +@@ -1249,7 +1246,6 @@ static struct snd_soc_dai_link mt8195_mt + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS, + .dpcm_playback = 1, +- .ops = &mt8195_rt5682_etdm_ops, + .be_hw_params_fixup = mt8195_etdm_hw_params_fixup, + SND_SOC_DAILINK_REG(ETDM1_OUT_BE), + }, +@@ -1381,7 +1377,7 @@ static int mt8195_mt6359_dev_probe(struc + struct snd_soc_dai_link *dai_link; + struct mtk_soc_card_data *soc_card_data; + struct mt8195_mt6359_priv *mach_priv; +- struct device_node *platform_node, *adsp_node, *dp_node, *hdmi_node; ++ struct device_node *platform_node, *adsp_node, *codec_node, *dp_node, *hdmi_node; + struct mt8195_card_data *card_data; + int is5682s = 0; + int init6359 = 0; +@@ -1401,8 +1397,12 @@ static int mt8195_mt6359_dev_probe(struc + if (!card->name) + card->name = card_data->name; + +- if (strstr(card->name, "_5682s")) ++ if (strstr(card->name, "_5682s")) { ++ codec_node = of_find_compatible_node(NULL, NULL, "realtek,rt5682s"); + is5682s = 1; ++ } else ++ codec_node = of_find_compatible_node(NULL, NULL, "realtek,rt5682i"); ++ + soc_card_data = devm_kzalloc(&pdev->dev, sizeof(*card_data), GFP_KERNEL); + if (!soc_card_data) + return -ENOMEM; +@@ -1488,12 +1488,27 @@ static int mt8195_mt6359_dev_probe(struc + dai_link->codecs->dai_name = "i2s-hifi"; + dai_link->init = mt8195_hdmi_codec_init; + } +- } else if (strcmp(dai_link->name, "ETDM1_OUT_BE") == 0 || +- strcmp(dai_link->name, "ETDM2_IN_BE") == 0) { +- dai_link->codecs->name = +- is5682s ? RT5682S_DEV0_NAME : RT5682_DEV0_NAME; +- dai_link->codecs->dai_name = +- is5682s ? RT5682S_CODEC_DAI : RT5682_CODEC_DAI; ++ } else if (strcmp(dai_link->name, "ETDM1_OUT_BE") == 0) { ++ if (!codec_node) { ++ dev_err(&pdev->dev, "Codec not found!\n"); ++ } else { ++ dai_link->codecs->of_node = codec_node; ++ dai_link->codecs->name = NULL; ++ dai_link->codecs->dai_name = ++ is5682s ? RT5682S_CODEC_DAI : RT5682_CODEC_DAI; ++ dai_link->init = mt8195_rt5682_init; ++ dai_link->ops = &mt8195_rt5682_etdm_ops; ++ } ++ } else if (strcmp(dai_link->name, "ETDM2_IN_BE") == 0) { ++ if (!codec_node) { ++ dev_err(&pdev->dev, "Codec not found!\n"); ++ } else { ++ dai_link->codecs->of_node = codec_node; ++ dai_link->codecs->name = NULL; ++ dai_link->codecs->dai_name = ++ is5682s ? RT5682S_CODEC_DAI : RT5682_CODEC_DAI; ++ dai_link->ops = &mt8195_rt5682_etdm_ops; ++ } + } else if (strcmp(dai_link->name, "DL_SRC_BE") == 0 || + strcmp(dai_link->name, "UL_SRC1_BE") == 0 || + strcmp(dai_link->name, "UL_SRC2_BE") == 0) { diff --git a/queue-6.6/asoc-mediatek-use-common-mtk_afe_pcm_platform-with-common-probe-cb.patch b/queue-6.6/asoc-mediatek-use-common-mtk_afe_pcm_platform-with-common-probe-cb.patch new file mode 100644 index 0000000000..fb311d23bf --- /dev/null +++ b/queue-6.6/asoc-mediatek-use-common-mtk_afe_pcm_platform-with-common-probe-cb.patch @@ -0,0 +1,245 @@ +From stable+bounces-288171-greg=kroah.com@vger.kernel.org Thu Jul 23 04:29:24 2026 +From: Sasha Levin +Date: Wed, 22 Jul 2026 22:29:13 -0400 +Subject: ASoC: mediatek: Use common mtk_afe_pcm_platform with common probe cb +To: stable@vger.kernel.org +Cc: AngeloGioacchino Del Regno , Alexandre Mergnat , Mark Brown , Sasha Levin +Message-ID: <20260723022915.2845450-4-sashal@kernel.org> + +From: AngeloGioacchino Del Regno + +[ Upstream commit 1d2a6b3b5189b6c3e51f29e050aa9b2337b774e8 ] + +Since the mtk-afe-platform-driver generic mtk_afe_pcm_platform now has +a common .probe() callback, there is no reason to keep duplicating this +function over and over in the SoC specific AFE-PCM drivers: switch over +to register with the common bits instead. + +Note that MT8186 was left out of this because it is registering some +extra sinegen controls in the AFE-PCM probe callback and needs extra +cleanups to be able to use the common bits. + +Reviewed-by: Alexandre Mergnat +Signed-off-by: AngeloGioacchino Del Regno +Link: https://lore.kernel.org/r/20240416071410.75620-13-angelogioacchino.delregno@collabora.com +Signed-off-by: Mark Brown +Stable-dep-of: e24d5dde56a5 ("ASoC: mediatek: mt8192: Check runtime resume during probe") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + sound/soc/mediatek/mt6797/mt6797-afe-pcm.c | 14 +------------- + sound/soc/mediatek/mt7986/mt7986-afe-pcm.c | 14 +------------- + sound/soc/mediatek/mt8183/mt8183-afe-pcm.c | 14 +------------- + sound/soc/mediatek/mt8188/mt8188-afe-pcm.c | 21 +-------------------- + sound/soc/mediatek/mt8192/mt8192-afe-pcm.c | 25 ++----------------------- + sound/soc/mediatek/mt8195/mt8195-afe-pcm.c | 21 +-------------------- + 6 files changed, 7 insertions(+), 102 deletions(-) + +--- a/sound/soc/mediatek/mt6797/mt6797-afe-pcm.c ++++ b/sound/soc/mediatek/mt6797/mt6797-afe-pcm.c +@@ -704,18 +704,6 @@ static int mt6797_afe_runtime_resume(str + return 0; + } + +-static int mt6797_afe_component_probe(struct snd_soc_component *component) +-{ +- return mtk_afe_add_sub_dai_control(component); +-} +- +-static const struct snd_soc_component_driver mt6797_afe_component = { +- .name = AFE_PCM_NAME, +- .probe = mt6797_afe_component_probe, +- .pointer = mtk_afe_pcm_pointer, +- .pcm_construct = mtk_afe_pcm_new, +-}; +- + static int mt6797_dai_memif_register(struct mtk_base_afe *afe) + { + struct mtk_base_afe_dai *dai; +@@ -852,7 +840,7 @@ static int mt6797_afe_pcm_dev_probe(stru + pm_runtime_get_sync(&pdev->dev); + + /* register component */ +- ret = devm_snd_soc_register_component(dev, &mt6797_afe_component, ++ ret = devm_snd_soc_register_component(dev, &mtk_afe_pcm_platform, + NULL, 0); + if (ret) { + dev_warn(dev, "err_platform\n"); +--- a/sound/soc/mediatek/mt7986/mt7986-afe-pcm.c ++++ b/sound/soc/mediatek/mt7986/mt7986-afe-pcm.c +@@ -429,18 +429,6 @@ static int mt7986_afe_runtime_resume(str + return 0; + } + +-static int mt7986_afe_component_probe(struct snd_soc_component *component) +-{ +- return mtk_afe_add_sub_dai_control(component); +-} +- +-static const struct snd_soc_component_driver mt7986_afe_component = { +- .name = AFE_PCM_NAME, +- .probe = mt7986_afe_component_probe, +- .pointer = mtk_afe_pcm_pointer, +- .pcm_construct = mtk_afe_pcm_new, +-}; +- + static int mt7986_dai_memif_register(struct mtk_base_afe *afe) + { + struct mtk_base_afe_dai *dai; +@@ -573,7 +561,7 @@ static int mt7986_afe_pcm_dev_probe(stru + + /* register component */ + ret = devm_snd_soc_register_component(&pdev->dev, +- &mt7986_afe_component, ++ &mtk_afe_pcm_platform, + NULL, 0); + if (ret) + return dev_err_probe(dev, ret, "Cannot register AFE component\n"); +--- a/sound/soc/mediatek/mt8183/mt8183-afe-pcm.c ++++ b/sound/soc/mediatek/mt8183/mt8183-afe-pcm.c +@@ -1043,18 +1043,6 @@ skip_regmap: + return 0; + } + +-static int mt8183_afe_component_probe(struct snd_soc_component *component) +-{ +- return mtk_afe_add_sub_dai_control(component); +-} +- +-static const struct snd_soc_component_driver mt8183_afe_component = { +- .name = AFE_PCM_NAME, +- .probe = mt8183_afe_component_probe, +- .pointer = mtk_afe_pcm_pointer, +- .pcm_construct = mtk_afe_pcm_new, +-}; +- + static int mt8183_dai_memif_register(struct mtk_base_afe *afe) + { + struct mtk_base_afe_dai *dai; +@@ -1250,7 +1238,7 @@ static int mt8183_afe_pcm_dev_probe(stru + + /* register component */ + ret = devm_snd_soc_register_component(&pdev->dev, +- &mt8183_afe_component, ++ &mtk_afe_pcm_platform, + NULL, 0); + if (ret) { + dev_warn(dev, "err_platform\n"); +--- a/sound/soc/mediatek/mt8188/mt8188-afe-pcm.c ++++ b/sound/soc/mediatek/mt8188/mt8188-afe-pcm.c +@@ -3027,25 +3027,6 @@ skip_regmap: + return 0; + } + +-static int mt8188_afe_component_probe(struct snd_soc_component *component) +-{ +- struct mtk_base_afe *afe = snd_soc_component_get_drvdata(component); +- int ret; +- +- snd_soc_component_init_regmap(component, afe->regmap); +- +- ret = mtk_afe_add_sub_dai_control(component); +- +- return ret; +-} +- +-static const struct snd_soc_component_driver mt8188_afe_component = { +- .name = AFE_PCM_NAME, +- .pointer = mtk_afe_pcm_pointer, +- .pcm_construct = mtk_afe_pcm_new, +- .probe = mt8188_afe_component_probe, +-}; +- + static int init_memif_priv_data(struct mtk_base_afe *afe) + { + struct mt8188_afe_private *afe_priv = afe->platform_priv; +@@ -3347,7 +3328,7 @@ static int mt8188_afe_pcm_dev_probe(stru + } + + /* register component */ +- ret = devm_snd_soc_register_component(dev, &mt8188_afe_component, ++ ret = devm_snd_soc_register_component(dev, &mtk_afe_pcm_platform, + afe->dai_drivers, afe->num_dai_drivers); + if (ret) { + dev_warn(dev, "err_platform\n"); +--- a/sound/soc/mediatek/mt8192/mt8192-afe-pcm.c ++++ b/sound/soc/mediatek/mt8192/mt8192-afe-pcm.c +@@ -2126,22 +2126,6 @@ skip_regmap: + return 0; + } + +-static int mt8192_afe_component_probe(struct snd_soc_component *component) +-{ +- return mtk_afe_add_sub_dai_control(component); +-} +- +-static const struct snd_soc_component_driver mt8192_afe_component = { +- .name = AFE_PCM_NAME, +- .probe = mt8192_afe_component_probe, +- .pointer = mtk_afe_pcm_pointer, +- .pcm_construct = mtk_afe_pcm_new, +-}; +- +-static const struct snd_soc_component_driver mt8192_afe_pcm_component = { +- .name = "mt8192-afe-pcm-dai", +-}; +- + static int mt8192_dai_memif_register(struct mtk_base_afe *afe) + { + struct mtk_base_afe_dai *dai; +@@ -2318,16 +2302,11 @@ static int mt8192_afe_pcm_dev_probe(stru + + /* register platform */ + ret = devm_snd_soc_register_component(&pdev->dev, +- &mt8192_afe_component, NULL, 0); +- if (ret) +- return dev_err_probe(dev, ret, "Couldn't register AFE component\n"); +- +- ret = devm_snd_soc_register_component(&pdev->dev, +- &mt8192_afe_pcm_component, ++ &mtk_afe_pcm_platform, + afe->dai_drivers, + afe->num_dai_drivers); + if (ret) +- return dev_err_probe(dev, ret, "Couldn't register AFE-PCM component\n"); ++ return dev_err_probe(dev, ret, "Couldn't register AFE component\n"); + + return 0; + } +--- a/sound/soc/mediatek/mt8195/mt8195-afe-pcm.c ++++ b/sound/soc/mediatek/mt8195/mt8195-afe-pcm.c +@@ -2944,25 +2944,6 @@ skip_regmap: + return 0; + } + +-static int mt8195_afe_component_probe(struct snd_soc_component *component) +-{ +- struct mtk_base_afe *afe = snd_soc_component_get_drvdata(component); +- int ret = 0; +- +- snd_soc_component_init_regmap(component, afe->regmap); +- +- ret = mtk_afe_add_sub_dai_control(component); +- +- return ret; +-} +- +-static const struct snd_soc_component_driver mt8195_afe_component = { +- .name = AFE_PCM_NAME, +- .pointer = mtk_afe_pcm_pointer, +- .pcm_construct = mtk_afe_pcm_new, +- .probe = mt8195_afe_component_probe, +-}; +- + static int init_memif_priv_data(struct mtk_base_afe *afe) + { + struct mt8195_afe_private *afe_priv = afe->platform_priv; +@@ -3164,7 +3145,7 @@ static int mt8195_afe_pcm_dev_probe(stru + } + + /* register component */ +- ret = devm_snd_soc_register_component(dev, &mt8195_afe_component, ++ ret = devm_snd_soc_register_component(dev, &mtk_afe_pcm_platform, + afe->dai_drivers, afe->num_dai_drivers); + if (ret) { + dev_warn(dev, "err_platform\n"); diff --git a/queue-6.6/ata-libata-core-reject-an-invalid-concurrent-positioning-ranges-count.patch b/queue-6.6/ata-libata-core-reject-an-invalid-concurrent-positioning-ranges-count.patch new file mode 100644 index 0000000000..2ff83f61b5 --- /dev/null +++ b/queue-6.6/ata-libata-core-reject-an-invalid-concurrent-positioning-ranges-count.patch @@ -0,0 +1,100 @@ +From stable+bounces-290234-greg=kroah.com@vger.kernel.org Wed Jul 29 04:40:14 2026 +From: Sasha Levin +Date: Tue, 28 Jul 2026 22:40:07 -0400 +Subject: ata: libata-core: Reject an invalid concurrent positioning ranges count +To: stable@vger.kernel.org +Cc: Bryam Vargas , Damien Le Moal , Niklas Cassel , Sasha Levin +Message-ID: <20260729024007.1127363-1-sashal@kernel.org> + +From: Bryam Vargas + +[ Upstream commit 533a0b940f901c15e5cbbd4b5d66e871c209e8ce ] + +ata_dev_config_cpr() takes the number of range descriptors from buf[0] +of the concurrent positioning ranges log (up to 255), which the device +reports independently of the log size in the GPL directory. The count is +then walked at a fixed 32-byte stride in two places with no bound: the +log read here, and the INQUIRY VPD page B9h emitter, which writes one +descriptor per range into the fixed 2048-byte ata_scsi_rbuf. A device +reporting a count larger than its own log overflows the read buffer (up +to 7704 bytes past a 512-byte slab), and a count above 62 overflows the +response buffer on the emit side. + +Bound the count once, on probe, against both the log the device returned +and the number of descriptors the VPD B9h response buffer can hold +(ATA_DEV_MAX_CPR, derived from the rbuf size). Reject an out-of-range +count with a warning; this keeps the emitter in bounds with no separate +change there. + +Suggested-by: Damien Le Moal +Fixes: fe22e1c2f705 ("libata: support concurrent positioning ranges log") +Fixes: c745dfc541e7 ("libata: fix reading concurrent positioning ranges log") +Cc: stable@vger.kernel.org +Signed-off-by: Bryam Vargas +Reviewed-by: Niklas Cassel +Signed-off-by: Damien Le Moal +[ adapted `kzalloc_flex()` allocation to `kzalloc(struct_size(...), GFP_KERNEL)` and adjusted context offsets. ] +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/ata/libata-core.c | 18 ++++++++++++++++++ + drivers/ata/libata-scsi.c | 2 -- + drivers/ata/libata.h | 9 +++++++++ + 3 files changed, 27 insertions(+), 2 deletions(-) + +--- a/drivers/ata/libata-core.c ++++ b/drivers/ata/libata-core.c +@@ -2794,6 +2794,24 @@ static void ata_dev_config_cpr(struct at + if (!nr_cpr) + goto out; + ++ /* ++ * The device reports the number of CPR descriptors independently of the ++ * log size, and that count is also used to emit VPD page B9h into the ++ * fixed-size rbuf. Reject a count larger than what that buffer can hold ++ * (ATA_DEV_MAX_CPR) or larger than the log the device actually returned. ++ */ ++ if (nr_cpr > ATA_DEV_MAX_CPR) { ++ ata_dev_warn(dev, ++ "Too many concurrent positioning ranges\n"); ++ goto out; ++ } ++ ++ if (buf_len < 64 + (size_t)nr_cpr * 32) { ++ ata_dev_warn(dev, ++ "Invalid number of concurrent positioning ranges\n"); ++ goto out; ++ } ++ + cpr_log = kzalloc(struct_size(cpr_log, cpr, nr_cpr), GFP_KERNEL); + if (!cpr_log) + goto out; +--- a/drivers/ata/libata-scsi.c ++++ b/drivers/ata/libata-scsi.c +@@ -37,8 +37,6 @@ + #include "libata.h" + #include "libata-transport.h" + +-#define ATA_SCSI_RBUF_SIZE 2048 +- + static DEFINE_SPINLOCK(ata_scsi_rbuf_lock); + static u8 ata_scsi_rbuf[ATA_SCSI_RBUF_SIZE]; + +--- a/drivers/ata/libata.h ++++ b/drivers/ata/libata.h +@@ -111,6 +111,15 @@ static inline void ata_acpi_bind_dev(str + #endif + + /* libata-scsi.c */ ++#define ATA_SCSI_RBUF_SIZE 2048 ++ ++/* ++ * Maximum number of concurrent positioning ranges (CPR) supported. The ACS ++ * specifications allow up to 255, but we limit this to the number of CPR ++ * descriptors that fit in the rbuf buffer used to emit VPD page B9h. ++ */ ++#define ATA_DEV_MAX_CPR min(255, ((ATA_SCSI_RBUF_SIZE - 64) / 32)) ++ + extern struct ata_device *ata_scsi_find_dev(struct ata_port *ap, + const struct scsi_device *scsidev); + extern int ata_scsi_add_hosts(struct ata_host *host, diff --git a/queue-6.6/audit-fix-recursive-locking-deadlock-in-audit_dupe_exe.patch b/queue-6.6/audit-fix-recursive-locking-deadlock-in-audit_dupe_exe.patch new file mode 100644 index 0000000000..6294c4623c --- /dev/null +++ b/queue-6.6/audit-fix-recursive-locking-deadlock-in-audit_dupe_exe.patch @@ -0,0 +1,364 @@ +From stable+bounces-287679-greg=kroah.com@vger.kernel.org Wed Jul 22 04:46:12 2026 +From: Sasha Levin +Date: Tue, 21 Jul 2026 20:51:15 -0400 +Subject: audit: fix recursive locking deadlock in audit_dupe_exe() +To: stable@vger.kernel.org +Cc: Ricardo Robaina , stable@kernel.org, Waiman Long , Richard Guy Briggs , Nathan Chancellor , Paul Moore , Sasha Levin +Message-ID: <20260722005116.605624-4-sashal@kernel.org> + +From: Ricardo Robaina + +[ Upstream commit 81905b5acbe77284734438df3fbec1158e6429a3 ] + +A deadlock occurs in the audit subsystem when duplicating +executable-related rules. + +When a file is moved (e.g., via do_renameat2()), the VFS layer locks +the parent directory (I_MUTEX_PARENT), which synchronously triggers an +fsnotify_move event. If an existing executable audit rule matches the +file being moved, the audit subsystem catches this event and calls +audit_dupe_exe() to duplicate the watch and update the rule. Then, +audit_alloc_mark() would call kern_path_parent() to resolve the path, +leading to a blind attempt to acquire the exact same I_MUTEX_PARENT lock +already held by the task, resulting in the following recursive locking +deadlock: + + ============================================ + WARNING: possible recursive locking detected + 6.12.0-55.27.1.el10_0.x86_64+debug #1 Not tainted + -------------------------------------------- + mv/5099 is trying to acquire lock: + ffff888132845358 (&inode->i_sb->s_type->i_mutex_dir_key/1){+.+.}-{3:3}, + at: __kern_path_locked+0x10a/0x2f0 + + but task is already holding lock: + ffff888132846b58 (&inode->i_sb->s_type->i_mutex_dir_key/1){+.+.}-{3:3}, + at: lock_two_directories+0x13f/0x2b0 + + other info that might help us debug this: + Possible unsafe locking scenario: + + CPU0 + ---- + lock(&inode->i_sb->s_type->i_mutex_dir_key/1); + lock(&inode->i_sb->s_type->i_mutex_dir_key/1); + + *** DEADLOCK *** + + May be due to missing lock nesting notation + + 6 locks held by mv/5099: + #0: ffff888112a9c440 (sb_writers#13) + at: do_renameat2+0x34c/0xbc0 + #1: ffff888112a9c790 (&type->s_vfs_rename_key#3) + at: do_renameat2+0x415/0xbc0 + #2: ffff888132846b58 (&inode->i_sb->s_type->i_mutex_dir_key/1) + at: lock_two_directories+0x13f/0x2b0 + #3: ffff888132845358 (&inode->i_sb->s_type->i_mutex_dir_key/5) + at: lock_two_directories+0x175/0x2b0 + #4: ffffffffb3a1fb10 (&fsnotify_mark_srcu) + at: fsnotify+0x454/0x28a0 + #5: ffffffffaf886230 (audit_filter_mutex) + at: audit_update_watch+0x36/0x11e0 + + stack backtrace: + Call Trace: + + dump_stack_lvl+0x6f/0xb0 + print_deadlock_bug.cold+0xbd/0xca + validate_chain+0x83a/0xf00 + __lock_acquire+0xcac/0x1d20 + lock_acquire.part.0+0x11b/0x360 + down_write_nested+0x9f/0x230 + __kern_path_locked+0x10a/0x2f0 + kern_path_locked+0x26/0x40 + audit_alloc_mark+0xfb/0x4f0 + audit_dupe_exe+0x6c/0xe0 + audit_dupe_rule+0x6c2/0xc00 + audit_update_watch+0x4cc/0x11e0 + audit_watch_handle_event+0x12c/0x1b0 + send_to_group+0x5d0/0x8b0 + fsnotify+0x615/0x28a0 + fsnotify_move+0x1d8/0x630 + vfs_rename+0xdcd/0x1df0 + do_renameat2+0x9d4/0xbc0 + __x64_sys_renameat+0x192/0x260 + do_syscall_64+0x92/0x180 + entry_SYSCALL_64_after_hwframe+0x76/0x7e + RIP: 0033:0x7f0491fe8c4e + Code: 0f 1f 40 00 48 8b 15 c1 e1 16 00 f7 d8 64 89 02 b8 ff ff ff ff + c3 66 0f 1f 44 00 00 f3 0f 1e fa 49 89 ca b8 08 01 00 00 0f 05 <48> + 3d 00 f0 ff ff 77 0a c3 66 0f 1f 84 00 00 00 00 00 48 8b 15 89 + RSP: 002b:00007ffc7210bf38 EFLAGS: 00000246 ORIG_RAX: 0000000000000108 + RAX: ffffffffffffffda RBX: 0000000000000000 RCX: 00007f0491fe8c4e + RDX: 0000000000000003 RSI: 00007ffc7210e6c8 RDI: 00000000ffffff9c + RBP: 0000000000000000 R08: 0000000000000000 R09: 0000000000000001 + R10: 00005575eb2dae2a R11: 0000000000000246 R12: 00005575eb2dae2a + R13: 00007ffc7210e6c8 R14: 0000000000000003 R15: 00000000ffffff9c + + +The aforementioned deadlock can be consistently reproduced by running +the script below: + + audit-dupe-exe-deadlock.sh + -------------------------- + #!/bin/bash + auditctl -D + mkdir -p /tmp/foo + touch /tmp/file + auditctl -a always,exit -F exe=/tmp/file -F path=/tmp/file -S all -k dr + mv /tmp/file /tmp/foo/file + rm -Rf /tmp/foo + +This patch fixes the issue by introducing struct audit_watch_ctx to pass +the fsnotify event context down to audit_alloc_mark(). By utilizing the +already-resolved directory inode provided by the event, we bypass the +kern_path_parent() path resolution entirely, safely avoiding the +recursive lock. Furthermore, it explicitly allows duplicate fsnotify +marks (allow_dups = 1) during the rename update, allowing the new rule's +mark to safely coexist with the old rule's mark until the old rule is +freed. + +P.S.: This issue was identified and reproduced during a comprehensive +code coverage analysis of the audit subsystem. The full report is +available at the link below: + +https://people.redhat.com/rrobaina/audit-code-coverage-analysis.pdf + +P.P.S: With the permission of both Ricardo and Nathan, I've squashed a +fixup patch from Nathan that addresses a compile time error when +CONFIG_AUDITSYSCALL=n. + +Cc: stable@kernel.org +Fixes: 34d99af52ad4 ("audit: implement audit by executable") +Acked-by: Waiman Long +Acked-by: Richard Guy Briggs +Signed-off-by: Nathan Chancellor +Signed-off-by: Ricardo Robaina +[PM: move link metadata into the msg, apply fix from NC] +Signed-off-by: Paul Moore +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + kernel/audit.h | 17 ++++++++++++----- + kernel/audit_fsnotify.c | 32 +++++++++++++++++++++++--------- + kernel/audit_watch.c | 25 +++++++++++++++++-------- + kernel/auditfilter.c | 9 +++++---- + 4 files changed, 57 insertions(+), 26 deletions(-) + +--- a/kernel/audit.h ++++ b/kernel/audit.h +@@ -250,8 +250,13 @@ extern int audit_del_rule(struct audit_e + extern void audit_free_rule_rcu(struct rcu_head *head); + extern struct list_head audit_filter_list[]; + +-extern struct audit_entry *audit_dupe_rule(struct audit_krule *old); ++struct audit_watch_ctx { ++ struct inode *dir; ++ struct inode *child; ++}; + ++extern struct audit_entry *audit_dupe_rule(struct audit_krule *old, ++ struct audit_watch_ctx *ctx); + extern void audit_log_d_path_exe(struct audit_buffer *ab, + struct mm_struct *mm); + +@@ -274,13 +279,15 @@ extern char *audit_watch_path(struct aud + extern int audit_watch_compare(struct audit_watch *watch, u64 ino, dev_t dev); + + extern struct audit_fsnotify_mark *audit_alloc_mark(struct audit_krule *krule, +- char *pathname, int len); ++ char *pathname, int len, ++ struct audit_watch_ctx *ctx); + extern char *audit_mark_path(struct audit_fsnotify_mark *mark); + extern void audit_remove_mark(struct audit_fsnotify_mark *audit_mark); + extern void audit_remove_mark_rule(struct audit_krule *krule); + extern int audit_mark_compare(struct audit_fsnotify_mark *mark, u64 ino, + dev_t dev); +-extern int audit_dupe_exe(struct audit_krule *new, struct audit_krule *old); ++extern int audit_dupe_exe(struct audit_krule *new, struct audit_krule *old, ++ struct audit_watch_ctx *ctx); + extern int audit_exe_compare(struct task_struct *tsk, + struct audit_fsnotify_mark *mark); + +@@ -311,13 +318,13 @@ extern struct list_head *audit_killed_tr + #define audit_watch_path(w) "" + #define audit_watch_compare(w, i, d) 0 + +-#define audit_alloc_mark(k, p, l) (ERR_PTR(-EINVAL)) ++#define audit_alloc_mark(k, p, l, c) (ERR_PTR(-EINVAL)) + #define audit_mark_path(m) "" + #define audit_remove_mark(m) do { } while (0) + #define audit_remove_mark_rule(k) do { } while (0) + #define audit_mark_compare(m, i, d) 0 + #define audit_exe_compare(t, m) (-EINVAL) +-#define audit_dupe_exe(n, o) (-EINVAL) ++#define audit_dupe_exe(n, o, c) (-EINVAL) + + #define audit_remove_tree_rule(rule) BUG() + #define audit_add_tree_rule(rule) -EINVAL +--- a/kernel/audit_fsnotify.c ++++ b/kernel/audit_fsnotify.c +@@ -71,19 +71,30 @@ static void audit_update_mark(struct aud + audit_mark->ino = inode ? inode->i_ino : AUDIT_INO_UNSET; + } + +-struct audit_fsnotify_mark *audit_alloc_mark(struct audit_krule *krule, char *pathname, int len) ++struct audit_fsnotify_mark *audit_alloc_mark(struct audit_krule *krule, char *pathname, ++ int len, struct audit_watch_ctx *ctx) + { + struct audit_fsnotify_mark *audit_mark; + struct path path; + struct dentry *dentry; +- int ret; ++ struct inode *dir, *child; ++ int ret, allow_dups; + + if (pathname[0] != '/' || pathname[len-1] == '/') + return ERR_PTR(-EINVAL); + +- dentry = kern_path_parent(pathname, &path); +- if (IS_ERR(dentry)) +- return ERR_CAST(dentry); /* returning an error */ ++ if (!ctx) { ++ dentry = kern_path_parent(pathname, &path); ++ if (IS_ERR(dentry)) ++ return ERR_CAST(dentry); /* returning an error */ ++ dir = d_inode(path.dentry); ++ child = d_inode(dentry); ++ allow_dups = 0; ++ } else { ++ dir = ctx->dir; ++ child = ctx->child; ++ allow_dups = 1; ++ } + + audit_mark = kzalloc(sizeof(*audit_mark), GFP_KERNEL); + if (unlikely(!audit_mark)) { +@@ -94,18 +105,21 @@ struct audit_fsnotify_mark *audit_alloc_ + fsnotify_init_mark(&audit_mark->mark, audit_fsnotify_group); + audit_mark->mark.mask = AUDIT_FS_EVENTS; + audit_mark->path = pathname; +- audit_update_mark(audit_mark, dentry->d_inode); + audit_mark->rule = krule; + +- ret = fsnotify_add_inode_mark(&audit_mark->mark, path.dentry->d_inode, 0); ++ audit_update_mark(audit_mark, child); ++ ret = fsnotify_add_inode_mark(&audit_mark->mark, dir, allow_dups); ++ + if (ret < 0) { + audit_mark->path = NULL; + fsnotify_put_mark(&audit_mark->mark); + audit_mark = ERR_PTR(ret); + } + out: +- dput(dentry); +- path_put(&path); ++ if (!ctx) { ++ dput(dentry); ++ path_put(&path); ++ } + return audit_mark; + } + +--- a/kernel/audit_watch.c ++++ b/kernel/audit_watch.c +@@ -244,7 +244,8 @@ static void audit_watch_log_rule_change( + /* Update inode info in audit rules based on filesystem event. */ + static void audit_update_watch(struct audit_parent *parent, + const struct qstr *dname, dev_t dev, +- u64 ino, unsigned int invalidating) ++ u64 ino, unsigned int invalidating, ++ struct audit_watch_ctx *ctx) + { + struct audit_watch *owatch, *nwatch, *nextw; + struct audit_krule *r, *nextr; +@@ -280,7 +281,7 @@ static void audit_update_watch(struct au + list_del(&oentry->rule.rlist); + list_del_rcu(&oentry->list); + +- nentry = audit_dupe_rule(&oentry->rule); ++ nentry = audit_dupe_rule(&oentry->rule, ctx); + if (IS_ERR(nentry)) { + list_del(&oentry->rule.list); + audit_panic("error updating watch, removing"); +@@ -479,10 +480,17 @@ static int audit_watch_handle_event(stru + if (WARN_ON_ONCE(inode_mark->group != audit_watch_group)) + return 0; + +- if (mask & (FS_CREATE|FS_MOVED_TO) && inode) +- audit_update_watch(parent, dname, inode->i_sb->s_dev, inode->i_ino, 0); +- else if (mask & (FS_DELETE|FS_MOVED_FROM)) +- audit_update_watch(parent, dname, AUDIT_DEV_UNSET, AUDIT_INO_UNSET, 1); ++ if (mask & (FS_CREATE|FS_MOVED_TO) && inode) { ++ struct audit_watch_ctx ctx = { .dir = dir, .child = inode }; ++ ++ audit_update_watch(parent, dname, inode->i_sb->s_dev, inode->i_ino, 0, ++ &ctx); ++ } else if (mask & (FS_DELETE|FS_MOVED_FROM)) { ++ struct audit_watch_ctx ctx = { .dir = dir, .child = NULL }; ++ ++ audit_update_watch(parent, dname, AUDIT_DEV_UNSET, AUDIT_INO_UNSET, 1, ++ &ctx); ++ } + else if (mask & (FS_DELETE_SELF|FS_UNMOUNT|FS_MOVE_SELF)) + audit_remove_parent_watches(parent); + +@@ -505,7 +513,8 @@ static int __init audit_watch_init(void) + } + device_initcall(audit_watch_init); + +-int audit_dupe_exe(struct audit_krule *new, struct audit_krule *old) ++int audit_dupe_exe(struct audit_krule *new, struct audit_krule *old, ++ struct audit_watch_ctx *ctx) + { + struct audit_fsnotify_mark *audit_mark; + char *pathname; +@@ -514,7 +523,7 @@ int audit_dupe_exe(struct audit_krule *n + if (!pathname) + return -ENOMEM; + +- audit_mark = audit_alloc_mark(new, pathname, strlen(pathname)); ++ audit_mark = audit_alloc_mark(new, pathname, strlen(pathname), ctx); + if (IS_ERR(audit_mark)) { + kfree(pathname); + return PTR_ERR(audit_mark); +--- a/kernel/auditfilter.c ++++ b/kernel/auditfilter.c +@@ -590,7 +590,7 @@ static struct audit_entry *audit_data_to + err = PTR_ERR(str); + goto exit_free; + } +- audit_mark = audit_alloc_mark(&entry->rule, str, f_val); ++ audit_mark = audit_alloc_mark(&entry->rule, str, f_val, NULL); + if (IS_ERR(audit_mark)) { + kfree(str); + err = PTR_ERR(audit_mark); +@@ -818,7 +818,8 @@ static inline int audit_dupe_lsm_field(s + * rule with the new rule in the filterlist, then free the old rule. + * The rlist element is undefined; list manipulations are handled apart from + * the initial copy. */ +-struct audit_entry *audit_dupe_rule(struct audit_krule *old) ++struct audit_entry *audit_dupe_rule(struct audit_krule *old, ++ struct audit_watch_ctx *ctx) + { + u32 fcount = old->field_count; + struct audit_entry *entry; +@@ -877,7 +878,7 @@ struct audit_entry *audit_dupe_rule(stru + new->filterkey = fk; + break; + case AUDIT_EXE: +- err = audit_dupe_exe(new, old); ++ err = audit_dupe_exe(new, old, ctx); + break; + } + if (err) { +@@ -1408,7 +1409,7 @@ static int update_lsm_rule(struct audit_ + if (!security_audit_rule_known(r)) + return 0; + +- nentry = audit_dupe_rule(r); ++ nentry = audit_dupe_rule(r, NULL); + if (entry->rule.exe) + audit_remove_mark(entry->rule.exe); + if (IS_ERR(nentry)) { diff --git a/queue-6.6/audit-use-unsigned-int-instead-of-unsigned.patch b/queue-6.6/audit-use-unsigned-int-instead-of-unsigned.patch new file mode 100644 index 0000000000..353e0b9644 --- /dev/null +++ b/queue-6.6/audit-use-unsigned-int-instead-of-unsigned.patch @@ -0,0 +1,190 @@ +From stable+bounces-287678-greg=kroah.com@vger.kernel.org Wed Jul 22 02:54:17 2026 +From: Sasha Levin +Date: Tue, 21 Jul 2026 20:51:14 -0400 +Subject: audit: use 'unsigned int' instead of 'unsigned' +To: stable@vger.kernel.org +Cc: Ricardo Robaina , Paul Moore , Sasha Levin +Message-ID: <20260722005116.605624-3-sashal@kernel.org> + +From: Ricardo Robaina + +[ Upstream commit 8b226771014beab1292081151a99530886ce54b4 ] + +Address checkpatch.pl warning below, across the audit subsystem: + + WARNING: Prefer 'unsigned int' to bare use of 'unsigned' + +Minor cleanup, no functional changes. + +Signed-off-by: Ricardo Robaina +Signed-off-by: Paul Moore +Stable-dep-of: 81905b5acbe7 ("audit: fix recursive locking deadlock in audit_dupe_exe()") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + include/linux/audit.h | 4 ++-- + include/linux/audit_arch.h | 12 ++++++------ + kernel/audit.c | 2 +- + kernel/audit.h | 2 +- + kernel/audit_tree.c | 2 +- + kernel/auditfilter.c | 8 ++++---- + kernel/auditsc.c | 2 +- + lib/compat_audit.c | 12 ++++++------ + 8 files changed, 22 insertions(+), 22 deletions(-) + +--- a/include/linux/audit.h ++++ b/include/linux/audit.h +@@ -122,8 +122,8 @@ enum audit_nfcfgop { + AUDIT_NFT_OP_INVALID, + }; + +-extern int __init audit_register_class(int class, unsigned *list); +-extern int audit_classify_syscall(int abi, unsigned syscall); ++extern int __init audit_register_class(int class, unsigned int *list); ++extern int audit_classify_syscall(int abi, unsigned int syscall); + extern int audit_classify_arch(int arch); + + /* audit_names->type values */ +--- a/include/linux/audit_arch.h ++++ b/include/linux/audit_arch.h +@@ -21,13 +21,13 @@ enum auditsc_class_t { + AUDITSC_NVALS /* count */ + }; + +-extern int audit_classify_compat_syscall(int abi, unsigned syscall); ++extern int audit_classify_compat_syscall(int abi, unsigned int syscall); + + /* only for compat system calls */ +-extern unsigned compat_write_class[]; +-extern unsigned compat_read_class[]; +-extern unsigned compat_dir_class[]; +-extern unsigned compat_chattr_class[]; +-extern unsigned compat_signal_class[]; ++extern unsigned int compat_write_class[]; ++extern unsigned int compat_read_class[]; ++extern unsigned int compat_dir_class[]; ++extern unsigned int compat_chattr_class[]; ++extern unsigned int compat_signal_class[]; + + #endif +--- a/kernel/audit.c ++++ b/kernel/audit.c +@@ -1989,7 +1989,7 @@ static void audit_log_vformat(struct aud + * here and AUDIT_BUFSIZ is at least 1024, then we can + * log everything that printk could have logged. */ + avail = audit_expand(ab, +- max_t(unsigned, AUDIT_BUFSIZ, 1+len-avail)); ++ max_t(unsigned int, AUDIT_BUFSIZ, 1+len-avail)); + if (!avail) + goto out_va_end; + len = vsnprintf(skb_tail_pointer(skb), avail, fmt, args2); +--- a/kernel/audit.h ++++ b/kernel/audit.h +@@ -227,7 +227,7 @@ static inline int audit_hash_ino(u64 ino + /* Indicates that audit should log the full pathname. */ + #define AUDIT_NAME_FULL -1 + +-extern int audit_match_class(int class, unsigned syscall); ++extern int audit_match_class(int class, unsigned int syscall); + extern int audit_comparator(const u32 left, const u32 op, const u32 right); + extern int audit_uid_comparator(kuid_t left, u32 op, kuid_t right); + extern int audit_gid_comparator(kgid_t left, u32 op, kgid_t right); +--- a/kernel/audit_tree.c ++++ b/kernel/audit_tree.c +@@ -33,7 +33,7 @@ struct audit_chunk { + struct audit_node { + struct list_head list; + struct audit_tree *owner; +- unsigned index; /* index; upper bit indicates 'will prune' */ ++ unsigned int index; /* index; upper bit indicates 'will prune' */ + } owners[]; + }; + +--- a/kernel/auditfilter.c ++++ b/kernel/auditfilter.c +@@ -165,13 +165,13 @@ static inline int audit_to_inode(struct + + static __u32 *classes[AUDIT_SYSCALL_CLASSES]; + +-int __init audit_register_class(int class, unsigned *list) ++int __init audit_register_class(int class, unsigned int *list) + { + __u32 *p = kcalloc(AUDIT_BITMASK_SIZE, sizeof(__u32), GFP_KERNEL); + if (!p) + return -ENOMEM; + while (*list != ~0U) { +- unsigned n = *list++; ++ unsigned int n = *list++; + if (n >= AUDIT_BITMASK_SIZE * 32 - AUDIT_SYSCALL_CLASSES) { + kfree(p); + return -EINVAL; +@@ -186,7 +186,7 @@ int __init audit_register_class(int clas + return 0; + } + +-int audit_match_class(int class, unsigned syscall) ++int audit_match_class(int class, unsigned int syscall) + { + if (unlikely(syscall >= AUDIT_BITMASK_SIZE * 32)) + return 0; +@@ -237,7 +237,7 @@ static int audit_match_signal(struct aud + /* Common user-space to kernel rule translation. */ + static inline struct audit_entry *audit_to_entry_common(struct audit_rule_data *rule) + { +- unsigned listnr; ++ unsigned int listnr; + struct audit_entry *entry; + int i, err; + +--- a/kernel/auditsc.c ++++ b/kernel/auditsc.c +@@ -150,7 +150,7 @@ static const struct audit_nfcfgop_tab au + + static int audit_match_perm(struct audit_context *ctx, int mask) + { +- unsigned n; ++ unsigned int n; + + if (unlikely(!ctx)) + return 0; +--- a/lib/compat_audit.c ++++ b/lib/compat_audit.c +@@ -4,32 +4,32 @@ + #include + #include + +-unsigned compat_dir_class[] = { ++unsigned int compat_dir_class[] = { + #include + ~0U + }; + +-unsigned compat_read_class[] = { ++unsigned int compat_read_class[] = { + #include + ~0U + }; + +-unsigned compat_write_class[] = { ++unsigned int compat_write_class[] = { + #include + ~0U + }; + +-unsigned compat_chattr_class[] = { ++unsigned int compat_chattr_class[] = { + #include + ~0U + }; + +-unsigned compat_signal_class[] = { ++unsigned int compat_signal_class[] = { + #include + ~0U + }; + +-int audit_classify_compat_syscall(int abi, unsigned syscall) ++int audit_classify_compat_syscall(int abi, unsigned int syscall) + { + switch (syscall) { + #ifdef __NR_open diff --git a/queue-6.6/audit-widen-ino-fields-to-u64.patch b/queue-6.6/audit-widen-ino-fields-to-u64.patch new file mode 100644 index 0000000000..eb47ca2048 --- /dev/null +++ b/queue-6.6/audit-widen-ino-fields-to-u64.patch @@ -0,0 +1,159 @@ +From stable+bounces-287677-greg=kroah.com@vger.kernel.org Wed Jul 22 04:46:03 2026 +From: Sasha Levin +Date: Tue, 21 Jul 2026 20:51:13 -0400 +Subject: audit: widen ino fields to u64 +To: stable@vger.kernel.org +Cc: Jeff Layton , Paul Moore , Christian Brauner , Sasha Levin +Message-ID: <20260722005116.605624-2-sashal@kernel.org> + +From: Jeff Layton + +[ Upstream commit 125dfa218134df7cc112667e92984de9d8cd0bf6 ] + +inode->i_ino is being widened from unsigned long to u64. The audit +subsystem uses unsigned long ino in struct fields, function parameters, +and local variables that store inode numbers from arbitrary filesystems. +On 32-bit platforms this truncates inode numbers that exceed 32 bits, +which will cause incorrect audit log entries and broken watch/mark +comparisons. + +Widen all audit ino fields, parameters, and locals to u64, and update +the inode format string from %lu to %llu to match. + +Signed-off-by: Jeff Layton +Link: https://patch.msgid.link/20260304-iino-u64-v3-2-2257ad83d372@kernel.org +Acked-by: Paul Moore +Signed-off-by: Christian Brauner +Stable-dep-of: 81905b5acbe7 ("audit: fix recursive locking deadlock in audit_dupe_exe()") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + include/linux/audit.h | 2 +- + kernel/audit.h | 9 ++++----- + kernel/audit_fsnotify.c | 2 +- + kernel/audit_watch.c | 10 +++++----- + kernel/auditsc.c | 4 ++-- + 5 files changed, 13 insertions(+), 14 deletions(-) + +--- a/include/linux/audit.h ++++ b/include/linux/audit.h +@@ -16,7 +16,7 @@ + #include + #include + +-#define AUDIT_INO_UNSET ((unsigned long)-1) ++#define AUDIT_INO_UNSET ((u64)-1) + #define AUDIT_DEV_UNSET ((dev_t)-1) + + struct audit_sig_info { +--- a/kernel/audit.h ++++ b/kernel/audit.h +@@ -75,7 +75,7 @@ struct audit_names { + int name_len; /* number of chars to log */ + bool hidden; /* don't log this record */ + +- unsigned long ino; ++ u64 ino; + dev_t dev; + umode_t mode; + kuid_t uid; +@@ -219,9 +219,9 @@ extern int auditd_test_task(struct task_ + #define AUDIT_INODE_BUCKETS 32 + extern struct list_head audit_inode_hash[AUDIT_INODE_BUCKETS]; + +-static inline int audit_hash_ino(u32 ino) ++static inline int audit_hash_ino(u64 ino) + { +- return (ino & (AUDIT_INODE_BUCKETS-1)); ++ return ((u32)ino & (AUDIT_INODE_BUCKETS-1)); + } + + /* Indicates that audit should log the full pathname. */ +@@ -271,8 +271,7 @@ extern int audit_to_watch(struct audit_k + extern int audit_add_watch(struct audit_krule *krule, struct list_head **list); + extern void audit_remove_watch_rule(struct audit_krule *krule); + extern char *audit_watch_path(struct audit_watch *watch); +-extern int audit_watch_compare(struct audit_watch *watch, unsigned long ino, +- dev_t dev); ++extern int audit_watch_compare(struct audit_watch *watch, u64 ino, dev_t dev); + + extern struct audit_fsnotify_mark *audit_alloc_mark(struct audit_krule *krule, + char *pathname, int len); +--- a/kernel/audit_fsnotify.c ++++ b/kernel/audit_fsnotify.c +@@ -25,7 +25,7 @@ + */ + struct audit_fsnotify_mark { + dev_t dev; /* associated superblock device */ +- unsigned long ino; /* associated inode number */ ++ u64 ino; /* associated inode number */ + char *path; /* insertion path */ + struct fsnotify_mark mark; /* fsnotify mark on the inode */ + struct audit_krule *rule; +--- a/kernel/audit_watch.c ++++ b/kernel/audit_watch.c +@@ -37,7 +37,7 @@ struct audit_watch { + refcount_t count; /* reference count */ + dev_t dev; /* associated superblock device */ + char *path; /* insertion path */ +- unsigned long ino; /* associated inode number */ ++ u64 ino; /* associated inode number */ + struct audit_parent *parent; /* associated parent */ + struct list_head wlist; /* entry in parent->watches list */ + struct list_head rules; /* anchor for krule->rlist */ +@@ -125,7 +125,7 @@ char *audit_watch_path(struct audit_watc + return watch->path; + } + +-int audit_watch_compare(struct audit_watch *watch, unsigned long ino, dev_t dev) ++int audit_watch_compare(struct audit_watch *watch, u64 ino, dev_t dev) + { + return (watch->ino != AUDIT_INO_UNSET) && + (watch->ino == ino) && +@@ -285,7 +285,7 @@ static void audit_update_watch(struct au + list_del(&oentry->rule.list); + audit_panic("error updating watch, removing"); + } else { +- int h = audit_hash_ino((u32)ino); ++ int h = audit_hash_ino(ino); + + /* + * nentry->rule.watch == oentry->rule.watch so +@@ -439,7 +439,7 @@ int audit_add_watch(struct audit_krule * + + audit_add_to_parent(krule, parent); + +- h = audit_hash_ino((u32)watch->ino); ++ h = audit_hash_ino(watch->ino); + *list = &audit_inode_hash[h]; + error: + path_put(&parent_path); +@@ -527,7 +527,7 @@ int audit_dupe_exe(struct audit_krule *n + int audit_exe_compare(struct task_struct *tsk, struct audit_fsnotify_mark *mark) + { + struct file *exe_file; +- unsigned long ino; ++ u64 ino; + dev_t dev; + + /* only do exe filtering if we are recording @current events/records */ +--- a/kernel/auditsc.c ++++ b/kernel/auditsc.c +@@ -884,7 +884,7 @@ static int audit_filter_inode_name(struc + struct audit_names *n, + struct audit_context *ctx) + { +- int h = audit_hash_ino((u32)n->ino); ++ int h = audit_hash_ino(n->ino); + struct list_head *list = &audit_inode_hash[h]; + + return __audit_filter_op(tsk, ctx, list, n, ctx->major); +@@ -1549,7 +1549,7 @@ static void audit_log_name(struct audit_ + audit_log_format(ab, " name=(null)"); + + if (n->ino != AUDIT_INO_UNSET) +- audit_log_format(ab, " inode=%lu dev=%02x:%02x mode=%#ho ouid=%u ogid=%u rdev=%02x:%02x", ++ audit_log_format(ab, " inode=%llu dev=%02x:%02x mode=%#ho ouid=%u ogid=%u rdev=%02x:%02x", + n->ino, + MAJOR(n->dev), + MINOR(n->dev), diff --git a/queue-6.6/bootconfig-do-not-put-quotes-on-cmdline-items-unless-necessary.patch b/queue-6.6/bootconfig-do-not-put-quotes-on-cmdline-items-unless-necessary.patch new file mode 100644 index 0000000000..387a0e59f5 --- /dev/null +++ b/queue-6.6/bootconfig-do-not-put-quotes-on-cmdline-items-unless-necessary.patch @@ -0,0 +1,68 @@ +From stable+bounces-290112-greg=kroah.com@vger.kernel.org Tue Jul 28 20:22:36 2026 +From: Sasha Levin +Date: Tue, 28 Jul 2026 14:06:41 -0400 +Subject: bootconfig: do not put quotes on cmdline items unless necessary +To: stable@vger.kernel.org +Cc: Rasmus Villemoes , Masami Hiramatsu , Andrew Morton , Sasha Levin +Message-ID: <20260728180643.289803-1-sashal@kernel.org> + +From: Rasmus Villemoes + +[ Upstream commit 212f863fa8811c780abacc1d0404c573fdc0a2de ] + +When trying to migrate to using bootconfig to embed the kernel's and +PID1's command line with the kernel image itself, and so allowing changing +that without modifying the bootloader, I noticed that /proc/cmdline +changed from e.g. + + console=ttymxc0,115200n8 cma=128M quiet -- --log-level=notice + +to + + console="ttymxc0,115200n8" cma="128M" quiet -- --log-level="notice" + +The kernel parameters are parsed just fine, and the quotes are indeed +stripped from the actual argv[] given to PID1. However, the quoting +doesn't really serve any purpose and looks excessive, and might confuse +some (naive) userspace tool trying to parse /proc/cmdline. So do not +quote the value unless it contains whitespace. + +Link: https://lkml.kernel.org/r/20240320101952.62135-1-linux@rasmusvillemoes.dk +Signed-off-by: Rasmus Villemoes +Cc: Masami Hiramatsu +Signed-off-by: Andrew Morton +Stable-dep-of: dec4d8118c17 ("bootconfig: fix NULL-pointer arithmetic in xbc_snprint_cmdline()") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + init/main.c | 12 +++++++++--- + 1 file changed, 9 insertions(+), 3 deletions(-) + +--- a/init/main.c ++++ b/init/main.c +@@ -325,7 +325,7 @@ static int __init xbc_snprint_cmdline(ch + { + struct xbc_node *knode, *vnode; + char *end = buf + size; +- const char *val; ++ const char *val, *q; + int ret; + + xbc_node_for_each_key_value(root, knode, val) { +@@ -343,8 +343,14 @@ static int __init xbc_snprint_cmdline(ch + continue; + } + xbc_array_for_each_value(vnode, val) { +- ret = snprintf(buf, rest(buf, end), "%s=\"%s\" ", +- xbc_namebuf, val); ++ /* ++ * For prettier and more readable /proc/cmdline, only ++ * quote the value when necessary, i.e. when it contains ++ * whitespace. ++ */ ++ q = strpbrk(val, " \t\r\n") ? "\"" : ""; ++ ret = snprintf(buf, rest(buf, end), "%s=%s%s%s ", ++ xbc_namebuf, q, val, q); + if (ret < 0) + return ret; + buf += ret; diff --git a/queue-6.6/bootconfig-fix-null-pointer-arithmetic-in-xbc_snprint_cmdline.patch b/queue-6.6/bootconfig-fix-null-pointer-arithmetic-in-xbc_snprint_cmdline.patch new file mode 100644 index 0000000000..2331ccd31e --- /dev/null +++ b/queue-6.6/bootconfig-fix-null-pointer-arithmetic-in-xbc_snprint_cmdline.patch @@ -0,0 +1,97 @@ +From stable+bounces-290114-greg=kroah.com@vger.kernel.org Tue Jul 28 20:29:44 2026 +From: Sasha Levin +Date: Tue, 28 Jul 2026 14:06:43 -0400 +Subject: bootconfig: fix NULL-pointer arithmetic in xbc_snprint_cmdline() +To: stable@vger.kernel.org +Cc: Breno Leitao , "Masami Hiramatsu (Google)" , Sasha Levin +Message-ID: <20260728180643.289803-3-sashal@kernel.org> + +From: Breno Leitao + +[ Upstream commit dec4d8118c179b3d12bca7e609054c6011c4f2ce ] + +xbc_snprint_cmdline() is meant to be called twice: first with +buf=NULL, size=0 to probe the rendered length, then with a real +buffer to fill it (the standard snprintf() two-pass pattern). The +probe call makes the function compute "buf + size" (NULL + 0) and, +on every iteration, advance "buf += ret" from that NULL base and +pass the result back into snprintf(). + +Pointer arithmetic on a NULL pointer is undefined behavior. It is +harmless in the in-kernel callers today, but the follow-up patches +run this same code in the userspace tools/bootconfig parser at kernel +build time, where host UBSan / FORTIFY_SOURCE abort the build. + +Track a running written length (size_t) instead of mutating @buf, and +only form "buf + len" when @buf is non-NULL. snprintf(NULL, 0, ...) +is itself well defined and returns the would-be length, so the +two-pass "probe then fill" usage returns identical byte counts. + +Link: https://lore.kernel.org/all/20260626-bootconfig_using_tools-v7-1-24ab72139c29@debian.org/ + +Fixes: 51887d03aca1 ("bootconfig: init: Allow admin to use bootconfig for kernel command line") +Cc: stable@vger.kernel.org +Signed-off-by: Breno Leitao +Signed-off-by: Masami Hiramatsu (Google) +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + lib/bootconfig.c | 23 ++++++++++++++++------- + 1 file changed, 16 insertions(+), 7 deletions(-) + +--- a/lib/bootconfig.c ++++ b/lib/bootconfig.c +@@ -426,10 +426,18 @@ static char xbc_namebuf[XBC_KEYLEN_MAX] + int __init xbc_snprint_cmdline(char *buf, size_t size, struct xbc_node *root) + { + struct xbc_node *knode, *vnode; +- char *end = buf + size; + const char *val, *q; ++ size_t len = 0; + int ret; + ++ /* ++ * Track the running written length rather than advancing @buf, so we ++ * never form "buf + size" or "buf += ret" while @buf is NULL (the ++ * size-probe call passes buf=NULL, size=0). NULL pointer arithmetic ++ * is undefined behavior and trips host UBSan / FORTIFY_SOURCE when ++ * this renderer runs at kernel build time. snprintf(NULL, 0, ...) ++ * itself is well defined and returns the would-be length. ++ */ + xbc_node_for_each_key_value(root, knode, val) { + ret = xbc_node_compose_key_after(root, knode, + xbc_namebuf, XBC_KEYLEN_MAX); +@@ -438,10 +446,11 @@ int __init xbc_snprint_cmdline(char *buf + + vnode = xbc_node_get_child(knode); + if (!vnode) { +- ret = snprintf(buf, rest(buf, end), "%s ", xbc_namebuf); ++ ret = snprintf(buf ? buf + len : NULL, rest(len, size), ++ "%s ", xbc_namebuf); + if (ret < 0) + return ret; +- buf += ret; ++ len += ret; + continue; + } + xbc_array_for_each_value(vnode, val) { +@@ -451,15 +460,15 @@ int __init xbc_snprint_cmdline(char *buf + * whitespace. + */ + q = strpbrk(val, " \t\r\n") ? "\"" : ""; +- ret = snprintf(buf, rest(buf, end), "%s=%s%s%s ", +- xbc_namebuf, q, val, q); ++ ret = snprintf(buf ? buf + len : NULL, rest(len, size), ++ "%s=%s%s%s ", xbc_namebuf, q, val, q); + if (ret < 0) + return ret; +- buf += ret; ++ len += ret; + } + } + +- return buf - (end - size); ++ return len; + } + #undef rest + diff --git a/queue-6.6/bootconfig-move-xbc_snprint_cmdline-to-lib-bootconfig.c.patch b/queue-6.6/bootconfig-move-xbc_snprint_cmdline-to-lib-bootconfig.c.patch new file mode 100644 index 0000000000..64245e7e8a --- /dev/null +++ b/queue-6.6/bootconfig-move-xbc_snprint_cmdline-to-lib-bootconfig.c.patch @@ -0,0 +1,165 @@ +From stable+bounces-290113-greg=kroah.com@vger.kernel.org Tue Jul 28 20:13:24 2026 +From: Sasha Levin +Date: Tue, 28 Jul 2026 14:06:42 -0400 +Subject: bootconfig: move xbc_snprint_cmdline() to lib/bootconfig.c +To: stable@vger.kernel.org +Cc: Breno Leitao , "Masami Hiramatsu (Google)" , Sasha Levin +Message-ID: <20260728180643.289803-2-sashal@kernel.org> + +From: Breno Leitao + +[ Upstream commit 5a643e4623238e14b03d75ca0d4eda0645720cee ] + +Move xbc_snprint_cmdline() from init/main.c to lib/bootconfig.c so the +function (and its xbc_namebuf scratch buffer) becomes part of the shared +parser library. tools/bootconfig already compiles lib/bootconfig.c +directly, which lets a follow-up patch reuse the same renderer in the +userspace tool to convert a bootconfig file into a flat cmdline string +at build time. + +No functional change. + +Link: https://lore.kernel.org/all/20260508-bootconfig_using_tools-v1-1-1132219aa773@debian.org/ + +Signed-off-by: Breno Leitao +Signed-off-by: Masami Hiramatsu (Google) +Stable-dep-of: dec4d8118c17 ("bootconfig: fix NULL-pointer arithmetic in xbc_snprint_cmdline()") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + include/linux/bootconfig.h | 3 ++ + init/main.c | 45 ------------------------------------ + lib/bootconfig.c | 56 +++++++++++++++++++++++++++++++++++++++++++++ + 3 files changed, 59 insertions(+), 45 deletions(-) + +--- a/include/linux/bootconfig.h ++++ b/include/linux/bootconfig.h +@@ -264,6 +264,9 @@ static inline struct xbc_node * __init x + int __init xbc_node_compose_key_after(struct xbc_node *root, + struct xbc_node *node, char *buf, size_t size); + ++/* Render key/value pairs under @root as a flat cmdline string */ ++int __init xbc_snprint_cmdline(char *buf, size_t size, struct xbc_node *root); ++ + /** + * xbc_node_compose_key() - Compose full key string of the XBC node + * @node: An XBC node. +--- a/init/main.c ++++ b/init/main.c +@@ -316,51 +316,6 @@ static void * __init get_boot_config_fro + + #ifdef CONFIG_BOOT_CONFIG + +-static char xbc_namebuf[XBC_KEYLEN_MAX] __initdata; +- +-#define rest(dst, end) ((end) > (dst) ? (end) - (dst) : 0) +- +-static int __init xbc_snprint_cmdline(char *buf, size_t size, +- struct xbc_node *root) +-{ +- struct xbc_node *knode, *vnode; +- char *end = buf + size; +- const char *val, *q; +- int ret; +- +- xbc_node_for_each_key_value(root, knode, val) { +- ret = xbc_node_compose_key_after(root, knode, +- xbc_namebuf, XBC_KEYLEN_MAX); +- if (ret < 0) +- return ret; +- +- vnode = xbc_node_get_child(knode); +- if (!vnode) { +- ret = snprintf(buf, rest(buf, end), "%s ", xbc_namebuf); +- if (ret < 0) +- return ret; +- buf += ret; +- continue; +- } +- xbc_array_for_each_value(vnode, val) { +- /* +- * For prettier and more readable /proc/cmdline, only +- * quote the value when necessary, i.e. when it contains +- * whitespace. +- */ +- q = strpbrk(val, " \t\r\n") ? "\"" : ""; +- ret = snprintf(buf, rest(buf, end), "%s=%s%s%s ", +- xbc_namebuf, q, val, q); +- if (ret < 0) +- return ret; +- buf += ret; +- } +- } +- +- return buf - (end - size); +-} +-#undef rest +- + /* Make an extra command line under given key word */ + static char * __init xbc_make_cmdline(const char *key) + { +--- a/lib/bootconfig.c ++++ b/lib/bootconfig.c +@@ -407,6 +407,62 @@ const char * __init xbc_node_find_next_k + return ""; /* No value key */ + } + ++static char xbc_namebuf[XBC_KEYLEN_MAX] __initdata; ++ ++#define rest(dst, end) ((end) > (dst) ? (end) - (dst) : 0) ++ ++/** ++ * xbc_snprint_cmdline() - Render bootconfig keys under @root as a cmdline string ++ * @buf: Destination buffer (may be NULL when @size is 0 to query the length) ++ * @size: Size of @buf in bytes ++ * @root: Subtree root whose key=value pairs should be rendered ++ * ++ * Walk all key/value pairs under @root and emit them as a space-separated ++ * cmdline string into @buf. Values containing whitespace are quoted with ++ * double quotes. Returns the number of bytes that would be written if @buf ++ * were large enough (matching snprintf semantics), or a negative errno on ++ * failure. ++ */ ++int __init xbc_snprint_cmdline(char *buf, size_t size, struct xbc_node *root) ++{ ++ struct xbc_node *knode, *vnode; ++ char *end = buf + size; ++ const char *val, *q; ++ int ret; ++ ++ xbc_node_for_each_key_value(root, knode, val) { ++ ret = xbc_node_compose_key_after(root, knode, ++ xbc_namebuf, XBC_KEYLEN_MAX); ++ if (ret < 0) ++ return ret; ++ ++ vnode = xbc_node_get_child(knode); ++ if (!vnode) { ++ ret = snprintf(buf, rest(buf, end), "%s ", xbc_namebuf); ++ if (ret < 0) ++ return ret; ++ buf += ret; ++ continue; ++ } ++ xbc_array_for_each_value(vnode, val) { ++ /* ++ * For prettier and more readable /proc/cmdline, only ++ * quote the value when necessary, i.e. when it contains ++ * whitespace. ++ */ ++ q = strpbrk(val, " \t\r\n") ? "\"" : ""; ++ ret = snprintf(buf, rest(buf, end), "%s=%s%s%s ", ++ xbc_namebuf, q, val, q); ++ if (ret < 0) ++ return ret; ++ buf += ret; ++ } ++ } ++ ++ return buf - (end - size); ++} ++#undef rest ++ + /* XBC parse and tree build */ + + static int __init xbc_init_node(struct xbc_node *node, char *data, uint32_t flag) diff --git a/queue-6.6/bpf-reject-bpf_map_type_inode_storage-creation-if-bpf-lsm-is-uninitialized.patch b/queue-6.6/bpf-reject-bpf_map_type_inode_storage-creation-if-bpf-lsm-is-uninitialized.patch new file mode 100644 index 0000000000..f9f1afb256 --- /dev/null +++ b/queue-6.6/bpf-reject-bpf_map_type_inode_storage-creation-if-bpf-lsm-is-uninitialized.patch @@ -0,0 +1,115 @@ +From stable+bounces-279054-greg=kroah.com@vger.kernel.org Tue Jul 21 17:55:51 2026 +From: Sasha Levin +Date: Tue, 21 Jul 2026 11:48:28 -0400 +Subject: bpf: Reject BPF_MAP_TYPE_INODE_STORAGE creation if BPF LSM is uninitialized +To: stable@vger.kernel.org +Cc: Matt Bobrowski , oxsignal , Daniel Borkmann , Emil Tsalapatis , Amery Hung , Sasha Levin +Message-ID: <20260721154828.4004522-1-sashal@kernel.org> + +From: Matt Bobrowski + +[ Upstream commit a6f0643e4f63cfaa0d5d4a69de4f132eac4b8fe4 ] + +When CONFIG_BPF_LSM=y is set, BPF inode storage maps +(BPF_MAP_TYPE_INODE_STORAGE) are compiled into the kernel. However, +if the BPF LSM is not explicitly enabled at boot time (e.g. omitted +from the "lsm=" boot parameter), lsm_prepare() is never executed for +the BPF LSM. + +Consequently, the BPF inode security blob offset +(bpf_lsm_blob_sizes.lbs_inode) is never initialized and remains at +its default compiled size of 8 bytes instead of being updated to a +valid offset past the reserved struct rcu_head (typically 16 bytes +or more). + +When a privileged user creates and updates a BPF_MAP_TYPE_INODE_STORAGE +map, bpf_inode() evaluates inode->i_security + 8. This erroneously +aliases the struct rcu_head.func callback pointer at the beginning +of the inode->i_security blob. During subsequent map element cleanup +or inode destruction, writing NULL to owner_storage clears the queued +RCU callback pointer. When rcu_do_batch() later executes the queued +callback, it attempts an instruction fetch at address 0x0, triggering +an immediate kernel panic. + +Fix this by introducing a global bpf_lsm_initialized boolean flag +marked with __ro_after_init. Set this flag to true inside bpf_lsm_init() +when the LSM framework successfully registers the BPF LSM. Gate map +allocation in inode_storage_map_alloc() on this flag, returning +-EOPNOTSUPP if the BPF LSM is in turn uninitialized. + +This fail-fast approach prevents userspace from allocating inode +storage maps when the supporting BPF LSM infrastructure is absent, +avoiding zombie map states. + +Fixes: 8ea636848aca ("bpf: Implement bpf_local_storage for inodes") +Reported-by: oxsignal +Signed-off-by: Matt Bobrowski +Signed-off-by: Daniel Borkmann +Reviewed-by: Emil Tsalapatis +Reviewed-by: Amery Hung +Link: https://lore.kernel.org/bpf/20260628201103.3624525-1-mattbobrowski@google.com +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + include/linux/bpf_lsm.h | 4 ++++ + kernel/bpf/bpf_inode_storage.c | 9 +++++++++ + security/bpf/hooks.c | 3 +++ + 3 files changed, 16 insertions(+) + +--- a/include/linux/bpf_lsm.h ++++ b/include/linux/bpf_lsm.h +@@ -13,6 +13,8 @@ + + #ifdef CONFIG_BPF_LSM + ++extern bool bpf_lsm_initialized __ro_after_init; ++ + #define LSM_HOOK(RET, DEFAULT, NAME, ...) \ + RET bpf_lsm_##NAME(__VA_ARGS__); + #include +@@ -47,6 +49,8 @@ void bpf_lsm_find_cgroup_shim(const stru + + #else /* !CONFIG_BPF_LSM */ + ++#define bpf_lsm_initialized false ++ + static inline bool bpf_lsm_is_sleepable_hook(u32 btf_id) + { + return false; +--- a/kernel/bpf/bpf_inode_storage.c ++++ b/kernel/bpf/bpf_inode_storage.c +@@ -191,6 +191,15 @@ static int notsupp_get_next_key(struct b + + static struct bpf_map *inode_storage_map_alloc(union bpf_attr *attr) + { ++ /* ++ * Do not allow allocation of BPF_MAP_TYPE_INODE_STORAGE if the BPF LSM ++ * was not initialized by the LSM framework at boot. Without proper ++ * initialization, the BPF inode security blob offset remains unprepared, ++ * causing bpf_inode() to calculate an invalid memory offset and corrupt ++ * inode->i_security. ++ */ ++ if (!bpf_lsm_initialized) ++ return ERR_PTR(-EOPNOTSUPP); + return bpf_local_storage_map_alloc(attr, &inode_cache, false); + } + +--- a/security/bpf/hooks.c ++++ b/security/bpf/hooks.c +@@ -6,6 +6,8 @@ + #include + #include + ++bool bpf_lsm_initialized __ro_after_init; ++ + static struct security_hook_list bpf_lsm_hooks[] __ro_after_init = { + #define LSM_HOOK(RET, DEFAULT, NAME, ...) \ + LSM_HOOK_INIT(NAME, bpf_lsm_##NAME), +@@ -18,6 +20,7 @@ static struct security_hook_list bpf_lsm + static int __init bpf_lsm_init(void) + { + security_add_hooks(bpf_lsm_hooks, ARRAY_SIZE(bpf_lsm_hooks), "bpf"); ++ bpf_lsm_initialized = true; + pr_info("LSM support for eBPF active\n"); + return 0; + } diff --git a/queue-6.6/dm-verity-avoid-double-increment-of-use_bh_wq_enabled.patch b/queue-6.6/dm-verity-avoid-double-increment-of-use_bh_wq_enabled.patch new file mode 100644 index 0000000000..e305fdf435 --- /dev/null +++ b/queue-6.6/dm-verity-avoid-double-increment-of-use_bh_wq_enabled.patch @@ -0,0 +1,41 @@ +From stable+bounces-289647-greg=kroah.com@vger.kernel.org Mon Jul 27 21:23:42 2026 +From: Sasha Levin +Date: Mon, 27 Jul 2026 15:17:17 -0400 +Subject: dm-verity: avoid double increment of &use_bh_wq_enabled +To: stable@vger.kernel.org +Cc: Mikulas Patocka , Sasha Levin +Message-ID: <20260727191717.1692026-1-sashal@kernel.org> + +From: Mikulas Patocka + +[ Upstream commit 72e9ec2fe32b00994f41719cf77423fca67d48b2 ] + +verity_parse_opt_args is called twice, first with the only_modifier_opts, +first with only_modifier_opts == true and then with only_modifier_opts == +false. Thus, the static branch &use_bh_wq_enabled was incremented twice +and the destructor verity_dtr would only decrement it once. + +Fix tihs bug by only incrementing it on the first call, on the second +call, when v->use_bh_wq is true, do nothing. + +Signed-off-by: Mikulas Patocka +Assisted-by: Claude:claude-opus-4-6 +Cc: stable@vger.kernel.org +Fixes: df326e7a0699 ("dm verity: allow optional args to alter primary args handling") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/md/dm-verity-target.c | 2 ++ + 1 file changed, 2 insertions(+) + +--- a/drivers/md/dm-verity-target.c ++++ b/drivers/md/dm-verity-target.c +@@ -1172,6 +1172,8 @@ static int verity_parse_opt_args(struct + continue; + + } else if (!strcasecmp(arg_name, DM_VERITY_OPT_TASKLET_VERIFY)) { ++ if (v->use_tasklet) ++ continue; + v->use_tasklet = true; + static_branch_inc(&use_tasklet_enabled); + continue; diff --git a/queue-6.6/dm-verity-make-error-counter-atomic.patch b/queue-6.6/dm-verity-make-error-counter-atomic.patch new file mode 100644 index 0000000000..943fc033f7 --- /dev/null +++ b/queue-6.6/dm-verity-make-error-counter-atomic.patch @@ -0,0 +1,71 @@ +From stable+bounces-289649-greg=kroah.com@vger.kernel.org Mon Jul 27 21:29:53 2026 +From: Sasha Levin +Date: Mon, 27 Jul 2026 15:22:11 -0400 +Subject: dm-verity: make error counter atomic +To: stable@vger.kernel.org +Cc: Mikulas Patocka , Sasha Levin +Message-ID: <20260727192211.1722315-1-sashal@kernel.org> + +From: Mikulas Patocka + +[ Upstream commit 8ec4d9c5a5cf4b61fc087f871465b1f79b393325 ] + +The error counter "v->corrupted_errs" was not atomic, thus it could be +subject to race conditions. The call to +dm_audit_log_target("max-corrupted-errors") may be skipped due to the +races. + +Signed-off-by: Mikulas Patocka +Assisted-by: Claude:claude-opus-4.6 +Fixes: 65ff5b7ddf05 ("dm verity: add error handling modes for corrupted blocks") +Cc: stable@vger.kernel.org +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/md/dm-verity-target.c | 12 +++++++----- + drivers/md/dm-verity.h | 2 +- + 2 files changed, 8 insertions(+), 6 deletions(-) + +--- a/drivers/md/dm-verity-target.c ++++ b/drivers/md/dm-verity-target.c +@@ -227,14 +227,16 @@ static int verity_handle_err(struct dm_v + char *envp[] = { verity_env, NULL }; + const char *type_str = ""; + struct mapped_device *md = dm_table_get_md(v->ti->table); ++ int ce; + + /* Corruption should be visible in device status in all modes */ + v->hash_failed = true; + +- if (v->corrupted_errs >= DM_VERITY_MAX_CORRUPTED_ERRS) +- goto out; +- +- v->corrupted_errs++; ++ ce = atomic_read(&v->corrupted_errs); ++ do { ++ if (ce >= DM_VERITY_MAX_CORRUPTED_ERRS) ++ goto out; ++ } while (!atomic_try_cmpxchg(&v->corrupted_errs, &ce, ce + 1)); + + switch (type) { + case DM_VERITY_BLOCK_TYPE_DATA: +@@ -250,7 +252,7 @@ static int verity_handle_err(struct dm_v + DMERR_LIMIT("%s: %s block %llu is corrupted", v->data_dev->name, + type_str, block); + +- if (v->corrupted_errs == DM_VERITY_MAX_CORRUPTED_ERRS) { ++ if (ce + 1 == DM_VERITY_MAX_CORRUPTED_ERRS) { + DMERR("%s: reached maximum errors", v->data_dev->name); + dm_audit_log_target(DM_MSG_PREFIX, "max-corrupted-errors", v->ti, 0); + } +--- a/drivers/md/dm-verity.h ++++ b/drivers/md/dm-verity.h +@@ -58,7 +58,7 @@ struct dm_verity { + unsigned int digest_size; /* digest size for the current hash algorithm */ + unsigned int ahash_reqsize;/* the size of temporary space for crypto */ + enum verity_mode mode; /* mode for handling verification errors */ +- unsigned int corrupted_errs;/* Number of errors for corrupted blocks */ ++ atomic_t corrupted_errs;/* Number of errors for corrupted blocks */ + + struct workqueue_struct *verify_wq; + diff --git a/queue-6.6/dma-buf-udmabuf-skip-redundant-cpu-sync-to-fix-cacheline-eexist-warning.patch b/queue-6.6/dma-buf-udmabuf-skip-redundant-cpu-sync-to-fix-cacheline-eexist-warning.patch new file mode 100644 index 0000000000..22f8a9ffec --- /dev/null +++ b/queue-6.6/dma-buf-udmabuf-skip-redundant-cpu-sync-to-fix-cacheline-eexist-warning.patch @@ -0,0 +1,85 @@ +From stable+bounces-281369-greg=kroah.com@vger.kernel.org Tue Jul 21 21:47:00 2026 +From: Sasha Levin +Date: Tue, 21 Jul 2026 14:28:03 -0400 +Subject: dma-buf/udmabuf: skip redundant cpu sync to fix cacheline EEXIST warning +To: stable@vger.kernel.org +Cc: Mikhail Gavrilov , Vivek Kasireddy , Sasha Levin +Message-ID: <20260721182803.332607-2-sashal@kernel.org> + +From: Mikhail Gavrilov + +[ Upstream commit 504e2b4ab97a51d56d966cd36d0997ad30b65b2d ] + +When CONFIG_DMA_API_DEBUG_SG is enabled, importing a udmabuf into a DRM +driver (e.g. amdgpu for video playback in GNOME Videos / Showtime) +triggers a spurious warning: + + DMA-API: amdgpu 0000:03:00.0: cacheline tracking EEXIST, \ + overlapping mappings aren't supported + WARNING: kernel/dma/debug.c:619 at add_dma_entry+0x473/0x5f0 + +The call chain is: + + amdgpu_cs_ioctl + -> amdgpu_ttm_backend_bind + -> dma_buf_map_attachment + -> [udmabuf] map_udmabuf -> get_sg_table + -> dma_map_sgtable(dev, sg, direction, 0) // attrs=0 + -> debug_dma_map_sg -> add_dma_entry -> EEXIST + +This happens because udmabuf builds a per-page scatter-gather list via +sg_set_folio(). When begin_cpu_udmabuf() has already created an sg +table mapped for the misc device, and an importer such as amdgpu maps +the same pages for its own device via map_udmabuf(), the DMA debug +infrastructure sees two active mappings whose physical addresses share +cacheline boundaries and warns about the overlap. + +The DMA_ATTR_SKIP_CPU_SYNC flag suppresses this check in +add_dma_entry() because it signals that no CPU cache maintenance is +performed at map/unmap time, making the cacheline overlap harmless. + +All other major dma-buf exporters already pass this flag: + - drm_gem_map_dma_buf() passes DMA_ATTR_SKIP_CPU_SYNC + - amdgpu_dma_buf_map() passes DMA_ATTR_SKIP_CPU_SYNC + +The CPU sync at map/unmap time is also redundant for udmabuf: +begin_cpu_udmabuf() and end_cpu_udmabuf() already perform explicit +cache synchronization via dma_sync_sgtable_for_cpu/device() when CPU +access is requested through the dma-buf interface. + +Pass DMA_ATTR_SKIP_CPU_SYNC to dma_map_sgtable() and +dma_unmap_sgtable() in udmabuf to suppress the spurious warning and +skip the redundant sync. + +Fixes: 284562e1f348 ("udmabuf: implement begin_cpu_access/end_cpu_access hooks") +Cc: stable@vger.kernel.org +Signed-off-by: Mikhail Gavrilov +Acked-by: Vivek Kasireddy +Signed-off-by: Vivek Kasireddy +Link: https://patch.msgid.link/20260331061657.79983-1-mikhail.v.gavrilov@gmail.com +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/dma-buf/udmabuf.c | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +--- a/drivers/dma-buf/udmabuf.c ++++ b/drivers/dma-buf/udmabuf.c +@@ -102,7 +102,7 @@ static struct sg_table *get_sg_table(str + if (ret < 0) + goto err_alloc; + +- ret = dma_map_sgtable(dev, sg, direction, 0); ++ ret = dma_map_sgtable(dev, sg, direction, DMA_ATTR_SKIP_CPU_SYNC); + if (ret < 0) + goto err_map; + return sg; +@@ -117,7 +117,7 @@ err_alloc: + static void put_sg_table(struct device *dev, struct sg_table *sg, + enum dma_data_direction direction) + { +- dma_unmap_sgtable(dev, sg, direction, 0); ++ dma_unmap_sgtable(dev, sg, direction, DMA_ATTR_SKIP_CPU_SYNC); + sg_free_table(sg); + kfree(sg); + } diff --git a/queue-6.6/dma-dw-edma-fix-build-warning-in-dw_edma_pcie_probe.patch b/queue-6.6/dma-dw-edma-fix-build-warning-in-dw_edma_pcie_probe.patch new file mode 100644 index 0000000000..e0a3b406a6 --- /dev/null +++ b/queue-6.6/dma-dw-edma-fix-build-warning-in-dw_edma_pcie_probe.patch @@ -0,0 +1,177 @@ +From stable+bounces-289284-greg=kroah.com@vger.kernel.org Sun Jul 26 15:35:25 2026 +From: Sasha Levin +Date: Sun, 26 Jul 2026 09:35:15 -0400 +Subject: dma: dw-edma: Fix build warning in dw_edma_pcie_probe() +To: stable@vger.kernel.org +Cc: Abinash Singh , Abinash Singh , Manivannan Sadhasivam , Vinod Koul , Sasha Levin +Message-ID: <20260726133517.364299-1-sashal@kernel.org> + +From: Abinash Singh + +[ Upstream commit 3df63fa8f2afd051848e37ef1b8299dee28d4f87 ] + +The function dw_edma_pcie_probe() in dw-edma-pcie.c triggered a +frame size warning: +ld.lld:warning: + drivers/dma/dw-edma/dw-edma-pcie.c:162:0: stack frame size (1040) exceeds limit (1024) in function 'dw_edma_pcie_probe' + +This patch reduces the stack usage by dynamically allocating the +`vsec_data` structure using kmalloc(), rather than placing it on +the stack. This eliminates the overflow warning and improves kernel +robustness. + +Signed-off-by: Abinash Singh +Acked-by: Manivannan Sadhasivam +Link: https://lore.kernel.org/r/20250705160055.808165-1-abinashsinghlalotra@gmail.com +Signed-off-by: Vinod Koul +Stable-dep-of: 11d7cfe0c119 ("dmaengine: dw-edma-pcie: Reject devices without driver data") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/dma/dw-edma/dw-edma-pcie.c | 62 +++++++++++++++++++------------------ + 1 file changed, 33 insertions(+), 29 deletions(-) + +--- a/drivers/dma/dw-edma/dw-edma-pcie.c ++++ b/drivers/dma/dw-edma/dw-edma-pcie.c +@@ -160,12 +160,16 @@ static int dw_edma_pcie_probe(struct pci + const struct pci_device_id *pid) + { + struct dw_edma_pcie_data *pdata = (void *)pid->driver_data; +- struct dw_edma_pcie_data vsec_data; ++ struct dw_edma_pcie_data *vsec_data __free(kfree) = NULL; + struct device *dev = &pdev->dev; + struct dw_edma_chip *chip; + int err, nr_irqs; + int i, mask; + ++ vsec_data = kmalloc(sizeof(*vsec_data), GFP_KERNEL); ++ if (!vsec_data) ++ return -ENOMEM; ++ + /* Enable PCI device */ + err = pcim_enable_device(pdev); + if (err) { +@@ -173,23 +177,23 @@ static int dw_edma_pcie_probe(struct pci + return err; + } + +- memcpy(&vsec_data, pdata, sizeof(struct dw_edma_pcie_data)); ++ memcpy(vsec_data, pdata, sizeof(struct dw_edma_pcie_data)); + + /* + * Tries to find if exists a PCIe Vendor-Specific Extended Capability + * for the DMA, if one exists, then reconfigures it. + */ +- dw_edma_pcie_get_vsec_dma_data(pdev, &vsec_data); ++ dw_edma_pcie_get_vsec_dma_data(pdev, vsec_data); + + /* Mapping PCI BAR regions */ +- mask = BIT(vsec_data.rg.bar); +- for (i = 0; i < vsec_data.wr_ch_cnt; i++) { +- mask |= BIT(vsec_data.ll_wr[i].bar); +- mask |= BIT(vsec_data.dt_wr[i].bar); +- } +- for (i = 0; i < vsec_data.rd_ch_cnt; i++) { +- mask |= BIT(vsec_data.ll_rd[i].bar); +- mask |= BIT(vsec_data.dt_rd[i].bar); ++ mask = BIT(vsec_data->rg.bar); ++ for (i = 0; i < vsec_data->wr_ch_cnt; i++) { ++ mask |= BIT(vsec_data->ll_wr[i].bar); ++ mask |= BIT(vsec_data->dt_wr[i].bar); ++ } ++ for (i = 0; i < vsec_data->rd_ch_cnt; i++) { ++ mask |= BIT(vsec_data->ll_rd[i].bar); ++ mask |= BIT(vsec_data->dt_rd[i].bar); + } + err = pcim_iomap_regions(pdev, mask, pci_name(pdev)); + if (err) { +@@ -212,7 +216,7 @@ static int dw_edma_pcie_probe(struct pci + return -ENOMEM; + + /* IRQs allocation */ +- nr_irqs = pci_alloc_irq_vectors(pdev, 1, vsec_data.irqs, ++ nr_irqs = pci_alloc_irq_vectors(pdev, 1, vsec_data->irqs, + PCI_IRQ_MSI | PCI_IRQ_MSIX); + if (nr_irqs < 1) { + pci_err(pdev, "fail to alloc IRQ vector (number of IRQs=%u)\n", +@@ -223,22 +227,22 @@ static int dw_edma_pcie_probe(struct pci + /* Data structure initialization */ + chip->dev = dev; + +- chip->mf = vsec_data.mf; ++ chip->mf = vsec_data->mf; + chip->nr_irqs = nr_irqs; + chip->ops = &dw_edma_pcie_plat_ops; + +- chip->ll_wr_cnt = vsec_data.wr_ch_cnt; +- chip->ll_rd_cnt = vsec_data.rd_ch_cnt; ++ chip->ll_wr_cnt = vsec_data->wr_ch_cnt; ++ chip->ll_rd_cnt = vsec_data->rd_ch_cnt; + +- chip->reg_base = pcim_iomap_table(pdev)[vsec_data.rg.bar]; ++ chip->reg_base = pcim_iomap_table(pdev)[vsec_data->rg.bar]; + if (!chip->reg_base) + return -ENOMEM; + + for (i = 0; i < chip->ll_wr_cnt; i++) { + struct dw_edma_region *ll_region = &chip->ll_region_wr[i]; + struct dw_edma_region *dt_region = &chip->dt_region_wr[i]; +- struct dw_edma_block *ll_block = &vsec_data.ll_wr[i]; +- struct dw_edma_block *dt_block = &vsec_data.dt_wr[i]; ++ struct dw_edma_block *ll_block = &vsec_data->ll_wr[i]; ++ struct dw_edma_block *dt_block = &vsec_data->dt_wr[i]; + + ll_region->vaddr.io = pcim_iomap_table(pdev)[ll_block->bar]; + if (!ll_region->vaddr.io) +@@ -262,8 +266,8 @@ static int dw_edma_pcie_probe(struct pci + for (i = 0; i < chip->ll_rd_cnt; i++) { + struct dw_edma_region *ll_region = &chip->ll_region_rd[i]; + struct dw_edma_region *dt_region = &chip->dt_region_rd[i]; +- struct dw_edma_block *ll_block = &vsec_data.ll_rd[i]; +- struct dw_edma_block *dt_block = &vsec_data.dt_rd[i]; ++ struct dw_edma_block *ll_block = &vsec_data->ll_rd[i]; ++ struct dw_edma_block *dt_block = &vsec_data->dt_rd[i]; + + ll_region->vaddr.io = pcim_iomap_table(pdev)[ll_block->bar]; + if (!ll_region->vaddr.io) +@@ -295,31 +299,31 @@ static int dw_edma_pcie_probe(struct pci + pci_dbg(pdev, "Version:\tUnknown (0x%x)\n", chip->mf); + + pci_dbg(pdev, "Registers:\tBAR=%u, off=0x%.8lx, sz=0x%zx bytes, addr(v=%p)\n", +- vsec_data.rg.bar, vsec_data.rg.off, vsec_data.rg.sz, ++ vsec_data->rg.bar, vsec_data->rg.off, vsec_data->rg.sz, + chip->reg_base); + + + for (i = 0; i < chip->ll_wr_cnt; i++) { + pci_dbg(pdev, "L. List:\tWRITE CH%.2u, BAR=%u, off=0x%.8lx, sz=0x%zx bytes, addr(v=%p, p=%pa)\n", +- i, vsec_data.ll_wr[i].bar, +- vsec_data.ll_wr[i].off, chip->ll_region_wr[i].sz, ++ i, vsec_data->ll_wr[i].bar, ++ vsec_data->ll_wr[i].off, chip->ll_region_wr[i].sz, + chip->ll_region_wr[i].vaddr.io, &chip->ll_region_wr[i].paddr); + + pci_dbg(pdev, "Data:\tWRITE CH%.2u, BAR=%u, off=0x%.8lx, sz=0x%zx bytes, addr(v=%p, p=%pa)\n", +- i, vsec_data.dt_wr[i].bar, +- vsec_data.dt_wr[i].off, chip->dt_region_wr[i].sz, ++ i, vsec_data->dt_wr[i].bar, ++ vsec_data->dt_wr[i].off, chip->dt_region_wr[i].sz, + chip->dt_region_wr[i].vaddr.io, &chip->dt_region_wr[i].paddr); + } + + for (i = 0; i < chip->ll_rd_cnt; i++) { + pci_dbg(pdev, "L. List:\tREAD CH%.2u, BAR=%u, off=0x%.8lx, sz=0x%zx bytes, addr(v=%p, p=%pa)\n", +- i, vsec_data.ll_rd[i].bar, +- vsec_data.ll_rd[i].off, chip->ll_region_rd[i].sz, ++ i, vsec_data->ll_rd[i].bar, ++ vsec_data->ll_rd[i].off, chip->ll_region_rd[i].sz, + chip->ll_region_rd[i].vaddr.io, &chip->ll_region_rd[i].paddr); + + pci_dbg(pdev, "Data:\tREAD CH%.2u, BAR=%u, off=0x%.8lx, sz=0x%zx bytes, addr(v=%p, p=%pa)\n", +- i, vsec_data.dt_rd[i].bar, +- vsec_data.dt_rd[i].off, chip->dt_region_rd[i].sz, ++ i, vsec_data->dt_rd[i].bar, ++ vsec_data->dt_rd[i].off, chip->dt_region_rd[i].sz, + chip->dt_region_rd[i].vaddr.io, &chip->dt_region_rd[i].paddr); + } + diff --git a/queue-6.6/dmaengine-dw-edma-fix-confusing-cleanup.h-syntax.patch b/queue-6.6/dmaengine-dw-edma-fix-confusing-cleanup.h-syntax.patch new file mode 100644 index 0000000000..a39f6fb2c0 --- /dev/null +++ b/queue-6.6/dmaengine-dw-edma-fix-confusing-cleanup.h-syntax.patch @@ -0,0 +1,55 @@ +From stable+bounces-289285-greg=kroah.com@vger.kernel.org Sun Jul 26 15:35:25 2026 +From: Sasha Levin +Date: Sun, 26 Jul 2026 09:35:16 -0400 +Subject: dmaengine: dw-edma: Fix confusing cleanup.h syntax +To: stable@vger.kernel.org +Cc: Krzysztof Kozlowski , Manivannan Sadhasivam , Vinod Koul , Sasha Levin +Message-ID: <20260726133517.364299-2-sashal@kernel.org> + +From: Krzysztof Kozlowski + +[ Upstream commit f9ef8dedee34e2d7828d5a6a0643cd969aaa8437 ] + +Initializing automatic __free variables to NULL without need (e.g. +branches with different allocations), followed by actual allocation is +in contrary to explicit coding rules guiding cleanup.h: + +"Given that the "__free(...) = NULL" pattern for variables defined at +the top of the function poses this potential interdependency problem the +recommendation is to always define and assign variables in one statement +and not group variable definitions at the top of the function when +__free() is used." + +Code does not have a bug, but is less readable and uses discouraged +coding practice, so fix that by moving declaration to the place of +assignment. + +Signed-off-by: Krzysztof Kozlowski +Reviewed-by: Manivannan Sadhasivam +Link: https://patch.msgid.link/20251208020729.4654-2-krzysztof.kozlowski@oss.qualcomm.com +Signed-off-by: Vinod Koul +Stable-dep-of: 11d7cfe0c119 ("dmaengine: dw-edma-pcie: Reject devices without driver data") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/dma/dw-edma/dw-edma-pcie.c | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +--- a/drivers/dma/dw-edma/dw-edma-pcie.c ++++ b/drivers/dma/dw-edma/dw-edma-pcie.c +@@ -160,13 +160,13 @@ static int dw_edma_pcie_probe(struct pci + const struct pci_device_id *pid) + { + struct dw_edma_pcie_data *pdata = (void *)pid->driver_data; +- struct dw_edma_pcie_data *vsec_data __free(kfree) = NULL; + struct device *dev = &pdev->dev; + struct dw_edma_chip *chip; + int err, nr_irqs; + int i, mask; + +- vsec_data = kmalloc(sizeof(*vsec_data), GFP_KERNEL); ++ struct dw_edma_pcie_data *vsec_data __free(kfree) = ++ kmalloc(sizeof(*vsec_data), GFP_KERNEL); + if (!vsec_data) + return -ENOMEM; + diff --git a/queue-6.6/dmaengine-dw-edma-pcie-reject-devices-without-driver-data.patch b/queue-6.6/dmaengine-dw-edma-pcie-reject-devices-without-driver-data.patch new file mode 100644 index 0000000000..952466d59a --- /dev/null +++ b/queue-6.6/dmaengine-dw-edma-pcie-reject-devices-without-driver-data.patch @@ -0,0 +1,43 @@ +From stable+bounces-289286-greg=kroah.com@vger.kernel.org Sun Jul 26 15:35:25 2026 +From: Sasha Levin +Date: Sun, 26 Jul 2026 09:35:17 -0400 +Subject: dmaengine: dw-edma-pcie: Reject devices without driver data +To: stable@vger.kernel.org +Cc: Koichiro Den , Frank Li , Vinod Koul , Sasha Levin +Message-ID: <20260726133517.364299-3-sashal@kernel.org> + +From: Koichiro Den + +[ Upstream commit 11d7cfe0c119691b2dafbb699bbca90258c678aa ] + +dw_edma_pcie_probe() treats the PCI device ID driver_data as the +template for the controller layout and copies it unconditionally. A +device bound dynamically via sysfs can match the driver without that +data, which leads to a NULL pointer dereference. + +Reject such matches before enabling the device. + +Fixes: 41aaff2a2ac0 ("dmaengine: Add Synopsys eDMA IP PCIe glue-logic") +Cc: stable@vger.kernel.org +Signed-off-by: Koichiro Den +Reviewed-by: Frank Li +Link: https://patch.msgid.link/20260521142153.2957432-3-den@valinux.co.jp +Signed-off-by: Vinod Koul +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/dma/dw-edma/dw-edma-pcie.c | 3 +++ + 1 file changed, 3 insertions(+) + +--- a/drivers/dma/dw-edma/dw-edma-pcie.c ++++ b/drivers/dma/dw-edma/dw-edma-pcie.c +@@ -165,6 +165,9 @@ static int dw_edma_pcie_probe(struct pci + int err, nr_irqs; + int i, mask; + ++ if (!pdata) ++ return -ENODEV; ++ + struct dw_edma_pcie_data *vsec_data __free(kfree) = + kmalloc(sizeof(*vsec_data), GFP_KERNEL); + if (!vsec_data) diff --git a/queue-6.6/fbcon-rename-struct-fbcon_ops-to-struct-fbcon_par.patch b/queue-6.6/fbcon-rename-struct-fbcon_ops-to-struct-fbcon_par.patch new file mode 100644 index 0000000000..4581b4bbd2 --- /dev/null +++ b/queue-6.6/fbcon-rename-struct-fbcon_ops-to-struct-fbcon_par.patch @@ -0,0 +1,2490 @@ +From stable+bounces-287937-greg=kroah.com@vger.kernel.org Wed Jul 22 18:09:58 2026 +From: Sasha Levin +Date: Wed, 22 Jul 2026 12:07:57 -0400 +Subject: fbcon: Rename struct fbcon_ops to struct fbcon_par +To: stable@vger.kernel.org +Cc: Thomas Zimmermann , Sam Ravnborg , Sasha Levin +Message-ID: <20260722160758.1740441-1-sashal@kernel.org> + +From: Thomas Zimmermann + +[ Upstream commit a6adbbc4c32a016146e117b1e9e5242724a75e10 ] + +The type struct fbcon_ops contains fbcon state and callbacks. As the +callbacks will be removed from struct fbcon_ops, rename the data type +to struct fbcon_par. Also rename the variables from ops to par. + +The _par postfix ("private access registers") is used throughout the +fbdev subsystem for per-driver state. The fbcon pointer within struct +fb_info is also named fbcon_par. Hence, the new naming fits existing +practice. + +v2: +- rename struct fbcon_ops to struct fbcon_par +- fix build for CONFIG_FB_TILEBITTING=n (kernel test robot) +- fix indention + +Signed-off-by: Thomas Zimmermann +Reviewed-by: Sam Ravnborg +Link: https://lore.kernel.org/r/20250909124616.143365-3-tzimmermann@suse.de +Stable-dep-of: 84202754fb17 ("fbcon: Use correct type for vc_resize() return value") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/video/fbdev/core/bitblit.c | 122 ++++----- + drivers/video/fbdev/core/fbcon.c | 417 +++++++++++++++----------------- + drivers/video/fbdev/core/fbcon.h | 6 + drivers/video/fbdev/core/fbcon_ccw.c | 148 +++++------ + drivers/video/fbdev/core/fbcon_cw.c | 148 +++++------ + drivers/video/fbdev/core/fbcon_rotate.c | 44 +-- + drivers/video/fbdev/core/fbcon_rotate.h | 6 + drivers/video/fbdev/core/fbcon_ud.c | 164 ++++++------ + drivers/video/fbdev/core/softcursor.c | 20 - + drivers/video/fbdev/core/tileblit.c | 28 +- + 10 files changed, 547 insertions(+), 556 deletions(-) + +--- a/drivers/video/fbdev/core/bitblit.c ++++ b/drivers/video/fbdev/core/bitblit.c +@@ -261,10 +261,10 @@ static void bit_cursor(struct vc_data *v + int fg, int bg) + { + struct fb_cursor cursor; +- struct fbcon_ops *ops = info->fbcon_par; ++ struct fbcon_par *par = info->fbcon_par; + unsigned short charmask = vc->vc_hi_font_mask ? 0x1ff : 0xff; + int w = DIV_ROUND_UP(vc->vc_font.width, 8), c; +- int y = real_y(ops->p, vc->state.y); ++ int y = real_y(par->p, vc->state.y); + int attribute, use_sw = vc->vc_cursor_type & CUR_SW; + int err = 1; + char *src; +@@ -278,10 +278,10 @@ static void bit_cursor(struct vc_data *v + attribute = get_attribute(info, c); + src = vc->vc_font.data + ((c & charmask) * (w * vc->vc_font.height)); + +- if (ops->cursor_state.image.data != src || +- ops->cursor_reset) { +- ops->cursor_state.image.data = src; +- cursor.set |= FB_CUR_SETIMAGE; ++ if (par->cursor_state.image.data != src || ++ par->cursor_reset) { ++ par->cursor_state.image.data = src; ++ cursor.set |= FB_CUR_SETIMAGE; + } + + if (attribute) { +@@ -290,46 +290,46 @@ static void bit_cursor(struct vc_data *v + dst = kmalloc_array(w, vc->vc_font.height, GFP_ATOMIC); + if (!dst) + return; +- kfree(ops->cursor_data); +- ops->cursor_data = dst; ++ kfree(par->cursor_data); ++ par->cursor_data = dst; + update_attr(dst, src, attribute, vc); + src = dst; + } + +- if (ops->cursor_state.image.fg_color != fg || +- ops->cursor_state.image.bg_color != bg || +- ops->cursor_reset) { +- ops->cursor_state.image.fg_color = fg; +- ops->cursor_state.image.bg_color = bg; ++ if (par->cursor_state.image.fg_color != fg || ++ par->cursor_state.image.bg_color != bg || ++ par->cursor_reset) { ++ par->cursor_state.image.fg_color = fg; ++ par->cursor_state.image.bg_color = bg; + cursor.set |= FB_CUR_SETCMAP; + } + +- if ((ops->cursor_state.image.dx != (vc->vc_font.width * vc->state.x)) || +- (ops->cursor_state.image.dy != (vc->vc_font.height * y)) || +- ops->cursor_reset) { +- ops->cursor_state.image.dx = vc->vc_font.width * vc->state.x; +- ops->cursor_state.image.dy = vc->vc_font.height * y; ++ if ((par->cursor_state.image.dx != (vc->vc_font.width * vc->state.x)) || ++ (par->cursor_state.image.dy != (vc->vc_font.height * y)) || ++ par->cursor_reset) { ++ par->cursor_state.image.dx = vc->vc_font.width * vc->state.x; ++ par->cursor_state.image.dy = vc->vc_font.height * y; + cursor.set |= FB_CUR_SETPOS; + } + +- if (ops->cursor_state.image.height != vc->vc_font.height || +- ops->cursor_state.image.width != vc->vc_font.width || +- ops->cursor_reset) { +- ops->cursor_state.image.height = vc->vc_font.height; +- ops->cursor_state.image.width = vc->vc_font.width; ++ if (par->cursor_state.image.height != vc->vc_font.height || ++ par->cursor_state.image.width != vc->vc_font.width || ++ par->cursor_reset) { ++ par->cursor_state.image.height = vc->vc_font.height; ++ par->cursor_state.image.width = vc->vc_font.width; + cursor.set |= FB_CUR_SETSIZE; + } + +- if (ops->cursor_state.hot.x || ops->cursor_state.hot.y || +- ops->cursor_reset) { +- ops->cursor_state.hot.x = cursor.hot.y = 0; ++ if (par->cursor_state.hot.x || par->cursor_state.hot.y || ++ par->cursor_reset) { ++ par->cursor_state.hot.x = cursor.hot.y = 0; + cursor.set |= FB_CUR_SETHOT; + } + + if (cursor.set & FB_CUR_SETSIZE || +- vc->vc_cursor_type != ops->p->cursor_shape || +- ops->cursor_state.mask == NULL || +- ops->cursor_reset) { ++ vc->vc_cursor_type != par->p->cursor_shape || ++ par->cursor_state.mask == NULL || ++ par->cursor_reset) { + char *mask = kmalloc_array(w, vc->vc_font.height, GFP_ATOMIC); + int cur_height, size, i = 0; + u8 msk = 0xff; +@@ -337,13 +337,13 @@ static void bit_cursor(struct vc_data *v + if (!mask) + return; + +- kfree(ops->cursor_state.mask); +- ops->cursor_state.mask = mask; ++ kfree(par->cursor_state.mask); ++ par->cursor_state.mask = mask; + +- ops->p->cursor_shape = vc->vc_cursor_type; ++ par->p->cursor_shape = vc->vc_cursor_type; + cursor.set |= FB_CUR_SETSHAPE; + +- switch (CUR_SIZE(ops->p->cursor_shape)) { ++ switch (CUR_SIZE(par->p->cursor_shape)) { + case CUR_NONE: + cur_height = 0; + break; +@@ -374,26 +374,26 @@ static void bit_cursor(struct vc_data *v + + switch (mode) { + case CM_ERASE: +- ops->cursor_state.enable = 0; ++ par->cursor_state.enable = 0; + break; + case CM_DRAW: + case CM_MOVE: + default: +- ops->cursor_state.enable = (use_sw) ? 0 : 1; ++ par->cursor_state.enable = (use_sw) ? 0 : 1; + break; + } + + cursor.image.data = src; +- cursor.image.fg_color = ops->cursor_state.image.fg_color; +- cursor.image.bg_color = ops->cursor_state.image.bg_color; +- cursor.image.dx = ops->cursor_state.image.dx; +- cursor.image.dy = ops->cursor_state.image.dy; +- cursor.image.height = ops->cursor_state.image.height; +- cursor.image.width = ops->cursor_state.image.width; +- cursor.hot.x = ops->cursor_state.hot.x; +- cursor.hot.y = ops->cursor_state.hot.y; +- cursor.mask = ops->cursor_state.mask; +- cursor.enable = ops->cursor_state.enable; ++ cursor.image.fg_color = par->cursor_state.image.fg_color; ++ cursor.image.bg_color = par->cursor_state.image.bg_color; ++ cursor.image.dx = par->cursor_state.image.dx; ++ cursor.image.dy = par->cursor_state.image.dy; ++ cursor.image.height = par->cursor_state.image.height; ++ cursor.image.width = par->cursor_state.image.width; ++ cursor.hot.x = par->cursor_state.hot.x; ++ cursor.hot.y = par->cursor_state.hot.y; ++ cursor.mask = par->cursor_state.mask; ++ cursor.enable = par->cursor_state.enable; + cursor.image.depth = 1; + cursor.rop = ROP_XOR; + +@@ -403,31 +403,31 @@ static void bit_cursor(struct vc_data *v + if (err) + soft_cursor(info, &cursor); + +- ops->cursor_reset = 0; ++ par->cursor_reset = 0; + } + + static int bit_update_start(struct fb_info *info) + { +- struct fbcon_ops *ops = info->fbcon_par; ++ struct fbcon_par *par = info->fbcon_par; + int err; + +- err = fb_pan_display(info, &ops->var); +- ops->var.xoffset = info->var.xoffset; +- ops->var.yoffset = info->var.yoffset; +- ops->var.vmode = info->var.vmode; ++ err = fb_pan_display(info, &par->var); ++ par->var.xoffset = info->var.xoffset; ++ par->var.yoffset = info->var.yoffset; ++ par->var.vmode = info->var.vmode; + return err; + } + +-void fbcon_set_bitops(struct fbcon_ops *ops) ++void fbcon_set_bitops(struct fbcon_par *par) + { +- ops->bmove = bit_bmove; +- ops->clear = bit_clear; +- ops->putcs = bit_putcs; +- ops->clear_margins = bit_clear_margins; +- ops->cursor = bit_cursor; +- ops->update_start = bit_update_start; +- ops->rotate_font = NULL; ++ par->bmove = bit_bmove; ++ par->clear = bit_clear; ++ par->putcs = bit_putcs; ++ par->clear_margins = bit_clear_margins; ++ par->cursor = bit_cursor; ++ par->update_start = bit_update_start; ++ par->rotate_font = NULL; + +- if (ops->rotate) +- fbcon_set_rotate(ops); ++ if (par->rotate) ++ fbcon_set_rotate(par); + } +--- a/drivers/video/fbdev/core/fbcon.c ++++ b/drivers/video/fbdev/core/fbcon.c +@@ -198,27 +198,27 @@ static struct device *fbcon_device; + #ifdef CONFIG_FRAMEBUFFER_CONSOLE_ROTATION + static inline void fbcon_set_rotation(struct fb_info *info) + { +- struct fbcon_ops *ops = info->fbcon_par; ++ struct fbcon_par *par = info->fbcon_par; + + if (!(info->flags & FBINFO_MISC_TILEBLITTING) && +- ops->p->con_rotate < 4) +- ops->rotate = ops->p->con_rotate; ++ par->p->con_rotate < 4) ++ par->rotate = par->p->con_rotate; + else +- ops->rotate = 0; ++ par->rotate = 0; + } + + static void fbcon_rotate(struct fb_info *info, u32 rotate) + { +- struct fbcon_ops *ops= info->fbcon_par; ++ struct fbcon_par *par = info->fbcon_par; + struct fb_info *fb_info; + +- if (!ops || ops->currcon == -1) ++ if (!par || par->currcon == -1) + return; + +- fb_info = fbcon_info_from_console(ops->currcon); ++ fb_info = fbcon_info_from_console(par->currcon); + + if (info == fb_info) { +- struct fbcon_display *p = &fb_display[ops->currcon]; ++ struct fbcon_display *p = &fb_display[par->currcon]; + + if (rotate < 4) + p->con_rotate = rotate; +@@ -231,12 +231,12 @@ static void fbcon_rotate(struct fb_info + + static void fbcon_rotate_all(struct fb_info *info, u32 rotate) + { +- struct fbcon_ops *ops = info->fbcon_par; ++ struct fbcon_par *par = info->fbcon_par; + struct vc_data *vc; + struct fbcon_display *p; + int i; + +- if (!ops || ops->currcon < 0 || rotate > 3) ++ if (!par || par->currcon < 0 || rotate > 3) + return; + + for (i = first_fb_vc; i <= last_fb_vc; i++) { +@@ -254,9 +254,9 @@ static void fbcon_rotate_all(struct fb_i + #else + static inline void fbcon_set_rotation(struct fb_info *info) + { +- struct fbcon_ops *ops = info->fbcon_par; ++ struct fbcon_par *par = info->fbcon_par; + +- ops->rotate = FB_ROTATE_UR; ++ par->rotate = FB_ROTATE_UR; + } + + static void fbcon_rotate(struct fb_info *info, u32 rotate) +@@ -272,17 +272,17 @@ static void fbcon_rotate_all(struct fb_i + + static int fbcon_get_rotate(struct fb_info *info) + { +- struct fbcon_ops *ops = info->fbcon_par; ++ struct fbcon_par *par = info->fbcon_par; + +- return (ops) ? ops->rotate : 0; ++ return (par) ? par->rotate : 0; + } + + static inline int fbcon_is_inactive(struct vc_data *vc, struct fb_info *info) + { +- struct fbcon_ops *ops = info->fbcon_par; ++ struct fbcon_par *par = info->fbcon_par; + + return (info->state != FBINFO_STATE_RUNNING || +- vc->vc_mode != KD_TEXT || ops->graphics); ++ vc->vc_mode != KD_TEXT || par->graphics); + } + + static int get_color(struct vc_data *vc, struct fb_info *info, +@@ -354,7 +354,7 @@ static int get_color(struct vc_data *vc, + + static void fb_flashcursor(struct work_struct *work) + { +- struct fbcon_ops *ops = container_of(work, struct fbcon_ops, cursor_work.work); ++ struct fbcon_par *par = container_of(work, struct fbcon_par, cursor_work.work); + struct fb_info *info; + struct vc_data *vc = NULL; + int c; +@@ -369,10 +369,10 @@ static void fb_flashcursor(struct work_s + return; + + /* protected by console_lock */ +- info = ops->info; ++ info = par->info; + +- if (ops->currcon != -1) +- vc = vc_cons[ops->currcon].d; ++ if (par->currcon != -1) ++ vc = vc_cons[par->currcon].d; + + if (!vc || !con_is_visible(vc) || + fbcon_info_from_console(vc->vc_num) != info || +@@ -382,30 +382,30 @@ static void fb_flashcursor(struct work_s + } + + c = scr_readw((u16 *) vc->vc_pos); +- mode = (!ops->cursor_flash || ops->cursor_state.enable) ? ++ mode = (!par->cursor_flash || par->cursor_state.enable) ? + CM_ERASE : CM_DRAW; +- ops->cursor(vc, info, mode, get_color(vc, info, c, 1), ++ par->cursor(vc, info, mode, get_color(vc, info, c, 1), + get_color(vc, info, c, 0)); + console_unlock(); + +- queue_delayed_work(system_power_efficient_wq, &ops->cursor_work, +- ops->cur_blink_jiffies); ++ queue_delayed_work(system_power_efficient_wq, &par->cursor_work, ++ par->cur_blink_jiffies); + } + + static void fbcon_add_cursor_work(struct fb_info *info) + { +- struct fbcon_ops *ops = info->fbcon_par; ++ struct fbcon_par *par = info->fbcon_par; + + if (!fbcon_cursor_noblink) +- queue_delayed_work(system_power_efficient_wq, &ops->cursor_work, +- ops->cur_blink_jiffies); ++ queue_delayed_work(system_power_efficient_wq, &par->cursor_work, ++ par->cur_blink_jiffies); + } + + static void fbcon_del_cursor_work(struct fb_info *info) + { +- struct fbcon_ops *ops = info->fbcon_par; ++ struct fbcon_par *par = info->fbcon_par; + +- cancel_delayed_work_sync(&ops->cursor_work); ++ cancel_delayed_work_sync(&par->cursor_work); + } + + #ifndef MODULE +@@ -559,7 +559,7 @@ static void fbcon_prepare_logo(struct vc + int cols, int rows, int new_cols, int new_rows) + { + /* Need to make room for the logo */ +- struct fbcon_ops *ops = info->fbcon_par; ++ struct fbcon_par *par = info->fbcon_par; + int cnt, erase = vc->vc_video_erase_char, step; + unsigned short *save = NULL, *r, *q; + int logo_height; +@@ -575,7 +575,7 @@ static void fbcon_prepare_logo(struct vc + */ + if (fb_get_color_depth(&info->var, &info->fix) == 1) + erase &= ~0x400; +- logo_height = fb_prepare_logo(info, ops->rotate); ++ logo_height = fb_prepare_logo(info, par->rotate); + logo_lines = DIV_ROUND_UP(logo_height, vc->vc_font.height); + q = (unsigned short *) (vc->vc_origin + + vc->vc_size_row * rows); +@@ -648,15 +648,15 @@ static void fbcon_prepare_logo(struct vc + #ifdef CONFIG_FB_TILEBLITTING + static void set_blitting_type(struct vc_data *vc, struct fb_info *info) + { +- struct fbcon_ops *ops = info->fbcon_par; ++ struct fbcon_par *par = info->fbcon_par; + +- ops->p = &fb_display[vc->vc_num]; ++ par->p = &fb_display[vc->vc_num]; + + if ((info->flags & FBINFO_MISC_TILEBLITTING)) + fbcon_set_tileops(vc, info); + else { + fbcon_set_rotation(info); +- fbcon_set_bitops(ops); ++ fbcon_set_bitops(par); + } + } + +@@ -673,12 +673,12 @@ static int fbcon_invalid_charcount(struc + #else + static void set_blitting_type(struct vc_data *vc, struct fb_info *info) + { +- struct fbcon_ops *ops = info->fbcon_par; ++ struct fbcon_par *par = info->fbcon_par; + + info->flags &= ~FBINFO_MISC_TILEBLITTING; +- ops->p = &fb_display[vc->vc_num]; ++ par->p = &fb_display[vc->vc_num]; + fbcon_set_rotation(info); +- fbcon_set_bitops(ops); ++ fbcon_set_bitops(par); + } + + static int fbcon_invalid_charcount(struct fb_info *info, unsigned charcount) +@@ -698,13 +698,13 @@ static void fbcon_release(struct fb_info + module_put(info->fbops->owner); + + if (info->fbcon_par) { +- struct fbcon_ops *ops = info->fbcon_par; ++ struct fbcon_par *par = info->fbcon_par; + + fbcon_del_cursor_work(info); +- kfree(ops->cursor_state.mask); +- kfree(ops->cursor_data); +- kfree(ops->cursor_src); +- kfree(ops->fontbuffer); ++ kfree(par->cursor_state.mask); ++ kfree(par->cursor_data); ++ kfree(par->cursor_src); ++ kfree(par->fontbuffer); + kfree(info->fbcon_par); + info->fbcon_par = NULL; + } +@@ -712,7 +712,7 @@ static void fbcon_release(struct fb_info + + static int fbcon_open(struct fb_info *info) + { +- struct fbcon_ops *ops; ++ struct fbcon_par *par; + + if (!try_module_get(info->fbops->owner)) + return -ENODEV; +@@ -726,16 +726,16 @@ static int fbcon_open(struct fb_info *in + } + unlock_fb_info(info); + +- ops = kzalloc(sizeof(struct fbcon_ops), GFP_KERNEL); +- if (!ops) { ++ par = kzalloc(sizeof(*par), GFP_KERNEL); ++ if (!par) { + fbcon_release(info); + return -ENOMEM; + } + +- INIT_DELAYED_WORK(&ops->cursor_work, fb_flashcursor); +- ops->info = info; +- info->fbcon_par = ops; +- ops->cur_blink_jiffies = HZ / 5; ++ INIT_DELAYED_WORK(&par->cursor_work, fb_flashcursor); ++ par->info = info; ++ info->fbcon_par = par; ++ par->cur_blink_jiffies = HZ / 5; + + return 0; + } +@@ -782,12 +782,12 @@ static void con2fb_release_oldinfo(struc + static void con2fb_init_display(struct vc_data *vc, struct fb_info *info, + int unit, int show_logo) + { +- struct fbcon_ops *ops = info->fbcon_par; ++ struct fbcon_par *par = info->fbcon_par; + int ret; + +- ops->currcon = fg_console; ++ par->currcon = fg_console; + +- if (info->fbops->fb_set_par && !ops->initialized) { ++ if (info->fbops->fb_set_par && !par->initialized) { + ret = info->fbops->fb_set_par(info); + + if (ret) +@@ -796,8 +796,8 @@ static void con2fb_init_display(struct v + "error code %d\n", ret); + } + +- ops->initialized = true; +- ops->graphics = 0; ++ par->initialized = true; ++ par->graphics = 0; + fbcon_set_disp(info, &info->var, unit); + + if (show_logo) { +@@ -934,7 +934,7 @@ static const char *fbcon_startup(void) + struct vc_data *vc = vc_cons[fg_console].d; + const struct font_desc *font = NULL; + struct fb_info *info = NULL; +- struct fbcon_ops *ops; ++ struct fbcon_par *par; + int rows, cols; + + /* +@@ -954,10 +954,10 @@ static const char *fbcon_startup(void) + if (fbcon_open(info)) + return NULL; + +- ops = info->fbcon_par; +- ops->currcon = -1; +- ops->graphics = 1; +- ops->cur_rotate = -1; ++ par = info->fbcon_par; ++ par->currcon = -1; ++ par->graphics = 1; ++ par->cur_rotate = -1; + + p->con_rotate = initial_rotation; + if (p->con_rotate == -1) +@@ -980,8 +980,8 @@ static const char *fbcon_startup(void) + vc->vc_font.charcount = font->charcount; + } + +- cols = FBCON_SWAP(ops->rotate, info->var.xres, info->var.yres); +- rows = FBCON_SWAP(ops->rotate, info->var.yres, info->var.xres); ++ cols = FBCON_SWAP(par->rotate, info->var.xres, info->var.yres); ++ rows = FBCON_SWAP(par->rotate, info->var.yres, info->var.xres); + cols /= vc->vc_font.width; + rows /= vc->vc_font.height; + vc_resize(vc, cols, rows); +@@ -999,7 +999,7 @@ static const char *fbcon_startup(void) + static void fbcon_init(struct vc_data *vc, bool init) + { + struct fb_info *info; +- struct fbcon_ops *ops; ++ struct fbcon_par *par; + struct vc_data **default_mode = vc->vc_display_fg; + struct vc_data *svc = *default_mode; + struct fbcon_display *t, *p = &fb_display[vc->vc_num]; +@@ -1074,8 +1074,8 @@ static void fbcon_init(struct vc_data *v + if (!*vc->uni_pagedict_loc) + con_copy_unimap(vc, svc); + +- ops = info->fbcon_par; +- ops->cur_blink_jiffies = msecs_to_jiffies(vc->vc_cur_blink_ms); ++ par = info->fbcon_par; ++ par->cur_blink_jiffies = msecs_to_jiffies(vc->vc_cur_blink_ms); + + p->con_rotate = initial_rotation; + if (p->con_rotate == -1) +@@ -1087,8 +1087,8 @@ static void fbcon_init(struct vc_data *v + + cols = vc->vc_cols; + rows = vc->vc_rows; +- new_cols = FBCON_SWAP(ops->rotate, info->var.xres, info->var.yres); +- new_rows = FBCON_SWAP(ops->rotate, info->var.yres, info->var.xres); ++ new_cols = FBCON_SWAP(par->rotate, info->var.xres, info->var.yres); ++ new_rows = FBCON_SWAP(par->rotate, info->var.yres, info->var.xres); + new_cols /= vc->vc_font.width; + new_rows /= vc->vc_font.height; + +@@ -1100,7 +1100,7 @@ static void fbcon_init(struct vc_data *v + * We need to do it in fbcon_init() to prevent screen corruption. + */ + if (con_is_visible(vc) && vc->vc_mode == KD_TEXT) { +- if (info->fbops->fb_set_par && !ops->initialized) { ++ if (info->fbops->fb_set_par && !par->initialized) { + ret = info->fbops->fb_set_par(info); + + if (ret) +@@ -1109,10 +1109,10 @@ static void fbcon_init(struct vc_data *v + "error code %d\n", ret); + } + +- ops->initialized = true; ++ par->initialized = true; + } + +- ops->graphics = 0; ++ par->graphics = 0; + + #ifdef CONFIG_FRAMEBUFFER_CONSOLE_LEGACY_ACCELERATION + if ((info->flags & FBINFO_HWACCEL_COPYAREA) && +@@ -1136,12 +1136,12 @@ static void fbcon_init(struct vc_data *v + if (logo) + fbcon_prepare_logo(vc, info, cols, rows, new_cols, new_rows); + +- if (ops->rotate_font && ops->rotate_font(info, vc)) { +- ops->rotate = FB_ROTATE_UR; ++ if (par->rotate_font && par->rotate_font(info, vc)) { ++ par->rotate = FB_ROTATE_UR; + set_blitting_type(vc, info); + } + +- ops->p = &fb_display[fg_console]; ++ par->p = &fb_display[fg_console]; + } + + static void fbcon_free_font(struct fbcon_display *p) +@@ -1179,7 +1179,7 @@ static void fbcon_deinit(struct vc_data + { + struct fbcon_display *p = &fb_display[vc->vc_num]; + struct fb_info *info; +- struct fbcon_ops *ops; ++ struct fbcon_par *par; + int idx; + + fbcon_free_font(p); +@@ -1194,15 +1194,15 @@ static void fbcon_deinit(struct vc_data + if (!info) + goto finished; + +- ops = info->fbcon_par; ++ par = info->fbcon_par; + +- if (!ops) ++ if (!par) + goto finished; + + if (con_is_visible(vc)) + fbcon_del_cursor_work(info); + +- ops->initialized = false; ++ par->initialized = false; + finished: + + fbcon_free_font(p); +@@ -1249,7 +1249,7 @@ static void __fbcon_clear(struct vc_data + unsigned int height, unsigned int width) + { + struct fb_info *info = fbcon_info_from_console(vc->vc_num); +- struct fbcon_ops *ops = info->fbcon_par; ++ struct fbcon_par *par = info->fbcon_par; + int fg, bg; + struct fbcon_display *p = &fb_display[vc->vc_num]; + u_int y_break; +@@ -1264,7 +1264,7 @@ static void __fbcon_clear(struct vc_data + vc->vc_top = 0; + /* + * If the font dimensions are not an integral of the display +- * dimensions then the ops->clear below won't end up clearing ++ * dimensions then the par->clear below won't end up clearing + * the margins. Call clear_margins here in case the logo + * bitmap stretched into the margin area. + */ +@@ -1278,11 +1278,10 @@ static void __fbcon_clear(struct vc_data + y_break = p->vrows - p->yscroll; + if (sy < y_break && sy + height - 1 >= y_break) { + u_int b = y_break - sy; +- ops->clear(vc, info, real_y(p, sy), sx, b, width, fg, bg); +- ops->clear(vc, info, real_y(p, sy + b), sx, height - b, +- width, fg, bg); ++ par->clear(vc, info, real_y(p, sy), sx, b, width, fg, bg); ++ par->clear(vc, info, real_y(p, sy + b), sx, height - b, width, fg, bg); + } else +- ops->clear(vc, info, real_y(p, sy), sx, height, width, fg, bg); ++ par->clear(vc, info, real_y(p, sy), sx, height, width, fg, bg); + } + + static void fbcon_clear(struct vc_data *vc, unsigned int sy, unsigned int sx, +@@ -1296,10 +1295,10 @@ static void fbcon_putcs(struct vc_data * + { + struct fb_info *info = fbcon_info_from_console(vc->vc_num); + struct fbcon_display *p = &fb_display[vc->vc_num]; +- struct fbcon_ops *ops = info->fbcon_par; ++ struct fbcon_par *par = info->fbcon_par; + + if (!fbcon_is_inactive(vc, info)) +- ops->putcs(vc, info, s, count, real_y(p, ypos), xpos, ++ par->putcs(vc, info, s, count, real_y(p, ypos), xpos, + get_color(vc, info, scr_readw(s), 1), + get_color(vc, info, scr_readw(s), 0)); + } +@@ -1315,19 +1314,19 @@ static void fbcon_putc(struct vc_data *v + static void fbcon_clear_margins(struct vc_data *vc, int bottom_only) + { + struct fb_info *info = fbcon_info_from_console(vc->vc_num); +- struct fbcon_ops *ops = info->fbcon_par; ++ struct fbcon_par *par = info->fbcon_par; + + if (!fbcon_is_inactive(vc, info)) +- ops->clear_margins(vc, info, margin_color, bottom_only); ++ par->clear_margins(vc, info, margin_color, bottom_only); + } + + static void fbcon_cursor(struct vc_data *vc, int mode) + { + struct fb_info *info = fbcon_info_from_console(vc->vc_num); +- struct fbcon_ops *ops = info->fbcon_par; ++ struct fbcon_par *par = info->fbcon_par; + int c = scr_readw((u16 *) vc->vc_pos); + +- ops->cur_blink_jiffies = msecs_to_jiffies(vc->vc_cur_blink_ms); ++ par->cur_blink_jiffies = msecs_to_jiffies(vc->vc_cur_blink_ms); + + if (fbcon_is_inactive(vc, info) || vc->vc_deccm != 1) + return; +@@ -1337,12 +1336,12 @@ static void fbcon_cursor(struct vc_data + else + fbcon_add_cursor_work(info); + +- ops->cursor_flash = (mode == CM_ERASE) ? 0 : 1; ++ par->cursor_flash = (mode == CM_ERASE) ? 0 : 1; + +- if (!ops->cursor) ++ if (!par->cursor) + return; + +- ops->cursor(vc, info, mode, get_color(vc, info, c, 1), ++ par->cursor(vc, info, mode, get_color(vc, info, c, 1), + get_color(vc, info, c, 0)); + } + +@@ -1356,7 +1355,7 @@ static void fbcon_set_disp(struct fb_inf + struct fbcon_display *p, *t; + struct vc_data **default_mode, *vc; + struct vc_data *svc; +- struct fbcon_ops *ops = info->fbcon_par; ++ struct fbcon_par *par = info->fbcon_par; + int rows, cols; + unsigned long ret = 0; + +@@ -1389,7 +1388,7 @@ static void fbcon_set_disp(struct fb_inf + var->yoffset = info->var.yoffset; + var->xoffset = info->var.xoffset; + fb_set_var(info, var); +- ops->var = info->var; ++ par->var = info->var; + vc->vc_can_do_color = (fb_get_color_depth(&info->var, &info->fix)!=1); + vc->vc_complement_mask = vc->vc_can_do_color ? 0x7700 : 0x0800; + if (vc->vc_font.charcount == 256) { +@@ -1405,8 +1404,8 @@ static void fbcon_set_disp(struct fb_inf + if (!*vc->uni_pagedict_loc) + con_copy_unimap(vc, svc); + +- cols = FBCON_SWAP(ops->rotate, info->var.xres, info->var.yres); +- rows = FBCON_SWAP(ops->rotate, info->var.yres, info->var.xres); ++ cols = FBCON_SWAP(par->rotate, info->var.xres, info->var.yres); ++ rows = FBCON_SWAP(par->rotate, info->var.yres, info->var.xres); + cols /= vc->vc_font.width; + rows /= vc->vc_font.height; + ret = vc_resize(vc, cols, rows); +@@ -1418,16 +1417,16 @@ static void fbcon_set_disp(struct fb_inf + static __inline__ void ywrap_up(struct vc_data *vc, int count) + { + struct fb_info *info = fbcon_info_from_console(vc->vc_num); +- struct fbcon_ops *ops = info->fbcon_par; ++ struct fbcon_par *par = info->fbcon_par; + struct fbcon_display *p = &fb_display[vc->vc_num]; + + p->yscroll += count; + if (p->yscroll >= p->vrows) /* Deal with wrap */ + p->yscroll -= p->vrows; +- ops->var.xoffset = 0; +- ops->var.yoffset = p->yscroll * vc->vc_font.height; +- ops->var.vmode |= FB_VMODE_YWRAP; +- ops->update_start(info); ++ par->var.xoffset = 0; ++ par->var.yoffset = p->yscroll * vc->vc_font.height; ++ par->var.vmode |= FB_VMODE_YWRAP; ++ par->update_start(info); + scrollback_max += count; + if (scrollback_max > scrollback_phys_max) + scrollback_max = scrollback_phys_max; +@@ -1437,16 +1436,16 @@ static __inline__ void ywrap_up(struct v + static __inline__ void ywrap_down(struct vc_data *vc, int count) + { + struct fb_info *info = fbcon_info_from_console(vc->vc_num); +- struct fbcon_ops *ops = info->fbcon_par; ++ struct fbcon_par *par = info->fbcon_par; + struct fbcon_display *p = &fb_display[vc->vc_num]; + + p->yscroll -= count; + if (p->yscroll < 0) /* Deal with wrap */ + p->yscroll += p->vrows; +- ops->var.xoffset = 0; +- ops->var.yoffset = p->yscroll * vc->vc_font.height; +- ops->var.vmode |= FB_VMODE_YWRAP; +- ops->update_start(info); ++ par->var.xoffset = 0; ++ par->var.yoffset = p->yscroll * vc->vc_font.height; ++ par->var.vmode |= FB_VMODE_YWRAP; ++ par->update_start(info); + scrollback_max -= count; + if (scrollback_max < 0) + scrollback_max = 0; +@@ -1457,19 +1456,19 @@ static __inline__ void ypan_up(struct vc + { + struct fb_info *info = fbcon_info_from_console(vc->vc_num); + struct fbcon_display *p = &fb_display[vc->vc_num]; +- struct fbcon_ops *ops = info->fbcon_par; ++ struct fbcon_par *par = info->fbcon_par; + + p->yscroll += count; + if (p->yscroll > p->vrows - vc->vc_rows) { +- ops->bmove(vc, info, p->vrows - vc->vc_rows, ++ par->bmove(vc, info, p->vrows - vc->vc_rows, + 0, 0, 0, vc->vc_rows, vc->vc_cols); + p->yscroll -= p->vrows - vc->vc_rows; + } + +- ops->var.xoffset = 0; +- ops->var.yoffset = p->yscroll * vc->vc_font.height; +- ops->var.vmode &= ~FB_VMODE_YWRAP; +- ops->update_start(info); ++ par->var.xoffset = 0; ++ par->var.yoffset = p->yscroll * vc->vc_font.height; ++ par->var.vmode &= ~FB_VMODE_YWRAP; ++ par->update_start(info); + fbcon_clear_margins(vc, 1); + scrollback_max += count; + if (scrollback_max > scrollback_phys_max) +@@ -1480,7 +1479,7 @@ static __inline__ void ypan_up(struct vc + static __inline__ void ypan_up_redraw(struct vc_data *vc, int t, int count) + { + struct fb_info *info = fbcon_info_from_console(vc->vc_num); +- struct fbcon_ops *ops = info->fbcon_par; ++ struct fbcon_par *par = info->fbcon_par; + struct fbcon_display *p = &fb_display[vc->vc_num]; + + p->yscroll += count; +@@ -1490,10 +1489,10 @@ static __inline__ void ypan_up_redraw(st + fbcon_redraw_move(vc, p, t + count, vc->vc_rows - count, t); + } + +- ops->var.xoffset = 0; +- ops->var.yoffset = p->yscroll * vc->vc_font.height; +- ops->var.vmode &= ~FB_VMODE_YWRAP; +- ops->update_start(info); ++ par->var.xoffset = 0; ++ par->var.yoffset = p->yscroll * vc->vc_font.height; ++ par->var.vmode &= ~FB_VMODE_YWRAP; ++ par->update_start(info); + fbcon_clear_margins(vc, 1); + scrollback_max += count; + if (scrollback_max > scrollback_phys_max) +@@ -1505,19 +1504,19 @@ static __inline__ void ypan_down(struct + { + struct fb_info *info = fbcon_info_from_console(vc->vc_num); + struct fbcon_display *p = &fb_display[vc->vc_num]; +- struct fbcon_ops *ops = info->fbcon_par; ++ struct fbcon_par *par = info->fbcon_par; + + p->yscroll -= count; + if (p->yscroll < 0) { +- ops->bmove(vc, info, 0, 0, p->vrows - vc->vc_rows, ++ par->bmove(vc, info, 0, 0, p->vrows - vc->vc_rows, + 0, vc->vc_rows, vc->vc_cols); + p->yscroll += p->vrows - vc->vc_rows; + } + +- ops->var.xoffset = 0; +- ops->var.yoffset = p->yscroll * vc->vc_font.height; +- ops->var.vmode &= ~FB_VMODE_YWRAP; +- ops->update_start(info); ++ par->var.xoffset = 0; ++ par->var.yoffset = p->yscroll * vc->vc_font.height; ++ par->var.vmode &= ~FB_VMODE_YWRAP; ++ par->update_start(info); + fbcon_clear_margins(vc, 1); + scrollback_max -= count; + if (scrollback_max < 0) +@@ -1528,7 +1527,7 @@ static __inline__ void ypan_down(struct + static __inline__ void ypan_down_redraw(struct vc_data *vc, int t, int count) + { + struct fb_info *info = fbcon_info_from_console(vc->vc_num); +- struct fbcon_ops *ops = info->fbcon_par; ++ struct fbcon_par *par = info->fbcon_par; + struct fbcon_display *p = &fb_display[vc->vc_num]; + + p->yscroll -= count; +@@ -1538,10 +1537,10 @@ static __inline__ void ypan_down_redraw( + fbcon_redraw_move(vc, p, t, vc->vc_rows - count, t + count); + } + +- ops->var.xoffset = 0; +- ops->var.yoffset = p->yscroll * vc->vc_font.height; +- ops->var.vmode &= ~FB_VMODE_YWRAP; +- ops->update_start(info); ++ par->var.xoffset = 0; ++ par->var.yoffset = p->yscroll * vc->vc_font.height; ++ par->var.vmode &= ~FB_VMODE_YWRAP; ++ par->update_start(info); + fbcon_clear_margins(vc, 1); + scrollback_max -= count; + if (scrollback_max < 0) +@@ -1590,7 +1589,7 @@ static void fbcon_redraw_blit(struct vc_ + unsigned short *d = (unsigned short *) + (vc->vc_origin + vc->vc_size_row * line); + unsigned short *s = d + offset; +- struct fbcon_ops *ops = info->fbcon_par; ++ struct fbcon_par *par = info->fbcon_par; + + while (count--) { + unsigned short *start = s; +@@ -1603,8 +1602,8 @@ static void fbcon_redraw_blit(struct vc_ + + if (c == scr_readw(d)) { + if (s > start) { +- ops->bmove(vc, info, line + ycount, x, +- line, x, 1, s-start); ++ par->bmove(vc, info, line + ycount, x, ++ line, x, 1, s - start); + x += s - start + 1; + start = s + 1; + } else { +@@ -1619,8 +1618,7 @@ static void fbcon_redraw_blit(struct vc_ + d++; + } while (s < le); + if (s > start) +- ops->bmove(vc, info, line + ycount, x, line, x, 1, +- s-start); ++ par->bmove(vc, info, line + ycount, x, line, x, 1, s - start); + console_conditional_schedule(); + if (ycount > 0) + line++; +@@ -1691,7 +1689,7 @@ static void fbcon_bmove_rec(struct vc_da + int dy, int dx, int height, int width, u_int y_break) + { + struct fb_info *info = fbcon_info_from_console(vc->vc_num); +- struct fbcon_ops *ops = info->fbcon_par; ++ struct fbcon_par *par = info->fbcon_par; + u_int b; + + if (sy < y_break && sy + height > y_break) { +@@ -1725,8 +1723,7 @@ static void fbcon_bmove_rec(struct vc_da + } + return; + } +- ops->bmove(vc, info, real_y(p, sy), sx, real_y(p, dy), dx, +- height, width); ++ par->bmove(vc, info, real_y(p, sy), sx, real_y(p, dy), dx, height, width); + } + + static void fbcon_bmove(struct vc_data *vc, int sy, int sx, int dy, int dx, +@@ -1953,15 +1950,13 @@ static void updatescrollmode_accel(struc + struct vc_data *vc) + { + #ifdef CONFIG_FRAMEBUFFER_CONSOLE_LEGACY_ACCELERATION +- struct fbcon_ops *ops = info->fbcon_par; ++ struct fbcon_par *par = info->fbcon_par; + int cap = info->flags; + u16 t = 0; +- int ypan = FBCON_SWAP(ops->rotate, info->fix.ypanstep, +- info->fix.xpanstep); +- int ywrap = FBCON_SWAP(ops->rotate, info->fix.ywrapstep, t); +- int yres = FBCON_SWAP(ops->rotate, info->var.yres, info->var.xres); +- int vyres = FBCON_SWAP(ops->rotate, info->var.yres_virtual, +- info->var.xres_virtual); ++ int ypan = FBCON_SWAP(par->rotate, info->fix.ypanstep, info->fix.xpanstep); ++ int ywrap = FBCON_SWAP(par->rotate, info->fix.ywrapstep, t); ++ int yres = FBCON_SWAP(par->rotate, info->var.yres, info->var.xres); ++ int vyres = FBCON_SWAP(par->rotate, info->var.yres_virtual, info->var.xres_virtual); + int good_pan = (cap & FBINFO_HWACCEL_YPAN) && + divides(ypan, vc->vc_font.height) && vyres > yres; + int good_wrap = (cap & FBINFO_HWACCEL_YWRAP) && +@@ -1994,11 +1989,10 @@ static void updatescrollmode(struct fbco + struct fb_info *info, + struct vc_data *vc) + { +- struct fbcon_ops *ops = info->fbcon_par; ++ struct fbcon_par *par = info->fbcon_par; + int fh = vc->vc_font.height; +- int yres = FBCON_SWAP(ops->rotate, info->var.yres, info->var.xres); +- int vyres = FBCON_SWAP(ops->rotate, info->var.yres_virtual, +- info->var.xres_virtual); ++ int yres = FBCON_SWAP(par->rotate, info->var.yres, info->var.xres); ++ int vyres = FBCON_SWAP(par->rotate, info->var.yres_virtual, info->var.xres_virtual); + + p->vrows = vyres/fh; + if (yres > (fh * (vc->vc_rows + 1))) +@@ -2017,7 +2011,7 @@ static int fbcon_resize(struct vc_data * + unsigned int height, unsigned int user) + { + struct fb_info *info = fbcon_info_from_console(vc->vc_num); +- struct fbcon_ops *ops = info->fbcon_par; ++ struct fbcon_par *par = info->fbcon_par; + struct fbcon_display *p = &fb_display[vc->vc_num]; + struct fb_var_screeninfo var = info->var; + int x_diff, y_diff, virt_w, virt_h, virt_fw, virt_fh; +@@ -2040,12 +2034,10 @@ static int fbcon_resize(struct vc_data * + return -EINVAL; + } + +- virt_w = FBCON_SWAP(ops->rotate, width, height); +- virt_h = FBCON_SWAP(ops->rotate, height, width); +- virt_fw = FBCON_SWAP(ops->rotate, vc->vc_font.width, +- vc->vc_font.height); +- virt_fh = FBCON_SWAP(ops->rotate, vc->vc_font.height, +- vc->vc_font.width); ++ virt_w = FBCON_SWAP(par->rotate, width, height); ++ virt_h = FBCON_SWAP(par->rotate, height, width); ++ virt_fw = FBCON_SWAP(par->rotate, vc->vc_font.width, vc->vc_font.height); ++ virt_fh = FBCON_SWAP(par->rotate, vc->vc_font.height, vc->vc_font.width); + var.xres = virt_w * virt_fw; + var.yres = virt_h * virt_fh; + x_diff = info->var.xres - var.xres; +@@ -2071,7 +2063,7 @@ static int fbcon_resize(struct vc_data * + fb_set_var(info, &var); + } + var_to_display(p, &info->var, info); +- ops->var = info->var; ++ par->var = info->var; + } + updatescrollmode(p, info, vc); + return 0; +@@ -2080,13 +2072,13 @@ static int fbcon_resize(struct vc_data * + static bool fbcon_switch(struct vc_data *vc) + { + struct fb_info *info, *old_info = NULL; +- struct fbcon_ops *ops; ++ struct fbcon_par *par; + struct fbcon_display *p = &fb_display[vc->vc_num]; + struct fb_var_screeninfo var; + int i, ret, prev_console; + + info = fbcon_info_from_console(vc->vc_num); +- ops = info->fbcon_par; ++ par = info->fbcon_par; + + if (logo_shown >= 0) { + struct vc_data *conp2 = vc_cons[logo_shown].d; +@@ -2097,7 +2089,7 @@ static bool fbcon_switch(struct vc_data + logo_shown = FBCON_LOGO_CANSHOW; + } + +- prev_console = ops->currcon; ++ prev_console = par->currcon; + if (prev_console != -1) + old_info = fbcon_info_from_console(prev_console); + /* +@@ -2110,9 +2102,9 @@ static bool fbcon_switch(struct vc_data + */ + fbcon_for_each_registered_fb(i) { + if (fbcon_registered_fb[i]->fbcon_par) { +- struct fbcon_ops *o = fbcon_registered_fb[i]->fbcon_par; ++ struct fbcon_par *par = fbcon_registered_fb[i]->fbcon_par; + +- o->currcon = vc->vc_num; ++ par->currcon = vc->vc_num; + } + } + memset(&var, 0, sizeof(struct fb_var_screeninfo)); +@@ -2126,7 +2118,7 @@ static bool fbcon_switch(struct vc_data + info->var.activate = var.activate; + var.vmode |= info->var.vmode & ~FB_VMODE_MASK; + fb_set_var(info, &var); +- ops->var = info->var; ++ par->var = info->var; + + if (old_info != NULL && (old_info != info || + info->flags & FBINFO_MISC_ALWAYS_SETPAR)) { +@@ -2144,16 +2136,16 @@ static bool fbcon_switch(struct vc_data + } + + if (fbcon_is_inactive(vc, info) || +- ops->blank_state != FB_BLANK_UNBLANK) ++ par->blank_state != FB_BLANK_UNBLANK) + fbcon_del_cursor_work(info); + else + fbcon_add_cursor_work(info); + + set_blitting_type(vc, info); +- ops->cursor_reset = 1; ++ par->cursor_reset = 1; + +- if (ops->rotate_font && ops->rotate_font(info, vc)) { +- ops->rotate = FB_ROTATE_UR; ++ if (par->rotate_font && par->rotate_font(info, vc)) { ++ par->rotate = FB_ROTATE_UR; + set_blitting_type(vc, info); + } + +@@ -2184,8 +2176,8 @@ static bool fbcon_switch(struct vc_data + scrollback_current = 0; + + if (!fbcon_is_inactive(vc, info)) { +- ops->var.xoffset = ops->var.yoffset = p->yscroll = 0; +- ops->update_start(info); ++ par->var.xoffset = par->var.yoffset = p->yscroll = 0; ++ par->update_start(info); + } + + fbcon_set_palette(vc, color_table); +@@ -2194,7 +2186,7 @@ static bool fbcon_switch(struct vc_data + if (logo_shown == FBCON_LOGO_DRAW) { + + logo_shown = fg_console; +- fb_show_logo(info, ops->rotate); ++ fb_show_logo(info, par->rotate); + update_region(vc, + vc->vc_origin + vc->vc_size_row * vc->vc_top, + vc->vc_size_row * (vc->vc_bottom - +@@ -2222,27 +2214,27 @@ static void fbcon_generic_blank(struct v + static int fbcon_blank(struct vc_data *vc, int blank, int mode_switch) + { + struct fb_info *info = fbcon_info_from_console(vc->vc_num); +- struct fbcon_ops *ops = info->fbcon_par; ++ struct fbcon_par *par = info->fbcon_par; + + if (mode_switch) { + struct fb_var_screeninfo var = info->var; + +- ops->graphics = 1; ++ par->graphics = 1; + + if (!blank) { + var.activate = FB_ACTIVATE_NOW | FB_ACTIVATE_FORCE | + FB_ACTIVATE_KD_TEXT; + fb_set_var(info, &var); +- ops->graphics = 0; +- ops->var = info->var; ++ par->graphics = 0; ++ par->var = info->var; + } + } + + if (!fbcon_is_inactive(vc, info)) { +- if (ops->blank_state != blank) { +- ops->blank_state = blank; ++ if (par->blank_state != blank) { ++ par->blank_state = blank; + fbcon_cursor(vc, blank ? CM_ERASE : CM_DRAW); +- ops->cursor_flash = (!blank); ++ par->cursor_flash = (!blank); + + if (fb_blank(info, blank)) + fbcon_generic_blank(vc, info, blank); +@@ -2253,7 +2245,7 @@ static int fbcon_blank(struct vc_data *v + } + + if (mode_switch || fbcon_is_inactive(vc, info) || +- ops->blank_state != FB_BLANK_UNBLANK) ++ par->blank_state != FB_BLANK_UNBLANK) + fbcon_del_cursor_work(info); + else + fbcon_add_cursor_work(info); +@@ -2264,10 +2256,10 @@ static int fbcon_blank(struct vc_data *v + static int fbcon_debug_enter(struct vc_data *vc) + { + struct fb_info *info = fbcon_info_from_console(vc->vc_num); +- struct fbcon_ops *ops = info->fbcon_par; ++ struct fbcon_par *par = info->fbcon_par; + +- ops->save_graphics = ops->graphics; +- ops->graphics = 0; ++ par->save_graphics = par->graphics; ++ par->graphics = 0; + if (info->fbops->fb_debug_enter) + info->fbops->fb_debug_enter(info); + fbcon_set_palette(vc, color_table); +@@ -2277,9 +2269,9 @@ static int fbcon_debug_enter(struct vc_d + static int fbcon_debug_leave(struct vc_data *vc) + { + struct fb_info *info = fbcon_info_from_console(vc->vc_num); +- struct fbcon_ops *ops = info->fbcon_par; ++ struct fbcon_par *par = info->fbcon_par; + +- ops->graphics = ops->save_graphics; ++ par->graphics = par->save_graphics; + if (info->fbops->fb_debug_leave) + info->fbops->fb_debug_leave(info); + return 0; +@@ -2415,7 +2407,7 @@ static int fbcon_do_set_font(struct vc_d + const u8 * data, int userfont) + { + struct fb_info *info = fbcon_info_from_console(vc->vc_num); +- struct fbcon_ops *ops = info->fbcon_par; ++ struct fbcon_par *par = info->fbcon_par; + struct fbcon_display *p = &fb_display[vc->vc_num]; + int resize, ret, old_userfont, old_width, old_height, old_charcount; + u8 *old_data = vc->vc_font.data; +@@ -2442,8 +2434,8 @@ static int fbcon_do_set_font(struct vc_d + if (resize) { + int cols, rows; + +- cols = FBCON_SWAP(ops->rotate, info->var.xres, info->var.yres); +- rows = FBCON_SWAP(ops->rotate, info->var.yres, info->var.xres); ++ cols = FBCON_SWAP(par->rotate, info->var.xres, info->var.yres); ++ rows = FBCON_SWAP(par->rotate, info->var.yres, info->var.xres); + cols /= w; + rows /= h; + ret = vc_resize(vc, cols, rows); +@@ -2676,11 +2668,11 @@ static void fbcon_invert_region(struct v + void fbcon_suspended(struct fb_info *info) + { + struct vc_data *vc = NULL; +- struct fbcon_ops *ops = info->fbcon_par; ++ struct fbcon_par *par = info->fbcon_par; + +- if (!ops || ops->currcon < 0) ++ if (!par || par->currcon < 0) + return; +- vc = vc_cons[ops->currcon].d; ++ vc = vc_cons[par->currcon].d; + + /* Clear cursor, restore saved data */ + fbcon_cursor(vc, CM_ERASE); +@@ -2689,27 +2681,27 @@ void fbcon_suspended(struct fb_info *inf + void fbcon_resumed(struct fb_info *info) + { + struct vc_data *vc; +- struct fbcon_ops *ops = info->fbcon_par; ++ struct fbcon_par *par = info->fbcon_par; + +- if (!ops || ops->currcon < 0) ++ if (!par || par->currcon < 0) + return; +- vc = vc_cons[ops->currcon].d; ++ vc = vc_cons[par->currcon].d; + + update_screen(vc); + } + + static void fbcon_modechanged(struct fb_info *info) + { +- struct fbcon_ops *ops = info->fbcon_par; ++ struct fbcon_par *par = info->fbcon_par; + struct vc_data *vc; + struct fbcon_display *p; + int rows, cols; + +- if (!ops || ops->currcon < 0) ++ if (!par || par->currcon < 0) + return; +- vc = vc_cons[ops->currcon].d; ++ vc = vc_cons[par->currcon].d; + if (vc->vc_mode != KD_TEXT || +- fbcon_info_from_console(ops->currcon) != info) ++ fbcon_info_from_console(par->currcon) != info) + return; + + p = &fb_display[vc->vc_num]; +@@ -2717,8 +2709,8 @@ static void fbcon_modechanged(struct fb_ + + if (con_is_visible(vc)) { + var_to_display(p, &info->var, info); +- cols = FBCON_SWAP(ops->rotate, info->var.xres, info->var.yres); +- rows = FBCON_SWAP(ops->rotate, info->var.yres, info->var.xres); ++ cols = FBCON_SWAP(par->rotate, info->var.xres, info->var.yres); ++ rows = FBCON_SWAP(par->rotate, info->var.yres, info->var.xres); + cols /= vc->vc_font.width; + rows /= vc->vc_font.height; + vc_resize(vc, cols, rows); +@@ -2727,8 +2719,8 @@ static void fbcon_modechanged(struct fb_ + scrollback_current = 0; + + if (!fbcon_is_inactive(vc, info)) { +- ops->var.xoffset = ops->var.yoffset = p->yscroll = 0; +- ops->update_start(info); ++ par->var.xoffset = par->var.yoffset = p->yscroll = 0; ++ par->update_start(info); + } + + fbcon_set_palette(vc, color_table); +@@ -2738,12 +2730,12 @@ static void fbcon_modechanged(struct fb_ + + static void fbcon_set_all_vcs(struct fb_info *info) + { +- struct fbcon_ops *ops = info->fbcon_par; ++ struct fbcon_par *par = info->fbcon_par; + struct vc_data *vc; + struct fbcon_display *p; + int i, rows, cols, fg = -1; + +- if (!ops || ops->currcon < 0) ++ if (!par || par->currcon < 0) + return; + + for (i = first_fb_vc; i <= last_fb_vc; i++) { +@@ -2760,8 +2752,8 @@ static void fbcon_set_all_vcs(struct fb_ + p = &fb_display[vc->vc_num]; + set_blitting_type(vc, info); + var_to_display(p, &info->var, info); +- cols = FBCON_SWAP(ops->rotate, info->var.xres, info->var.yres); +- rows = FBCON_SWAP(ops->rotate, info->var.yres, info->var.xres); ++ cols = FBCON_SWAP(par->rotate, info->var.xres, info->var.yres); ++ rows = FBCON_SWAP(par->rotate, info->var.yres, info->var.xres); + cols /= vc->vc_font.width; + rows /= vc->vc_font.height; + vc_resize(vc, cols, rows); +@@ -2784,13 +2776,13 @@ EXPORT_SYMBOL(fbcon_update_vcs); + /* let fbcon check if it supports a new screen resolution */ + int fbcon_modechange_possible(struct fb_info *info, struct fb_var_screeninfo *var) + { +- struct fbcon_ops *ops = info->fbcon_par; ++ struct fbcon_par *par = info->fbcon_par; + struct vc_data *vc; + unsigned int i; + + WARN_CONSOLE_UNLOCKED(); + +- if (!ops) ++ if (!par) + return 0; + + /* prevent setting a screen size which is smaller than font size */ +@@ -3088,15 +3080,14 @@ int fbcon_fb_registered(struct fb_info * + + void fbcon_fb_blanked(struct fb_info *info, int blank) + { +- struct fbcon_ops *ops = info->fbcon_par; ++ struct fbcon_par *par = info->fbcon_par; + struct vc_data *vc; + +- if (!ops || ops->currcon < 0) ++ if (!par || par->currcon < 0) + return; + +- vc = vc_cons[ops->currcon].d; +- if (vc->vc_mode != KD_TEXT || +- fbcon_info_from_console(ops->currcon) != info) ++ vc = vc_cons[par->currcon].d; ++ if (vc->vc_mode != KD_TEXT || fbcon_info_from_console(par->currcon) != info) + return; + + if (con_is_visible(vc)) { +@@ -3105,7 +3096,7 @@ void fbcon_fb_blanked(struct fb_info *in + else + do_unblank_screen(0); + } +- ops->blank_state = blank; ++ par->blank_state = blank; + } + + void fbcon_new_modelist(struct fb_info *info) +@@ -3296,7 +3287,7 @@ static ssize_t show_cursor_blink(struct + struct device_attribute *attr, char *buf) + { + struct fb_info *info; +- struct fbcon_ops *ops; ++ struct fbcon_par *par; + int idx, blink = -1; + + console_lock(); +@@ -3306,12 +3297,12 @@ static ssize_t show_cursor_blink(struct + goto err; + + info = fbcon_registered_fb[idx]; +- ops = info->fbcon_par; ++ par = info->fbcon_par; + +- if (!ops) ++ if (!par) + goto err; + +- blink = delayed_work_pending(&ops->cursor_work); ++ blink = delayed_work_pending(&par->cursor_work); + err: + console_unlock(); + return sysfs_emit(buf, "%d\n", blink); +--- a/drivers/video/fbdev/core/fbcon.h ++++ b/drivers/video/fbdev/core/fbcon.h +@@ -50,7 +50,7 @@ struct fbcon_display { + const struct fb_videomode *mode; + }; + +-struct fbcon_ops { ++struct fbcon_par { + void (*bmove)(struct vc_data *vc, struct fb_info *info, int sy, + int sx, int dy, int dx, int height, int width); + void (*clear)(struct vc_data *vc, struct fb_info *info, int sy, +@@ -185,7 +185,7 @@ static inline u_short fb_scrollmode(stru + #ifdef CONFIG_FB_TILEBLITTING + extern void fbcon_set_tileops(struct vc_data *vc, struct fb_info *info); + #endif +-extern void fbcon_set_bitops(struct fbcon_ops *ops); ++extern void fbcon_set_bitops(struct fbcon_par *par); + extern int soft_cursor(struct fb_info *info, struct fb_cursor *cursor); + + #define FBCON_ATTRIBUTE_UNDERLINE 1 +@@ -224,7 +224,7 @@ static inline int get_attribute(struct f + (i == FB_ROTATE_UR || i == FB_ROTATE_UD) ? _r : _v; }) + + #ifdef CONFIG_FRAMEBUFFER_CONSOLE_ROTATION +-extern void fbcon_set_rotate(struct fbcon_ops *ops); ++extern void fbcon_set_rotate(struct fbcon_par *par); + #else + #define fbcon_set_rotate(x) do {} while(0) + #endif /* CONFIG_FRAMEBUFFER_CONSOLE_ROTATION */ +--- a/drivers/video/fbdev/core/fbcon_ccw.c ++++ b/drivers/video/fbdev/core/fbcon_ccw.c +@@ -63,9 +63,9 @@ static void ccw_update_attr(u8 *dst, u8 + static void ccw_bmove(struct vc_data *vc, struct fb_info *info, int sy, + int sx, int dy, int dx, int height, int width) + { +- struct fbcon_ops *ops = info->fbcon_par; ++ struct fbcon_par *par = info->fbcon_par; + struct fb_copyarea area; +- u32 vyres = GETVYRES(ops->p, info); ++ u32 vyres = GETVYRES(par->p, info); + + area.sx = sy * vc->vc_font.height; + area.sy = vyres - ((sx + width) * vc->vc_font.width); +@@ -80,9 +80,9 @@ static void ccw_bmove(struct vc_data *vc + static void ccw_clear(struct vc_data *vc, struct fb_info *info, int sy, + int sx, int height, int width, int fg, int bg) + { +- struct fbcon_ops *ops = info->fbcon_par; ++ struct fbcon_par *par = info->fbcon_par; + struct fb_fillrect region; +- u32 vyres = GETVYRES(ops->p, info); ++ u32 vyres = GETVYRES(par->p, info); + + region.color = bg; + region.dx = sy * vc->vc_font.height; +@@ -99,13 +99,13 @@ static inline void ccw_putcs_aligned(str + u32 d_pitch, u32 s_pitch, u32 cellsize, + struct fb_image *image, u8 *buf, u8 *dst) + { +- struct fbcon_ops *ops = info->fbcon_par; ++ struct fbcon_par *par = info->fbcon_par; + u16 charmask = vc->vc_hi_font_mask ? 0x1ff : 0xff; + u32 idx = (vc->vc_font.height + 7) >> 3; + u8 *src; + + while (cnt--) { +- src = ops->fontbuffer + (scr_readw(s--) & charmask)*cellsize; ++ src = par->fontbuffer + (scr_readw(s--) & charmask) * cellsize; + + if (attr) { + ccw_update_attr(buf, src, attr, vc); +@@ -130,7 +130,7 @@ static void ccw_putcs(struct vc_data *vc + int fg, int bg) + { + struct fb_image image; +- struct fbcon_ops *ops = info->fbcon_par; ++ struct fbcon_par *par = info->fbcon_par; + u32 width = (vc->vc_font.height + 7)/8; + u32 cellsize = width * vc->vc_font.width; + u32 maxcnt = info->pixmap.size/cellsize; +@@ -139,9 +139,9 @@ static void ccw_putcs(struct vc_data *vc + u32 cnt, pitch, size; + u32 attribute = get_attribute(info, scr_readw(s)); + u8 *dst, *buf = NULL; +- u32 vyres = GETVYRES(ops->p, info); ++ u32 vyres = GETVYRES(par->p, info); + +- if (!ops->fontbuffer) ++ if (!par->fontbuffer) + return; + + image.fg_color = fg; +@@ -221,28 +221,28 @@ static void ccw_cursor(struct vc_data *v + int fg, int bg) + { + struct fb_cursor cursor; +- struct fbcon_ops *ops = info->fbcon_par; ++ struct fbcon_par *par = info->fbcon_par; + unsigned short charmask = vc->vc_hi_font_mask ? 0x1ff : 0xff; + int w = (vc->vc_font.height + 7) >> 3, c; +- int y = real_y(ops->p, vc->state.y); ++ int y = real_y(par->p, vc->state.y); + int attribute, use_sw = vc->vc_cursor_type & CUR_SW; + int err = 1, dx, dy; + char *src; +- u32 vyres = GETVYRES(ops->p, info); ++ u32 vyres = GETVYRES(par->p, info); + +- if (!ops->fontbuffer) ++ if (!par->fontbuffer) + return; + + cursor.set = 0; + + c = scr_readw((u16 *) vc->vc_pos); + attribute = get_attribute(info, c); +- src = ops->fontbuffer + ((c & charmask) * (w * vc->vc_font.width)); ++ src = par->fontbuffer + ((c & charmask) * (w * vc->vc_font.width)); + +- if (ops->cursor_state.image.data != src || +- ops->cursor_reset) { +- ops->cursor_state.image.data = src; +- cursor.set |= FB_CUR_SETIMAGE; ++ if (par->cursor_state.image.data != src || ++ par->cursor_reset) { ++ par->cursor_state.image.data = src; ++ cursor.set |= FB_CUR_SETIMAGE; + } + + if (attribute) { +@@ -251,49 +251,49 @@ static void ccw_cursor(struct vc_data *v + dst = kmalloc_array(w, vc->vc_font.width, GFP_ATOMIC); + if (!dst) + return; +- kfree(ops->cursor_data); +- ops->cursor_data = dst; ++ kfree(par->cursor_data); ++ par->cursor_data = dst; + ccw_update_attr(dst, src, attribute, vc); + src = dst; + } + +- if (ops->cursor_state.image.fg_color != fg || +- ops->cursor_state.image.bg_color != bg || +- ops->cursor_reset) { +- ops->cursor_state.image.fg_color = fg; +- ops->cursor_state.image.bg_color = bg; ++ if (par->cursor_state.image.fg_color != fg || ++ par->cursor_state.image.bg_color != bg || ++ par->cursor_reset) { ++ par->cursor_state.image.fg_color = fg; ++ par->cursor_state.image.bg_color = bg; + cursor.set |= FB_CUR_SETCMAP; + } + +- if (ops->cursor_state.image.height != vc->vc_font.width || +- ops->cursor_state.image.width != vc->vc_font.height || +- ops->cursor_reset) { +- ops->cursor_state.image.height = vc->vc_font.width; +- ops->cursor_state.image.width = vc->vc_font.height; ++ if (par->cursor_state.image.height != vc->vc_font.width || ++ par->cursor_state.image.width != vc->vc_font.height || ++ par->cursor_reset) { ++ par->cursor_state.image.height = vc->vc_font.width; ++ par->cursor_state.image.width = vc->vc_font.height; + cursor.set |= FB_CUR_SETSIZE; + } + + dx = y * vc->vc_font.height; + dy = vyres - ((vc->state.x + 1) * vc->vc_font.width); + +- if (ops->cursor_state.image.dx != dx || +- ops->cursor_state.image.dy != dy || +- ops->cursor_reset) { +- ops->cursor_state.image.dx = dx; +- ops->cursor_state.image.dy = dy; ++ if (par->cursor_state.image.dx != dx || ++ par->cursor_state.image.dy != dy || ++ par->cursor_reset) { ++ par->cursor_state.image.dx = dx; ++ par->cursor_state.image.dy = dy; + cursor.set |= FB_CUR_SETPOS; + } + +- if (ops->cursor_state.hot.x || ops->cursor_state.hot.y || +- ops->cursor_reset) { +- ops->cursor_state.hot.x = cursor.hot.y = 0; ++ if (par->cursor_state.hot.x || par->cursor_state.hot.y || ++ par->cursor_reset) { ++ par->cursor_state.hot.x = cursor.hot.y = 0; + cursor.set |= FB_CUR_SETHOT; + } + + if (cursor.set & FB_CUR_SETSIZE || +- vc->vc_cursor_type != ops->p->cursor_shape || +- ops->cursor_state.mask == NULL || +- ops->cursor_reset) { ++ vc->vc_cursor_type != par->p->cursor_shape || ++ par->cursor_state.mask == NULL || ++ par->cursor_reset) { + char *tmp, *mask = kmalloc_array(w, vc->vc_font.width, + GFP_ATOMIC); + int cur_height, size, i = 0; +@@ -309,13 +309,13 @@ static void ccw_cursor(struct vc_data *v + return; + } + +- kfree(ops->cursor_state.mask); +- ops->cursor_state.mask = mask; ++ kfree(par->cursor_state.mask); ++ par->cursor_state.mask = mask; + +- ops->p->cursor_shape = vc->vc_cursor_type; ++ par->p->cursor_shape = vc->vc_cursor_type; + cursor.set |= FB_CUR_SETSHAPE; + +- switch (CUR_SIZE(ops->p->cursor_shape)) { ++ switch (CUR_SIZE(par->p->cursor_shape)) { + case CUR_NONE: + cur_height = 0; + break; +@@ -350,26 +350,26 @@ static void ccw_cursor(struct vc_data *v + + switch (mode) { + case CM_ERASE: +- ops->cursor_state.enable = 0; ++ par->cursor_state.enable = 0; + break; + case CM_DRAW: + case CM_MOVE: + default: +- ops->cursor_state.enable = (use_sw) ? 0 : 1; ++ par->cursor_state.enable = (use_sw) ? 0 : 1; + break; + } + + cursor.image.data = src; +- cursor.image.fg_color = ops->cursor_state.image.fg_color; +- cursor.image.bg_color = ops->cursor_state.image.bg_color; +- cursor.image.dx = ops->cursor_state.image.dx; +- cursor.image.dy = ops->cursor_state.image.dy; +- cursor.image.height = ops->cursor_state.image.height; +- cursor.image.width = ops->cursor_state.image.width; +- cursor.hot.x = ops->cursor_state.hot.x; +- cursor.hot.y = ops->cursor_state.hot.y; +- cursor.mask = ops->cursor_state.mask; +- cursor.enable = ops->cursor_state.enable; ++ cursor.image.fg_color = par->cursor_state.image.fg_color; ++ cursor.image.bg_color = par->cursor_state.image.bg_color; ++ cursor.image.dx = par->cursor_state.image.dx; ++ cursor.image.dy = par->cursor_state.image.dy; ++ cursor.image.height = par->cursor_state.image.height; ++ cursor.image.width = par->cursor_state.image.width; ++ cursor.hot.x = par->cursor_state.hot.x; ++ cursor.hot.y = par->cursor_state.hot.y; ++ cursor.mask = par->cursor_state.mask; ++ cursor.enable = par->cursor_state.enable; + cursor.image.depth = 1; + cursor.rop = ROP_XOR; + +@@ -379,32 +379,32 @@ static void ccw_cursor(struct vc_data *v + if (err) + soft_cursor(info, &cursor); + +- ops->cursor_reset = 0; ++ par->cursor_reset = 0; + } + + static int ccw_update_start(struct fb_info *info) + { +- struct fbcon_ops *ops = info->fbcon_par; ++ struct fbcon_par *par = info->fbcon_par; + u32 yoffset; +- u32 vyres = GETVYRES(ops->p, info); ++ u32 vyres = GETVYRES(par->p, info); + int err; + +- yoffset = (vyres - info->var.yres) - ops->var.xoffset; +- ops->var.xoffset = ops->var.yoffset; +- ops->var.yoffset = yoffset; +- err = fb_pan_display(info, &ops->var); +- ops->var.xoffset = info->var.xoffset; +- ops->var.yoffset = info->var.yoffset; +- ops->var.vmode = info->var.vmode; ++ yoffset = (vyres - info->var.yres) - par->var.xoffset; ++ par->var.xoffset = par->var.yoffset; ++ par->var.yoffset = yoffset; ++ err = fb_pan_display(info, &par->var); ++ par->var.xoffset = info->var.xoffset; ++ par->var.yoffset = info->var.yoffset; ++ par->var.vmode = info->var.vmode; + return err; + } + +-void fbcon_rotate_ccw(struct fbcon_ops *ops) ++void fbcon_rotate_ccw(struct fbcon_par *par) + { +- ops->bmove = ccw_bmove; +- ops->clear = ccw_clear; +- ops->putcs = ccw_putcs; +- ops->clear_margins = ccw_clear_margins; +- ops->cursor = ccw_cursor; +- ops->update_start = ccw_update_start; ++ par->bmove = ccw_bmove; ++ par->clear = ccw_clear; ++ par->putcs = ccw_putcs; ++ par->clear_margins = ccw_clear_margins; ++ par->cursor = ccw_cursor; ++ par->update_start = ccw_update_start; + } +--- a/drivers/video/fbdev/core/fbcon_cw.c ++++ b/drivers/video/fbdev/core/fbcon_cw.c +@@ -48,9 +48,9 @@ static void cw_update_attr(u8 *dst, u8 * + static void cw_bmove(struct vc_data *vc, struct fb_info *info, int sy, + int sx, int dy, int dx, int height, int width) + { +- struct fbcon_ops *ops = info->fbcon_par; ++ struct fbcon_par *par = info->fbcon_par; + struct fb_copyarea area; +- u32 vxres = GETVXRES(ops->p, info); ++ u32 vxres = GETVXRES(par->p, info); + + area.sx = vxres - ((sy + height) * vc->vc_font.height); + area.sy = sx * vc->vc_font.width; +@@ -65,9 +65,9 @@ static void cw_bmove(struct vc_data *vc, + static void cw_clear(struct vc_data *vc, struct fb_info *info, int sy, + int sx, int height, int width, int fg, int bg) + { +- struct fbcon_ops *ops = info->fbcon_par; ++ struct fbcon_par *par = info->fbcon_par; + struct fb_fillrect region; +- u32 vxres = GETVXRES(ops->p, info); ++ u32 vxres = GETVXRES(par->p, info); + + region.color = bg; + region.dx = vxres - ((sy + height) * vc->vc_font.height); +@@ -84,13 +84,13 @@ static inline void cw_putcs_aligned(stru + u32 d_pitch, u32 s_pitch, u32 cellsize, + struct fb_image *image, u8 *buf, u8 *dst) + { +- struct fbcon_ops *ops = info->fbcon_par; ++ struct fbcon_par *par = info->fbcon_par; + u16 charmask = vc->vc_hi_font_mask ? 0x1ff : 0xff; + u32 idx = (vc->vc_font.height + 7) >> 3; + u8 *src; + + while (cnt--) { +- src = ops->fontbuffer + (scr_readw(s++) & charmask)*cellsize; ++ src = par->fontbuffer + (scr_readw(s++) & charmask) * cellsize; + + if (attr) { + cw_update_attr(buf, src, attr, vc); +@@ -115,7 +115,7 @@ static void cw_putcs(struct vc_data *vc, + int fg, int bg) + { + struct fb_image image; +- struct fbcon_ops *ops = info->fbcon_par; ++ struct fbcon_par *par = info->fbcon_par; + u32 width = (vc->vc_font.height + 7)/8; + u32 cellsize = width * vc->vc_font.width; + u32 maxcnt = info->pixmap.size/cellsize; +@@ -124,9 +124,9 @@ static void cw_putcs(struct vc_data *vc, + u32 cnt, pitch, size; + u32 attribute = get_attribute(info, scr_readw(s)); + u8 *dst, *buf = NULL; +- u32 vxres = GETVXRES(ops->p, info); ++ u32 vxres = GETVXRES(par->p, info); + +- if (!ops->fontbuffer) ++ if (!par->fontbuffer) + return; + + image.fg_color = fg; +@@ -204,28 +204,28 @@ static void cw_cursor(struct vc_data *vc + int fg, int bg) + { + struct fb_cursor cursor; +- struct fbcon_ops *ops = info->fbcon_par; ++ struct fbcon_par *par = info->fbcon_par; + unsigned short charmask = vc->vc_hi_font_mask ? 0x1ff : 0xff; + int w = (vc->vc_font.height + 7) >> 3, c; +- int y = real_y(ops->p, vc->state.y); ++ int y = real_y(par->p, vc->state.y); + int attribute, use_sw = vc->vc_cursor_type & CUR_SW; + int err = 1, dx, dy; + char *src; +- u32 vxres = GETVXRES(ops->p, info); ++ u32 vxres = GETVXRES(par->p, info); + +- if (!ops->fontbuffer) ++ if (!par->fontbuffer) + return; + + cursor.set = 0; + + c = scr_readw((u16 *) vc->vc_pos); + attribute = get_attribute(info, c); +- src = ops->fontbuffer + ((c & charmask) * (w * vc->vc_font.width)); ++ src = par->fontbuffer + ((c & charmask) * (w * vc->vc_font.width)); + +- if (ops->cursor_state.image.data != src || +- ops->cursor_reset) { +- ops->cursor_state.image.data = src; +- cursor.set |= FB_CUR_SETIMAGE; ++ if (par->cursor_state.image.data != src || ++ par->cursor_reset) { ++ par->cursor_state.image.data = src; ++ cursor.set |= FB_CUR_SETIMAGE; + } + + if (attribute) { +@@ -234,49 +234,49 @@ static void cw_cursor(struct vc_data *vc + dst = kmalloc_array(w, vc->vc_font.width, GFP_ATOMIC); + if (!dst) + return; +- kfree(ops->cursor_data); +- ops->cursor_data = dst; ++ kfree(par->cursor_data); ++ par->cursor_data = dst; + cw_update_attr(dst, src, attribute, vc); + src = dst; + } + +- if (ops->cursor_state.image.fg_color != fg || +- ops->cursor_state.image.bg_color != bg || +- ops->cursor_reset) { +- ops->cursor_state.image.fg_color = fg; +- ops->cursor_state.image.bg_color = bg; ++ if (par->cursor_state.image.fg_color != fg || ++ par->cursor_state.image.bg_color != bg || ++ par->cursor_reset) { ++ par->cursor_state.image.fg_color = fg; ++ par->cursor_state.image.bg_color = bg; + cursor.set |= FB_CUR_SETCMAP; + } + +- if (ops->cursor_state.image.height != vc->vc_font.width || +- ops->cursor_state.image.width != vc->vc_font.height || +- ops->cursor_reset) { +- ops->cursor_state.image.height = vc->vc_font.width; +- ops->cursor_state.image.width = vc->vc_font.height; ++ if (par->cursor_state.image.height != vc->vc_font.width || ++ par->cursor_state.image.width != vc->vc_font.height || ++ par->cursor_reset) { ++ par->cursor_state.image.height = vc->vc_font.width; ++ par->cursor_state.image.width = vc->vc_font.height; + cursor.set |= FB_CUR_SETSIZE; + } + + dx = vxres - ((y * vc->vc_font.height) + vc->vc_font.height); + dy = vc->state.x * vc->vc_font.width; + +- if (ops->cursor_state.image.dx != dx || +- ops->cursor_state.image.dy != dy || +- ops->cursor_reset) { +- ops->cursor_state.image.dx = dx; +- ops->cursor_state.image.dy = dy; ++ if (par->cursor_state.image.dx != dx || ++ par->cursor_state.image.dy != dy || ++ par->cursor_reset) { ++ par->cursor_state.image.dx = dx; ++ par->cursor_state.image.dy = dy; + cursor.set |= FB_CUR_SETPOS; + } + +- if (ops->cursor_state.hot.x || ops->cursor_state.hot.y || +- ops->cursor_reset) { +- ops->cursor_state.hot.x = cursor.hot.y = 0; ++ if (par->cursor_state.hot.x || par->cursor_state.hot.y || ++ par->cursor_reset) { ++ par->cursor_state.hot.x = cursor.hot.y = 0; + cursor.set |= FB_CUR_SETHOT; + } + + if (cursor.set & FB_CUR_SETSIZE || +- vc->vc_cursor_type != ops->p->cursor_shape || +- ops->cursor_state.mask == NULL || +- ops->cursor_reset) { ++ vc->vc_cursor_type != par->p->cursor_shape || ++ par->cursor_state.mask == NULL || ++ par->cursor_reset) { + char *tmp, *mask = kmalloc_array(w, vc->vc_font.width, + GFP_ATOMIC); + int cur_height, size, i = 0; +@@ -292,13 +292,13 @@ static void cw_cursor(struct vc_data *vc + return; + } + +- kfree(ops->cursor_state.mask); +- ops->cursor_state.mask = mask; ++ kfree(par->cursor_state.mask); ++ par->cursor_state.mask = mask; + +- ops->p->cursor_shape = vc->vc_cursor_type; ++ par->p->cursor_shape = vc->vc_cursor_type; + cursor.set |= FB_CUR_SETSHAPE; + +- switch (CUR_SIZE(ops->p->cursor_shape)) { ++ switch (CUR_SIZE(par->p->cursor_shape)) { + case CUR_NONE: + cur_height = 0; + break; +@@ -333,26 +333,26 @@ static void cw_cursor(struct vc_data *vc + + switch (mode) { + case CM_ERASE: +- ops->cursor_state.enable = 0; ++ par->cursor_state.enable = 0; + break; + case CM_DRAW: + case CM_MOVE: + default: +- ops->cursor_state.enable = (use_sw) ? 0 : 1; ++ par->cursor_state.enable = (use_sw) ? 0 : 1; + break; + } + + cursor.image.data = src; +- cursor.image.fg_color = ops->cursor_state.image.fg_color; +- cursor.image.bg_color = ops->cursor_state.image.bg_color; +- cursor.image.dx = ops->cursor_state.image.dx; +- cursor.image.dy = ops->cursor_state.image.dy; +- cursor.image.height = ops->cursor_state.image.height; +- cursor.image.width = ops->cursor_state.image.width; +- cursor.hot.x = ops->cursor_state.hot.x; +- cursor.hot.y = ops->cursor_state.hot.y; +- cursor.mask = ops->cursor_state.mask; +- cursor.enable = ops->cursor_state.enable; ++ cursor.image.fg_color = par->cursor_state.image.fg_color; ++ cursor.image.bg_color = par->cursor_state.image.bg_color; ++ cursor.image.dx = par->cursor_state.image.dx; ++ cursor.image.dy = par->cursor_state.image.dy; ++ cursor.image.height = par->cursor_state.image.height; ++ cursor.image.width = par->cursor_state.image.width; ++ cursor.hot.x = par->cursor_state.hot.x; ++ cursor.hot.y = par->cursor_state.hot.y; ++ cursor.mask = par->cursor_state.mask; ++ cursor.enable = par->cursor_state.enable; + cursor.image.depth = 1; + cursor.rop = ROP_XOR; + +@@ -362,32 +362,32 @@ static void cw_cursor(struct vc_data *vc + if (err) + soft_cursor(info, &cursor); + +- ops->cursor_reset = 0; ++ par->cursor_reset = 0; + } + + static int cw_update_start(struct fb_info *info) + { +- struct fbcon_ops *ops = info->fbcon_par; +- u32 vxres = GETVXRES(ops->p, info); ++ struct fbcon_par *par = info->fbcon_par; ++ u32 vxres = GETVXRES(par->p, info); + u32 xoffset; + int err; + +- xoffset = vxres - (info->var.xres + ops->var.yoffset); +- ops->var.yoffset = ops->var.xoffset; +- ops->var.xoffset = xoffset; +- err = fb_pan_display(info, &ops->var); +- ops->var.xoffset = info->var.xoffset; +- ops->var.yoffset = info->var.yoffset; +- ops->var.vmode = info->var.vmode; ++ xoffset = vxres - (info->var.xres + par->var.yoffset); ++ par->var.yoffset = par->var.xoffset; ++ par->var.xoffset = xoffset; ++ err = fb_pan_display(info, &par->var); ++ par->var.xoffset = info->var.xoffset; ++ par->var.yoffset = info->var.yoffset; ++ par->var.vmode = info->var.vmode; + return err; + } + +-void fbcon_rotate_cw(struct fbcon_ops *ops) ++void fbcon_rotate_cw(struct fbcon_par *par) + { +- ops->bmove = cw_bmove; +- ops->clear = cw_clear; +- ops->putcs = cw_putcs; +- ops->clear_margins = cw_clear_margins; +- ops->cursor = cw_cursor; +- ops->update_start = cw_update_start; ++ par->bmove = cw_bmove; ++ par->clear = cw_clear; ++ par->putcs = cw_putcs; ++ par->clear_margins = cw_clear_margins; ++ par->cursor = cw_cursor; ++ par->update_start = cw_update_start; + } +--- a/drivers/video/fbdev/core/fbcon_rotate.c ++++ b/drivers/video/fbdev/core/fbcon_rotate.c +@@ -20,35 +20,35 @@ + + static int fbcon_rotate_font(struct fb_info *info, struct vc_data *vc) + { +- struct fbcon_ops *ops = info->fbcon_par; ++ struct fbcon_par *par = info->fbcon_par; + int len, err = 0; + int s_cellsize, d_cellsize, i; + const u8 *src; + u8 *dst; + +- if (vc->vc_font.data == ops->fontdata && +- ops->p->con_rotate == ops->cur_rotate) ++ if (vc->vc_font.data == par->fontdata && ++ par->p->con_rotate == par->cur_rotate) + goto finished; + +- src = ops->fontdata = vc->vc_font.data; +- ops->cur_rotate = ops->p->con_rotate; ++ src = par->fontdata = vc->vc_font.data; ++ par->cur_rotate = par->p->con_rotate; + len = vc->vc_font.charcount; + s_cellsize = ((vc->vc_font.width + 7)/8) * + vc->vc_font.height; + d_cellsize = s_cellsize; + +- if (ops->rotate == FB_ROTATE_CW || +- ops->rotate == FB_ROTATE_CCW) ++ if (par->rotate == FB_ROTATE_CW || ++ par->rotate == FB_ROTATE_CCW) + d_cellsize = ((vc->vc_font.height + 7)/8) * + vc->vc_font.width; + + if (info->fbops->fb_sync) + info->fbops->fb_sync(info); + +- if (ops->fd_size < d_cellsize * len) { +- kfree(ops->fontbuffer); +- ops->fontbuffer = NULL; +- ops->fd_size = 0; ++ if (par->fd_size < d_cellsize * len) { ++ kfree(par->fontbuffer); ++ par->fontbuffer = NULL; ++ par->fd_size = 0; + + dst = kmalloc_array(len, d_cellsize, GFP_KERNEL); + +@@ -57,14 +57,14 @@ static int fbcon_rotate_font(struct fb_i + goto finished; + } + +- ops->fd_size = d_cellsize * len; +- ops->fontbuffer = dst; ++ par->fd_size = d_cellsize * len; ++ par->fontbuffer = dst; + } + +- dst = ops->fontbuffer; +- memset(dst, 0, ops->fd_size); ++ dst = par->fontbuffer; ++ memset(dst, 0, par->fd_size); + +- switch (ops->rotate) { ++ switch (par->rotate) { + case FB_ROTATE_UD: + for (i = len; i--; ) { + rotate_ud(src, dst, vc->vc_font.width, +@@ -96,19 +96,19 @@ finished: + return err; + } + +-void fbcon_set_rotate(struct fbcon_ops *ops) ++void fbcon_set_rotate(struct fbcon_par *par) + { +- ops->rotate_font = fbcon_rotate_font; ++ par->rotate_font = fbcon_rotate_font; + +- switch(ops->rotate) { ++ switch (par->rotate) { + case FB_ROTATE_CW: +- fbcon_rotate_cw(ops); ++ fbcon_rotate_cw(par); + break; + case FB_ROTATE_UD: +- fbcon_rotate_ud(ops); ++ fbcon_rotate_ud(par); + break; + case FB_ROTATE_CCW: +- fbcon_rotate_ccw(ops); ++ fbcon_rotate_ccw(par); + break; + } + } +--- a/drivers/video/fbdev/core/fbcon_rotate.h ++++ b/drivers/video/fbdev/core/fbcon_rotate.h +@@ -90,7 +90,7 @@ static inline void rotate_ccw(const char + } + } + +-extern void fbcon_rotate_cw(struct fbcon_ops *ops); +-extern void fbcon_rotate_ud(struct fbcon_ops *ops); +-extern void fbcon_rotate_ccw(struct fbcon_ops *ops); ++extern void fbcon_rotate_cw(struct fbcon_par *par); ++extern void fbcon_rotate_ud(struct fbcon_par *par); ++extern void fbcon_rotate_ccw(struct fbcon_par *par); + #endif +--- a/drivers/video/fbdev/core/fbcon_ud.c ++++ b/drivers/video/fbdev/core/fbcon_ud.c +@@ -48,10 +48,10 @@ static void ud_update_attr(u8 *dst, u8 * + static void ud_bmove(struct vc_data *vc, struct fb_info *info, int sy, + int sx, int dy, int dx, int height, int width) + { +- struct fbcon_ops *ops = info->fbcon_par; ++ struct fbcon_par *par = info->fbcon_par; + struct fb_copyarea area; +- u32 vyres = GETVYRES(ops->p, info); +- u32 vxres = GETVXRES(ops->p, info); ++ u32 vyres = GETVYRES(par->p, info); ++ u32 vxres = GETVXRES(par->p, info); + + area.sy = vyres - ((sy + height) * vc->vc_font.height); + area.sx = vxres - ((sx + width) * vc->vc_font.width); +@@ -66,10 +66,10 @@ static void ud_bmove(struct vc_data *vc, + static void ud_clear(struct vc_data *vc, struct fb_info *info, int sy, + int sx, int height, int width, int fg, int bg) + { +- struct fbcon_ops *ops = info->fbcon_par; ++ struct fbcon_par *par = info->fbcon_par; + struct fb_fillrect region; +- u32 vyres = GETVYRES(ops->p, info); +- u32 vxres = GETVXRES(ops->p, info); ++ u32 vyres = GETVYRES(par->p, info); ++ u32 vxres = GETVXRES(par->p, info); + + region.color = bg; + region.dy = vyres - ((sy + height) * vc->vc_font.height); +@@ -86,13 +86,13 @@ static inline void ud_putcs_aligned(stru + u32 d_pitch, u32 s_pitch, u32 cellsize, + struct fb_image *image, u8 *buf, u8 *dst) + { +- struct fbcon_ops *ops = info->fbcon_par; ++ struct fbcon_par *par = info->fbcon_par; + u16 charmask = vc->vc_hi_font_mask ? 0x1ff : 0xff; + u32 idx = vc->vc_font.width >> 3; + u8 *src; + + while (cnt--) { +- src = ops->fontbuffer + (scr_readw(s--) & charmask)*cellsize; ++ src = par->fontbuffer + (scr_readw(s--) & charmask) * cellsize; + + if (attr) { + ud_update_attr(buf, src, attr, vc); +@@ -119,7 +119,7 @@ static inline void ud_putcs_unaligned(st + struct fb_image *image, u8 *buf, + u8 *dst) + { +- struct fbcon_ops *ops = info->fbcon_par; ++ struct fbcon_par *par = info->fbcon_par; + u16 charmask = vc->vc_hi_font_mask ? 0x1ff : 0xff; + u32 shift_low = 0, mod = vc->vc_font.width % 8; + u32 shift_high = 8; +@@ -127,7 +127,7 @@ static inline void ud_putcs_unaligned(st + u8 *src; + + while (cnt--) { +- src = ops->fontbuffer + (scr_readw(s--) & charmask)*cellsize; ++ src = par->fontbuffer + (scr_readw(s--) & charmask) * cellsize; + + if (attr) { + ud_update_attr(buf, src, attr, vc); +@@ -152,7 +152,7 @@ static void ud_putcs(struct vc_data *vc, + int fg, int bg) + { + struct fb_image image; +- struct fbcon_ops *ops = info->fbcon_par; ++ struct fbcon_par *par = info->fbcon_par; + u32 width = (vc->vc_font.width + 7)/8; + u32 cellsize = width * vc->vc_font.height; + u32 maxcnt = info->pixmap.size/cellsize; +@@ -161,10 +161,10 @@ static void ud_putcs(struct vc_data *vc, + u32 mod = vc->vc_font.width % 8, cnt, pitch, size; + u32 attribute = get_attribute(info, scr_readw(s)); + u8 *dst, *buf = NULL; +- u32 vyres = GETVYRES(ops->p, info); +- u32 vxres = GETVXRES(ops->p, info); ++ u32 vyres = GETVYRES(par->p, info); ++ u32 vxres = GETVXRES(par->p, info); + +- if (!ops->fontbuffer) ++ if (!par->fontbuffer) + return; + + image.fg_color = fg; +@@ -251,29 +251,29 @@ static void ud_cursor(struct vc_data *vc + int fg, int bg) + { + struct fb_cursor cursor; +- struct fbcon_ops *ops = info->fbcon_par; ++ struct fbcon_par *par = info->fbcon_par; + unsigned short charmask = vc->vc_hi_font_mask ? 0x1ff : 0xff; + int w = (vc->vc_font.width + 7) >> 3, c; +- int y = real_y(ops->p, vc->state.y); ++ int y = real_y(par->p, vc->state.y); + int attribute, use_sw = vc->vc_cursor_type & CUR_SW; + int err = 1, dx, dy; + char *src; +- u32 vyres = GETVYRES(ops->p, info); +- u32 vxres = GETVXRES(ops->p, info); ++ u32 vyres = GETVYRES(par->p, info); ++ u32 vxres = GETVXRES(par->p, info); + +- if (!ops->fontbuffer) ++ if (!par->fontbuffer) + return; + + cursor.set = 0; + + c = scr_readw((u16 *) vc->vc_pos); + attribute = get_attribute(info, c); +- src = ops->fontbuffer + ((c & charmask) * (w * vc->vc_font.height)); ++ src = par->fontbuffer + ((c & charmask) * (w * vc->vc_font.height)); + +- if (ops->cursor_state.image.data != src || +- ops->cursor_reset) { +- ops->cursor_state.image.data = src; +- cursor.set |= FB_CUR_SETIMAGE; ++ if (par->cursor_state.image.data != src || ++ par->cursor_reset) { ++ par->cursor_state.image.data = src; ++ cursor.set |= FB_CUR_SETIMAGE; + } + + if (attribute) { +@@ -282,49 +282,49 @@ static void ud_cursor(struct vc_data *vc + dst = kmalloc_array(w, vc->vc_font.height, GFP_ATOMIC); + if (!dst) + return; +- kfree(ops->cursor_data); +- ops->cursor_data = dst; ++ kfree(par->cursor_data); ++ par->cursor_data = dst; + ud_update_attr(dst, src, attribute, vc); + src = dst; + } + +- if (ops->cursor_state.image.fg_color != fg || +- ops->cursor_state.image.bg_color != bg || +- ops->cursor_reset) { +- ops->cursor_state.image.fg_color = fg; +- ops->cursor_state.image.bg_color = bg; ++ if (par->cursor_state.image.fg_color != fg || ++ par->cursor_state.image.bg_color != bg || ++ par->cursor_reset) { ++ par->cursor_state.image.fg_color = fg; ++ par->cursor_state.image.bg_color = bg; + cursor.set |= FB_CUR_SETCMAP; + } + +- if (ops->cursor_state.image.height != vc->vc_font.height || +- ops->cursor_state.image.width != vc->vc_font.width || +- ops->cursor_reset) { +- ops->cursor_state.image.height = vc->vc_font.height; +- ops->cursor_state.image.width = vc->vc_font.width; ++ if (par->cursor_state.image.height != vc->vc_font.height || ++ par->cursor_state.image.width != vc->vc_font.width || ++ par->cursor_reset) { ++ par->cursor_state.image.height = vc->vc_font.height; ++ par->cursor_state.image.width = vc->vc_font.width; + cursor.set |= FB_CUR_SETSIZE; + } + + dy = vyres - ((y * vc->vc_font.height) + vc->vc_font.height); + dx = vxres - ((vc->state.x * vc->vc_font.width) + vc->vc_font.width); + +- if (ops->cursor_state.image.dx != dx || +- ops->cursor_state.image.dy != dy || +- ops->cursor_reset) { +- ops->cursor_state.image.dx = dx; +- ops->cursor_state.image.dy = dy; ++ if (par->cursor_state.image.dx != dx || ++ par->cursor_state.image.dy != dy || ++ par->cursor_reset) { ++ par->cursor_state.image.dx = dx; ++ par->cursor_state.image.dy = dy; + cursor.set |= FB_CUR_SETPOS; + } + +- if (ops->cursor_state.hot.x || ops->cursor_state.hot.y || +- ops->cursor_reset) { +- ops->cursor_state.hot.x = cursor.hot.y = 0; ++ if (par->cursor_state.hot.x || par->cursor_state.hot.y || ++ par->cursor_reset) { ++ par->cursor_state.hot.x = cursor.hot.y = 0; + cursor.set |= FB_CUR_SETHOT; + } + + if (cursor.set & FB_CUR_SETSIZE || +- vc->vc_cursor_type != ops->p->cursor_shape || +- ops->cursor_state.mask == NULL || +- ops->cursor_reset) { ++ vc->vc_cursor_type != par->p->cursor_shape || ++ par->cursor_state.mask == NULL || ++ par->cursor_reset) { + char *mask = kmalloc_array(w, vc->vc_font.height, GFP_ATOMIC); + int cur_height, size, i = 0; + u8 msk = 0xff; +@@ -332,13 +332,13 @@ static void ud_cursor(struct vc_data *vc + if (!mask) + return; + +- kfree(ops->cursor_state.mask); +- ops->cursor_state.mask = mask; ++ kfree(par->cursor_state.mask); ++ par->cursor_state.mask = mask; + +- ops->p->cursor_shape = vc->vc_cursor_type; ++ par->p->cursor_shape = vc->vc_cursor_type; + cursor.set |= FB_CUR_SETSHAPE; + +- switch (CUR_SIZE(ops->p->cursor_shape)) { ++ switch (CUR_SIZE(par->p->cursor_shape)) { + case CUR_NONE: + cur_height = 0; + break; +@@ -373,26 +373,26 @@ static void ud_cursor(struct vc_data *vc + + switch (mode) { + case CM_ERASE: +- ops->cursor_state.enable = 0; ++ par->cursor_state.enable = 0; + break; + case CM_DRAW: + case CM_MOVE: + default: +- ops->cursor_state.enable = (use_sw) ? 0 : 1; ++ par->cursor_state.enable = (use_sw) ? 0 : 1; + break; + } + + cursor.image.data = src; +- cursor.image.fg_color = ops->cursor_state.image.fg_color; +- cursor.image.bg_color = ops->cursor_state.image.bg_color; +- cursor.image.dx = ops->cursor_state.image.dx; +- cursor.image.dy = ops->cursor_state.image.dy; +- cursor.image.height = ops->cursor_state.image.height; +- cursor.image.width = ops->cursor_state.image.width; +- cursor.hot.x = ops->cursor_state.hot.x; +- cursor.hot.y = ops->cursor_state.hot.y; +- cursor.mask = ops->cursor_state.mask; +- cursor.enable = ops->cursor_state.enable; ++ cursor.image.fg_color = par->cursor_state.image.fg_color; ++ cursor.image.bg_color = par->cursor_state.image.bg_color; ++ cursor.image.dx = par->cursor_state.image.dx; ++ cursor.image.dy = par->cursor_state.image.dy; ++ cursor.image.height = par->cursor_state.image.height; ++ cursor.image.width = par->cursor_state.image.width; ++ cursor.hot.x = par->cursor_state.hot.x; ++ cursor.hot.y = par->cursor_state.hot.y; ++ cursor.mask = par->cursor_state.mask; ++ cursor.enable = par->cursor_state.enable; + cursor.image.depth = 1; + cursor.rop = ROP_XOR; + +@@ -402,36 +402,36 @@ static void ud_cursor(struct vc_data *vc + if (err) + soft_cursor(info, &cursor); + +- ops->cursor_reset = 0; ++ par->cursor_reset = 0; + } + + static int ud_update_start(struct fb_info *info) + { +- struct fbcon_ops *ops = info->fbcon_par; ++ struct fbcon_par *par = info->fbcon_par; + int xoffset, yoffset; +- u32 vyres = GETVYRES(ops->p, info); +- u32 vxres = GETVXRES(ops->p, info); ++ u32 vyres = GETVYRES(par->p, info); ++ u32 vxres = GETVXRES(par->p, info); + int err; + +- xoffset = vxres - info->var.xres - ops->var.xoffset; +- yoffset = vyres - info->var.yres - ops->var.yoffset; ++ xoffset = vxres - info->var.xres - par->var.xoffset; ++ yoffset = vyres - info->var.yres - par->var.yoffset; + if (yoffset < 0) + yoffset += vyres; +- ops->var.xoffset = xoffset; +- ops->var.yoffset = yoffset; +- err = fb_pan_display(info, &ops->var); +- ops->var.xoffset = info->var.xoffset; +- ops->var.yoffset = info->var.yoffset; +- ops->var.vmode = info->var.vmode; ++ par->var.xoffset = xoffset; ++ par->var.yoffset = yoffset; ++ err = fb_pan_display(info, &par->var); ++ par->var.xoffset = info->var.xoffset; ++ par->var.yoffset = info->var.yoffset; ++ par->var.vmode = info->var.vmode; + return err; + } + +-void fbcon_rotate_ud(struct fbcon_ops *ops) ++void fbcon_rotate_ud(struct fbcon_par *par) + { +- ops->bmove = ud_bmove; +- ops->clear = ud_clear; +- ops->putcs = ud_putcs; +- ops->clear_margins = ud_clear_margins; +- ops->cursor = ud_cursor; +- ops->update_start = ud_update_start; ++ par->bmove = ud_bmove; ++ par->clear = ud_clear; ++ par->putcs = ud_putcs; ++ par->clear_margins = ud_clear_margins; ++ par->cursor = ud_cursor; ++ par->update_start = ud_update_start; + } +--- a/drivers/video/fbdev/core/softcursor.c ++++ b/drivers/video/fbdev/core/softcursor.c +@@ -21,7 +21,7 @@ + + int soft_cursor(struct fb_info *info, struct fb_cursor *cursor) + { +- struct fbcon_ops *ops = info->fbcon_par; ++ struct fbcon_par *par = info->fbcon_par; + unsigned int scan_align = info->pixmap.scan_align - 1; + unsigned int buf_align = info->pixmap.buf_align - 1; + unsigned int i, size, dsize, s_pitch, d_pitch; +@@ -34,19 +34,19 @@ int soft_cursor(struct fb_info *info, st + s_pitch = (cursor->image.width + 7) >> 3; + dsize = s_pitch * cursor->image.height; + +- if (dsize + sizeof(struct fb_image) != ops->cursor_size) { +- kfree(ops->cursor_src); +- ops->cursor_size = dsize + sizeof(struct fb_image); +- +- ops->cursor_src = kmalloc(ops->cursor_size, GFP_ATOMIC); +- if (!ops->cursor_src) { +- ops->cursor_size = 0; ++ if (dsize + sizeof(struct fb_image) != par->cursor_size) { ++ kfree(par->cursor_src); ++ par->cursor_size = dsize + sizeof(struct fb_image); ++ ++ par->cursor_src = kmalloc(par->cursor_size, GFP_ATOMIC); ++ if (!par->cursor_src) { ++ par->cursor_size = 0; + return -ENOMEM; + } + } + +- src = ops->cursor_src + sizeof(struct fb_image); +- image = (struct fb_image *)ops->cursor_src; ++ src = par->cursor_src + sizeof(struct fb_image); ++ image = (struct fb_image *)par->cursor_src; + *image = cursor->image; + d_pitch = (s_pitch + scan_align) & ~scan_align; + +--- a/drivers/video/fbdev/core/tileblit.c ++++ b/drivers/video/fbdev/core/tileblit.c +@@ -151,34 +151,34 @@ static void tile_cursor(struct vc_data * + + static int tile_update_start(struct fb_info *info) + { +- struct fbcon_ops *ops = info->fbcon_par; ++ struct fbcon_par *par = info->fbcon_par; + int err; + +- err = fb_pan_display(info, &ops->var); +- ops->var.xoffset = info->var.xoffset; +- ops->var.yoffset = info->var.yoffset; +- ops->var.vmode = info->var.vmode; ++ err = fb_pan_display(info, &par->var); ++ par->var.xoffset = info->var.xoffset; ++ par->var.yoffset = info->var.yoffset; ++ par->var.vmode = info->var.vmode; + return err; + } + + void fbcon_set_tileops(struct vc_data *vc, struct fb_info *info) + { + struct fb_tilemap map; +- struct fbcon_ops *ops = info->fbcon_par; ++ struct fbcon_par *par = info->fbcon_par; + +- ops->bmove = tile_bmove; +- ops->clear = tile_clear; +- ops->putcs = tile_putcs; +- ops->clear_margins = tile_clear_margins; +- ops->cursor = tile_cursor; +- ops->update_start = tile_update_start; ++ par->bmove = tile_bmove; ++ par->clear = tile_clear; ++ par->putcs = tile_putcs; ++ par->clear_margins = tile_clear_margins; ++ par->cursor = tile_cursor; ++ par->update_start = tile_update_start; + +- if (ops->p) { ++ if (par->p) { + map.width = vc->vc_font.width; + map.height = vc->vc_font.height; + map.depth = 1; + map.length = vc->vc_font.charcount; +- map.data = ops->p->fontdata; ++ map.data = par->p->fontdata; + info->tileops->fb_settile(info, &map); + } + } diff --git a/queue-6.6/fbcon-use-correct-type-for-vc_resize-return-value.patch b/queue-6.6/fbcon-use-correct-type-for-vc_resize-return-value.patch new file mode 100644 index 0000000000..e80c39f39e --- /dev/null +++ b/queue-6.6/fbcon-use-correct-type-for-vc_resize-return-value.patch @@ -0,0 +1,45 @@ +From stable+bounces-287934-greg=kroah.com@vger.kernel.org Wed Jul 22 18:28:28 2026 +From: Sasha Levin +Date: Wed, 22 Jul 2026 12:07:58 -0400 +Subject: fbcon: Use correct type for vc_resize() return value +To: stable@vger.kernel.org +Cc: Jiacheng Yu , Thomas Zimmermann , Helge Deller , Sasha Levin +Message-ID: <20260722160758.1740441-2-sashal@kernel.org> + +From: Jiacheng Yu + +[ Upstream commit 84202754fb1727dc3ee87f47104e4162ecc8ba3a ] + +The return value of vc_resize() is int, but fbcon_set_disp() stores it +in an unsigned long variable. While the !ret check happens to work +correctly by coincidence (negative values become large positive values), +the types should match. Use int instead. + +Eliminates the following W=3 warning: + + drivers/video/fbdev/core/fbcon.c: In function 'fbcon_set_disp': + drivers/video/fbdev/core/fbcon.c:1494:14: warning: implicit conversion from 'int' to 'unsigned long' [-Wconversion] + +Fixes: af0db3c1f898 ("fbdev: Fix vmalloc out-of-bounds write in fast_imageblit") +Cc: stable@vger.kernel.org # v6.17+ +Signed-off-by: Jiacheng Yu +Reviewed-by: Thomas Zimmermann +Signed-off-by: Helge Deller +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/video/fbdev/core/fbcon.c | 3 +-- + 1 file changed, 1 insertion(+), 2 deletions(-) + +--- a/drivers/video/fbdev/core/fbcon.c ++++ b/drivers/video/fbdev/core/fbcon.c +@@ -1356,8 +1356,7 @@ static void fbcon_set_disp(struct fb_inf + struct vc_data **default_mode, *vc; + struct vc_data *svc; + struct fbcon_par *par = info->fbcon_par; +- int rows, cols; +- unsigned long ret = 0; ++ int rows, cols, ret; + + p = &fb_display[unit]; + diff --git a/queue-6.6/fbdev-efifb-fix-memory-leak-in-efifb_probe.patch b/queue-6.6/fbdev-efifb-fix-memory-leak-in-efifb_probe.patch new file mode 100644 index 0000000000..0085fed8fb --- /dev/null +++ b/queue-6.6/fbdev-efifb-fix-memory-leak-in-efifb_probe.patch @@ -0,0 +1,38 @@ +From stable+bounces-288126-greg=kroah.com@vger.kernel.org Thu Jul 23 01:53:30 2026 +From: Sasha Levin +Date: Wed, 22 Jul 2026 19:47:09 -0400 +Subject: fbdev: efifb: fix memory leak in efifb_probe() +To: stable@vger.kernel.org +Cc: Abdun Nihaal , Thomas Zimmermann , Helge Deller , Sasha Levin +Message-ID: <20260722234709.2613278-2-sashal@kernel.org> + +From: Abdun Nihaal + +[ Upstream commit 9b6eaf101656958397a6012bf43f6e2e42c9e5cb ] + +Since commit 73ce73c30ba9 ("fbdev: Transfer video= option strings to +caller; clarify ownership") the string returned from fb_get_options() +is expected to be freed by the caller, but the string is not freed in +efifb_probe(). Fix that by freeing the option string after setup. + +Fixes: 73ce73c30ba9 ("fbdev: Transfer video= option strings to caller; clarify ownership") +Cc: stable@vger.kernel.org +Signed-off-by: Abdun Nihaal +Reviewed-by: Thomas Zimmermann +Signed-off-by: Helge Deller +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/video/fbdev/efifb.c | 1 + + 1 file changed, 1 insertion(+) + +--- a/drivers/video/fbdev/efifb.c ++++ b/drivers/video/fbdev/efifb.c +@@ -375,6 +375,7 @@ static int efifb_probe(struct platform_d + if (fb_get_options("efifb", &option)) + return -ENODEV; + efifb_setup(si, option); ++ kfree(option); + + /* We don't get linelength from UGA Draw Protocol, only from + * EFI Graphics Protocol. So if it's not in DMI, and it's not diff --git a/queue-6.6/fbdev-efifb-replace-references-to-global-screen_info-by-local-pointer.patch b/queue-6.6/fbdev-efifb-replace-references-to-global-screen_info-by-local-pointer.patch new file mode 100644 index 0000000000..a547f7e5e7 --- /dev/null +++ b/queue-6.6/fbdev-efifb-replace-references-to-global-screen_info-by-local-pointer.patch @@ -0,0 +1,303 @@ +From stable+bounces-288125-greg=kroah.com@vger.kernel.org Thu Jul 23 01:53:27 2026 +From: Sasha Levin +Date: Wed, 22 Jul 2026 19:47:08 -0400 +Subject: fbdev/efifb: Replace references to global screen_info by local pointer +To: stable@vger.kernel.org +Cc: Thomas Zimmermann , Sui Jingfeng , Javier Martinez Canillas , Sasha Levin +Message-ID: <20260722234709.2613278-1-sashal@kernel.org> + +From: Thomas Zimmermann + +[ Upstream commit 8505e70821ade706f98f7114a2cbcd51326f7b82 ] + +Get the global screen_info's address once and access the data via +this pointer. Limits the use of global state. + +v3: + * use const screen_info in several places (Sui) + * fix build for deferred takeover (kernel test robot) + +Signed-off-by: Thomas Zimmermann +Tested-by: Sui Jingfeng +Reviewed-by: Javier Martinez Canillas +Link: https://patchwork.freedesktop.org/patch/msgid/20231206135153.2599-2-tzimmermann@suse.de +Stable-dep-of: 9b6eaf101656 ("fbdev: efifb: fix memory leak in efifb_probe()") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/video/fbdev/efifb.c | 124 ++++++++++++++++++++++---------------------- + 1 file changed, 64 insertions(+), 60 deletions(-) + +--- a/drivers/video/fbdev/efifb.c ++++ b/drivers/video/fbdev/efifb.c +@@ -108,7 +108,7 @@ static int efifb_setcolreg(unsigned regn + */ + #if defined CONFIG_FRAMEBUFFER_CONSOLE_DEFERRED_TAKEOVER && \ + defined CONFIG_ACPI_BGRT +-static void efifb_copy_bmp(u8 *src, u32 *dst, int width, struct screen_info *si) ++static void efifb_copy_bmp(u8 *src, u32 *dst, int width, const struct screen_info *si) + { + u8 r, g, b; + +@@ -130,7 +130,7 @@ static void efifb_copy_bmp(u8 *src, u32 + * resolution still fits, it will be displayed very close to the right edge of + * the display looking quite bad. This function checks for this. + */ +-static bool efifb_bgrt_sanity_check(struct screen_info *si, u32 bmp_width) ++static bool efifb_bgrt_sanity_check(const struct screen_info *si, u32 bmp_width) + { + /* + * All x86 firmwares horizontally center the image (the yoffset +@@ -141,16 +141,15 @@ static bool efifb_bgrt_sanity_check(stru + return bgrt_tab.image_offset_x == expected_xoffset; + } + #else +-static bool efifb_bgrt_sanity_check(struct screen_info *si, u32 bmp_width) ++static bool efifb_bgrt_sanity_check(const struct screen_info *si, u32 bmp_width) + { + return true; + } + #endif + +-static void efifb_show_boot_graphics(struct fb_info *info) ++static void efifb_show_boot_graphics(struct fb_info *info, const struct screen_info *si) + { + u32 bmp_width, bmp_height, bmp_pitch, dst_x, y, src_y; +- struct screen_info *si = &screen_info; + struct bmp_file_header *file_header; + struct bmp_dib_header *dib_header; + void *bgrt_image = NULL; +@@ -247,7 +246,8 @@ error: + pr_warn("efifb: Ignoring BGRT: unexpected or invalid BMP data\n"); + } + #else +-static inline void efifb_show_boot_graphics(struct fb_info *info) {} ++static inline void efifb_show_boot_graphics(struct fb_info *info, const struct screen_info *si) ++{ } + #endif + + /* +@@ -282,7 +282,7 @@ static const struct fb_ops efifb_ops = { + .fb_setcolreg = efifb_setcolreg, + }; + +-static int efifb_setup(char *options) ++static int efifb_setup(struct screen_info *si, char *options) + { + char *this_opt; + +@@ -290,16 +290,16 @@ static int efifb_setup(char *options) + while ((this_opt = strsep(&options, ",")) != NULL) { + if (!*this_opt) continue; + +- efifb_setup_from_dmi(&screen_info, this_opt); ++ efifb_setup_from_dmi(si, this_opt); + + if (!strncmp(this_opt, "base:", 5)) +- screen_info.lfb_base = simple_strtoul(this_opt+5, NULL, 0); ++ si->lfb_base = simple_strtoul(this_opt+5, NULL, 0); + else if (!strncmp(this_opt, "stride:", 7)) +- screen_info.lfb_linelength = simple_strtoul(this_opt+7, NULL, 0) * 4; ++ si->lfb_linelength = simple_strtoul(this_opt+7, NULL, 0) * 4; + else if (!strncmp(this_opt, "height:", 7)) +- screen_info.lfb_height = simple_strtoul(this_opt+7, NULL, 0); ++ si->lfb_height = simple_strtoul(this_opt+7, NULL, 0); + else if (!strncmp(this_opt, "width:", 6)) +- screen_info.lfb_width = simple_strtoul(this_opt+6, NULL, 0); ++ si->lfb_width = simple_strtoul(this_opt+6, NULL, 0); + else if (!strcmp(this_opt, "nowc")) + mem_flags &= ~EFI_MEMORY_WC; + else if (!strcmp(this_opt, "nobgrt")) +@@ -310,15 +310,15 @@ static int efifb_setup(char *options) + return 0; + } + +-static inline bool fb_base_is_valid(void) ++static inline bool fb_base_is_valid(struct screen_info *si) + { +- if (screen_info.lfb_base) ++ if (si->lfb_base) + return true; + +- if (!(screen_info.capabilities & VIDEO_CAPABILITY_64BIT_BASE)) ++ if (!(si->capabilities & VIDEO_CAPABILITY_64BIT_BASE)) + return false; + +- if (screen_info.ext_lfb_base) ++ if (si->ext_lfb_base) + return true; + + return false; +@@ -329,7 +329,10 @@ static ssize_t name##_show(struct device + struct device_attribute *attr, \ + char *buf) \ + { \ +- return sprintf(buf, fmt "\n", (screen_info.lfb_##name)); \ ++ struct screen_info *si = dev_get_platdata(dev); \ ++ if (!si) \ ++ return -ENODEV; \ ++ return sprintf(buf, fmt "\n", (si->lfb_##name)); \ + } \ + static DEVICE_ATTR_RO(name) + +@@ -356,6 +359,7 @@ static u64 bar_offset; + + static int efifb_probe(struct platform_device *dev) + { ++ struct screen_info *si = &screen_info; + struct fb_info *info; + struct efifb_par *par; + int err, orientation; +@@ -365,48 +369,48 @@ static int efifb_probe(struct platform_d + char *option = NULL; + efi_memory_desc_t md; + +- if (screen_info.orig_video_isVGA != VIDEO_TYPE_EFI || pci_dev_disabled) ++ if (si->orig_video_isVGA != VIDEO_TYPE_EFI || pci_dev_disabled) + return -ENODEV; + + if (fb_get_options("efifb", &option)) + return -ENODEV; +- efifb_setup(option); ++ efifb_setup(si, option); + + /* We don't get linelength from UGA Draw Protocol, only from + * EFI Graphics Protocol. So if it's not in DMI, and it's not + * passed in from the user, we really can't use the framebuffer. + */ +- if (!screen_info.lfb_linelength) ++ if (!si->lfb_linelength) + return -ENODEV; + +- if (!screen_info.lfb_depth) +- screen_info.lfb_depth = 32; +- if (!screen_info.pages) +- screen_info.pages = 1; +- if (!fb_base_is_valid()) { ++ if (!si->lfb_depth) ++ si->lfb_depth = 32; ++ if (!si->pages) ++ si->pages = 1; ++ if (!fb_base_is_valid(si)) { + printk(KERN_DEBUG "efifb: invalid framebuffer address\n"); + return -ENODEV; + } + printk(KERN_INFO "efifb: probing for efifb\n"); + + /* just assume they're all unset if any are */ +- if (!screen_info.blue_size) { +- screen_info.blue_size = 8; +- screen_info.blue_pos = 0; +- screen_info.green_size = 8; +- screen_info.green_pos = 8; +- screen_info.red_size = 8; +- screen_info.red_pos = 16; +- screen_info.rsvd_size = 8; +- screen_info.rsvd_pos = 24; ++ if (!si->blue_size) { ++ si->blue_size = 8; ++ si->blue_pos = 0; ++ si->green_size = 8; ++ si->green_pos = 8; ++ si->red_size = 8; ++ si->red_pos = 16; ++ si->rsvd_size = 8; ++ si->rsvd_pos = 24; + } + +- efifb_fix.smem_start = screen_info.lfb_base; ++ efifb_fix.smem_start = si->lfb_base; + +- if (screen_info.capabilities & VIDEO_CAPABILITY_64BIT_BASE) { ++ if (si->capabilities & VIDEO_CAPABILITY_64BIT_BASE) { + u64 ext_lfb_base; + +- ext_lfb_base = (u64)(unsigned long)screen_info.ext_lfb_base << 32; ++ ext_lfb_base = (u64)(unsigned long)si->ext_lfb_base << 32; + efifb_fix.smem_start |= ext_lfb_base; + } + +@@ -417,10 +421,10 @@ static int efifb_probe(struct platform_d + efifb_fix.smem_start = bar_resource->start + bar_offset; + } + +- efifb_defined.bits_per_pixel = screen_info.lfb_depth; +- efifb_defined.xres = screen_info.lfb_width; +- efifb_defined.yres = screen_info.lfb_height; +- efifb_fix.line_length = screen_info.lfb_linelength; ++ efifb_defined.bits_per_pixel = si->lfb_depth; ++ efifb_defined.xres = si->lfb_width; ++ efifb_defined.yres = si->lfb_height; ++ efifb_fix.line_length = si->lfb_linelength; + + /* size_vmode -- that is the amount of memory needed for the + * used video mode, i.e. the minimum amount of +@@ -430,7 +434,7 @@ static int efifb_probe(struct platform_d + /* size_total -- all video memory we have. Used for + * entries, ressource allocation and bounds + * checking. */ +- size_total = screen_info.lfb_size; ++ size_total = si->lfb_size; + if (size_total < size_vmode) + size_total = size_vmode; + +@@ -505,14 +509,14 @@ static int efifb_probe(struct platform_d + goto err_release_fb; + } + +- efifb_show_boot_graphics(info); ++ efifb_show_boot_graphics(info, si); + + pr_info("efifb: framebuffer at 0x%lx, using %dk, total %dk\n", + efifb_fix.smem_start, size_remap/1024, size_total/1024); + pr_info("efifb: mode is %dx%dx%d, linelength=%d, pages=%d\n", + efifb_defined.xres, efifb_defined.yres, + efifb_defined.bits_per_pixel, efifb_fix.line_length, +- screen_info.pages); ++ si->pages); + + efifb_defined.xres_virtual = efifb_defined.xres; + efifb_defined.yres_virtual = efifb_fix.smem_len / +@@ -526,26 +530,26 @@ static int efifb_probe(struct platform_d + efifb_defined.left_margin = (efifb_defined.xres / 8) & 0xf8; + efifb_defined.hsync_len = (efifb_defined.xres / 8) & 0xf8; + +- efifb_defined.red.offset = screen_info.red_pos; +- efifb_defined.red.length = screen_info.red_size; +- efifb_defined.green.offset = screen_info.green_pos; +- efifb_defined.green.length = screen_info.green_size; +- efifb_defined.blue.offset = screen_info.blue_pos; +- efifb_defined.blue.length = screen_info.blue_size; +- efifb_defined.transp.offset = screen_info.rsvd_pos; +- efifb_defined.transp.length = screen_info.rsvd_size; ++ efifb_defined.red.offset = si->red_pos; ++ efifb_defined.red.length = si->red_size; ++ efifb_defined.green.offset = si->green_pos; ++ efifb_defined.green.length = si->green_size; ++ efifb_defined.blue.offset = si->blue_pos; ++ efifb_defined.blue.length = si->blue_size; ++ efifb_defined.transp.offset = si->rsvd_pos; ++ efifb_defined.transp.length = si->rsvd_size; + + pr_info("efifb: %s: " + "size=%d:%d:%d:%d, shift=%d:%d:%d:%d\n", + "Truecolor", +- screen_info.rsvd_size, +- screen_info.red_size, +- screen_info.green_size, +- screen_info.blue_size, +- screen_info.rsvd_pos, +- screen_info.red_pos, +- screen_info.green_pos, +- screen_info.blue_pos); ++ si->rsvd_size, ++ si->red_size, ++ si->green_size, ++ si->blue_size, ++ si->rsvd_pos, ++ si->red_pos, ++ si->green_pos, ++ si->blue_pos); + + efifb_fix.ypanstep = 0; + efifb_fix.ywrapstep = 0; diff --git a/queue-6.6/firmware_loader-introduce-__free-cleanup-hanler.patch b/queue-6.6/firmware_loader-introduce-__free-cleanup-hanler.patch new file mode 100644 index 0000000000..9c1293d36c --- /dev/null +++ b/queue-6.6/firmware_loader-introduce-__free-cleanup-hanler.patch @@ -0,0 +1,70 @@ +From stable+bounces-289740-greg=kroah.com@vger.kernel.org Tue Jul 28 01:34:46 2026 +From: Sasha Levin +Date: Mon, 27 Jul 2026 19:34:36 -0400 +Subject: firmware_loader: introduce __free() cleanup hanler +To: stable@vger.kernel.org +Cc: Dmitry Torokhov , Luis Chamberalin , Greg Kroah-Hartman , Sasha Levin +Message-ID: <20260727233437.2425385-1-sashal@kernel.org> + +From: Dmitry Torokhov + +[ Upstream commit 8dde8fa0cc3edce73c050b9882d06c1a575f6402 ] + +Define cleanup handler using facilities from linux/cleanup.h to simplify +error handling in code using firmware loader. This will allow writing code +like this: + +int driver_update_firmware(...) +{ + const struct firmware *fw_entry __free(firmware) = NULL; + int error; + + ... + error = request_firmware(&fw_entry, fw_name, dev); + if (error) { + dev_err(dev, "failed to request firmware %s: %d", + fw_name, error); + return error; + } + + error = check_firmware_valid(fw_entry); + if (error) + return error; + + guard(mutex)(&instance->lock); + + error = use_firmware(instance, fw); + if (error) + return error; + + return 0; +} + +Signed-off-by: Dmitry Torokhov +Acked-by: Luis Chamberalin +Link: https://lore.kernel.org/r/ZaeQw7VXhnirX4pQ@google.com +Signed-off-by: Greg Kroah-Hartman +Stable-dep-of: d48795b5cd68 ("Input: ims-pcu - fix firmware leak in async update") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + include/linux/firmware.h | 3 +++ + 1 file changed, 3 insertions(+) + +--- a/include/linux/firmware.h ++++ b/include/linux/firmware.h +@@ -4,6 +4,7 @@ + + #include + #include ++#include + #include + + #define FW_ACTION_NOUEVENT 0 +@@ -196,4 +197,6 @@ static inline void firmware_upload_unreg + + int firmware_request_cache(struct device *device, const char *name); + ++DEFINE_FREE(firmware, struct firmware *, release_firmware(_T)) ++ + #endif diff --git a/queue-6.6/fpga-dfl-afu-validate-dma-mapping-length-in-afu_dma_map_region.patch b/queue-6.6/fpga-dfl-afu-validate-dma-mapping-length-in-afu_dma_map_region.patch new file mode 100644 index 0000000000..0d7361d01f --- /dev/null +++ b/queue-6.6/fpga-dfl-afu-validate-dma-mapping-length-in-afu_dma_map_region.patch @@ -0,0 +1,46 @@ +From stable+bounces-282091-greg=kroah.com@vger.kernel.org Wed Jul 22 00:10:20 2026 +From: Sasha Levin +Date: Tue, 21 Jul 2026 14:59:09 -0400 +Subject: fpga: dfl-afu: validate DMA mapping length in afu_dma_map_region() +To: stable@vger.kernel.org +Cc: Sebastian Alba Vives , Xu Yilun , Xu Yilun , Sasha Levin +Message-ID: <20260721185909.397655-1-sashal@kernel.org> + +From: Sebastian Alba Vives + +[ Upstream commit fc3b071a7c8dc0f5d56defddf6e6fd5aaa3e1e27 ] + +afu_ioctl_dma_map() accepts a 64-bit length from userspace via +DFL_FPGA_PORT_DMA_MAP ioctl without an upper bound check. The value +is passed to afu_dma_pin_pages() where npages is derived as +length >> PAGE_SHIFT and passed to pin_user_pages_fast() which takes +int nr_pages, causing implicit truncation if length is very large. + +Validate map.length at the ioctl entry point before calling +afu_dma_map_region(), rejecting values whose page count exceeds +INT_MAX. + +Fixes: fa8dda1edef9 ("fpga: dfl: afu: add DFL_FPGA_PORT_DMA_MAP/UNMAP ioctls support") +Cc: stable@vger.kernel.org +Signed-off-by: Sebastian Alba Vives +Reviewed-by: Xu Yilun +Link: https://lore.kernel.org/r/20260518190742.61426-3-sebasjosue84@gmail.com +Signed-off-by: Xu Yilun +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/fpga/dfl-afu-main.c | 3 +++ + 1 file changed, 3 insertions(+) + +--- a/drivers/fpga/dfl-afu-main.c ++++ b/drivers/fpga/dfl-afu-main.c +@@ -720,6 +720,9 @@ afu_ioctl_dma_map(struct dfl_feature_pla + if (map.argsz < minsz || map.flags) + return -EINVAL; + ++ if (map.length >> PAGE_SHIFT > (u64)INT_MAX) ++ return -EINVAL; ++ + ret = afu_dma_map_region(pdata, map.user_addr, map.length, &map.iova); + if (ret) + return ret; diff --git a/queue-6.6/fs-resctrl-fix-double-add-of-pseudo-locked-region-s-rmid-to-free-list.patch b/queue-6.6/fs-resctrl-fix-double-add-of-pseudo-locked-region-s-rmid-to-free-list.patch new file mode 100644 index 0000000000..a9c4b91799 --- /dev/null +++ b/queue-6.6/fs-resctrl-fix-double-add-of-pseudo-locked-region-s-rmid-to-free-list.patch @@ -0,0 +1,60 @@ +From stable+bounces-290844-greg=kroah.com@vger.kernel.org Wed Jul 29 19:14:11 2026 +From: Sasha Levin +Date: Wed, 29 Jul 2026 12:57:53 -0400 +Subject: fs/resctrl: Fix double-add of pseudo-locked region's RMID to free list +To: stable@vger.kernel.org +Cc: Reinette Chatre , "Borislav Petkov (AMD)" , stable@kernel.org, Sasha Levin +Message-ID: <20260729165753.3661352-1-sashal@kernel.org> + +From: Reinette Chatre + +[ Upstream commit b9f089723aee892efc77c349ae47a6b452b293c4 ] + +A pseudo-locked group's RMID is freed when it is created. On unmount +rmdir_all_sub() unconditionally frees all RMID of all groups, resulting +in a double-free of the pseudo-locked group's RMID. The consequence of this +is that the original free results in the pseudo-locked group's RMID being +added to the rmid_free_lru linked list and the second free then attempts +to add the same RMID entry to the rmid_free_lru again. + +Do not double-free a pseudo-locked group's RMID. + +Fixes: e0bdfe8e36f3 ("x86/intel_rdt: Support creation/removal of pseudo-locked region") +Signed-off-by: Reinette Chatre +Signed-off-by: Borislav Petkov (AMD) +Cc: +Link: https://patch.msgid.link/551432dd7e624a862b8e58314c38aaba0afff3e9.1783377598.git.reinette.chatre@intel.com +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + arch/x86/kernel/cpu/resctrl/rdtgroup.c | 12 +++++++----- + 1 file changed, 7 insertions(+), 5 deletions(-) + +--- a/arch/x86/kernel/cpu/resctrl/rdtgroup.c ++++ b/arch/x86/kernel/cpu/resctrl/rdtgroup.c +@@ -2744,10 +2744,6 @@ static void rmdir_all_sub(void) + if (rdtgrp == &rdtgroup_default) + continue; + +- if (rdtgrp->mode == RDT_MODE_PSEUDO_LOCKSETUP || +- rdtgrp->mode == RDT_MODE_PSEUDO_LOCKED) +- rdtgroup_pseudo_lock_remove(rdtgrp); +- + /* + * Give any CPUs back to the default group. We cannot copy + * cpu_online_mask because a CPU might have executed the +@@ -2756,7 +2752,13 @@ static void rmdir_all_sub(void) + cpumask_or(&rdtgroup_default.cpu_mask, + &rdtgroup_default.cpu_mask, &rdtgrp->cpu_mask); + +- free_rmid(rdtgrp->mon.rmid); ++ if (rdtgrp->mode == RDT_MODE_PSEUDO_LOCKSETUP || ++ rdtgrp->mode == RDT_MODE_PSEUDO_LOCKED) { ++ rdtgroup_pseudo_lock_remove(rdtgrp); ++ } else { ++ /* Pseudo-locked group's RMID is freed during setup. */ ++ free_rmid(rdtgrp->mon.rmid); ++ } + + kernfs_remove(rdtgrp->kn); + list_del(&rdtgrp->rdtgroup_list); diff --git a/queue-6.6/gpio-mt7621-avoid-corruption-of-shared-interrupt-trigger-state.patch b/queue-6.6/gpio-mt7621-avoid-corruption-of-shared-interrupt-trigger-state.patch new file mode 100644 index 0000000000..a2ecd2ec07 --- /dev/null +++ b/queue-6.6/gpio-mt7621-avoid-corruption-of-shared-interrupt-trigger-state.patch @@ -0,0 +1,44 @@ +From stable+bounces-290054-greg=kroah.com@vger.kernel.org Tue Jul 28 18:32:41 2026 +From: Sasha Levin +Date: Tue, 28 Jul 2026 11:52:28 -0400 +Subject: gpio: mt7621: avoid corruption of shared interrupt trigger state +To: stable@vger.kernel.org +Cc: Sergio Paracuellos , Sashiko , Bartosz Golaszewski , Sasha Levin +Message-ID: <20260728155228.8665-1-sashal@kernel.org> + +From: Sergio Paracuellos + +[ Upstream commit 1781172526d1092323af443fa03f00e6de560401 ] + +The bank-shared fields like 'rising' and 'falling' are modified using +non-atomic read-modify-write operations. Since every gpio chip instance +represents an entire bank of 32 pins, if 'mediatek_gpio_irq_type()' is +called concurrently for different IRQs on the same bank a possible overwrite +of each other's configuration is possible. Thus, protect this state with +'gpio_generic_lock_irqsave' lock in the same way it is handled in irp_chip +'mediatek_gpio_irq_mask()' and 'mediatek_gpio_irq_unmask()' callbacks. + +Cc: stable@vger.kernel.org +Reported-by: Sashiko +Fixes: 4ba9c3afda41 ("gpio: mt7621: Add a driver for MT7621") +Signed-off-by: Sergio Paracuellos +Link: https://patch.msgid.link/20260626060112.2498324-2-sergio.paracuellos@gmail.com +Signed-off-by: Bartosz Golaszewski +[ Changed `guard(gpio_generic_lock_irqsave)(&rg->chip)` to `guard(spinlock_irqsave)(&rg->lock)` as the generic GPIO chip lock does not exist in this tree. ] +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/gpio/gpio-mt7621.c | 2 ++ + 1 file changed, 2 insertions(+) + +--- a/drivers/gpio/gpio-mt7621.c ++++ b/drivers/gpio/gpio-mt7621.c +@@ -156,6 +156,8 @@ mediatek_gpio_irq_type(struct irq_data * + int pin = d->hwirq; + u32 mask = BIT(pin); + ++ guard(spinlock_irqsave)(&rg->lock); ++ + if (type == IRQ_TYPE_PROBE) { + if ((rg->rising | rg->falling | + rg->hlevel | rg->llevel) & mask) diff --git a/queue-6.6/gpio-tegra-do-not-call-pinctrl-for-gpio-direction.patch b/queue-6.6/gpio-tegra-do-not-call-pinctrl-for-gpio-direction.patch new file mode 100644 index 0000000000..a7b12431dc --- /dev/null +++ b/queue-6.6/gpio-tegra-do-not-call-pinctrl-for-gpio-direction.patch @@ -0,0 +1,100 @@ +From stable+bounces-289949-greg=kroah.com@vger.kernel.org Tue Jul 28 14:19:41 2026 +From: Sasha Levin +Date: Tue, 28 Jul 2026 08:11:25 -0400 +Subject: gpio: tegra: do not call pinctrl for GPIO direction +To: stable@vger.kernel.org +Cc: Runyu Xiao , Bartosz Golaszewski , Sasha Levin +Message-ID: <20260728121125.3703669-2-sashal@kernel.org> + +From: Runyu Xiao + +[ Upstream commit d3e91a95b2b0fc6336dbf3ec90d831a1654d2720 ] + +tegra_gpio_direction_input() and tegra_gpio_direction_output() already +program the GPIO controller direction registers directly. The additional +pinctrl_gpio_direction_input/output() calls do not add a Tegra pinctrl +operation, because the Tegra pinmux ops provide GPIO request/free +handling but no gpio_set_direction hook. + +The extra call still enters the pinctrl core and takes pctldev->mutex. +Shared GPIO users can call the direction path while holding their +per-line spinlock, so this otherwise redundant pinctrl direction call can +sleep in an atomic context. + +This was found by our static analysis tool and then confirmed by manual +review of tegra_gpio_probe(), the Tegra GPIO direction callbacks and the +Tegra pinctrl ops. The reviewed path has a default non-sleeping +struct gpio_chip while the direction callback still enters the pinctrl +mutex path. + +A directed runtime validation kept the same non-sleeping chip registration +and drove: + + gpio_shared_proxy_direction_output() + gpiod_direction_output_raw_commit() + tegra_gpio_direction_output() + pinctrl_gpio_direction_output() + +Lockdep reported a sleep-in-atomic warning with the shared GPIO spinlock +held and pinctrl_get_device_gpio_range() plus tegra_gpio_direction_output() +on the stack. + +Do not mark the whole chip as can_sleep to paper over this: can_sleep +describes whether get()/set() may sleep, and Tegra value access is MMIO. +Remove the redundant pinctrl direction calls and keep pinctrl involvement +in the existing request/free path. + +Fixes: 11da90541283 ("gpio: tegra: Fix offset of pinctrl calls") +Cc: stable@vger.kernel.org +Signed-off-by: Runyu Xiao +Link: https://patch.msgid.link/20260619152439.1239561-1-runyu.xiao@seu.edu.cn +Signed-off-by: Bartosz Golaszewski +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/gpio/gpio-tegra.c | 18 ++---------------- + 1 file changed, 2 insertions(+), 16 deletions(-) + +--- a/drivers/gpio/gpio-tegra.c ++++ b/drivers/gpio/gpio-tegra.c +@@ -174,18 +174,11 @@ static int tegra_gpio_direction_input(st + unsigned int offset) + { + struct tegra_gpio_info *tgi = gpiochip_get_data(chip); +- int ret; + + tegra_gpio_mask_write(tgi, GPIO_MSK_OE(tgi, offset), offset, 0); + tegra_gpio_enable(tgi, offset); + +- ret = pinctrl_gpio_direction_input(chip, offset); +- if (ret < 0) +- dev_err(tgi->dev, +- "Failed to set pinctrl input direction of GPIO %d: %d", +- chip->base + offset, ret); +- +- return ret; ++ return 0; + } + + static int tegra_gpio_direction_output(struct gpio_chip *chip, +@@ -193,19 +186,12 @@ static int tegra_gpio_direction_output(s + int value) + { + struct tegra_gpio_info *tgi = gpiochip_get_data(chip); +- int ret; + + tegra_gpio_set(chip, offset, value); + tegra_gpio_mask_write(tgi, GPIO_MSK_OE(tgi, offset), offset, 1); + tegra_gpio_enable(tgi, offset); + +- ret = pinctrl_gpio_direction_output(chip, offset); +- if (ret < 0) +- dev_err(tgi->dev, +- "Failed to set pinctrl output direction of GPIO %d: %d", +- chip->base + offset, ret); +- +- return ret; ++ return 0; + } + + static int tegra_gpio_get_direction(struct gpio_chip *chip, diff --git a/queue-6.6/i2c-davinci-unregister-cpufreq-notifier-on-probe-failure.patch b/queue-6.6/i2c-davinci-unregister-cpufreq-notifier-on-probe-failure.patch new file mode 100644 index 0000000000..b42f1cde4e --- /dev/null +++ b/queue-6.6/i2c-davinci-unregister-cpufreq-notifier-on-probe-failure.patch @@ -0,0 +1,50 @@ +From stable+bounces-283225-greg=kroah.com@vger.kernel.org Tue Jul 21 22:02:49 2026 +From: Sasha Levin +Date: Tue, 21 Jul 2026 15:48:28 -0400 +Subject: i2c: davinci: Unregister cpufreq notifier on probe failure +To: stable@vger.kernel.org +Cc: Haoxiang Li , Bartosz Golaszewski , Andi Shyti , Sasha Levin +Message-ID: <20260721194828.416617-1-sashal@kernel.org> + +From: Haoxiang Li + +[ Upstream commit e43f32816a1b1fe5a86279411626fe3a9be56d45 ] + +davinci_i2c_probe() registers a cpufreq transition notifier before adding +the I2C adapter. If i2c_add_numbered_adapter() fails, the probe error path +releases the device resources without unregistering the notifier. + +Add a dedicated error path to unregister the cpufreq notifier after +i2c_add_numbered_adapter() fails. + +Fixes: 82c0de11b734 ("i2c: davinci: Add cpufreq support") +Signed-off-by: Haoxiang Li +Cc: # v2.6.36+ +Reviewed-by: Bartosz Golaszewski +Signed-off-by: Andi Shyti +Link: https://lore.kernel.org/r/20260610030513.2651018-1-haoxiang_li2024@163.com +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/i2c/busses/i2c-davinci.c | 4 +++- + 1 file changed, 3 insertions(+), 1 deletion(-) + +--- a/drivers/i2c/busses/i2c-davinci.c ++++ b/drivers/i2c/busses/i2c-davinci.c +@@ -866,13 +866,15 @@ static int davinci_i2c_probe(struct plat + adap->nr = pdev->id; + r = i2c_add_numbered_adapter(adap); + if (r) +- goto err_unuse_clocks; ++ goto err_cpufreq; + + pm_runtime_mark_last_busy(dev->dev); + pm_runtime_put_autosuspend(dev->dev); + + return 0; + ++err_cpufreq: ++ i2c_davinci_cpufreq_deregister(dev); + err_unuse_clocks: + pm_runtime_dont_use_autosuspend(dev->dev); + pm_runtime_put_sync(dev->dev); diff --git a/queue-6.6/i2c-i801-fix-hardware-state-machine-corruption-in-error-path.patch b/queue-6.6/i2c-i801-fix-hardware-state-machine-corruption-in-error-path.patch new file mode 100644 index 0000000000..c8b8c47389 --- /dev/null +++ b/queue-6.6/i2c-i801-fix-hardware-state-machine-corruption-in-error-path.patch @@ -0,0 +1,68 @@ +From stable+bounces-287786-greg=kroah.com@vger.kernel.org Wed Jul 22 12:36:09 2026 +From: Sasha Levin +Date: Wed, 22 Jul 2026 06:30:42 -0400 +Subject: i2c: i801: fix hardware state machine corruption in error path +To: stable@vger.kernel.org +Cc: Mingyu Wang <25181214217@stu.xidian.edu.cn>, Andi Shyti , Sasha Levin +Message-ID: <20260722103042.891373-1-sashal@kernel.org> + +From: Mingyu Wang <25181214217@stu.xidian.edu.cn> + +[ Upstream commit 10dd1a736d557e310a77117832874729a0175d57 ] + +A severe livelock and subsequent Hung Task panic were observed in the +i2c-i801 driver during concurrent Fuzzing. The crash is caused by an +unconditional hardware register cleanup in the error handling path of +i801_access(). + +When i801_check_pre() fails (e.g., returning -EBUSY because the SMBus +controller is actively used by BIOS/ACPI), the kernel does not actually +acquire the hardware ownership. However, the code jumps to the 'out' +label and executes: + + iowrite8(SMBHSTSTS_INUSE_STS | STATUS_FLAGS, SMBHSTSTS(priv)); + +This forcefully clears the INUSE_STS lock and resets the hardware status +flags without owning the controller. Doing so interrupts ongoing BIOS/ACPI +transactions and totally corrupts the SMBus hardware state machine. + +Consequently, all subsequent i801_access() calls fail at the pre-check +stage, triggering an endless stream of "SMBus is busy, can't use it!" +error logs. Over a slow serial console, this printk flood monopolizes +the CPU (Console Livelock), starving other processes trying to acquire +the mmap_lock down_read semaphore, ultimately triggering the hung task +watchdog. + +Fix this by moving the 'out' label below the hardware register cleanup. +If i801_check_pre() fails, we safely bypass the iowrite8() and only +release the software locks (pm_runtime and mutex), strictly adhering to +the rule of not releasing resources that were never acquired. + +Fixes: 1f760b87e54c ("i2c: i801: Call i801_check_pre() from i801_access()") +Signed-off-by: Mingyu Wang <25181214217@stu.xidian.edu.cn> +Cc: # v6.3+ +Signed-off-by: Andi Shyti +Link: https://lore.kernel.org/r/20260512093534.348655-1-w15303746062@163.com +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/i2c/busses/i2c-i801.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +--- a/drivers/i2c/busses/i2c-i801.c ++++ b/drivers/i2c/busses/i2c-i801.c +@@ -918,13 +918,13 @@ static s32 i801_access(struct i2c_adapte + */ + if (hwpec) + outb_p(inb_p(SMBAUXCTL(priv)) & ~SMBAUXCTL_CRC, SMBAUXCTL(priv)); +-out: + /* + * Unlock the SMBus device for use by BIOS/ACPI, + * and clear status flags if not done already. + */ + outb_p(SMBHSTSTS_INUSE_STS | STATUS_FLAGS, SMBHSTSTS(priv)); + ++out: + pm_runtime_mark_last_busy(&priv->pci_dev->dev); + pm_runtime_put_autosuspend(&priv->pci_dev->dev); + mutex_unlock(&priv->acpi_lock); diff --git a/queue-6.6/i2c-imx-fix-locked-bus-on-smbus-block-read-of-0-atomic.patch b/queue-6.6/i2c-imx-fix-locked-bus-on-smbus-block-read-of-0-atomic.patch new file mode 100644 index 0000000000..3215906bec --- /dev/null +++ b/queue-6.6/i2c-imx-fix-locked-bus-on-smbus-block-read-of-0-atomic.patch @@ -0,0 +1,87 @@ +From stable+bounces-289485-greg=kroah.com@vger.kernel.org Mon Jul 27 14:36:35 2026 +From: Sasha Levin +Date: Mon, 27 Jul 2026 08:28:46 -0400 +Subject: i2c: imx: fix locked bus on SMBus block-read of 0 (atomic) +To: stable@vger.kernel.org +Cc: Vincent Jardin , Oleksij Rempel , Carlos Song , Stefan Eichenberger , Andi Shyti , Sasha Levin +Message-ID: <20260727122846.4156347-2-sashal@kernel.org> + +From: Vincent Jardin + +[ Upstream commit cb2fc37857693b55909fb77dc2c87cfbc1cdc476 ] + +SMBus 3.1 6.5.7 allows a Block Read byte count of 0, but the atomic +(polling) path rejects it as -EPROTO. Worse, it returns without a +NACK+STOP: the next receive cycle has already started, so the target +keeps holding SDA and the bus stays stuck until a power cycle for +this i2c controller. + +Reading I2DR to obtain the count likewise arms the next byte on the +count > I2C_SMBUS_BLOCK_MAX path, which also returned -EPROTO directly +and left the bus held. + +Handle both: NACK the in-flight dummy byte (TXAK) and extend msgs->len so +the existing last-byte handling emits STOP; the dummy byte is discarded. +A count of 0 is a valid empty block read; a count above +I2C_SMBUS_BLOCK_MAX is still reported as -EPROTO, but only after the bus +has been released. + +The interrupt-driven path has the same flaw from a later commit and is +fixed separately, as it carries a different Fixes: tag and stable range. + +Fixes: 8e8782c71595 ("i2c: imx: add SMBus block read support") +Signed-off-by: Vincent Jardin +Cc: # v3.16+ +Acked-by: Oleksij Rempel +Acked-by: Carlos Song +Reviewed-by: Stefan Eichenberger +Signed-off-by: Andi Shyti +Link: https://lore.kernel.org/r/20260713-for-upstream-i2c-lx2160-fix-v1-v3-1-073ac9e103a5@free.fr +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/i2c/busses/i2c-imx.c | 19 ++++++++++++++++--- + 1 file changed, 16 insertions(+), 3 deletions(-) + +--- a/drivers/i2c/busses/i2c-imx.c ++++ b/drivers/i2c/busses/i2c-imx.c +@@ -1184,6 +1184,7 @@ static int i2c_imx_read(struct imx_i2c_s + int i, result; + unsigned int temp; + int block_data = msgs->flags & I2C_M_RECV_LEN; ++ int block_err = 0; + + result = i2c_imx_prepare_read(i2c_imx, msgs, atomic, false); + if (result) +@@ -1205,8 +1206,20 @@ static int i2c_imx_read(struct imx_i2c_s + */ + if ((!i) && block_data) { + len = imx_i2c_read_reg(i2c_imx, IMX_I2C_I2DR); +- if ((len == 0) || (len > I2C_SMBUS_BLOCK_MAX)) +- return -EPROTO; ++ if ((len == 0) || (len > I2C_SMBUS_BLOCK_MAX)) { ++ /* ++ * SMBus 3.1 6.5.7: support count byte of 0. ++ * I2C_SMBUS_BLOCK_MAX case should not hold the SDA either. ++ */ ++ if (len > I2C_SMBUS_BLOCK_MAX) ++ block_err = -EPROTO; ++ temp = imx_i2c_read_reg(i2c_imx, IMX_I2C_I2CR); ++ temp |= I2CR_TXAK; ++ imx_i2c_write_reg(temp, i2c_imx, IMX_I2C_I2CR); ++ msgs->buf[0] = 0; ++ msgs->len = 2; ++ continue; ++ } + dev_dbg(&i2c_imx->adapter.dev, + "<%s> read length: 0x%X\n", + __func__, len); +@@ -1254,7 +1267,7 @@ static int i2c_imx_read(struct imx_i2c_s + "<%s> read byte: B%d=0x%X\n", + __func__, i, msgs->buf[i]); + } +- return 0; ++ return block_err; + } + + static int i2c_imx_atomic_read(struct imx_i2c_struct *i2c_imx, struct i2c_msg *msgs, diff --git a/queue-6.6/i2c-imx-separate-atomic-dma-and-non-dma-use-case.patch b/queue-6.6/i2c-imx-separate-atomic-dma-and-non-dma-use-case.patch new file mode 100644 index 0000000000..2de99c229b --- /dev/null +++ b/queue-6.6/i2c-imx-separate-atomic-dma-and-non-dma-use-case.patch @@ -0,0 +1,194 @@ +From stable+bounces-289484-greg=kroah.com@vger.kernel.org Mon Jul 27 14:40:52 2026 +From: Sasha Levin +Date: Mon, 27 Jul 2026 08:28:45 -0400 +Subject: i2c: imx: separate atomic, dma and non-dma use case +To: stable@vger.kernel.org +Cc: Stefan Eichenberger , Frank Li , Oleksij Rempel , Andi Shyti , Sasha Levin +Message-ID: <20260727122846.4156347-1-sashal@kernel.org> + +From: Stefan Eichenberger + +[ Upstream commit b460b15b3cc23ef3639cc51043bf8b2a70ca1878 ] + +Separate the atomic, dma and non-dma use case as a preparation step for +moving the non-dma use case to the isr to avoid rescheduling while a +transfer is in progress. + +Signed-off-by: Stefan Eichenberger +Reviewed-by: Frank Li +Acked-by: Oleksij Rempel +Signed-off-by: Andi Shyti +Stable-dep-of: cb2fc3785769 ("i2c: imx: fix locked bus on SMBus block-read of 0 (atomic)") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/i2c/busses/i2c-imx.c | 107 ++++++++++++++++++++++++++++--------------- + 1 file changed, 70 insertions(+), 37 deletions(-) + +--- a/drivers/i2c/busses/i2c-imx.c ++++ b/drivers/i2c/busses/i2c-imx.c +@@ -1011,6 +1011,43 @@ static int i2c_imx_dma_write(struct imx_ + return i2c_imx_acked(i2c_imx); + } + ++static int i2c_imx_prepare_read(struct imx_i2c_struct *i2c_imx, ++ struct i2c_msg *msgs, bool atomic, ++ bool use_dma) ++{ ++ int result; ++ unsigned int temp = 0; ++ ++ /* write slave address */ ++ imx_i2c_write_reg(i2c_8bit_addr_from_msg(msgs), i2c_imx, IMX_I2C_I2DR); ++ result = i2c_imx_trx_complete(i2c_imx, atomic); ++ if (result) ++ return result; ++ result = i2c_imx_acked(i2c_imx); ++ if (result) ++ return result; ++ ++ dev_dbg(&i2c_imx->adapter.dev, "<%s> setup bus\n", __func__); ++ ++ /* setup bus to read data */ ++ temp = imx_i2c_read_reg(i2c_imx, IMX_I2C_I2CR); ++ temp &= ~I2CR_MTX; ++ ++ /* ++ * Reset the I2CR_TXAK flag initially for SMBus block read since the ++ * length is unknown ++ */ ++ if (msgs->len - 1) ++ temp &= ~I2CR_TXAK; ++ if (use_dma) ++ temp |= I2CR_DMAEN; ++ ++ imx_i2c_write_reg(temp, i2c_imx, IMX_I2C_I2CR); ++ imx_i2c_read_reg(i2c_imx, IMX_I2C_I2DR); /* dummy read */ ++ ++ return 0; ++} ++ + static int i2c_imx_dma_read(struct imx_i2c_struct *i2c_imx, + struct i2c_msg *msgs, bool is_lastmsg) + { +@@ -1021,6 +1058,11 @@ static int i2c_imx_dma_read(struct imx_i + struct imx_i2c_dma *dma = i2c_imx->dma; + struct device *dev = &i2c_imx->adapter.dev; + ++ result = i2c_imx_prepare_read(i2c_imx, msgs, false, true); ++ if (result) ++ return result; ++ ++ dev_dbg(&i2c_imx->adapter.dev, "<%s> read data\n", __func__); + + dma->chan_using = dma->chan_rx; + dma->dma_transfer_dir = DMA_DEV_TO_MEM; +@@ -1131,50 +1173,24 @@ static int i2c_imx_write(struct imx_i2c_ + return 0; + } + ++static int i2c_imx_atomic_write(struct imx_i2c_struct *i2c_imx, struct i2c_msg *msgs) ++{ ++ return i2c_imx_write(i2c_imx, msgs, true); ++} ++ + static int i2c_imx_read(struct imx_i2c_struct *i2c_imx, struct i2c_msg *msgs, + bool is_lastmsg, bool atomic) + { + int i, result; + unsigned int temp; + int block_data = msgs->flags & I2C_M_RECV_LEN; +- int use_dma = i2c_imx->dma && msgs->flags & I2C_M_DMA_SAFE && +- msgs->len >= DMA_THRESHOLD && !block_data; +- +- dev_dbg(&i2c_imx->adapter.dev, +- "<%s> write slave address: addr=0x%x\n", +- __func__, i2c_8bit_addr_from_msg(msgs)); + +- /* write slave address */ +- imx_i2c_write_reg(i2c_8bit_addr_from_msg(msgs), i2c_imx, IMX_I2C_I2DR); +- result = i2c_imx_trx_complete(i2c_imx, atomic); +- if (result) +- return result; +- result = i2c_imx_acked(i2c_imx); ++ result = i2c_imx_prepare_read(i2c_imx, msgs, atomic, false); + if (result) + return result; + +- dev_dbg(&i2c_imx->adapter.dev, "<%s> setup bus\n", __func__); +- +- /* setup bus to read data */ +- temp = imx_i2c_read_reg(i2c_imx, IMX_I2C_I2CR); +- temp &= ~I2CR_MTX; +- +- /* +- * Reset the I2CR_TXAK flag initially for SMBus block read since the +- * length is unknown +- */ +- if ((msgs->len - 1) || block_data) +- temp &= ~I2CR_TXAK; +- if (use_dma) +- temp |= I2CR_DMAEN; +- imx_i2c_write_reg(temp, i2c_imx, IMX_I2C_I2CR); +- imx_i2c_read_reg(i2c_imx, IMX_I2C_I2DR); /* dummy read */ +- + dev_dbg(&i2c_imx->adapter.dev, "<%s> read data\n", __func__); + +- if (use_dma) +- return i2c_imx_dma_read(i2c_imx, msgs, is_lastmsg); +- + /* read data */ + for (i = 0; i < msgs->len; i++) { + u8 len = 0; +@@ -1241,6 +1257,12 @@ static int i2c_imx_read(struct imx_i2c_s + return 0; + } + ++static int i2c_imx_atomic_read(struct imx_i2c_struct *i2c_imx, struct i2c_msg *msgs, ++ bool is_lastmsg) ++{ ++ return i2c_imx_read(i2c_imx, msgs, is_lastmsg, true); ++} ++ + static int i2c_imx_xfer_common(struct i2c_adapter *adapter, + struct i2c_msg *msgs, int num, bool atomic) + { +@@ -1248,6 +1270,7 @@ static int i2c_imx_xfer_common(struct i2 + int result; + bool is_lastmsg = false; + struct imx_i2c_struct *i2c_imx = i2c_get_adapdata(adapter); ++ int use_dma = 0; + + /* Start I2C transfer */ + result = i2c_imx_start(i2c_imx, atomic); +@@ -1300,15 +1323,25 @@ static int i2c_imx_xfer_common(struct i2 + (temp & I2SR_SRW ? 1 : 0), (temp & I2SR_IIF ? 1 : 0), + (temp & I2SR_RXAK ? 1 : 0)); + #endif ++ ++ use_dma = i2c_imx->dma && msgs[i].len >= DMA_THRESHOLD && ++ msgs[i].flags & I2C_M_DMA_SAFE; + if (msgs[i].flags & I2C_M_RD) { +- result = i2c_imx_read(i2c_imx, &msgs[i], is_lastmsg, atomic); ++ int block_data = msgs->flags & I2C_M_RECV_LEN; ++ ++ if (atomic) ++ result = i2c_imx_atomic_read(i2c_imx, &msgs[i], is_lastmsg); ++ else if (use_dma && !block_data) ++ result = i2c_imx_dma_read(i2c_imx, &msgs[i], is_lastmsg); ++ else ++ result = i2c_imx_read(i2c_imx, &msgs[i], is_lastmsg, false); + } else { +- if (!atomic && +- i2c_imx->dma && msgs[i].len >= DMA_THRESHOLD && +- msgs[i].flags & I2C_M_DMA_SAFE) ++ if (atomic) ++ result = i2c_imx_atomic_write(i2c_imx, &msgs[i]); ++ else if (use_dma) + result = i2c_imx_dma_write(i2c_imx, &msgs[i]); + else +- result = i2c_imx_write(i2c_imx, &msgs[i], atomic); ++ result = i2c_imx_write(i2c_imx, &msgs[i], false); + } + if (result) + goto fail0; diff --git a/queue-6.6/input-ims-pcu-fix-firmware-leak-in-async-update.patch b/queue-6.6/input-ims-pcu-fix-firmware-leak-in-async-update.patch new file mode 100644 index 0000000000..5cdc529a36 --- /dev/null +++ b/queue-6.6/input-ims-pcu-fix-firmware-leak-in-async-update.patch @@ -0,0 +1,49 @@ +From stable+bounces-289741-greg=kroah.com@vger.kernel.org Tue Jul 28 01:34:46 2026 +From: Sasha Levin +Date: Mon, 27 Jul 2026 19:34:37 -0400 +Subject: Input: ims-pcu - fix firmware leak in async update +To: stable@vger.kernel.org +Cc: Dmitry Torokhov , Sashiko bot , Sasha Levin +Message-ID: <20260727233437.2425385-2-sashal@kernel.org> + +From: Dmitry Torokhov + +[ Upstream commit d48795b5cd6828d36b707e8d62fc9e5c90e004ab ] + +The firmware object was not being released if validation failed. +Use __free(firmware) to ensure the firmware is always released. + +Fixes: 628329d52474 ("Input: add IMS Passenger Control Unit driver") +Cc: stable@vger.kernel.org +Reported-by: Sashiko bot +Assisted-by: Gemini:gemini-3.1-pro +Signed-off-by: Dmitry Torokhov +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/input/misc/ims-pcu.c | 5 ++--- + 1 file changed, 2 insertions(+), 3 deletions(-) + +--- a/drivers/input/misc/ims-pcu.c ++++ b/drivers/input/misc/ims-pcu.c +@@ -964,9 +964,10 @@ out: + return retval; + } + +-static void ims_pcu_process_async_firmware(const struct firmware *fw, ++static void ims_pcu_process_async_firmware(const struct firmware *_fw, + void *context) + { ++ const struct firmware *fw __free(firmware) = _fw; + struct ims_pcu *pcu = context; + int error; + +@@ -987,8 +988,6 @@ static void ims_pcu_process_async_firmwa + ims_pcu_handle_firmware_update(pcu, fw); + mutex_unlock(&pcu->cmd_mutex); + +- release_firmware(fw); +- + out: + complete(&pcu->async_firmware_done); + } diff --git a/queue-6.6/iommu-amd-don-t-split-flush-for-amd_iommu_domain_flush_all.patch b/queue-6.6/iommu-amd-don-t-split-flush-for-amd_iommu_domain_flush_all.patch new file mode 100644 index 0000000000..3e2f176447 --- /dev/null +++ b/queue-6.6/iommu-amd-don-t-split-flush-for-amd_iommu_domain_flush_all.patch @@ -0,0 +1,65 @@ +From stable+bounces-279630-greg=kroah.com@vger.kernel.org Tue Jul 21 19:24:14 2026 +From: Sasha Levin +Date: Tue, 21 Jul 2026 13:12:57 -0400 +Subject: iommu/amd: Don't split flush for amd_iommu_domain_flush_all() +To: stable@vger.kernel.org +Cc: Weinan Liu , Josef Bacik , Jason Gunthorpe , Wei Wang , Samiullah Khawaja , Suravee Suthikulpanit , Vasant Hegde , Joerg Roedel , Sasha Levin +Message-ID: <20260721171257.107653-1-sashal@kernel.org> + +From: Weinan Liu + +[ Upstream commit 69fe699afe1afcb730164b86c228483c2da05f94 ] + +We have observed multiple full invalidations occurring during device +detach when we are done using the vfio-device. + +blocked_domain_attach_device() + -> detach_device() + -> amd_iommu_domain_flush_all() + -> amd_iommu_domain_flush_pages(..., CMD_INV_IOMMU_ALL_PAGES_ADDRESS) + + while (size != 0) { + + -> __domain_flush_pages( flush_size /* power of 2 flush_size */) + -> domain_flush_pages_v1() + -> build_inv_iommu_pages() + -> build_inv_address() + + } + +build_inv_address() will trigger a full invalidation if the chunk +size > (1 << 51). Consequently, the guest will issue multiple full +invalidations for a single call to amd_iommu_domain_flush_all() + +Without this patch, we will see 10 time instead of 1 time full +invalidations for every amd_iommu_domain_flush_all(). + +Cc: stable@vger.kernel.org +Fixes: a270be1b3fdf ("iommu/amd: Use only natural aligned flushes in a VM") +Suggested-by: Josef Bacik +Suggested-by: Jason Gunthorpe +Signed-off-by: Weinan Liu +Reviewed-by: Wei Wang +Reviewed-by: Jason Gunthorpe +Reviewed-by: Samiullah Khawaja +Reviewed-by: Suravee Suthikulpanit +Reviewed-by: Vasant Hegde +Signed-off-by: Joerg Roedel +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/iommu/amd/iommu.c | 3 ++- + 1 file changed, 2 insertions(+), 1 deletion(-) + +--- a/drivers/iommu/amd/iommu.c ++++ b/drivers/iommu/amd/iommu.c +@@ -1441,7 +1441,8 @@ static void __domain_flush_pages(struct + static void domain_flush_pages(struct protection_domain *domain, + u64 address, size_t size, int pde) + { +- if (likely(!amd_iommu_np_cache)) { ++ if (likely(!amd_iommu_np_cache) || ++ size >= (1ULL<<52)) { + __domain_flush_pages(domain, address, size, pde); + return; + } diff --git a/queue-6.6/ipmi-fix-refcount-leak-in-i_ipmi_request.patch b/queue-6.6/ipmi-fix-refcount-leak-in-i_ipmi_request.patch new file mode 100644 index 0000000000..2d06593a66 --- /dev/null +++ b/queue-6.6/ipmi-fix-refcount-leak-in-i_ipmi_request.patch @@ -0,0 +1,60 @@ +From stable+bounces-290122-greg=kroah.com@vger.kernel.org Tue Jul 28 20:23:29 2026 +From: Sasha Levin +Date: Tue, 28 Jul 2026 14:07:42 -0400 +Subject: ipmi: fix refcount leak in i_ipmi_request() +To: stable@vger.kernel.org +Cc: Wentao Liang , Corey Minyard , Sasha Levin +Message-ID: <20260728180742.291994-1-sashal@kernel.org> + +From: Wentao Liang + +[ Upstream commit a3f3859cecacb64f18fd446271ece9a3b3f2d4de ] + +When a caller provides a `supplied_recv` message to i_ipmi_request(), +the function increments the user's `nr_msgs` reference count. If an +error occurs later, the out_err cleanup path only frees the recv_msg +if the function allocated it itself (i.e., !supplied_recv). In the +supplied_recv case the cleanup is skipped, leaving the reference count +elevated. The caller ipmi_request_supply_msgs() does not release the +supplied_recv on error, so the reference is permanently leaked. + +Fix this by explicitly reverting the reference count operations when a +supplied recv_msg with a valid user pointer is present in the error +path: decrement nr_msgs and drop the user's kref. + +Cc: stable@vger.kernel.org +Fixes: b52da4054ee0 ("ipmi: Rework user message limit handling") +Signed-off-by: Wentao Liang +Message-ID: <20260603120634.3758747-1-vulab@iscas.ac.cn> +Signed-off-by: Corey Minyard +[ changed `free_ipmi_user` to `free_user` in the two added `kref_put()` calls ] +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/char/ipmi/ipmi_msghandler.c | 8 ++++++++ + 1 file changed, 8 insertions(+) + +--- a/drivers/char/ipmi/ipmi_msghandler.c ++++ b/drivers/char/ipmi/ipmi_msghandler.c +@@ -2331,6 +2331,10 @@ static int i_ipmi_request(struct ipmi_us + if (smi_msg == NULL) { + if (!supplied_recv) + ipmi_free_recv_msg(recv_msg); ++ else if (recv_msg->user) { ++ atomic_dec(&recv_msg->user->nr_msgs); ++ kref_put(&recv_msg->user->refcount, free_user); ++ } + return -ENOMEM; + } + } +@@ -2373,6 +2377,10 @@ out_err: + ipmi_free_smi_msg(smi_msg); + if (!supplied_recv) + ipmi_free_recv_msg(recv_msg); ++ else if (recv_msg->user) { ++ atomic_dec(&recv_msg->user->nr_msgs); ++ kref_put(&recv_msg->user->refcount, free_user); ++ } + } else { + dev_dbg(intf->si_dev, "Send: %*ph\n", + smi_msg->data_size, smi_msg->data); diff --git a/queue-6.6/kvm-introduce-vcpu-wants_to_run.patch b/queue-6.6/kvm-introduce-vcpu-wants_to_run.patch new file mode 100644 index 0000000000..c660d7bce6 --- /dev/null +++ b/queue-6.6/kvm-introduce-vcpu-wants_to_run.patch @@ -0,0 +1,139 @@ +From stable+bounces-291165-greg=kroah.com@vger.kernel.org Thu Jul 30 14:53:52 2026 +From: Sasha Levin +Date: Thu, 30 Jul 2026 08:15:40 -0400 +Subject: KVM: Introduce vcpu->wants_to_run +To: stable@vger.kernel.org +Cc: David Matlack , Sean Christopherson , Sasha Levin +Message-ID: <20260730121541.2209429-1-sashal@kernel.org> + +From: David Matlack + +[ Upstream commit a6816314af5749cd88944bfdceb270c627cdf348 ] + +Introduce vcpu->wants_to_run to indicate when a vCPU is in its core run +loop, i.e. when the vCPU is running the KVM_RUN ioctl and immediate_exit +was not set. + +Replace all references to vcpu->run->immediate_exit with +!vcpu->wants_to_run to avoid TOCTOU races with userspace. For example, a +malicious userspace could invoked KVM_RUN with immediate_exit=true and +then after KVM reads it to set wants_to_run=false, flip it to false. +This would result in the vCPU running in KVM_RUN with +wants_to_run=false. This wouldn't cause any real bugs today but is a +dangerous landmine. + +Signed-off-by: David Matlack +Link: https://lore.kernel.org/r/20240503181734.1467938-2-dmatlack@google.com +Signed-off-by: Sean Christopherson +Stable-dep-of: e800decd9c0a ("KVM: x86: Only reset TSC Deadline Timer in apic_timer_expired on KVM_RUN") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + arch/arm64/kvm/arm.c | 2 +- + arch/mips/kvm/mips.c | 2 +- + arch/powerpc/kvm/powerpc.c | 2 +- + arch/riscv/kvm/vcpu.c | 2 +- + arch/s390/kvm/kvm-s390.c | 2 +- + arch/x86/kvm/x86.c | 4 ++-- + include/linux/kvm_host.h | 1 + + virt/kvm/kvm_main.c | 3 +++ + 8 files changed, 11 insertions(+), 7 deletions(-) + +--- a/arch/arm64/kvm/arm.c ++++ b/arch/arm64/kvm/arm.c +@@ -915,7 +915,7 @@ int kvm_arch_vcpu_ioctl_run(struct kvm_v + + vcpu_load(vcpu); + +- if (run->immediate_exit) { ++ if (!vcpu->wants_to_run) { + ret = -EINTR; + goto out; + } +--- a/arch/mips/kvm/mips.c ++++ b/arch/mips/kvm/mips.c +@@ -436,7 +436,7 @@ int kvm_arch_vcpu_ioctl_run(struct kvm_v + vcpu->mmio_needed = 0; + } + +- if (vcpu->run->immediate_exit) ++ if (!vcpu->wants_to_run) + goto out; + + lose_fpu(1); +--- a/arch/powerpc/kvm/powerpc.c ++++ b/arch/powerpc/kvm/powerpc.c +@@ -1858,7 +1858,7 @@ int kvm_arch_vcpu_ioctl_run(struct kvm_v + + kvm_sigset_activate(vcpu); + +- if (run->immediate_exit) ++ if (!vcpu->wants_to_run) + r = -EINTR; + else + r = kvmppc_vcpu_run(vcpu); +--- a/arch/riscv/kvm/vcpu.c ++++ b/arch/riscv/kvm/vcpu.c +@@ -704,7 +704,7 @@ int kvm_arch_vcpu_ioctl_run(struct kvm_v + return ret; + } + +- if (run->immediate_exit) { ++ if (!vcpu->wants_to_run) { + kvm_vcpu_srcu_read_unlock(vcpu); + return -EINTR; + } +--- a/arch/s390/kvm/kvm-s390.c ++++ b/arch/s390/kvm/kvm-s390.c +@@ -5048,7 +5048,7 @@ int kvm_arch_vcpu_ioctl_run(struct kvm_v + if (vcpu->kvm->arch.pv.dumping) + return -EINVAL; + +- if (kvm_run->immediate_exit) ++ if (!vcpu->wants_to_run) + return -EINTR; + + if (kvm_run->kvm_valid_regs & ~KVM_SYNC_S390_VALID_FIELDS || +--- a/arch/x86/kvm/x86.c ++++ b/arch/x86/kvm/x86.c +@@ -11224,7 +11224,7 @@ int kvm_arch_vcpu_ioctl_run(struct kvm_v + + kvm_vcpu_srcu_read_lock(vcpu); + if (unlikely(vcpu->arch.mp_state == KVM_MP_STATE_UNINITIALIZED)) { +- if (kvm_run->immediate_exit) { ++ if (!vcpu->wants_to_run) { + r = -EINTR; + goto out; + } +@@ -11302,7 +11302,7 @@ int kvm_arch_vcpu_ioctl_run(struct kvm_v + WARN_ON_ONCE(vcpu->mmio_needed); + } + +- if (kvm_run->immediate_exit) { ++ if (!vcpu->wants_to_run) { + r = -EINTR; + goto out; + } +--- a/include/linux/kvm_host.h ++++ b/include/linux/kvm_host.h +@@ -378,6 +378,7 @@ struct kvm_vcpu { + bool dy_eligible; + } spin_loop; + #endif ++ bool wants_to_run; + bool preempted; + bool ready; + struct kvm_vcpu_arch arch; +--- a/virt/kvm/kvm_main.c ++++ b/virt/kvm/kvm_main.c +@@ -4160,7 +4160,10 @@ static long kvm_vcpu_ioctl(struct file * + synchronize_rcu(); + put_pid(oldpid); + } ++ vcpu->wants_to_run = !READ_ONCE(vcpu->run->immediate_exit); + r = kvm_arch_vcpu_ioctl_run(vcpu); ++ vcpu->wants_to_run = false; ++ + trace_kvm_userspace_exit(vcpu->run->exit_reason, r); + break; + } diff --git a/queue-6.6/kvm-x86-only-reset-tsc-deadline-timer-in-apic_timer_expired-on-kvm_run.patch b/queue-6.6/kvm-x86-only-reset-tsc-deadline-timer-in-apic_timer_expired-on-kvm_run.patch new file mode 100644 index 0000000000..49bda306cd --- /dev/null +++ b/queue-6.6/kvm-x86-only-reset-tsc-deadline-timer-in-apic_timer_expired-on-kvm_run.patch @@ -0,0 +1,68 @@ +From stable+bounces-291164-greg=kroah.com@vger.kernel.org Thu Jul 30 14:53:37 2026 +From: Sasha Levin +Date: Thu, 30 Jul 2026 08:15:41 -0400 +Subject: KVM: x86: Only reset TSC Deadline Timer in apic_timer_expired on KVM_RUN +To: stable@vger.kernel.org +Cc: Venkatesh Srinivas , David Matlack , Sean Christopherson , Jim Mattson , James Houghton , Chao Gao , Paolo Bonzini , Sasha Levin +Message-ID: <20260730121541.2209429-2-sashal@kernel.org> + +From: Venkatesh Srinivas + +[ Upstream commit e800decd9c0ac4349bcd8f8f9b29fd21fe93165e ] + +On Intel platforms with a VMX preemption timer and APICv, if a VMM +calls KVM_GET_LAPIC before KVM_GET_MSRS to save the vCPU state, it is +possible to lose a pending timer interrupt. + +If the thread running these ioctls is migrated to another core after +calling KVM_GET_LAPIC but before KVM_GET_MSRS and the guest is using +their LAPIC timer in TSC-deadline mode, not only does the save LAPIC +state not carry the pending interrupt, the TSCDEADLINE MSR will be +zeroed. + +After migration across CPUs, KVM_GET_MSRS calls vcpu_load, posting the +interrupt and clearing the MSR: +vcpu_load() -> + kvm_arch_vcpu_load() -> + kvm_lapic_restart_hv_timer() -> + start_hv_timer() -> + apic_timer_expired() -> + kvm_apic_inject_pending_timer_irqs() + . post interrupt into the LAPIC state + . clear IA32_TSCDEADLINE + +The saved LAPIC state will be missing the pending interrupt and the saved +MSR will be zero. Oops. + +Fix by only posting an interrupt when we're attempting to enter the guest +(vcpu->wants_to_run == true), not for vcpu_load from other paths. + +Assisted-by: gemini:gemini-3.1-pro-preview +Debugged-by: David Matlack +Debugged-by: Sean Christopherson +Debugged-by: Jim Mattson +Debugged-by: James Houghton +Signed-off-by: Venkatesh Srinivas +Message-ID: <20260715234234.15382-2-venkateshs@chromium.org> +Reviewed-by: James Houghton +Reviewed-by: Chao Gao +Cc: stable@vger.kernel.org +Fixes: ae95f566b3d2 ("KVM: X86: TSCDEADLINE MSR emulation fastpath", 2020-05-15) +Signed-off-by: Paolo Bonzini +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + arch/x86/kvm/lapic.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +--- a/arch/x86/kvm/lapic.c ++++ b/arch/x86/kvm/lapic.c +@@ -1912,7 +1912,7 @@ static void apic_timer_expired(struct kv + if (apic_lvtt_tscdeadline(apic) || ktimer->hv_timer_in_use) + ktimer->expired_tscdeadline = ktimer->tscdeadline; + +- if (!from_timer_fn && apic->apicv_active) { ++ if (!from_timer_fn && apic->apicv_active && vcpu->wants_to_run) { + WARN_ON(kvm_get_running_vcpu() != vcpu); + kvm_apic_inject_pending_timer_irqs(apic); + return; diff --git a/queue-6.6/locking-rt-fix-the-incorrect-rcu-protection-in-rt_spin_unlock.patch b/queue-6.6/locking-rt-fix-the-incorrect-rcu-protection-in-rt_spin_unlock.patch new file mode 100644 index 0000000000..749be7c830 --- /dev/null +++ b/queue-6.6/locking-rt-fix-the-incorrect-rcu-protection-in-rt_spin_unlock.patch @@ -0,0 +1,110 @@ +From stable+bounces-289818-greg=kroah.com@vger.kernel.org Tue Jul 28 05:13:09 2026 +From: Sasha Levin +Date: Mon, 27 Jul 2026 23:02:02 -0400 +Subject: locking/rt: Fix the incorrect RCU protection in rt_spin_unlock() +To: stable@vger.kernel.org +Cc: Thomas Gleixner , syzbot+000c800a02097aaa10ed@syzkaller.appspotmail.com, Jann Horn , Sebastian Andrzej Siewior , Al Viro , Sasha Levin +Message-ID: <20260728030202.3123520-1-sashal@kernel.org> + +From: Thomas Gleixner + +[ Upstream commit 89038cc87d80c77e7aa6f42a64b2573b74af339f ] + +rt_spin_unlock() releases the RCU protection before unlocking the +lock. That opens the door for the following UAF scenario: + + T1 T2 + spin_lock(&p->lock); rcu_read_lock(); + invalidate(p); p = rcu_dereference(ptr); + rcu_assign_pointer(ptr, NULL); if (!p) return; + spin_unlock(&p->lock); spin_lock(&p->lock) + lock(&lock->lock); + rcu_read_lock(); + kfree_rcu(p); rcu_read_unlock(); + .... + spin_unlock(&p->lock) + rcu_read_unlock(); // Ends grace period + rcu_do_batch() + kfree(p); + UAF -> rt_mutex_cmpxchg_release(&lock->lock...) + +Regular spinlocks keep preemption disabled accross the unlock operation, +which provides full RCU protection, but the RT substitution fails to +resemble that. Same applies for the rwlock substitution. + +Move the rcu_read_unlock() invocation past the unlock operations to match +the non-RT semantics. This makes it asymmetric vs. rt_xxx_lock(), but +that's harmless as the caller needs to hold RCU read lock across the lock +operation. The migrate_enable() call stays before the unlock operation +because there is no per CPU operation in the unlock path which would +require migration to be kept disabled. + +Fixes: 0f383b6dc96e ("locking/spinlock: Provide RT variant") +Reported-by: syzbot+000c800a02097aaa10ed@syzkaller.appspotmail.com +Decoded-by: Jann Horn +Signed-off-by: Thomas Gleixner +Reviewed-by: Sebastian Andrzej Siewior +Acked-by: Al Viro +Cc: stable@vger.kernel.org +Link: https://patch.msgid.link/87jyrud75z.ffs@fw13 +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + kernel/locking/spinlock_rt.c | 27 ++++++++++++++++++++++++--- + 1 file changed, 24 insertions(+), 3 deletions(-) + +--- a/kernel/locking/spinlock_rt.c ++++ b/kernel/locking/spinlock_rt.c +@@ -77,10 +77,27 @@ void __sched rt_spin_unlock(spinlock_t * + { + spin_release(&lock->dep_map, _RET_IP_); + migrate_enable(); +- rcu_read_unlock(); + + if (unlikely(!rt_mutex_cmpxchg_release(&lock->lock, current, NULL))) + rt_mutex_slowunlock(&lock->lock); ++ ++ /* ++ * This must be last to prevent the following UAF: ++ * ++ * T1 T2 ++ * spin_lock(&p->lock); rcu_read_lock(); ++ * invalidate(p); p = rcu_dereference(ptr); ++ * rcu_assign_pointer(ptr, NULL); if (!p) return; ++ * spin_unlock(&p->lock); spin_lock(&p->lock); ++ * kfree_rcu(p); rcu_read_unlock(); ++ * .... ++ * spin_unlock(&p->lock) ++ * rcu_read_unlock(); // Ends grace period ++ * rcu_do_batch() ++ * kfree(p); ++ * UAF -> rt_mutex_cmpxchg_release(&p->lock.lock...) ++ */ ++ rcu_read_unlock(); + } + EXPORT_SYMBOL(rt_spin_unlock); + +@@ -255,17 +272,21 @@ void __sched rt_read_unlock(rwlock_t *rw + { + rwlock_release(&rwlock->dep_map, _RET_IP_); + migrate_enable(); +- rcu_read_unlock(); + rwbase_read_unlock(&rwlock->rwbase, TASK_RTLOCK_WAIT); ++ ++ /* This must be last. See comment in rt_spin_unlock() */ ++ rcu_read_unlock(); + } + EXPORT_SYMBOL(rt_read_unlock); + + void __sched rt_write_unlock(rwlock_t *rwlock) + { + rwlock_release(&rwlock->dep_map, _RET_IP_); +- rcu_read_unlock(); + migrate_enable(); + rwbase_write_unlock(&rwlock->rwbase); ++ ++ /* This must be last. See comment in rt_spin_unlock() */ ++ rcu_read_unlock(); + } + EXPORT_SYMBOL(rt_write_unlock); + diff --git a/queue-6.6/lsm-infrastructure-management-of-the-sock-security.patch b/queue-6.6/lsm-infrastructure-management-of-the-sock-security.patch new file mode 100644 index 0000000000..574754c299 --- /dev/null +++ b/queue-6.6/lsm-infrastructure-management-of-the-sock-security.patch @@ -0,0 +1,985 @@ +From stable+bounces-288352-greg=kroah.com@vger.kernel.org Thu Jul 23 16:21:24 2026 +From: Sasha Levin +Date: Thu, 23 Jul 2026 10:19:19 -0400 +Subject: lsm: infrastructure management of the sock security +To: stable@vger.kernel.org +Cc: Casey Schaufler , Paul Moore , Kees Cook , John Johansen , Stephen Smalley , Sasha Levin +Message-ID: <20260723141920.3218519-1-sashal@kernel.org> + +From: Casey Schaufler + +[ Upstream commit 2aff9d20d50ac45dd13a013ef5231f4fb8912356 ] + +Move management of the sock->sk_security blob out +of the individual security modules and into the security +infrastructure. Instead of allocating the blobs from within +the modules the modules tell the infrastructure how much +space is required, and the space is allocated there. + +Acked-by: Paul Moore +Reviewed-by: Kees Cook +Reviewed-by: John Johansen +Acked-by: Stephen Smalley +Signed-off-by: Casey Schaufler +[PM: subject tweak] +Signed-off-by: Paul Moore +Stable-dep-of: 56acfeb10019 ("selinux: avoid sk_socket dereference in selinux_sctp_bind_connect()") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + include/linux/lsm_hooks.h | 1 + security/apparmor/include/net.h | 6 ++ + security/apparmor/lsm.c | 38 ++++-------------- + security/apparmor/net.c | 2 + security/security.c | 36 ++++++++++++++++- + security/selinux/hooks.c | 80 +++++++++++++++++--------------------- + security/selinux/include/objsec.h | 5 ++ + security/selinux/netlabel.c | 23 +++++----- + security/smack/smack.h | 5 ++ + security/smack/smack_lsm.c | 70 +++++++++++++++------------------ + security/smack/smack_netfilter.c | 4 - + 11 files changed, 146 insertions(+), 124 deletions(-) + +--- a/include/linux/lsm_hooks.h ++++ b/include/linux/lsm_hooks.h +@@ -61,6 +61,7 @@ struct lsm_blob_sizes { + int lbs_file; + int lbs_backing_file; + int lbs_inode; ++ int lbs_sock; + int lbs_superblock; + int lbs_ipc; + int lbs_msg_msg; +--- a/security/apparmor/include/net.h ++++ b/security/apparmor/include/net.h +@@ -51,8 +51,12 @@ struct aa_sk_ctx { + struct aa_label *peer; + }; + +-#define SK_CTX(X) ((X)->sk_security) + #define SOCK_ctx(X) SOCK_INODE(X)->i_security ++static inline struct aa_sk_ctx *aa_sock(const struct sock *sk) ++{ ++ return sk->sk_security + apparmor_blob_sizes.lbs_sock; ++} ++ + #define DEFINE_AUDIT_NET(NAME, OP, SK, F, T, P) \ + struct lsm_network_audit NAME ## _net = { .sk = (SK), \ + .family = (F)}; \ +--- a/security/apparmor/lsm.c ++++ b/security/apparmor/lsm.c +@@ -851,32 +851,14 @@ static int apparmor_task_kill(struct tas + } + + /** +- * apparmor_sk_alloc_security - allocate and attach the sk_security field +- */ +-static int apparmor_sk_alloc_security(struct sock *sk, int family, gfp_t flags) +-{ +- struct aa_sk_ctx *ctx; +- +- ctx = kzalloc(sizeof(*ctx), flags); +- if (!ctx) +- return -ENOMEM; +- +- SK_CTX(sk) = ctx; +- +- return 0; +-} +- +-/** + * apparmor_sk_free_security - free the sk_security field + */ + static void apparmor_sk_free_security(struct sock *sk) + { +- struct aa_sk_ctx *ctx = SK_CTX(sk); ++ struct aa_sk_ctx *ctx = aa_sock(sk); + +- SK_CTX(sk) = NULL; + aa_put_label(ctx->label); + aa_put_label(ctx->peer); +- kfree(ctx); + } + + /** +@@ -885,8 +867,8 @@ static void apparmor_sk_free_security(st + static void apparmor_sk_clone_security(const struct sock *sk, + struct sock *newsk) + { +- struct aa_sk_ctx *ctx = SK_CTX(sk); +- struct aa_sk_ctx *new = SK_CTX(newsk); ++ struct aa_sk_ctx *ctx = aa_sock(sk); ++ struct aa_sk_ctx *new = aa_sock(newsk); + + if (new->label) + aa_put_label(new->label); +@@ -940,7 +922,7 @@ static int apparmor_socket_post_create(s + label = aa_get_current_label(); + + if (sock->sk) { +- struct aa_sk_ctx *ctx = SK_CTX(sock->sk); ++ struct aa_sk_ctx *ctx = aa_sock(sock->sk); + + aa_put_label(ctx->label); + ctx->label = aa_get_label(label); +@@ -1139,7 +1121,7 @@ static int apparmor_socket_shutdown(stru + */ + static int apparmor_socket_sock_rcv_skb(struct sock *sk, struct sk_buff *skb) + { +- struct aa_sk_ctx *ctx = SK_CTX(sk); ++ struct aa_sk_ctx *ctx = aa_sock(sk); + + if (!skb->secmark) + return 0; +@@ -1159,7 +1141,7 @@ static int apparmor_socket_sock_rcv_skb( + + static struct aa_label *sk_peer_label(struct sock *sk) + { +- struct aa_sk_ctx *ctx = SK_CTX(sk); ++ struct aa_sk_ctx *ctx = aa_sock(sk); + + if (ctx->peer) + return ctx->peer; +@@ -1240,7 +1222,7 @@ static int apparmor_socket_getpeersec_dg + */ + static void apparmor_sock_graft(struct sock *sk, struct socket *parent) + { +- struct aa_sk_ctx *ctx = SK_CTX(sk); ++ struct aa_sk_ctx *ctx = aa_sock(sk); + + if (!ctx->label) + ctx->label = aa_get_current_label(); +@@ -1250,7 +1232,7 @@ static void apparmor_sock_graft(struct s + static int apparmor_inet_conn_request(const struct sock *sk, struct sk_buff *skb, + struct request_sock *req) + { +- struct aa_sk_ctx *ctx = SK_CTX(sk); ++ struct aa_sk_ctx *ctx = aa_sock(sk); + + if (!skb->secmark) + return 0; +@@ -1267,6 +1249,7 @@ struct lsm_blob_sizes apparmor_blob_size + .lbs_cred = sizeof(struct aa_label *), + .lbs_file = sizeof(struct aa_file_ctx), + .lbs_task = sizeof(struct aa_task_ctx), ++ .lbs_sock = sizeof(struct aa_sk_ctx), + }; + + static struct security_hook_list apparmor_hooks[] __ro_after_init = { +@@ -1305,7 +1288,6 @@ static struct security_hook_list apparmo + LSM_HOOK_INIT(getprocattr, apparmor_getprocattr), + LSM_HOOK_INIT(setprocattr, apparmor_setprocattr), + +- LSM_HOOK_INIT(sk_alloc_security, apparmor_sk_alloc_security), + LSM_HOOK_INIT(sk_free_security, apparmor_sk_free_security), + LSM_HOOK_INIT(sk_clone_security, apparmor_sk_clone_security), + +@@ -1864,7 +1846,7 @@ static unsigned int apparmor_ip_postrout + if (sk == NULL) + return NF_ACCEPT; + +- ctx = SK_CTX(sk); ++ ctx = aa_sock(sk); + if (!apparmor_secmark_check(ctx->label, OP_SENDMSG, AA_MAY_SEND, + skb->secmark, sk)) + return NF_ACCEPT; +--- a/security/apparmor/net.c ++++ b/security/apparmor/net.c +@@ -152,7 +152,7 @@ static int aa_label_sk_perm(const struct + const char *op, u32 request, + struct sock *sk) + { +- struct aa_sk_ctx *ctx = SK_CTX(sk); ++ struct aa_sk_ctx *ctx = aa_sock(sk); + int error = 0; + + AA_BUG(!label); +--- a/security/security.c ++++ b/security/security.c +@@ -30,6 +30,7 @@ + #include + #include + #include ++#include + + /* How many LSMs were built into the kernel? */ + #define LSM_COUNT (__end_lsm_info - __start_lsm_info) +@@ -212,6 +213,7 @@ static void __init lsm_set_blob_sizes(st + lsm_set_blob_size(&needed->lbs_inode, &blob_sizes.lbs_inode); + lsm_set_blob_size(&needed->lbs_ipc, &blob_sizes.lbs_ipc); + lsm_set_blob_size(&needed->lbs_msg_msg, &blob_sizes.lbs_msg_msg); ++ lsm_set_blob_size(&needed->lbs_sock, &blob_sizes.lbs_sock); + lsm_set_blob_size(&needed->lbs_superblock, &blob_sizes.lbs_superblock); + lsm_set_blob_size(&needed->lbs_task, &blob_sizes.lbs_task); + lsm_set_blob_size(&needed->lbs_xattr_count, +@@ -381,6 +383,7 @@ static void __init ordered_lsm_init(void + init_debug("inode blob size = %d\n", blob_sizes.lbs_inode); + init_debug("ipc blob size = %d\n", blob_sizes.lbs_ipc); + init_debug("msg_msg blob size = %d\n", blob_sizes.lbs_msg_msg); ++ init_debug("sock blob size = %d\n", blob_sizes.lbs_sock); + init_debug("superblock blob size = %d\n", blob_sizes.lbs_superblock); + init_debug("task blob size = %d\n", blob_sizes.lbs_task); + init_debug("xattr slots = %d\n", blob_sizes.lbs_xattr_count); +@@ -4560,6 +4563,28 @@ int security_socket_getpeersec_dgram(str + EXPORT_SYMBOL(security_socket_getpeersec_dgram); + + /** ++ * lsm_sock_alloc - allocate a composite sock blob ++ * @sock: the sock that needs a blob ++ * @priority: allocation mode ++ * ++ * Allocate the sock blob for all the modules ++ * ++ * Returns 0, or -ENOMEM if memory can't be allocated. ++ */ ++static int lsm_sock_alloc(struct sock *sock, gfp_t priority) ++{ ++ if (blob_sizes.lbs_sock == 0) { ++ sock->sk_security = NULL; ++ return 0; ++ } ++ ++ sock->sk_security = kzalloc(blob_sizes.lbs_sock, priority); ++ if (sock->sk_security == NULL) ++ return -ENOMEM; ++ return 0; ++} ++ ++/** + * security_sk_alloc() - Allocate and initialize a sock's LSM blob + * @sk: sock + * @family: protocol family +@@ -4572,7 +4597,14 @@ EXPORT_SYMBOL(security_socket_getpeersec + */ + int security_sk_alloc(struct sock *sk, int family, gfp_t priority) + { +- return call_int_hook(sk_alloc_security, 0, sk, family, priority); ++ int rc = lsm_sock_alloc(sk, priority); ++ ++ if (unlikely(rc)) ++ return rc; ++ rc = call_int_hook(sk_alloc_security, 0, sk, family, priority); ++ if (unlikely(rc)) ++ security_sk_free(sk); ++ return rc; + } + + /** +@@ -4584,6 +4616,8 @@ int security_sk_alloc(struct sock *sk, i + void security_sk_free(struct sock *sk) + { + call_void_hook(sk_free_security, sk); ++ kfree(sk->sk_security); ++ sk->sk_security = NULL; + } + + /** +--- a/security/selinux/hooks.c ++++ b/security/selinux/hooks.c +@@ -4695,7 +4695,7 @@ static int socket_sockcreate_sid(const s + + static int sock_has_perm(struct sock *sk, u32 perms) + { +- struct sk_security_struct *sksec = sk->sk_security; ++ struct sk_security_struct *sksec = selinux_sock(sk); + struct common_audit_data ad; + struct lsm_network_audit net; + +@@ -4748,7 +4748,7 @@ static int selinux_socket_post_create(st + isec->initialized = LABEL_INITIALIZED; + + if (sock->sk) { +- sksec = sock->sk->sk_security; ++ sksec = selinux_sock(sock->sk); + sksec->sclass = sclass; + sksec->sid = sid; + /* Allows detection of the first association on this socket */ +@@ -4764,8 +4764,8 @@ static int selinux_socket_post_create(st + static int selinux_socket_socketpair(struct socket *socka, + struct socket *sockb) + { +- struct sk_security_struct *sksec_a = socka->sk->sk_security; +- struct sk_security_struct *sksec_b = sockb->sk->sk_security; ++ struct sk_security_struct *sksec_a = selinux_sock(socka->sk); ++ struct sk_security_struct *sksec_b = selinux_sock(sockb->sk); + + sksec_a->peer_sid = sksec_b->sid; + sksec_b->peer_sid = sksec_a->sid; +@@ -4780,7 +4780,7 @@ static int selinux_socket_socketpair(str + static int selinux_socket_bind(struct socket *sock, struct sockaddr *address, int addrlen) + { + struct sock *sk = sock->sk; +- struct sk_security_struct *sksec = sk->sk_security; ++ struct sk_security_struct *sksec = selinux_sock(sk); + u16 family; + int err; + +@@ -4920,7 +4920,7 @@ static int selinux_socket_connect_helper + struct sockaddr *address, int addrlen) + { + struct sock *sk = sock->sk; +- struct sk_security_struct *sksec = sk->sk_security; ++ struct sk_security_struct *sksec = selinux_sock(sk); + int err; + + err = sock_has_perm(sk, SOCKET__CONNECT); +@@ -5115,9 +5115,9 @@ static int selinux_socket_unix_stream_co + struct sock *other, + struct sock *newsk) + { +- struct sk_security_struct *sksec_sock = sock->sk_security; +- struct sk_security_struct *sksec_other = other->sk_security; +- struct sk_security_struct *sksec_new = newsk->sk_security; ++ struct sk_security_struct *sksec_sock = selinux_sock(sock); ++ struct sk_security_struct *sksec_other = selinux_sock(other); ++ struct sk_security_struct *sksec_new = selinux_sock(newsk); + struct common_audit_data ad; + struct lsm_network_audit net; + int err; +@@ -5146,8 +5146,8 @@ static int selinux_socket_unix_stream_co + static int selinux_socket_unix_may_send(struct socket *sock, + struct socket *other) + { +- struct sk_security_struct *ssec = sock->sk->sk_security; +- struct sk_security_struct *osec = other->sk->sk_security; ++ struct sk_security_struct *ssec = selinux_sock(sock->sk); ++ struct sk_security_struct *osec = selinux_sock(other->sk); + struct common_audit_data ad; + struct lsm_network_audit net; + +@@ -5184,7 +5184,7 @@ static int selinux_sock_rcv_skb_compat(s + u16 family) + { + int err = 0; +- struct sk_security_struct *sksec = sk->sk_security; ++ struct sk_security_struct *sksec = selinux_sock(sk); + u32 sk_sid = sksec->sid; + struct common_audit_data ad; + struct lsm_network_audit net; +@@ -5213,7 +5213,7 @@ static int selinux_sock_rcv_skb_compat(s + static int selinux_socket_sock_rcv_skb(struct sock *sk, struct sk_buff *skb) + { + int err, peerlbl_active, secmark_active; +- struct sk_security_struct *sksec = sk->sk_security; ++ struct sk_security_struct *sksec = selinux_sock(sk); + u16 family = sk->sk_family; + u32 sk_sid = sksec->sid; + struct common_audit_data ad; +@@ -5281,7 +5281,7 @@ static int selinux_socket_getpeersec_str + int err = 0; + char *scontext = NULL; + u32 scontext_len; +- struct sk_security_struct *sksec = sock->sk->sk_security; ++ struct sk_security_struct *sksec = selinux_sock(sock->sk); + u32 peer_sid = SECSID_NULL; + + if (sksec->sclass == SECCLASS_UNIX_STREAM_SOCKET || +@@ -5339,34 +5339,27 @@ out: + + static int selinux_sk_alloc_security(struct sock *sk, int family, gfp_t priority) + { +- struct sk_security_struct *sksec; +- +- sksec = kzalloc(sizeof(*sksec), priority); +- if (!sksec) +- return -ENOMEM; ++ struct sk_security_struct *sksec = selinux_sock(sk); + + sksec->peer_sid = SECINITSID_UNLABELED; + sksec->sid = SECINITSID_UNLABELED; + sksec->sclass = SECCLASS_SOCKET; + selinux_netlbl_sk_security_reset(sksec); +- sk->sk_security = sksec; + + return 0; + } + + static void selinux_sk_free_security(struct sock *sk) + { +- struct sk_security_struct *sksec = sk->sk_security; ++ struct sk_security_struct *sksec = selinux_sock(sk); + +- sk->sk_security = NULL; + selinux_netlbl_sk_security_free(sksec); +- kfree(sksec); + } + + static void selinux_sk_clone_security(const struct sock *sk, struct sock *newsk) + { +- struct sk_security_struct *sksec = sk->sk_security; +- struct sk_security_struct *newsksec = newsk->sk_security; ++ struct sk_security_struct *sksec = selinux_sock(sk); ++ struct sk_security_struct *newsksec = selinux_sock(newsk); + + newsksec->sid = sksec->sid; + newsksec->peer_sid = sksec->peer_sid; +@@ -5380,7 +5373,7 @@ static void selinux_sk_getsecid(const st + if (!sk) + *secid = SECINITSID_ANY_SOCKET; + else { +- const struct sk_security_struct *sksec = sk->sk_security; ++ const struct sk_security_struct *sksec = selinux_sock(sk); + + *secid = sksec->sid; + } +@@ -5390,7 +5383,7 @@ static void selinux_sock_graft(struct so + { + struct inode_security_struct *isec = + inode_security_novalidate(SOCK_INODE(parent)); +- struct sk_security_struct *sksec = sk->sk_security; ++ struct sk_security_struct *sksec = selinux_sock(sk); + + if (sk->sk_family == PF_INET || sk->sk_family == PF_INET6 || + sk->sk_family == PF_UNIX) +@@ -5407,7 +5400,7 @@ static int selinux_sctp_process_new_asso + { + struct sock *sk = asoc->base.sk; + u16 family = sk->sk_family; +- struct sk_security_struct *sksec = sk->sk_security; ++ struct sk_security_struct *sksec = selinux_sock(sk); + struct common_audit_data ad; + struct lsm_network_audit net; + int err; +@@ -5462,7 +5455,7 @@ static int selinux_sctp_process_new_asso + static int selinux_sctp_assoc_request(struct sctp_association *asoc, + struct sk_buff *skb) + { +- struct sk_security_struct *sksec = asoc->base.sk->sk_security; ++ struct sk_security_struct *sksec = selinux_sock(asoc->base.sk); + u32 conn_sid; + int err; + +@@ -5495,7 +5488,7 @@ static int selinux_sctp_assoc_request(st + static int selinux_sctp_assoc_established(struct sctp_association *asoc, + struct sk_buff *skb) + { +- struct sk_security_struct *sksec = asoc->base.sk->sk_security; ++ struct sk_security_struct *sksec = selinux_sock(asoc->base.sk); + + if (!selinux_policycap_extsockclass()) + return 0; +@@ -5594,8 +5587,8 @@ static int selinux_sctp_bind_connect(str + static void selinux_sctp_sk_clone(struct sctp_association *asoc, struct sock *sk, + struct sock *newsk) + { +- struct sk_security_struct *sksec = sk->sk_security; +- struct sk_security_struct *newsksec = newsk->sk_security; ++ struct sk_security_struct *sksec = selinux_sock(sk); ++ struct sk_security_struct *newsksec = selinux_sock(newsk); + + /* If policy does not support SECCLASS_SCTP_SOCKET then call + * the non-sctp clone version. +@@ -5611,8 +5604,8 @@ static void selinux_sctp_sk_clone(struct + + static int selinux_mptcp_add_subflow(struct sock *sk, struct sock *ssk) + { +- struct sk_security_struct *ssksec = ssk->sk_security; +- struct sk_security_struct *sksec = sk->sk_security; ++ struct sk_security_struct *ssksec = selinux_sock(ssk); ++ struct sk_security_struct *sksec = selinux_sock(sk); + + ssksec->sclass = sksec->sclass; + ssksec->sid = sksec->sid; +@@ -5627,7 +5620,7 @@ static int selinux_mptcp_add_subflow(str + static int selinux_inet_conn_request(const struct sock *sk, struct sk_buff *skb, + struct request_sock *req) + { +- struct sk_security_struct *sksec = sk->sk_security; ++ struct sk_security_struct *sksec = selinux_sock(sk); + int err; + u16 family = req->rsk_ops->family; + u32 connsid; +@@ -5648,7 +5641,7 @@ static int selinux_inet_conn_request(con + static void selinux_inet_csk_clone(struct sock *newsk, + const struct request_sock *req) + { +- struct sk_security_struct *newsksec = newsk->sk_security; ++ struct sk_security_struct *newsksec = selinux_sock(newsk); + + newsksec->sid = req->secid; + newsksec->peer_sid = req->peer_secid; +@@ -5665,7 +5658,7 @@ static void selinux_inet_csk_clone(struc + static void selinux_inet_conn_established(struct sock *sk, struct sk_buff *skb) + { + u16 family = sk->sk_family; +- struct sk_security_struct *sksec = sk->sk_security; ++ struct sk_security_struct *sksec = selinux_sock(sk); + + /* handle mapped IPv4 packets arriving via IPv6 sockets */ + if (family == PF_INET6 && skb->protocol == htons(ETH_P_IP)) +@@ -5746,7 +5739,7 @@ static int selinux_tun_dev_attach_queue( + static int selinux_tun_dev_attach(struct sock *sk, void *security) + { + struct tun_security_struct *tunsec = security; +- struct sk_security_struct *sksec = sk->sk_security; ++ struct sk_security_struct *sksec = selinux_sock(sk); + + /* we don't currently perform any NetLabel based labeling here and it + * isn't clear that we would want to do so anyway; while we could apply +@@ -5869,7 +5862,7 @@ static unsigned int selinux_ip_output(vo + return NF_ACCEPT; + + /* standard practice, label using the parent socket */ +- sksec = sk->sk_security; ++ sksec = selinux_sock(sk); + sid = sksec->sid; + } else + sid = SECINITSID_KERNEL; +@@ -5892,7 +5885,7 @@ static unsigned int selinux_ip_postroute + sk = skb_to_full_sk(skb); + if (sk == NULL) + return NF_ACCEPT; +- sksec = sk->sk_security; ++ sksec = selinux_sock(sk); + + ad_net_init_from_iif(&ad, &net, state->out->ifindex, state->pf); + if (selinux_parse_skb(skb, &ad, NULL, 0, &proto)) +@@ -5981,7 +5974,7 @@ static unsigned int selinux_ip_postroute + u32 skb_sid; + struct sk_security_struct *sksec; + +- sksec = sk->sk_security; ++ sksec = selinux_sock(sk); + if (selinux_skb_peerlbl_sid(skb, family, &skb_sid)) + return NF_DROP; + /* At this point, if the returned skb peerlbl is SECSID_NULL +@@ -6010,7 +6003,7 @@ static unsigned int selinux_ip_postroute + } else { + /* Locally generated packet, fetch the security label from the + * associated socket. */ +- struct sk_security_struct *sksec = sk->sk_security; ++ struct sk_security_struct *sksec = selinux_sock(sk); + peer_sid = sksec->sid; + secmark_perm = PACKET__SEND; + } +@@ -6053,7 +6046,7 @@ static int selinux_netlink_send(struct s + unsigned int data_len = skb->len; + unsigned char *data = skb->data; + struct nlmsghdr *nlh; +- struct sk_security_struct *sksec = sk->sk_security; ++ struct sk_security_struct *sksec = selinux_sock(sk); + u16 sclass = sksec->sclass; + u32 perm; + +@@ -7008,6 +7001,7 @@ struct lsm_blob_sizes selinux_blob_sizes + .lbs_inode = sizeof(struct inode_security_struct), + .lbs_ipc = sizeof(struct ipc_security_struct), + .lbs_msg_msg = sizeof(struct msg_security_struct), ++ .lbs_sock = sizeof(struct sk_security_struct), + .lbs_superblock = sizeof(struct superblock_security_struct), + .lbs_xattr_count = SELINUX_INODE_INIT_XATTRS, + }; +--- a/security/selinux/include/objsec.h ++++ b/security/selinux/include/objsec.h +@@ -205,4 +205,9 @@ static inline struct superblock_security + return superblock->s_security + selinux_blob_sizes.lbs_superblock; + } + ++static inline struct sk_security_struct *selinux_sock(const struct sock *sock) ++{ ++ return sock->sk_security + selinux_blob_sizes.lbs_sock; ++} ++ + #endif /* _SELINUX_OBJSEC_H_ */ +--- a/security/selinux/netlabel.c ++++ b/security/selinux/netlabel.c +@@ -17,6 +17,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -68,7 +69,7 @@ static int selinux_netlbl_sidlookup_cach + static struct netlbl_lsm_secattr *selinux_netlbl_sock_genattr(struct sock *sk) + { + int rc; +- struct sk_security_struct *sksec = sk->sk_security; ++ struct sk_security_struct *sksec = selinux_sock(sk); + struct netlbl_lsm_secattr *secattr; + + if (sksec->nlbl_secattr != NULL) +@@ -100,7 +101,7 @@ static struct netlbl_lsm_secattr *selinu + const struct sock *sk, + u32 sid) + { +- struct sk_security_struct *sksec = sk->sk_security; ++ struct sk_security_struct *sksec = selinux_sock(sk); + struct netlbl_lsm_secattr *secattr = sksec->nlbl_secattr; + + if (secattr == NULL) +@@ -240,7 +241,7 @@ int selinux_netlbl_skbuff_setsid(struct + * being labeled by it's parent socket, if it is just exit */ + sk = skb_to_full_sk(skb); + if (sk != NULL) { +- struct sk_security_struct *sksec = sk->sk_security; ++ struct sk_security_struct *sksec = selinux_sock(sk); + + if (sksec->nlbl_state != NLBL_REQSKB) + return 0; +@@ -277,7 +278,7 @@ int selinux_netlbl_sctp_assoc_request(st + { + int rc; + struct netlbl_lsm_secattr secattr; +- struct sk_security_struct *sksec = asoc->base.sk->sk_security; ++ struct sk_security_struct *sksec = selinux_sock(asoc->base.sk); + struct sockaddr_in addr4; + struct sockaddr_in6 addr6; + +@@ -356,7 +357,7 @@ inet_conn_request_return: + */ + void selinux_netlbl_inet_csk_clone(struct sock *sk, u16 family) + { +- struct sk_security_struct *sksec = sk->sk_security; ++ struct sk_security_struct *sksec = selinux_sock(sk); + + if (family == PF_INET) + sksec->nlbl_state = NLBL_LABELED; +@@ -374,8 +375,8 @@ void selinux_netlbl_inet_csk_clone(struc + */ + void selinux_netlbl_sctp_sk_clone(struct sock *sk, struct sock *newsk) + { +- struct sk_security_struct *sksec = sk->sk_security; +- struct sk_security_struct *newsksec = newsk->sk_security; ++ struct sk_security_struct *sksec = selinux_sock(sk); ++ struct sk_security_struct *newsksec = selinux_sock(newsk); + + newsksec->nlbl_state = sksec->nlbl_state; + } +@@ -393,7 +394,7 @@ void selinux_netlbl_sctp_sk_clone(struct + int selinux_netlbl_socket_post_create(struct sock *sk, u16 family) + { + int rc; +- struct sk_security_struct *sksec = sk->sk_security; ++ struct sk_security_struct *sksec = selinux_sock(sk); + struct netlbl_lsm_secattr *secattr; + + if (family != PF_INET && family != PF_INET6) +@@ -507,7 +508,7 @@ int selinux_netlbl_socket_setsockopt(str + { + int rc = 0; + struct sock *sk = sock->sk; +- struct sk_security_struct *sksec = sk->sk_security; ++ struct sk_security_struct *sksec = selinux_sock(sk); + struct netlbl_lsm_secattr secattr; + + if (selinux_netlbl_option(level, optname) && +@@ -545,7 +546,7 @@ static int selinux_netlbl_socket_connect + struct sockaddr *addr) + { + int rc; +- struct sk_security_struct *sksec = sk->sk_security; ++ struct sk_security_struct *sksec = selinux_sock(sk); + struct netlbl_lsm_secattr *secattr; + + /* connected sockets are allowed to disconnect when the address family +@@ -584,7 +585,7 @@ static int selinux_netlbl_socket_connect + int selinux_netlbl_socket_connect_locked(struct sock *sk, + struct sockaddr *addr) + { +- struct sk_security_struct *sksec = sk->sk_security; ++ struct sk_security_struct *sksec = selinux_sock(sk); + + if (sksec->nlbl_state != NLBL_REQSKB && + sksec->nlbl_state != NLBL_CONNLABELED) +--- a/security/smack/smack.h ++++ b/security/smack/smack.h +@@ -361,6 +361,11 @@ static inline struct superblock_smack *s + return superblock->s_security + smack_blob_sizes.lbs_superblock; + } + ++static inline struct socket_smack *smack_sock(const struct sock *sock) ++{ ++ return sock->sk_security + smack_blob_sizes.lbs_sock; ++} ++ + /* + * Is the directory transmuting? + */ +--- a/security/smack/smack_lsm.c ++++ b/security/smack/smack_lsm.c +@@ -1572,7 +1572,7 @@ static int smack_inode_getsecurity(struc + if (sock == NULL || sock->sk == NULL) + return -EOPNOTSUPP; + +- ssp = sock->sk->sk_security; ++ ssp = smack_sock(sock->sk); + + if (strcmp(name, XATTR_SMACK_IPIN) == 0) + isp = ssp->smk_in; +@@ -1960,7 +1960,7 @@ static int smack_file_receive(struct fil + + if (inode->i_sb->s_magic == SOCKFS_MAGIC) { + sock = SOCKET_I(inode); +- ssp = sock->sk->sk_security; ++ ssp = smack_sock(sock->sk); + tsp = smack_cred(current_cred()); + /* + * If the receiving process can't write to the +@@ -2380,11 +2380,7 @@ static void smack_task_to_inode(struct t + static int smack_sk_alloc_security(struct sock *sk, int family, gfp_t gfp_flags) + { + struct smack_known *skp = smk_of_current(); +- struct socket_smack *ssp; +- +- ssp = kzalloc(sizeof(struct socket_smack), gfp_flags); +- if (ssp == NULL) +- return -ENOMEM; ++ struct socket_smack *ssp = smack_sock(sk); + + /* + * Sockets created by kernel threads receive web label. +@@ -2398,11 +2394,10 @@ static int smack_sk_alloc_security(struc + } + ssp->smk_packet = NULL; + +- sk->sk_security = ssp; +- + return 0; + } + ++#ifdef SMACK_IPV6_PORT_LABELING + /** + * smack_sk_free_security - Free a socket blob + * @sk: the socket +@@ -2411,7 +2406,6 @@ static int smack_sk_alloc_security(struc + */ + static void smack_sk_free_security(struct sock *sk) + { +-#ifdef SMACK_IPV6_PORT_LABELING + struct smk_port_label *spp; + + if (sk->sk_family == PF_INET6) { +@@ -2424,9 +2418,8 @@ static void smack_sk_free_security(struc + } + rcu_read_unlock(); + } +-#endif +- kfree(sk->sk_security); + } ++#endif + + /** + * smack_sk_clone_security - Copy security context +@@ -2437,8 +2430,8 @@ static void smack_sk_free_security(struc + */ + static void smack_sk_clone_security(const struct sock *sk, struct sock *newsk) + { +- struct socket_smack *ssp_old = sk->sk_security; +- struct socket_smack *ssp_new = newsk->sk_security; ++ struct socket_smack *ssp_old = smack_sock(sk); ++ struct socket_smack *ssp_new = smack_sock(newsk); + + *ssp_new = *ssp_old; + } +@@ -2556,7 +2549,7 @@ static struct smack_known *smack_ipv6hos + */ + static int smack_netlbl_add(struct sock *sk) + { +- struct socket_smack *ssp = sk->sk_security; ++ struct socket_smack *ssp = smack_sock(sk); + struct smack_known *skp = ssp->smk_out; + int rc; + +@@ -2588,7 +2581,7 @@ static int smack_netlbl_add(struct sock + */ + static void smack_netlbl_delete(struct sock *sk) + { +- struct socket_smack *ssp = sk->sk_security; ++ struct socket_smack *ssp = smack_sock(sk); + + /* + * Take the label off the socket if one is set. +@@ -2620,7 +2613,7 @@ static int smk_ipv4_check(struct sock *s + struct smack_known *skp; + int rc = 0; + struct smack_known *hkp; +- struct socket_smack *ssp = sk->sk_security; ++ struct socket_smack *ssp = smack_sock(sk); + struct smk_audit_info ad; + + rcu_read_lock(); +@@ -2695,7 +2688,7 @@ static void smk_ipv6_port_label(struct s + { + struct sock *sk = sock->sk; + struct sockaddr_in6 *addr6; +- struct socket_smack *ssp = sock->sk->sk_security; ++ struct socket_smack *ssp = smack_sock(sock->sk); + struct smk_port_label *spp; + unsigned short port = 0; + +@@ -2783,7 +2776,7 @@ static int smk_ipv6_port_check(struct so + int act) + { + struct smk_port_label *spp; +- struct socket_smack *ssp = sk->sk_security; ++ struct socket_smack *ssp = smack_sock(sk); + struct smack_known *skp = NULL; + unsigned short port; + struct smack_known *object; +@@ -2886,7 +2879,7 @@ static int smack_inode_setsecurity(struc + if (sock == NULL || sock->sk == NULL) + return -EOPNOTSUPP; + +- ssp = sock->sk->sk_security; ++ ssp = smack_sock(sock->sk); + + if (strcmp(name, XATTR_SMACK_IPIN) == 0) + ssp->smk_in = skp; +@@ -2934,7 +2927,7 @@ static int smack_socket_post_create(stru + * Sockets created by kernel threads receive web label. + */ + if (unlikely(current->flags & PF_KTHREAD)) { +- ssp = sock->sk->sk_security; ++ ssp = smack_sock(sock->sk); + ssp->smk_in = &smack_known_web; + ssp->smk_out = &smack_known_web; + } +@@ -2959,8 +2952,8 @@ static int smack_socket_post_create(stru + static int smack_socket_socketpair(struct socket *socka, + struct socket *sockb) + { +- struct socket_smack *asp = socka->sk->sk_security; +- struct socket_smack *bsp = sockb->sk->sk_security; ++ struct socket_smack *asp = smack_sock(socka->sk); ++ struct socket_smack *bsp = smack_sock(sockb->sk); + + asp->smk_packet = bsp->smk_out; + bsp->smk_packet = asp->smk_out; +@@ -3025,7 +3018,7 @@ static int smack_socket_connect(struct s + if (__is_defined(SMACK_IPV6_SECMARK_LABELING)) + rsp = smack_ipv6host_label(sip); + if (rsp != NULL) { +- struct socket_smack *ssp = sock->sk->sk_security; ++ struct socket_smack *ssp = smack_sock(sock->sk); + + rc = smk_ipv6_check(ssp->smk_out, rsp, sip, + SMK_CONNECTING); +@@ -3763,9 +3756,9 @@ static int smack_unix_stream_connect(str + { + struct smack_known *skp; + struct smack_known *okp; +- struct socket_smack *ssp = sock->sk_security; +- struct socket_smack *osp = other->sk_security; +- struct socket_smack *nsp = newsk->sk_security; ++ struct socket_smack *ssp = smack_sock(sock); ++ struct socket_smack *osp = smack_sock(other); ++ struct socket_smack *nsp = smack_sock(newsk); + struct smk_audit_info ad; + int rc = 0; + #ifdef CONFIG_AUDIT +@@ -3817,8 +3810,8 @@ static int smack_unix_stream_connect(str + */ + static int smack_unix_may_send(struct socket *sock, struct socket *other) + { +- struct socket_smack *ssp = sock->sk->sk_security; +- struct socket_smack *osp = other->sk->sk_security; ++ struct socket_smack *ssp = smack_sock(sock->sk); ++ struct socket_smack *osp = smack_sock(other->sk); + struct smk_audit_info ad; + int rc; + +@@ -3855,7 +3848,7 @@ static int smack_socket_sendmsg(struct s + struct sockaddr_in6 *sap = (struct sockaddr_in6 *) msg->msg_name; + #endif + #ifdef SMACK_IPV6_SECMARK_LABELING +- struct socket_smack *ssp = sock->sk->sk_security; ++ struct socket_smack *ssp = smack_sock(sock->sk); + struct smack_known *rsp; + #endif + int rc = 0; +@@ -4067,7 +4060,7 @@ static struct smack_known *smack_from_ne + netlbl_secattr_init(&secattr); + + if (sk) +- ssp = sk->sk_security; ++ ssp = smack_sock(sk); + + if (netlbl_skbuff_getattr(skb, family, &secattr) == 0) { + skp = smack_from_secattr(&secattr, ssp); +@@ -4089,7 +4082,7 @@ static struct smack_known *smack_from_ne + */ + static int smack_socket_sock_rcv_skb(struct sock *sk, struct sk_buff *skb) + { +- struct socket_smack *ssp = sk->sk_security; ++ struct socket_smack *ssp = smack_sock(sk); + struct smack_known *skp = NULL; + int rc = 0; + struct smk_audit_info ad; +@@ -4193,7 +4186,7 @@ static int smack_socket_getpeersec_strea + u32 slen = 1; + int rc = 0; + +- ssp = sock->sk->sk_security; ++ ssp = smack_sock(sock->sk); + if (ssp->smk_packet != NULL) { + rcp = ssp->smk_packet->smk_known; + slen = strlen(rcp) + 1; +@@ -4243,7 +4236,7 @@ static int smack_socket_getpeersec_dgram + + switch (family) { + case PF_UNIX: +- ssp = sock->sk->sk_security; ++ ssp = smack_sock(sock->sk); + s = ssp->smk_out->smk_secid; + break; + case PF_INET: +@@ -4292,7 +4285,7 @@ static void smack_sock_graft(struct sock + (sk->sk_family != PF_INET && sk->sk_family != PF_INET6)) + return; + +- ssp = sk->sk_security; ++ ssp = smack_sock(sk); + ssp->smk_in = skp; + ssp->smk_out = skp; + /* cssp->smk_packet is already set in smack_inet_csk_clone() */ +@@ -4312,7 +4305,7 @@ static int smack_inet_conn_request(const + { + u16 family = sk->sk_family; + struct smack_known *skp; +- struct socket_smack *ssp = sk->sk_security; ++ struct socket_smack *ssp = smack_sock(sk); + struct sockaddr_in addr; + struct iphdr *hdr; + struct smack_known *hskp; +@@ -4398,7 +4391,7 @@ static int smack_inet_conn_request(const + static void smack_inet_csk_clone(struct sock *sk, + const struct request_sock *req) + { +- struct socket_smack *ssp = sk->sk_security; ++ struct socket_smack *ssp = smack_sock(sk); + struct smack_known *skp; + + if (req->peer_secid != 0) { +@@ -4968,6 +4961,7 @@ struct lsm_blob_sizes smack_blob_sizes _ + .lbs_inode = sizeof(struct inode_smack), + .lbs_ipc = sizeof(struct smack_known *), + .lbs_msg_msg = sizeof(struct smack_known *), ++ .lbs_sock = sizeof(struct socket_smack), + .lbs_superblock = sizeof(struct superblock_smack), + .lbs_xattr_count = SMACK_INODE_INIT_XATTRS, + }; +@@ -5084,7 +5078,9 @@ static struct security_hook_list smack_h + LSM_HOOK_INIT(socket_getpeersec_stream, smack_socket_getpeersec_stream), + LSM_HOOK_INIT(socket_getpeersec_dgram, smack_socket_getpeersec_dgram), + LSM_HOOK_INIT(sk_alloc_security, smack_sk_alloc_security), ++#ifdef SMACK_IPV6_PORT_LABELING + LSM_HOOK_INIT(sk_free_security, smack_sk_free_security), ++#endif + LSM_HOOK_INIT(sk_clone_security, smack_sk_clone_security), + LSM_HOOK_INIT(sock_graft, smack_sock_graft), + LSM_HOOK_INIT(inet_conn_request, smack_inet_conn_request), +--- a/security/smack/smack_netfilter.c ++++ b/security/smack/smack_netfilter.c +@@ -26,8 +26,8 @@ static unsigned int smack_ip_output(void + struct socket_smack *ssp; + struct smack_known *skp; + +- if (sk && sk->sk_security) { +- ssp = sk->sk_security; ++ if (sk) { ++ ssp = smack_sock(sk); + skp = ssp->smk_out; + skb->secmark = skp->smk_secid; + } diff --git a/queue-6.6/mm-mm_init-fix-pageblock-migratetype-for-zone_device-compound-pages.patch b/queue-6.6/mm-mm_init-fix-pageblock-migratetype-for-zone_device-compound-pages.patch new file mode 100644 index 0000000000..9e4222ea7c --- /dev/null +++ b/queue-6.6/mm-mm_init-fix-pageblock-migratetype-for-zone_device-compound-pages.patch @@ -0,0 +1,115 @@ +From stable+bounces-289088-greg=kroah.com@vger.kernel.org Sat Jul 25 12:55:29 2026 +From: Sasha Levin +Date: Sat, 25 Jul 2026 06:55:21 -0400 +Subject: mm/mm_init: fix pageblock migratetype for ZONE_DEVICE compound pages +To: stable@vger.kernel.org +Cc: Muchun Song , "Mike Rapoport (Microsoft)" , Oscar Salvador , "David Hildenbrand (Arm)" , "Liam R. Howlett" , "Aneesh Kumar K.V" , Joao Martins , Lorenzo Stoakes , Madhavan Srinivasan , Michael Ellerman , Michal Hocko , Nicholas Piggin , Suren Baghdasaryan , Vlastimil Babka , Andrew Morton , Sasha Levin +Message-ID: <20260725105521.1855856-1-sashal@kernel.org> + +From: Muchun Song + +[ Upstream commit 94405c6136839f7c462249c8b4b957bcb9527a9d ] + +The memmap_init_zone_device() function only initializes the migratetype of +the first pageblock of a compound page. If the compound page size exceeds +pageblock_nr_pages (e.g., 1GB hugepages with 2MB pageblocks), subsequent +pageblocks in the compound page remain uninitialized. + +Move the migratetype initialization out of __init_zone_device_page() and +into a separate pageblock_migratetype_init_range() function. This +iterates over the entire PFN range of the memory, ensuring that all +pageblocks are correctly initialized. + +Also remove the stale confusing comment about MEMINIT_HOTPLUG above the +migratetype setting since it is an obsolete relic from commit 966cf44f637e +("mm: defer ZONE_DEVICE page initialization to the point where we init +pgmap") and no longer makes sense here. + +Link: https://lore.kernel.org/20260428081855.1249045-6-songmuchun@bytedance.com +Fixes: c4386bd8ee3a ("mm/memremap: add ZONE_DEVICE support for compound pages") +Signed-off-by: Muchun Song +Reviewed-by: Mike Rapoport (Microsoft) +Reviewed-by: Oscar Salvador +Acked-by: David Hildenbrand (Arm) +Acked-by: Liam R. Howlett +Cc: "Aneesh Kumar K.V" +Cc: Joao Martins +Cc: Lorenzo Stoakes +Cc: Madhavan Srinivasan +Cc: Michael Ellerman +Cc: Michal Hocko +Cc: Nicholas Piggin +Cc: Suren Baghdasaryan +Cc: Vlastimil Babka +Cc: +Signed-off-by: Andrew Morton +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + mm/mm_init.c | 34 +++++++++++++++++++--------------- + 1 file changed, 19 insertions(+), 15 deletions(-) + +--- a/mm/mm_init.c ++++ b/mm/mm_init.c +@@ -649,6 +649,20 @@ static inline void fixup_hashdist(void) + static inline void fixup_hashdist(void) {} + #endif /* CONFIG_NUMA */ + ++#ifdef CONFIG_ZONE_DEVICE ++static __meminit void pageblock_migratetype_init_range(unsigned long pfn, ++ unsigned long nr_pages, int migratetype) ++{ ++ const unsigned long end = pfn + nr_pages; ++ ++ for (pfn = pageblock_align(pfn); pfn < end; pfn += pageblock_nr_pages) { ++ set_pageblock_migratetype(pfn_to_page(pfn), migratetype); ++ if (IS_ALIGNED(pfn, PAGES_PER_SECTION)) ++ cond_resched(); ++ } ++} ++#endif ++ + #ifdef CONFIG_DEFERRED_STRUCT_PAGE_INIT + static inline void pgdat_set_deferred_range(pg_data_t *pgdat) + { +@@ -993,21 +1007,6 @@ static void __ref __init_zone_device_pag + page->zone_device_data = NULL; + + /* +- * Mark the block movable so that blocks are reserved for +- * movable at startup. This will force kernel allocations +- * to reserve their blocks rather than leaking throughout +- * the address space during boot when many long-lived +- * kernel allocations are made. +- * +- * Please note that MEMINIT_HOTPLUG path doesn't clear memmap +- * because this is done early in section_activate() +- */ +- if (pageblock_aligned(pfn)) { +- set_pageblock_migratetype(page, MIGRATE_MOVABLE); +- cond_resched(); +- } +- +- /* + * ZONE_DEVICE pages are released directly to the driver page allocator + * which will set the page count to 1 when allocating the page. + */ +@@ -1098,6 +1097,9 @@ void __ref memmap_init_zone_device(struc + + __init_zone_device_page(page, pfn, zone_idx, nid, pgmap); + ++ if (IS_ALIGNED(pfn, PAGES_PER_SECTION)) ++ cond_resched(); ++ + if (pfns_per_compound == 1) + continue; + +@@ -1105,6 +1107,8 @@ void __ref memmap_init_zone_device(struc + compound_nr_pages(pfn, altmap, pgmap)); + } + ++ pageblock_migratetype_init_range(start_pfn, nr_pages, MIGRATE_MOVABLE); ++ + pr_debug("%s initialised %lu pages in %ums\n", __func__, + nr_pages, jiffies_to_msecs(jiffies - start)); + } diff --git a/queue-6.6/mmc-vub300-fix-use-after-free-on-disconnect.patch b/queue-6.6/mmc-vub300-fix-use-after-free-on-disconnect.patch new file mode 100644 index 0000000000..e963d13783 --- /dev/null +++ b/queue-6.6/mmc-vub300-fix-use-after-free-on-disconnect.patch @@ -0,0 +1,90 @@ +From stable+bounces-289774-greg=kroah.com@vger.kernel.org Tue Jul 28 02:59:39 2026 +From: Sasha Levin +Date: Mon, 27 Jul 2026 20:58:03 -0400 +Subject: mmc: vub300: fix use-after-free on disconnect +To: stable@vger.kernel.org +Cc: Johan Hovold , Binbin Zhou , Ulf Hansson +Message-ID: <20260728005806.2694893-1-sashal@kernel.org> + +From: Johan Hovold + +The vub300 driver maintains an explicit reference count for the +controller and its driver data and the last reference can in theory be +dropped after the driver has been unbound. + +This specifically means that the controller allocation must not be +device managed as that can lead to use-after-free. + +Note that the lifetime is currently also incorrectly tied the parent USB +device rather than interface, which can lead to memory leaks if the +driver is unbound without its device being physically disconnected (e.g. +on probe deferral). + +Fix both issues by reverting to non-managed allocation of the controller. + +Fixes: dcfdd698dc52 ("mmc: vub300: Use devm_mmc_alloc_host() helper") +Cc: stable@vger.kernel.org # 6.17+ +Cc: Binbin Zhou +Signed-off-by: Johan Hovold +Signed-off-by: Ulf Hansson +(cherry picked from commit 8f4d20a710225ec7a565f6a0459862d3b1f32330) +Signed-off-by: Greg Kroah-Hartman +--- + drivers/mmc/host/vub300.c | 12 ++++++------ + 1 file changed, 6 insertions(+), 6 deletions(-) + +--- a/drivers/mmc/host/vub300.c ++++ b/drivers/mmc/host/vub300.c +@@ -2279,7 +2279,7 @@ static int vub300_probe(struct usb_inter + dev_err(&vub300->udev->dev, + "Could not find two sets of bulk-in/out endpoint pairs\n"); + retval = -EINVAL; +- goto error5; ++ goto err_free_host; + } + retval = + usb_control_msg(vub300->udev, usb_rcvctrlpipe(vub300->udev, 0), +@@ -2288,14 +2288,14 @@ static int vub300_probe(struct usb_inter + 0x0000, 0x0000, &vub300->hc_info, + sizeof(vub300->hc_info), 1000); + if (retval < 0) +- goto error5; ++ goto err_free_host; + retval = + usb_control_msg(vub300->udev, usb_sndctrlpipe(vub300->udev, 0), + SET_ROM_WAIT_STATES, + USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + firmware_rom_wait_states, 0x0000, NULL, 0, 1000); + if (retval < 0) +- goto error5; ++ goto err_free_host; + dev_info(&vub300->udev->dev, + "operating_mode = %s %s %d MHz %s %d byte USB packets\n", + (mmc->caps & MMC_CAP_SDIO_IRQ) ? "IRQs" : "POLL", +@@ -2310,7 +2310,7 @@ static int vub300_probe(struct usb_inter + 0x0000, 0x0000, &vub300->system_port_status, + sizeof(vub300->system_port_status), 1000); + if (retval < 0) { +- goto error5; ++ goto err_free_host; + } else if (sizeof(vub300->system_port_status) == retval) { + vub300->card_present = + (0x0001 & vub300->system_port_status.port_flags) ? 1 : 0; +@@ -2318,7 +2318,7 @@ static int vub300_probe(struct usb_inter + (0x0010 & vub300->system_port_status.port_flags) ? 1 : 0; + } else { + retval = -EINVAL; +- goto error5; ++ goto err_free_host; + } + usb_set_intfdata(interface, vub300); + INIT_DELAYED_WORK(&vub300->pollwork, vub300_pollwork_thread); +@@ -2348,7 +2348,7 @@ static int vub300_probe(struct usb_inter + return 0; + error6: + del_timer_sync(&vub300->inactivity_timer); +-error5: ++err_free_host: + mmc_free_host(mmc); + /* + * and hence also frees vub300 diff --git a/queue-6.6/mmc-vub300-fix-use-after-free-on-probe-failure.patch b/queue-6.6/mmc-vub300-fix-use-after-free-on-probe-failure.patch new file mode 100644 index 0000000000..01305de9cf --- /dev/null +++ b/queue-6.6/mmc-vub300-fix-use-after-free-on-probe-failure.patch @@ -0,0 +1,87 @@ +From stable+bounces-289776-greg=kroah.com@vger.kernel.org Tue Jul 28 02:59:45 2026 +From: Sasha Levin +Date: Mon, 27 Jul 2026 20:58:05 -0400 +Subject: mmc: vub300: fix use-after-free on probe failure +To: stable@vger.kernel.org +Cc: Guangshuo Li , Johan Hovold , Ulf Hansson +Message-ID: <20260728005806.2694893-3-sashal@kernel.org> + +From: Guangshuo Li + +The vub300 driver lifetime-manages its controller state using +vub300->kref, with vub300_delete() freeing the mmc host when the last +reference is dropped. The probe error path after the inactivity timer has +been armed still bypasses that lifetime rule, however, and falls through +to mmc_free_host() directly if mmc_add_host() fails. + +The race window is between arming the inactivity timer and reaching the +probe error unwind after mmc_add_host() fails: + + probe thread timer/workqueue + ------------ --------------- + kref_init(&vub300->kref) ref = 1 + kref_get(&vub300->kref) ref = 2, timer ref + add_timer(inactivity_timer) fires after one second + | + | race window + |<----------------------------------------------------> + | + mmc_add_host(mmc) + inactivity timer fires + vub300_queue_dead_work() + kref_get() ref = 3 + queue_work(deadwork) + mmc_add_host() fails + timer_delete_sync() + mmc_free_host(mmc) + frees vub300 + deadwork runs + use-after-free + +The inactivity timeout is one second, so this would require +mmc_add_host() to both fail and take more than one second to do so. This +is unlikely to happen in practice, but the error path is still wrong. + +timer_delete_sync() only waits for the timer callback itself. It does +not flush deadwork that the callback may already have queued. As a +result, queued deadwork can still hold a kref while the probe error path +directly frees the backing mmc host, including the vub300 storage. + +Fix this by using the same lifetime mechanism as disconnect. Clear +vub300->interface so that the timer callback and any queued deadwork +return early and drop their references, then drop the initial probe +reference and return without falling through to err_free_host. + +Fixes: 0613ad2401f8 ("mmc: vub300: fix return value check of mmc_add_host()") +Signed-off-by: Guangshuo Li +Reviewed-by: Johan Hovold +Cc: stable@vger.kernel.org +Signed-off-by: Ulf Hansson +(cherry picked from commit a3b5f242997a3be7404112fd48784881560aea57) +Signed-off-by: Greg Kroah-Hartman +--- + drivers/mmc/host/vub300.c | 10 +++++++--- + 1 file changed, 7 insertions(+), 3 deletions(-) + +--- a/drivers/mmc/host/vub300.c ++++ b/drivers/mmc/host/vub300.c +@@ -2343,12 +2343,16 @@ static int vub300_probe(struct usb_inter + interface_to_InterfaceNumber(interface)); + retval = mmc_add_host(mmc); + if (retval) +- goto err_delete_timer; ++ goto err_stop_io; + + return 0; + +-err_delete_timer: +- timer_delete_sync(&vub300->inactivity_timer); ++err_stop_io: ++ vub300->interface = NULL; ++ kref_put(&vub300->kref, vub300_delete); ++ ++ return retval; ++ + err_free_host: + mmc_free_host(mmc); + /* diff --git a/queue-6.6/mmc-vub300-rename-probe-error-labels.patch b/queue-6.6/mmc-vub300-rename-probe-error-labels.patch new file mode 100644 index 0000000000..d4c0d0c739 --- /dev/null +++ b/queue-6.6/mmc-vub300-rename-probe-error-labels.patch @@ -0,0 +1,79 @@ +From stable+bounces-289775-greg=kroah.com@vger.kernel.org Tue Jul 28 02:59:41 2026 +From: Sasha Levin +Date: Mon, 27 Jul 2026 20:58:04 -0400 +Subject: mmc: vub300: rename probe error labels +To: stable@vger.kernel.org +Cc: Johan Hovold , Ulf Hansson +Message-ID: <20260728005806.2694893-2-sashal@kernel.org> + +From: Johan Hovold + +Error labels should be named after what they do. + +Rename the probe error labels. + +Signed-off-by: Johan Hovold +Signed-off-by: Ulf Hansson +(cherry picked from commit 5b8b35d6f4fa758dd5e8ae18526ea1c73f6787e0) +Signed-off-by: Greg Kroah-Hartman +--- + drivers/mmc/host/vub300.c | 20 +++++++++++--------- + 1 file changed, 11 insertions(+), 9 deletions(-) + +--- a/drivers/mmc/host/vub300.c ++++ b/drivers/mmc/host/vub300.c +@@ -2114,19 +2114,19 @@ static int vub300_probe(struct usb_inter + command_out_urb = usb_alloc_urb(0, GFP_KERNEL); + if (!command_out_urb) { + retval = -ENOMEM; +- goto error0; ++ goto err_put_udev; + } + command_res_urb = usb_alloc_urb(0, GFP_KERNEL); + if (!command_res_urb) { + retval = -ENOMEM; +- goto error1; ++ goto err_free_out_urb; + } + /* this also allocates memory for our VUB300 mmc host device */ + mmc = mmc_alloc_host(sizeof(struct vub300_mmc_host), &udev->dev); + if (!mmc) { + retval = -ENOMEM; + dev_err(&udev->dev, "not enough memory for the mmc_host\n"); +- goto error4; ++ goto err_free_res_urb; + } + /* MMC core transfer sizes tunable parameters */ + mmc->caps = 0; +@@ -2343,23 +2343,25 @@ static int vub300_probe(struct usb_inter + interface_to_InterfaceNumber(interface)); + retval = mmc_add_host(mmc); + if (retval) +- goto error6; ++ goto err_delete_timer; + + return 0; +-error6: +- del_timer_sync(&vub300->inactivity_timer); ++ ++err_delete_timer: ++ timer_delete_sync(&vub300->inactivity_timer); + err_free_host: + mmc_free_host(mmc); + /* + * and hence also frees vub300 + * which is contained at the end of struct mmc + */ +-error4: ++err_free_res_urb: + usb_free_urb(command_res_urb); +-error1: ++err_free_out_urb: + usb_free_urb(command_out_urb); +-error0: ++err_put_udev: + usb_put_dev(udev); ++ + return retval; + } + diff --git a/queue-6.6/mtd-maps-vmu-flash-fix-fault-in-unaligned-fixup.patch b/queue-6.6/mtd-maps-vmu-flash-fix-fault-in-unaligned-fixup.patch new file mode 100644 index 0000000000..600b4991cb --- /dev/null +++ b/queue-6.6/mtd-maps-vmu-flash-fix-fault-in-unaligned-fixup.patch @@ -0,0 +1,58 @@ +From stable+bounces-289096-greg=kroah.com@vger.kernel.org Sat Jul 25 14:15:04 2026 +From: Sasha Levin +Date: Sat, 25 Jul 2026 08:14:47 -0400 +Subject: mtd: maps: vmu-flash: fix fault in unaligned fixup +To: stable@vger.kernel.org +Cc: Florian Fuchs , Miquel Raynal , Sasha Levin +Message-ID: <20260725121447.2570457-1-sashal@kernel.org> + +From: Florian Fuchs + +[ Upstream commit 79d1661502c6e4b6f626185cef72cf2fa78116e1 ] + +Use kzalloc_obj() / kzalloc_objs() to allocate the memcard structs, +instead of kmalloc_obj() / kmalloc_objs() to prevent access to +uninitialized data. + +Fixes runtime error: Fault in unaligned fixup: 0000 [#1] at +mtd_get_fact_prot_info. + +Fixes: 47a72688fae7 ("mtd: flash mapping support for Dreamcast VMU.") +Cc: stable@vger.kernel.org +Signed-off-by: Florian Fuchs +Signed-off-by: Miquel Raynal +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/mtd/maps/vmu-flash.c | 8 +++----- + 1 file changed, 3 insertions(+), 5 deletions(-) + +--- a/drivers/mtd/maps/vmu-flash.c ++++ b/drivers/mtd/maps/vmu-flash.c +@@ -610,7 +610,7 @@ static int vmu_connect(struct maple_devi + + basic_flash_data = be32_to_cpu(mdev->devinfo.function_data[c - 1]); + +- card = kmalloc(sizeof(struct memcard), GFP_KERNEL); ++ card = kzalloc_obj(struct memcard); + if (!card) { + error = -ENOMEM; + goto fail_nomem; +@@ -628,15 +628,13 @@ static int vmu_connect(struct maple_devi + * Not sure there are actually any multi-partition devices in the + * real world, but the hardware supports them, so, so will we + */ +- card->parts = kmalloc_array(card->partitions, sizeof(struct vmupart), +- GFP_KERNEL); ++ card->parts = kzalloc_objs(struct vmupart, card->partitions); + if (!card->parts) { + error = -ENOMEM; + goto fail_partitions; + } + +- card->mtd = kmalloc_array(card->partitions, sizeof(struct mtd_info), +- GFP_KERNEL); ++ card->mtd = kzalloc_objs(struct mtd_info, card->partitions); + if (!card->mtd) { + error = -ENOMEM; + goto fail_mtd_info; diff --git a/queue-6.6/mtd-rawnand-add-a-helper-for-calculating-a-page-index.patch b/queue-6.6/mtd-rawnand-add-a-helper-for-calculating-a-page-index.patch new file mode 100644 index 0000000000..291cea5bfb --- /dev/null +++ b/queue-6.6/mtd-rawnand-add-a-helper-for-calculating-a-page-index.patch @@ -0,0 +1,60 @@ +From stable+bounces-289264-greg=kroah.com@vger.kernel.org Sun Jul 26 14:38:52 2026 +From: Sasha Levin +Date: Sun, 26 Jul 2026 08:38:42 -0400 +Subject: mtd: rawnand: Add a helper for calculating a page index +To: stable@vger.kernel.org +Cc: Miquel Raynal , Sasha Levin +Message-ID: <20260726123844.64620-1-sashal@kernel.org> + +From: Miquel Raynal + +[ Upstream commit df9803bf5a91e3599f12b53c94722f2c4e144a86 ] + +For LUN crossing boundaries, it is handy to know what is the index of +the last page in a LUN. This helper will soon be reused. At the same +time I rename page_per_lun to ppl in the calling function to clarify the +lines. + +Cc: stable@vger.kernel.org # v6.7 +Signed-off-by: Miquel Raynal +Link: https://lore.kernel.org/linux-mtd/20240223115545.354541-3-miquel.raynal@bootlin.com +Stable-dep-of: 8e4531667d71 ("mtd: rawnand: Pause continuous reads at block boundaries") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/mtd/nand/raw/nand_base.c | 16 +++++++++++----- + 1 file changed, 11 insertions(+), 5 deletions(-) + +--- a/drivers/mtd/nand/raw/nand_base.c ++++ b/drivers/mtd/nand/raw/nand_base.c +@@ -1212,19 +1212,25 @@ static int nand_lp_exec_read_page_op(str + return nand_exec_op(chip, &op); + } + ++static unsigned int rawnand_last_page_of_lun(unsigned int pages_per_lun, unsigned int lun) ++{ ++ /* lun is expected to be very small */ ++ return (lun * pages_per_lun) + pages_per_lun - 1; ++} ++ + static void rawnand_cap_cont_reads(struct nand_chip *chip) + { + struct nand_memory_organization *memorg; +- unsigned int pages_per_lun, first_lun, last_lun; ++ unsigned int ppl, first_lun, last_lun; + + memorg = nanddev_get_memorg(&chip->base); +- pages_per_lun = memorg->pages_per_eraseblock * memorg->eraseblocks_per_lun; +- first_lun = chip->cont_read.first_page / pages_per_lun; +- last_lun = chip->cont_read.last_page / pages_per_lun; ++ ppl = memorg->pages_per_eraseblock * memorg->eraseblocks_per_lun; ++ first_lun = chip->cont_read.first_page / ppl; ++ last_lun = chip->cont_read.last_page / ppl; + + /* Prevent sequential cache reads across LUN boundaries */ + if (first_lun != last_lun) +- chip->cont_read.pause_page = first_lun * pages_per_lun + pages_per_lun - 1; ++ chip->cont_read.pause_page = rawnand_last_page_of_lun(ppl, first_lun); + else + chip->cont_read.pause_page = chip->cont_read.last_page; + } diff --git a/queue-6.6/mtd-rawnand-ensure-all-continuous-terms-are-always-in-sync.patch b/queue-6.6/mtd-rawnand-ensure-all-continuous-terms-are-always-in-sync.patch new file mode 100644 index 0000000000..cf989d69a2 --- /dev/null +++ b/queue-6.6/mtd-rawnand-ensure-all-continuous-terms-are-always-in-sync.patch @@ -0,0 +1,77 @@ +From stable+bounces-289265-greg=kroah.com@vger.kernel.org Sun Jul 26 14:38:57 2026 +From: Sasha Levin +Date: Sun, 26 Jul 2026 08:38:43 -0400 +Subject: mtd: rawnand: Ensure all continuous terms are always in sync +To: stable@vger.kernel.org +Cc: Miquel Raynal , Sasha Levin +Message-ID: <20260726123844.64620-2-sashal@kernel.org> + +From: Miquel Raynal + +[ Upstream commit 6fb075fca63c3486612986eeff84ed4179644038 ] + +While crossing a LUN boundary, it is probably safer (and clearer) to +keep all members of the continuous read structure aligned, including the +pause page (which is the last page of the lun or the last page of the +continuous read). Once these members properly in sync, we can use the +rawnand_cap_cont_reads() helper everywhere to "prepare" the next +continuous read if there is one. + +Fixes: bbcd80f53a5e ("mtd: rawnand: Prevent crossing LUN boundaries during sequential reads") +Cc: stable@vger.kernel.org +Signed-off-by: Miquel Raynal +Link: https://lore.kernel.org/linux-mtd/20240223115545.354541-4-miquel.raynal@bootlin.com +Stable-dep-of: 8e4531667d71 ("mtd: rawnand: Pause continuous reads at block boundaries") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/mtd/nand/raw/nand_base.c | 23 ++++++++++++++--------- + 1 file changed, 14 insertions(+), 9 deletions(-) + +--- a/drivers/mtd/nand/raw/nand_base.c ++++ b/drivers/mtd/nand/raw/nand_base.c +@@ -1233,6 +1233,15 @@ static void rawnand_cap_cont_reads(struc + chip->cont_read.pause_page = rawnand_last_page_of_lun(ppl, first_lun); + else + chip->cont_read.pause_page = chip->cont_read.last_page; ++ ++ if (chip->cont_read.first_page == chip->cont_read.pause_page) { ++ chip->cont_read.first_page++; ++ chip->cont_read.pause_page = min(chip->cont_read.last_page, ++ rawnand_last_page_of_lun(ppl, first_lun + 1)); ++ } ++ ++ if (chip->cont_read.first_page >= chip->cont_read.last_page) ++ chip->cont_read.ongoing = false; + } + + static int nand_lp_exec_cont_read_page_op(struct nand_chip *chip, unsigned int page, +@@ -1299,12 +1308,11 @@ static int nand_lp_exec_cont_read_page_o + if (!chip->cont_read.ongoing) + return 0; + +- if (page == chip->cont_read.pause_page && +- page != chip->cont_read.last_page) { +- chip->cont_read.first_page = chip->cont_read.pause_page + 1; +- rawnand_cap_cont_reads(chip); +- } else if (page == chip->cont_read.last_page) { ++ if (page == chip->cont_read.last_page) { + chip->cont_read.ongoing = false; ++ } else if (page == chip->cont_read.pause_page) { ++ chip->cont_read.first_page++; ++ rawnand_cap_cont_reads(chip); + } + + return 0; +@@ -3512,10 +3520,7 @@ static void rawnand_cont_read_skip_first + return; + + chip->cont_read.first_page++; +- if (chip->cont_read.first_page == chip->cont_read.pause_page) +- chip->cont_read.first_page++; +- if (chip->cont_read.first_page >= chip->cont_read.last_page) +- chip->cont_read.ongoing = false; ++ rawnand_cap_cont_reads(chip); + } + + /** diff --git a/queue-6.6/mtd-rawnand-pause-continuous-reads-at-block-boundaries.patch b/queue-6.6/mtd-rawnand-pause-continuous-reads-at-block-boundaries.patch new file mode 100644 index 0000000000..5861cea842 --- /dev/null +++ b/queue-6.6/mtd-rawnand-pause-continuous-reads-at-block-boundaries.patch @@ -0,0 +1,72 @@ +From stable+bounces-289266-greg=kroah.com@vger.kernel.org Sun Jul 26 14:38:56 2026 +From: Sasha Levin +Date: Sun, 26 Jul 2026 08:38:44 -0400 +Subject: mtd: rawnand: Pause continuous reads at block boundaries +To: stable@vger.kernel.org +Cc: Miquel Raynal , Sasha Levin +Message-ID: <20260726123844.64620-3-sashal@kernel.org> + +From: Miquel Raynal + +[ Upstream commit 8e4531667d718e2e9b193928cf9b2497fa0d01ef ] + +Some chips do not support sequential cached reads past block +boundaries, like Winbond. In practice when using UBI, this should very +rarely happen, but let's make sure it never happens. + +Cc: stable@vger.kernel.org +Fixes: 003fe4b9545b ("mtd: rawnand: Support for sequential cache reads") +Signed-off-by: Miquel Raynal +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/mtd/nand/raw/nand_base.c | 24 ++++++++++++------------ + 1 file changed, 12 insertions(+), 12 deletions(-) + +--- a/drivers/mtd/nand/raw/nand_base.c ++++ b/drivers/mtd/nand/raw/nand_base.c +@@ -1212,32 +1212,32 @@ static int nand_lp_exec_read_page_op(str + return nand_exec_op(chip, &op); + } + +-static unsigned int rawnand_last_page_of_lun(unsigned int pages_per_lun, unsigned int lun) ++static unsigned int rawnand_last_page_of_block(unsigned int ppb, unsigned int block) + { +- /* lun is expected to be very small */ +- return (lun * pages_per_lun) + pages_per_lun - 1; ++ /* block is expected to be very small */ ++ return (block * ppb) + ppb - 1; + } + + static void rawnand_cap_cont_reads(struct nand_chip *chip) + { + struct nand_memory_organization *memorg; +- unsigned int ppl, first_lun, last_lun; ++ unsigned int ppb, first_block, last_block; + + memorg = nanddev_get_memorg(&chip->base); +- ppl = memorg->pages_per_eraseblock * memorg->eraseblocks_per_lun; +- first_lun = chip->cont_read.first_page / ppl; +- last_lun = chip->cont_read.last_page / ppl; +- +- /* Prevent sequential cache reads across LUN boundaries */ +- if (first_lun != last_lun) +- chip->cont_read.pause_page = rawnand_last_page_of_lun(ppl, first_lun); ++ ppb = memorg->pages_per_eraseblock; ++ first_block = chip->cont_read.first_page / ppb; ++ last_block = chip->cont_read.last_page / ppb; ++ ++ /* Prevent sequential cache reads across block boundaries */ ++ if (first_block != last_block) ++ chip->cont_read.pause_page = rawnand_last_page_of_block(ppb, first_block); + else + chip->cont_read.pause_page = chip->cont_read.last_page; + + if (chip->cont_read.first_page == chip->cont_read.pause_page) { + chip->cont_read.first_page++; + chip->cont_read.pause_page = min(chip->cont_read.last_page, +- rawnand_last_page_of_lun(ppl, first_lun + 1)); ++ rawnand_last_page_of_block(ppb, first_block + 1)); + } + + if (chip->cont_read.first_page >= chip->cont_read.last_page) diff --git a/queue-6.6/net-ip6_tunnel-require-cap_net_admin-in-the-device-netns-for-changelink.patch b/queue-6.6/net-ip6_tunnel-require-cap_net_admin-in-the-device-netns-for-changelink.patch new file mode 100644 index 0000000000..cd0c44d872 --- /dev/null +++ b/queue-6.6/net-ip6_tunnel-require-cap_net_admin-in-the-device-netns-for-changelink.patch @@ -0,0 +1,48 @@ +From stable+bounces-289994-greg=kroah.com@vger.kernel.org Tue Jul 28 15:37:43 2026 +From: Sasha Levin +Date: Tue, 28 Jul 2026 09:30:33 -0400 +Subject: net: ip6_tunnel: require CAP_NET_ADMIN in the device netns for changelink +To: stable@vger.kernel.org +Cc: Maoyi Xie , Xiao Liang , Kuniyuki Iwashima , Jakub Kicinski , Sasha Levin +Message-ID: <20260728133033.3965038-1-sashal@kernel.org> + +From: Maoyi Xie + +[ Upstream commit 2496fa0b7d180b3ad356b514e7ff93bb14e6140a ] + +ip6_tnl_changelink() operates on at most two netns, dev_net(dev) and the +tunnel link netns t->net. They differ once the device is created in or +moved to a netns other than the one the request runs in. The rtnl +changelink path checks CAP_NET_ADMIN only against dev_net(dev), so a +caller privileged there but not in t->net can rewrite a tunnel that +lives in t->net. + +Gate ip6_tnl_changelink() on rtnl_dev_link_net_capable() at its top, +before any attribute is parsed. + +Reported-by: Xiao Liang +Closes: https://lore.kernel.org/netdev/CABAhCOSzP1vaThGV35_VnsRCb=87_CPjPVsTHbq905k8A+BuUg@mail.gmail.com/ +Fixes: 0bd8762824e7 ("ip6tnl: add x-netns support") +Cc: stable@vger.kernel.org +Signed-off-by: Maoyi Xie +Reviewed-by: Kuniyuki Iwashima +Link: https://patch.msgid.link/20260612085941.3158249-5-maoyixie.tju@gmail.com +Signed-off-by: Jakub Kicinski +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + net/ipv6/ip6_tunnel.c | 3 +++ + 1 file changed, 3 insertions(+) + +--- a/net/ipv6/ip6_tunnel.c ++++ b/net/ipv6/ip6_tunnel.c +@@ -2055,6 +2055,9 @@ static int ip6_tnl_changelink(struct net + struct ip6_tnl_net *ip6n = net_generic(net, ip6_tnl_net_id); + struct ip_tunnel_encap ipencap; + ++ if (!rtnl_dev_link_net_capable(dev, net)) ++ return -EPERM; ++ + if (dev == ip6n->fb_tnl_dev) + return -EINVAL; + diff --git a/queue-6.6/net-ipa-fix-smem-state-handle-leaks-in-smp2p-init.patch b/queue-6.6/net-ipa-fix-smem-state-handle-leaks-in-smp2p-init.patch new file mode 100644 index 0000000000..9c66f36822 --- /dev/null +++ b/queue-6.6/net-ipa-fix-smem-state-handle-leaks-in-smp2p-init.patch @@ -0,0 +1,91 @@ +From stable+bounces-290251-greg=kroah.com@vger.kernel.org Wed Jul 29 05:20:34 2026 +From: Sasha Levin +Date: Tue, 28 Jul 2026 23:20:20 -0400 +Subject: net: ipa: fix SMEM state handle leaks in SMP2P init +To: stable@vger.kernel.org +Cc: Haoxiang Li , Larysa Zaremba , Alex Elder , Jakub Kicinski , Sasha Levin +Message-ID: <20260729032020.1223984-1-sashal@kernel.org> + +From: Haoxiang Li + +[ Upstream commit 96ca1e658ae459276292bd6d971ab5d8c7e0379a ] + +ipa_smp2p_init() acquires two Qualcomm SMEM state handles with +qcom_smem_state_get(). However, neither the init error paths +nor ipa_smp2p_exit() release them. + +Release both handles with qcom_smem_state_put() in the init +error paths and in ipa_smp2p_exit(). + +Fixes: 530f9216a953 ("soc: qcom: ipa: AP/modem communications") +Cc: stable@vger.kernel.org +Signed-off-by: Haoxiang Li +Reviewed-by: Larysa Zaremba +Reviewed-by: Alex Elder +Link: https://patch.msgid.link/20260624065955.2822765-1-haoxiang_li2024@163.com +Signed-off-by: Jakub Kicinski +[ kzalloc_obj() context line kept as kzalloc(sizeof(*smp2p), GFP_KERNEL) since ipa_smp2p.c was not yet converted in this tree ] +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/net/ipa/ipa_smp2p.c | 30 ++++++++++++++++++++++-------- + 1 file changed, 22 insertions(+), 8 deletions(-) + +--- a/drivers/net/ipa/ipa_smp2p.c ++++ b/drivers/net/ipa/ipa_smp2p.c +@@ -233,19 +233,27 @@ int ipa_smp2p_init(struct ipa *ipa, bool + &valid_bit); + if (IS_ERR(valid_state)) + return PTR_ERR(valid_state); +- if (valid_bit >= 32) /* BITS_PER_U32 */ +- return -EINVAL; ++ if (valid_bit >= 32) { /* BITS_PER_U32 */ ++ ret = -EINVAL; ++ goto err_valid_state_put; ++ } + + enabled_state = qcom_smem_state_get(dev, "ipa-clock-enabled", + &enabled_bit); +- if (IS_ERR(enabled_state)) +- return PTR_ERR(enabled_state); +- if (enabled_bit >= 32) /* BITS_PER_U32 */ +- return -EINVAL; ++ if (IS_ERR(enabled_state)) { ++ ret = PTR_ERR(enabled_state); ++ goto err_valid_state_put; ++ } ++ if (enabled_bit >= 32) { /* BITS_PER_U32 */ ++ ret = -EINVAL; ++ goto err_enabled_state_put; ++ } + + smp2p = kzalloc(sizeof(*smp2p), GFP_KERNEL); +- if (!smp2p) +- return -ENOMEM; ++ if (!smp2p) { ++ ret = -ENOMEM; ++ goto err_enabled_state_put; ++ } + + smp2p->ipa = ipa; + +@@ -290,6 +298,10 @@ err_null_smp2p: + ipa->smp2p = NULL; + mutex_destroy(&smp2p->mutex); + kfree(smp2p); ++err_enabled_state_put: ++ qcom_smem_state_put(enabled_state); ++err_valid_state_put: ++ qcom_smem_state_put(valid_state); + + return ret; + } +@@ -306,6 +318,8 @@ void ipa_smp2p_exit(struct ipa *ipa) + ipa_smp2p_power_release(ipa); + ipa->smp2p = NULL; + mutex_destroy(&smp2p->mutex); ++ qcom_smem_state_put(smp2p->enabled_state); ++ qcom_smem_state_put(smp2p->valid_state); + kfree(smp2p); + } + diff --git a/queue-6.6/net-macb-drop-in-flight-tx-skbs-on-close.patch b/queue-6.6/net-macb-drop-in-flight-tx-skbs-on-close.patch new file mode 100644 index 0000000000..674535c50d --- /dev/null +++ b/queue-6.6/net-macb-drop-in-flight-tx-skbs-on-close.patch @@ -0,0 +1,64 @@ +From stable+bounces-290235-greg=kroah.com@vger.kernel.org Wed Jul 29 04:40:20 2026 +From: Sasha Levin +Date: Tue, 28 Jul 2026 22:40:09 -0400 +Subject: net: macb: drop in-flight Tx SKBs on close +To: stable@vger.kernel.org +Cc: "Théo Lebrun" , "Nicolai Buchwitz" , "Paolo Abeni" , "Sasha Levin" +Message-ID: <20260729024009.1127546-1-sashal@kernel.org> + +From: Théo Lebrun + +[ Upstream commit 27f575836cfebbf872dec020428742b10650a955 ] + +The MACB driver has since forever leaked the outgoing SKBs that +have not yet been marked as completed. They live in queue->tx_skb +which gets freed without remorse nor checking. + +macb_free_consistent() gets called in a few codepaths, but only close will +trigger the added expressions. In macb_open() and macb_alloc_consistent() +failure cases, queues' tx_skb just got allocated and are empty. + +Fixes: 89e5785fc8a6 ("[PATCH] Atmel MACB ethernet driver") +Cc: stable@vger.kernel.org +Reviewed-by: Nicolai Buchwitz +Signed-off-by: Théo Lebrun +Link: https://patch.msgid.link/20260702-macb-drop-tx-v4-1-1c833eebdbc8@bootlin.com +Signed-off-by: Paolo Abeni +[ adjusted context to free each queue's Tx/Rx rings individually since 6.12 lacks the single-dma_alloc_coherent refactor ] +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/net/ethernet/cadence/macb_main.c | 22 ++++++++++++++++++++-- + 1 file changed, 20 insertions(+), 2 deletions(-) + +--- a/drivers/net/ethernet/cadence/macb_main.c ++++ b/drivers/net/ethernet/cadence/macb_main.c +@@ -2582,8 +2582,26 @@ static void macb_free_consistent(struct + bp->macbgem_ops.mog_free_rx_buffers(bp); + + for (q = 0, queue = bp->queues; q < bp->num_queues; ++q, ++queue) { +- kfree(queue->tx_skb); +- queue->tx_skb = NULL; ++ if (queue->tx_skb) { ++ unsigned int dropped = 0, tail; ++ ++ for (tail = queue->tx_tail; tail != queue->tx_head; ++ tail++) { ++ if (macb_tx_skb(queue, tail)->skb) ++ dropped++; ++ macb_tx_unmap(bp, macb_tx_skb(queue, tail), 0); ++ } ++ ++ queue->stats.tx_dropped += dropped; ++ bp->dev->stats.tx_dropped += dropped; ++ ++ kfree(queue->tx_skb); ++ queue->tx_skb = NULL; ++ } ++ ++ queue->tx_head = 0; ++ queue->tx_tail = 0; ++ + if (queue->tx_ring) { + size = TX_RING_BYTES(bp) + bp->tx_bd_rd_prefetch; + dma_free_coherent(&bp->pdev->dev, size, diff --git a/queue-6.6/net-mana-validate-the-packet-length-reported-by-the-nic.patch b/queue-6.6/net-mana-validate-the-packet-length-reported-by-the-nic.patch new file mode 100644 index 0000000000..4673515806 --- /dev/null +++ b/queue-6.6/net-mana-validate-the-packet-length-reported-by-the-nic.patch @@ -0,0 +1,50 @@ +From stable+bounces-289837-greg=kroah.com@vger.kernel.org Tue Jul 28 05:27:25 2026 +From: Sasha Levin +Date: Mon, 27 Jul 2026 23:25:58 -0400 +Subject: net: mana: Validate the packet length reported by the NIC +To: stable@vger.kernel.org +Cc: Dexuan Cui , Haiyang Zhang , Paolo Abeni , Sasha Levin +Message-ID: <20260728032558.3151321-1-sashal@kernel.org> + +From: Dexuan Cui + +[ Upstream commit 2e2a83b4998af4384e677d3b2ac08565274279bf ] + +Validate the packet length reported in the RX CQE before passing it +to skb processing. The CQE is supplied by the NIC device and should +not be blindly trusted. + +Cc: stable@vger.kernel.org +Reviewed-by: Haiyang Zhang +Signed-off-by: Dexuan Cui +Fixes: ca9c54d2d6a5 ("net: mana: Add a driver for Microsoft Azure Network Adapter (MANA)") +Link: https://patch.msgid.link/20260702041237.617719-2-decui@microsoft.com +Signed-off-by: Paolo Abeni +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/net/ethernet/microsoft/mana/mana_en.c | 13 +++++++++++++ + 1 file changed, 13 insertions(+) + +--- a/drivers/net/ethernet/microsoft/mana/mana_en.c ++++ b/drivers/net/ethernet/microsoft/mana/mana_en.c +@@ -1739,6 +1739,19 @@ static void mana_process_rx_cqe(struct m + rxbuf_oob = &rxq->rx_oobs[curr]; + WARN_ON_ONCE(rxbuf_oob->wqe_inf.wqe_size_in_bu != 1); + ++ if (unlikely(pktlen > rxq->datasize)) { ++ /* Increase it even if mana_rx_skb() isn't called. */ ++ rxq->rx_cq.work_done++; ++ ++ ++ndev->stats.rx_dropped; ++ netdev_warn_once(ndev, ++ "Dropped oversized RX packet: len=%u, datasize=%u\n", ++ pktlen, rxq->datasize); ++ ++ /* Reuse the RX buffer since rxbuf_oob is unchanged. */ ++ goto drop; ++ } ++ + mana_refill_rx_oob(dev, rxq, rxbuf_oob, &old_buf, &old_fp); + + /* Unsuccessful refill will have old_buf == NULL. diff --git a/queue-6.6/netfilter-nf_conntrack_sip-remove-net-variable-shadowing.patch b/queue-6.6/netfilter-nf_conntrack_sip-remove-net-variable-shadowing.patch new file mode 100644 index 0000000000..3db12271f5 --- /dev/null +++ b/queue-6.6/netfilter-nf_conntrack_sip-remove-net-variable-shadowing.patch @@ -0,0 +1,39 @@ +From stable+bounces-288375-greg=kroah.com@vger.kernel.org Thu Jul 23 17:09:04 2026 +From: Sasha Levin +Date: Thu, 23 Jul 2026 10:53:18 -0400 +Subject: netfilter: nf_conntrack_sip: remove net variable shadowing +To: stable@vger.kernel.org +Cc: Florian Westphal , Sasha Levin +Message-ID: <20260723145319.3272168-1-sashal@kernel.org> + +From: Florian Westphal + +[ Upstream commit 7970d6aaf710db166de98c5356a260089896fae5 ] + +net is already set, derived from nf_conn. +I don't see how the device could be living in a different netns +than the conntrack entry. + +Remove the extra variable and re-use existing one. + +Signed-off-by: Florian Westphal +Stable-dep-of: e5e24a365a5e ("netfilter: nf_conntrack_sip: validate skb_dst() before accessing it") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + net/netfilter/nf_conntrack_sip.c | 3 +-- + 1 file changed, 1 insertion(+), 2 deletions(-) + +--- a/net/netfilter/nf_conntrack_sip.c ++++ b/net/netfilter/nf_conntrack_sip.c +@@ -948,9 +948,8 @@ static int set_expected_rtp_rtcp(struct + saddr = &ct->tuplehash[!dir].tuple.src.u3; + } else if (sip_external_media) { + struct net_device *dev = skb_dst(skb)->dev; +- struct net *net = dev_net(dev); +- struct flowi fl; + struct dst_entry *dst = NULL; ++ struct flowi fl; + + memset(&fl, 0, sizeof(fl)); + diff --git a/queue-6.6/netfilter-nf_conntrack_sip-validate-skb_dst-before-accessing-it.patch b/queue-6.6/netfilter-nf_conntrack_sip-validate-skb_dst-before-accessing-it.patch new file mode 100644 index 0000000000..2afd150010 --- /dev/null +++ b/queue-6.6/netfilter-nf_conntrack_sip-validate-skb_dst-before-accessing-it.patch @@ -0,0 +1,56 @@ +From stable+bounces-288376-greg=kroah.com@vger.kernel.org Thu Jul 23 17:03:48 2026 +From: Sasha Levin +Date: Thu, 23 Jul 2026 10:53:19 -0400 +Subject: netfilter: nf_conntrack_sip: validate skb_dst() before accessing it +To: stable@vger.kernel.org +Cc: Pablo Neira Ayuso , Ren Wei , Florian Westphal , Sasha Levin +Message-ID: <20260723145319.3272168-2-sashal@kernel.org> + +From: Pablo Neira Ayuso + +[ Upstream commit e5e24a365a5e024efef63cc49abb345fbd4852c5 ] + +tc ingress and openvswitch do not guarantee routing information to be +available. These subsystems use the conntrack helper infrastructure, and +the SIP helper relies on the skb_dst() to be present if +sip_external_media is set to 1 (which is disabled by default as a module +parameter). + +This effectively disables the sip_external_media toggle for these +subsystems without resulting in a crash. + +Fixes: cae3a2627520 ("openvswitch: Allow attaching helpers to ct action") +Fixes: b57dc7c13ea9 ("net/sched: Introduce action ct") +Cc: stable@vger.kernel.org +Reported-by: Ren Wei +Signed-off-by: Pablo Neira Ayuso +Signed-off-by: Florian Westphal +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + net/netfilter/nf_conntrack_sip.c | 7 +++++-- + 1 file changed, 5 insertions(+), 2 deletions(-) + +--- a/net/netfilter/nf_conntrack_sip.c ++++ b/net/netfilter/nf_conntrack_sip.c +@@ -947,7 +947,6 @@ static int set_expected_rtp_rtcp(struct + return NF_ACCEPT; + saddr = &ct->tuplehash[!dir].tuple.src.u3; + } else if (sip_external_media) { +- struct net_device *dev = skb_dst(skb)->dev; + struct dst_entry *dst = NULL; + struct flowi fl; + +@@ -969,7 +968,11 @@ static int set_expected_rtp_rtcp(struct + * through the same interface as the signalling peer. + */ + if (dst) { +- bool external_media = (dst->dev == dev); ++ const struct dst_entry *this_dst = skb_dst(skb); ++ bool external_media = false; ++ ++ if (this_dst && dst->dev == this_dst->dev) ++ external_media = true; + + dst_release(dst); + if (external_media) diff --git a/queue-6.6/netfilter-nf_tables-remove-register-tracking-infrastructure.patch b/queue-6.6/netfilter-nf_tables-remove-register-tracking-infrastructure.patch new file mode 100644 index 0000000000..ed61e526d6 --- /dev/null +++ b/queue-6.6/netfilter-nf_tables-remove-register-tracking-infrastructure.patch @@ -0,0 +1,1645 @@ +From stable+bounces-288525-greg=kroah.com@vger.kernel.org Thu Jul 23 21:59:53 2026 +From: Sasha Levin +Date: Thu, 23 Jul 2026 15:58:41 -0400 +Subject: netfilter: nf_tables: remove register tracking infrastructure +To: stable@vger.kernel.org +Cc: Florian Westphal , Jakub Kicinski , Sasha Levin +Message-ID: <20260723195842.3683768-2-sashal@kernel.org> + +From: Florian Westphal + +[ Upstream commit 6b94d081f81dd524626f7aab2b98a9de335edb72 ] + +This facility was disabled in commit +9e539c5b6d9c ("netfilter: nf_tables: disable expression reduction infra"), +because not all nft_exprs guarantee they will update the destination +register: some may set NFT_BREAK instead to cancel evaluation of the +rule. + +This has been dead code ever since. +There are no plans to salvage this at this time, so remove this. + +Signed-off-by: Florian Westphal +Link: https://patch.msgid.link/20260224205048.4718-10-fw@strlen.de +Signed-off-by: Jakub Kicinski +Stable-dep-of: d07955dd34ec ("netfilter: nft_fib: reject fib expression on the netdev egress hook") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + include/net/netfilter/nf_tables.h | 32 --------- + include/net/netfilter/nft_fib.h | 2 + include/net/netfilter/nft_meta.h | 3 + net/bridge/netfilter/nft_meta_bridge.c | 20 ----- + net/bridge/netfilter/nft_reject_bridge.c | 1 + net/ipv4/netfilter/nft_dup_ipv4.c | 1 + net/ipv4/netfilter/nft_fib_ipv4.c | 2 + net/ipv4/netfilter/nft_reject_ipv4.c | 1 + net/ipv6/netfilter/nft_dup_ipv6.c | 1 + net/ipv6/netfilter/nft_fib_ipv6.c | 2 + net/ipv6/netfilter/nft_reject_ipv6.c | 1 + net/netfilter/nf_tables_api.c | 67 ------------------- + net/netfilter/nft_bitwise.c | 104 ------------------------------- + net/netfilter/nft_byteorder.c | 11 --- + net/netfilter/nft_cmp.c | 3 + net/netfilter/nft_compat.c | 10 -- + net/netfilter/nft_connlimit.c | 1 + net/netfilter/nft_counter.c | 1 + net/netfilter/nft_ct.c | 46 ------------- + net/netfilter/nft_dup_netdev.c | 1 + net/netfilter/nft_dynset.c | 1 + net/netfilter/nft_exthdr.c | 34 ---------- + net/netfilter/nft_fib.c | 42 ------------ + net/netfilter/nft_fib_inet.c | 1 + net/netfilter/nft_fib_netdev.c | 1 + net/netfilter/nft_flow_offload.c | 1 + net/netfilter/nft_fwd_netdev.c | 2 + net/netfilter/nft_hash.c | 36 ---------- + net/netfilter/nft_immediate.c | 12 --- + net/netfilter/nft_last.c | 1 + net/netfilter/nft_limit.c | 2 + net/netfilter/nft_log.c | 1 + net/netfilter/nft_lookup.c | 12 --- + net/netfilter/nft_masq.c | 3 + net/netfilter/nft_meta.c | 45 ------------- + net/netfilter/nft_nat.c | 2 + net/netfilter/nft_numgen.c | 22 ------ + net/netfilter/nft_objref.c | 2 + net/netfilter/nft_osf.c | 25 ------- + net/netfilter/nft_payload.c | 47 -------------- + net/netfilter/nft_queue.c | 2 + net/netfilter/nft_quota.c | 1 + net/netfilter/nft_range.c | 1 + net/netfilter/nft_redir.c | 3 + net/netfilter/nft_reject_inet.c | 1 + net/netfilter/nft_reject_netdev.c | 1 + net/netfilter/nft_rt.c | 1 + net/netfilter/nft_socket.c | 26 ------- + net/netfilter/nft_synproxy.c | 1 + net/netfilter/nft_tproxy.c | 1 + net/netfilter/nft_tunnel.c | 26 ------- + net/netfilter/nft_xfrm.c | 27 -------- + 52 files changed, 693 deletions(-) + +--- a/include/net/netfilter/nf_tables.h ++++ b/include/net/netfilter/nf_tables.h +@@ -123,17 +123,6 @@ struct nft_regs { + }; + }; + +-struct nft_regs_track { +- struct { +- const struct nft_expr *selector; +- const struct nft_expr *bitwise; +- u8 num_reg; +- } regs[NFT_REG32_NUM]; +- +- const struct nft_expr *cur; +- const struct nft_expr *last; +-}; +- + /* Store/load an u8, u16 or u64 integer to/from the u32 data register. + * + * Note, when using concatenations, register allocation happens at 32-bit +@@ -420,8 +409,6 @@ int nft_expr_clone(struct nft_expr *dst, + void nft_expr_destroy(const struct nft_ctx *ctx, struct nft_expr *expr); + int nft_expr_dump(struct sk_buff *skb, unsigned int attr, + const struct nft_expr *expr, bool reset); +-bool nft_expr_reduce_bitwise(struct nft_regs_track *track, +- const struct nft_expr *expr); + + struct nft_set_ext; + +@@ -933,7 +920,6 @@ struct nft_offload_ctx; + * @destroy_clone: destruction clone function + * @dump: function to dump parameters + * @validate: validate expression, called during loop detection +- * @reduce: reduce expression + * @gc: garbage collection expression + * @offload: hardware offload expression + * @offload_action: function to report true/false to allocate one slot or not in the flow +@@ -967,8 +953,6 @@ struct nft_expr_ops { + bool reset); + int (*validate)(const struct nft_ctx *ctx, + const struct nft_expr *expr); +- bool (*reduce)(struct nft_regs_track *track, +- const struct nft_expr *expr); + bool (*gc)(struct net *net, + const struct nft_expr *expr); + int (*offload)(struct nft_offload_ctx *ctx, +@@ -1848,20 +1832,4 @@ static inline u64 nft_net_tstamp(const s + return nft_pernet(net)->tstamp; + } + +-#define __NFT_REDUCE_READONLY 1UL +-#define NFT_REDUCE_READONLY (void *)__NFT_REDUCE_READONLY +- +-void nft_reg_track_update(struct nft_regs_track *track, +- const struct nft_expr *expr, u8 dreg, u8 len); +-void nft_reg_track_cancel(struct nft_regs_track *track, u8 dreg, u8 len); +-void __nft_reg_track_cancel(struct nft_regs_track *track, u8 dreg); +- +-static inline bool nft_reg_track_cmp(struct nft_regs_track *track, +- const struct nft_expr *expr, u8 dreg) +-{ +- return track->regs[dreg].selector && +- track->regs[dreg].selector->ops == expr->ops && +- track->regs[dreg].num_reg == 0; +-} +- + #endif /* _NET_NF_TABLES_H */ +--- a/include/net/netfilter/nft_fib.h ++++ b/include/net/netfilter/nft_fib.h +@@ -36,6 +36,4 @@ void nft_fib6_eval(const struct nft_expr + void nft_fib_store_result(void *reg, const struct nft_fib *priv, + const struct net_device *dev); + +-bool nft_fib_reduce(struct nft_regs_track *track, +- const struct nft_expr *expr); + #endif +--- a/include/net/netfilter/nft_meta.h ++++ b/include/net/netfilter/nft_meta.h +@@ -43,9 +43,6 @@ void nft_meta_set_destroy(const struct n + int nft_meta_set_validate(const struct nft_ctx *ctx, + const struct nft_expr *expr); + +-bool nft_meta_get_reduce(struct nft_regs_track *track, +- const struct nft_expr *expr); +- + struct nft_inner_tun_ctx; + void nft_meta_inner_eval(const struct nft_expr *expr, + struct nft_regs *regs, const struct nft_pktinfo *pkt, +--- a/net/bridge/netfilter/nft_meta_bridge.c ++++ b/net/bridge/netfilter/nft_meta_bridge.c +@@ -104,7 +104,6 @@ static const struct nft_expr_ops nft_met + .eval = nft_meta_bridge_get_eval, + .init = nft_meta_bridge_get_init, + .dump = nft_meta_get_dump, +- .reduce = nft_meta_get_reduce, + }; + + static void nft_meta_bridge_set_eval(const struct nft_expr *expr, +@@ -151,24 +150,6 @@ static int nft_meta_bridge_set_init(cons + return 0; + } + +-static bool nft_meta_bridge_set_reduce(struct nft_regs_track *track, +- const struct nft_expr *expr) +-{ +- int i; +- +- for (i = 0; i < NFT_REG32_NUM; i++) { +- if (!track->regs[i].selector) +- continue; +- +- if (track->regs[i].selector->ops != &nft_meta_bridge_get_ops) +- continue; +- +- __nft_reg_track_cancel(track, i); +- } +- +- return false; +-} +- + static int nft_meta_bridge_set_validate(const struct nft_ctx *ctx, + const struct nft_expr *expr) + { +@@ -193,7 +174,6 @@ static const struct nft_expr_ops nft_met + .init = nft_meta_bridge_set_init, + .destroy = nft_meta_set_destroy, + .dump = nft_meta_set_dump, +- .reduce = nft_meta_bridge_set_reduce, + .validate = nft_meta_bridge_set_validate, + }; + +--- a/net/bridge/netfilter/nft_reject_bridge.c ++++ b/net/bridge/netfilter/nft_reject_bridge.c +@@ -184,7 +184,6 @@ static const struct nft_expr_ops nft_rej + .init = nft_reject_init, + .dump = nft_reject_dump, + .validate = nft_reject_bridge_validate, +- .reduce = NFT_REDUCE_READONLY, + }; + + static struct nft_expr_type nft_reject_bridge_type __read_mostly = { +--- a/net/ipv4/netfilter/nft_dup_ipv4.c ++++ b/net/ipv4/netfilter/nft_dup_ipv4.c +@@ -76,7 +76,6 @@ static const struct nft_expr_ops nft_dup + .eval = nft_dup_ipv4_eval, + .init = nft_dup_ipv4_init, + .dump = nft_dup_ipv4_dump, +- .reduce = NFT_REDUCE_READONLY, + }; + + static const struct nla_policy nft_dup_ipv4_policy[NFTA_DUP_MAX + 1] = { +--- a/net/ipv4/netfilter/nft_fib_ipv4.c ++++ b/net/ipv4/netfilter/nft_fib_ipv4.c +@@ -157,7 +157,6 @@ static const struct nft_expr_ops nft_fib + .init = nft_fib_init, + .dump = nft_fib_dump, + .validate = nft_fib_validate, +- .reduce = nft_fib_reduce, + }; + + static const struct nft_expr_ops nft_fib4_ops = { +@@ -167,7 +166,6 @@ static const struct nft_expr_ops nft_fib + .init = nft_fib_init, + .dump = nft_fib_dump, + .validate = nft_fib_validate, +- .reduce = nft_fib_reduce, + }; + + static const struct nft_expr_ops * +--- a/net/ipv4/netfilter/nft_reject_ipv4.c ++++ b/net/ipv4/netfilter/nft_reject_ipv4.c +@@ -45,7 +45,6 @@ static const struct nft_expr_ops nft_rej + .init = nft_reject_init, + .dump = nft_reject_dump, + .validate = nft_reject_validate, +- .reduce = NFT_REDUCE_READONLY, + }; + + static struct nft_expr_type nft_reject_ipv4_type __read_mostly = { +--- a/net/ipv6/netfilter/nft_dup_ipv6.c ++++ b/net/ipv6/netfilter/nft_dup_ipv6.c +@@ -74,7 +74,6 @@ static const struct nft_expr_ops nft_dup + .eval = nft_dup_ipv6_eval, + .init = nft_dup_ipv6_init, + .dump = nft_dup_ipv6_dump, +- .reduce = NFT_REDUCE_READONLY, + }; + + static const struct nla_policy nft_dup_ipv6_policy[NFTA_DUP_MAX + 1] = { +--- a/net/ipv6/netfilter/nft_fib_ipv6.c ++++ b/net/ipv6/netfilter/nft_fib_ipv6.c +@@ -226,7 +226,6 @@ static const struct nft_expr_ops nft_fib + .init = nft_fib_init, + .dump = nft_fib_dump, + .validate = nft_fib_validate, +- .reduce = nft_fib_reduce, + }; + + static const struct nft_expr_ops nft_fib6_ops = { +@@ -236,7 +235,6 @@ static const struct nft_expr_ops nft_fib + .init = nft_fib_init, + .dump = nft_fib_dump, + .validate = nft_fib_validate, +- .reduce = nft_fib_reduce, + }; + + static const struct nft_expr_ops * +--- a/net/ipv6/netfilter/nft_reject_ipv6.c ++++ b/net/ipv6/netfilter/nft_reject_ipv6.c +@@ -46,7 +46,6 @@ static const struct nft_expr_ops nft_rej + .init = nft_reject_init, + .dump = nft_reject_dump, + .validate = nft_reject_validate, +- .reduce = NFT_REDUCE_READONLY, + }; + + static struct nft_expr_type nft_reject_ipv6_type __read_mostly = { +--- a/net/netfilter/nf_tables_api.c ++++ b/net/netfilter/nf_tables_api.c +@@ -754,58 +754,6 @@ static int nft_delflowtable(struct nft_c + return 0; + } + +-static void __nft_reg_track_clobber(struct nft_regs_track *track, u8 dreg) +-{ +- int i; +- +- for (i = track->regs[dreg].num_reg; i > 0; i--) +- __nft_reg_track_cancel(track, dreg - i); +-} +- +-static void __nft_reg_track_update(struct nft_regs_track *track, +- const struct nft_expr *expr, +- u8 dreg, u8 num_reg) +-{ +- track->regs[dreg].selector = expr; +- track->regs[dreg].bitwise = NULL; +- track->regs[dreg].num_reg = num_reg; +-} +- +-void nft_reg_track_update(struct nft_regs_track *track, +- const struct nft_expr *expr, u8 dreg, u8 len) +-{ +- unsigned int regcount; +- int i; +- +- __nft_reg_track_clobber(track, dreg); +- +- regcount = DIV_ROUND_UP(len, NFT_REG32_SIZE); +- for (i = 0; i < regcount; i++, dreg++) +- __nft_reg_track_update(track, expr, dreg, i); +-} +-EXPORT_SYMBOL_GPL(nft_reg_track_update); +- +-void nft_reg_track_cancel(struct nft_regs_track *track, u8 dreg, u8 len) +-{ +- unsigned int regcount; +- int i; +- +- __nft_reg_track_clobber(track, dreg); +- +- regcount = DIV_ROUND_UP(len, NFT_REG32_SIZE); +- for (i = 0; i < regcount; i++, dreg++) +- __nft_reg_track_cancel(track, dreg); +-} +-EXPORT_SYMBOL_GPL(nft_reg_track_cancel); +- +-void __nft_reg_track_cancel(struct nft_regs_track *track, u8 dreg) +-{ +- track->regs[dreg].selector = NULL; +- track->regs[dreg].bitwise = NULL; +- track->regs[dreg].num_reg = 0; +-} +-EXPORT_SYMBOL_GPL(__nft_reg_track_cancel); +- + /* + * Tables + */ +@@ -9638,16 +9586,9 @@ void nf_tables_trans_destroy_flush_work( + } + EXPORT_SYMBOL_GPL(nf_tables_trans_destroy_flush_work); + +-static bool nft_expr_reduce(struct nft_regs_track *track, +- const struct nft_expr *expr) +-{ +- return false; +-} +- + static int nf_tables_commit_chain_prepare(struct net *net, struct nft_chain *chain) + { + const struct nft_expr *expr, *last; +- struct nft_regs_track track = {}; + unsigned int size, data_size; + void *data, *data_boundary; + struct nft_rule_dp *prule; +@@ -9684,15 +9625,7 @@ static int nf_tables_commit_chain_prepar + return -ENOMEM; + + size = 0; +- track.last = nft_expr_last(rule); + nft_rule_for_each_expr(expr, last, rule) { +- track.cur = expr; +- +- if (nft_expr_reduce(&track, expr)) { +- expr = track.cur; +- continue; +- } +- + if (WARN_ON_ONCE(data + size + expr->ops->size > data_boundary)) + return -ENOMEM; + +--- a/net/netfilter/nft_bitwise.c ++++ b/net/netfilter/nft_bitwise.c +@@ -402,61 +402,12 @@ static int nft_bitwise_offload(struct nf + return 0; + } + +-static bool nft_bitwise_reduce(struct nft_regs_track *track, +- const struct nft_expr *expr) +-{ +- const struct nft_bitwise *priv = nft_expr_priv(expr); +- const struct nft_bitwise *bitwise; +- unsigned int regcount; +- u8 dreg; +- int i; +- +- if (!track->regs[priv->sreg].selector) +- return false; +- +- bitwise = nft_expr_priv(track->regs[priv->dreg].selector); +- if (track->regs[priv->sreg].selector == track->regs[priv->dreg].selector && +- track->regs[priv->sreg].num_reg == 0 && +- track->regs[priv->dreg].bitwise && +- track->regs[priv->dreg].bitwise->ops == expr->ops && +- priv->sreg == bitwise->sreg && +- priv->sreg2 == bitwise->sreg2 && +- priv->dreg == bitwise->dreg && +- priv->op == bitwise->op && +- priv->len == bitwise->len && +- !memcmp(&priv->mask, &bitwise->mask, sizeof(priv->mask)) && +- !memcmp(&priv->xor, &bitwise->xor, sizeof(priv->xor)) && +- !memcmp(&priv->data, &bitwise->data, sizeof(priv->data))) { +- track->cur = expr; +- return true; +- } +- +- if (track->regs[priv->sreg].bitwise || +- track->regs[priv->sreg].num_reg != 0) { +- nft_reg_track_cancel(track, priv->dreg, priv->len); +- return false; +- } +- +- if (priv->sreg != priv->dreg) { +- nft_reg_track_update(track, track->regs[priv->sreg].selector, +- priv->dreg, priv->len); +- } +- +- dreg = priv->dreg; +- regcount = DIV_ROUND_UP(priv->len, NFT_REG32_SIZE); +- for (i = 0; i < regcount; i++, dreg++) +- track->regs[dreg].bitwise = expr; +- +- return false; +-} +- + static const struct nft_expr_ops nft_bitwise_ops = { + .type = &nft_bitwise_type, + .size = NFT_EXPR_SIZE(sizeof(struct nft_bitwise)), + .eval = nft_bitwise_eval, + .init = nft_bitwise_init, + .dump = nft_bitwise_dump, +- .reduce = nft_bitwise_reduce, + .offload = nft_bitwise_offload, + }; + +@@ -559,48 +510,12 @@ static int nft_bitwise_fast_offload(stru + return 0; + } + +-static bool nft_bitwise_fast_reduce(struct nft_regs_track *track, +- const struct nft_expr *expr) +-{ +- const struct nft_bitwise_fast_expr *priv = nft_expr_priv(expr); +- const struct nft_bitwise_fast_expr *bitwise; +- +- if (!track->regs[priv->sreg].selector) +- return false; +- +- bitwise = nft_expr_priv(track->regs[priv->dreg].selector); +- if (track->regs[priv->sreg].selector == track->regs[priv->dreg].selector && +- track->regs[priv->dreg].bitwise && +- track->regs[priv->dreg].bitwise->ops == expr->ops && +- priv->sreg == bitwise->sreg && +- priv->dreg == bitwise->dreg && +- priv->mask == bitwise->mask && +- priv->xor == bitwise->xor) { +- track->cur = expr; +- return true; +- } +- +- if (track->regs[priv->sreg].bitwise) { +- nft_reg_track_cancel(track, priv->dreg, NFT_REG32_SIZE); +- return false; +- } +- +- if (priv->sreg != priv->dreg) { +- track->regs[priv->dreg].selector = +- track->regs[priv->sreg].selector; +- } +- track->regs[priv->dreg].bitwise = expr; +- +- return false; +-} +- + const struct nft_expr_ops nft_bitwise_fast_ops = { + .type = &nft_bitwise_type, + .size = NFT_EXPR_SIZE(sizeof(struct nft_bitwise_fast_expr)), + .eval = NULL, /* inlined */ + .init = nft_bitwise_fast_init, + .dump = nft_bitwise_fast_dump, +- .reduce = nft_bitwise_fast_reduce, + .offload = nft_bitwise_fast_offload, + }; + +@@ -637,22 +552,3 @@ struct nft_expr_type nft_bitwise_type __ + .maxattr = NFTA_BITWISE_MAX, + .owner = THIS_MODULE, + }; +- +-bool nft_expr_reduce_bitwise(struct nft_regs_track *track, +- const struct nft_expr *expr) +-{ +- const struct nft_expr *last = track->last; +- const struct nft_expr *next; +- +- if (expr == last) +- return false; +- +- next = nft_expr_next(expr); +- if (next->ops == &nft_bitwise_ops) +- return nft_bitwise_reduce(track, next); +- else if (next->ops == &nft_bitwise_fast_ops) +- return nft_bitwise_fast_reduce(track, next); +- +- return false; +-} +-EXPORT_SYMBOL_GPL(nft_expr_reduce_bitwise); +--- a/net/netfilter/nft_byteorder.c ++++ b/net/netfilter/nft_byteorder.c +@@ -177,23 +177,12 @@ nla_put_failure: + return -1; + } + +-static bool nft_byteorder_reduce(struct nft_regs_track *track, +- const struct nft_expr *expr) +-{ +- struct nft_byteorder *priv = nft_expr_priv(expr); +- +- nft_reg_track_cancel(track, priv->dreg, priv->len); +- +- return false; +-} +- + static const struct nft_expr_ops nft_byteorder_ops = { + .type = &nft_byteorder_type, + .size = NFT_EXPR_SIZE(sizeof(struct nft_byteorder)), + .eval = nft_byteorder_eval, + .init = nft_byteorder_init, + .dump = nft_byteorder_dump, +- .reduce = nft_byteorder_reduce, + }; + + struct nft_expr_type nft_byteorder_type __read_mostly = { +--- a/net/netfilter/nft_cmp.c ++++ b/net/netfilter/nft_cmp.c +@@ -190,7 +190,6 @@ static const struct nft_expr_ops nft_cmp + .eval = nft_cmp_eval, + .init = nft_cmp_init, + .dump = nft_cmp_dump, +- .reduce = NFT_REDUCE_READONLY, + .offload = nft_cmp_offload, + }; + +@@ -282,7 +281,6 @@ const struct nft_expr_ops nft_cmp_fast_o + .eval = NULL, /* inlined */ + .init = nft_cmp_fast_init, + .dump = nft_cmp_fast_dump, +- .reduce = NFT_REDUCE_READONLY, + .offload = nft_cmp_fast_offload, + }; + +@@ -376,7 +374,6 @@ const struct nft_expr_ops nft_cmp16_fast + .eval = NULL, /* inlined */ + .init = nft_cmp16_fast_init, + .dump = nft_cmp16_fast_dump, +- .reduce = NFT_REDUCE_READONLY, + .offload = nft_cmp16_fast_offload, + }; + +--- a/net/netfilter/nft_compat.c ++++ b/net/netfilter/nft_compat.c +@@ -794,14 +794,6 @@ static const struct nfnetlink_subsystem + + static struct nft_expr_type nft_match_type; + +-static bool nft_match_reduce(struct nft_regs_track *track, +- const struct nft_expr *expr) +-{ +- const struct xt_match *match = expr->ops->data; +- +- return strcmp(match->name, "comment") == 0; +-} +- + static const struct nft_expr_ops * + nft_match_select_ops(const struct nft_ctx *ctx, + const struct nlattr * const tb[]) +@@ -844,7 +836,6 @@ nft_match_select_ops(const struct nft_ct + ops->dump = nft_match_dump; + ops->validate = nft_match_validate; + ops->data = match; +- ops->reduce = nft_match_reduce; + + matchsize = NFT_EXPR_SIZE(XT_ALIGN(match->matchsize)); + if (matchsize > NFT_MATCH_LARGE_THRESH) { +@@ -933,7 +924,6 @@ nft_target_select_ops(const struct nft_c + ops->destroy = nft_target_destroy; + ops->dump = nft_target_dump; + ops->data = target; +- ops->reduce = NFT_REDUCE_READONLY; + + if (family == NFPROTO_BRIDGE) { + ops->eval = nft_target_eval_bridge; +--- a/net/netfilter/nft_connlimit.c ++++ b/net/netfilter/nft_connlimit.c +@@ -247,7 +247,6 @@ static const struct nft_expr_ops nft_con + .destroy_clone = nft_connlimit_destroy_clone, + .dump = nft_connlimit_dump, + .gc = nft_connlimit_gc, +- .reduce = NFT_REDUCE_READONLY, + }; + + static struct nft_expr_type nft_connlimit_type __read_mostly = { +--- a/net/netfilter/nft_counter.c ++++ b/net/netfilter/nft_counter.c +@@ -301,7 +301,6 @@ static const struct nft_expr_ops nft_cou + .destroy_clone = nft_counter_destroy, + .dump = nft_counter_dump, + .clone = nft_counter_clone, +- .reduce = NFT_REDUCE_READONLY, + .offload = nft_counter_offload, + .offload_stats = nft_counter_offload_stats, + }; +--- a/net/netfilter/nft_ct.c ++++ b/net/netfilter/nft_ct.c +@@ -679,29 +679,6 @@ nla_put_failure: + return -1; + } + +-static bool nft_ct_get_reduce(struct nft_regs_track *track, +- const struct nft_expr *expr) +-{ +- const struct nft_ct *priv = nft_expr_priv(expr); +- const struct nft_ct *ct; +- +- if (!nft_reg_track_cmp(track, expr, priv->dreg)) { +- nft_reg_track_update(track, expr, priv->dreg, priv->len); +- return false; +- } +- +- ct = nft_expr_priv(track->regs[priv->dreg].selector); +- if (priv->key != ct->key) { +- nft_reg_track_update(track, expr, priv->dreg, priv->len); +- return false; +- } +- +- if (!track->regs[priv->dreg].bitwise) +- return true; +- +- return nft_expr_reduce_bitwise(track, expr); +-} +- + static int nft_ct_set_dump(struct sk_buff *skb, + const struct nft_expr *expr, bool reset) + { +@@ -736,27 +713,8 @@ static const struct nft_expr_ops nft_ct_ + .init = nft_ct_get_init, + .destroy = nft_ct_get_destroy, + .dump = nft_ct_get_dump, +- .reduce = nft_ct_get_reduce, + }; + +-static bool nft_ct_set_reduce(struct nft_regs_track *track, +- const struct nft_expr *expr) +-{ +- int i; +- +- for (i = 0; i < NFT_REG32_NUM; i++) { +- if (!track->regs[i].selector) +- continue; +- +- if (track->regs[i].selector->ops != &nft_ct_get_ops) +- continue; +- +- __nft_reg_track_cancel(track, i); +- } +- +- return false; +-} +- + #ifdef CONFIG_RETPOLINE + static const struct nft_expr_ops nft_ct_get_fast_ops = { + .type = &nft_ct_type, +@@ -765,7 +723,6 @@ static const struct nft_expr_ops nft_ct_ + .init = nft_ct_get_init, + .destroy = nft_ct_get_destroy, + .dump = nft_ct_get_dump, +- .reduce = nft_ct_set_reduce, + }; + #endif + +@@ -776,7 +733,6 @@ static const struct nft_expr_ops nft_ct_ + .init = nft_ct_set_init, + .destroy = nft_ct_set_destroy, + .dump = nft_ct_set_dump, +- .reduce = nft_ct_set_reduce, + }; + + #ifdef CONFIG_NF_CONNTRACK_ZONES +@@ -787,7 +743,6 @@ static const struct nft_expr_ops nft_ct_ + .init = nft_ct_set_init, + .destroy = nft_ct_set_destroy, + .dump = nft_ct_set_dump, +- .reduce = nft_ct_set_reduce, + }; + #endif + +@@ -857,7 +812,6 @@ static const struct nft_expr_ops nft_not + .type = &nft_notrack_type, + .size = NFT_EXPR_SIZE(0), + .eval = nft_notrack_eval, +- .reduce = NFT_REDUCE_READONLY, + }; + + static struct nft_expr_type nft_notrack_type __read_mostly = { +--- a/net/netfilter/nft_dup_netdev.c ++++ b/net/netfilter/nft_dup_netdev.c +@@ -80,7 +80,6 @@ static const struct nft_expr_ops nft_dup + .eval = nft_dup_netdev_eval, + .init = nft_dup_netdev_init, + .dump = nft_dup_netdev_dump, +- .reduce = NFT_REDUCE_READONLY, + .offload = nft_dup_netdev_offload, + .offload_action = nft_dup_netdev_offload_action, + }; +--- a/net/netfilter/nft_dynset.c ++++ b/net/netfilter/nft_dynset.c +@@ -430,7 +430,6 @@ static const struct nft_expr_ops nft_dyn + .activate = nft_dynset_activate, + .deactivate = nft_dynset_deactivate, + .dump = nft_dynset_dump, +- .reduce = NFT_REDUCE_READONLY, + }; + + struct nft_expr_type nft_dynset_type __read_mostly = { +--- a/net/netfilter/nft_exthdr.c ++++ b/net/netfilter/nft_exthdr.c +@@ -701,40 +701,12 @@ static int nft_exthdr_dump_strip(struct + return nft_exthdr_dump_common(skb, priv); + } + +-static bool nft_exthdr_reduce(struct nft_regs_track *track, +- const struct nft_expr *expr) +-{ +- const struct nft_exthdr *priv = nft_expr_priv(expr); +- const struct nft_exthdr *exthdr; +- +- if (!nft_reg_track_cmp(track, expr, priv->dreg)) { +- nft_reg_track_update(track, expr, priv->dreg, priv->len); +- return false; +- } +- +- exthdr = nft_expr_priv(track->regs[priv->dreg].selector); +- if (priv->type != exthdr->type || +- priv->op != exthdr->op || +- priv->flags != exthdr->flags || +- priv->offset != exthdr->offset || +- priv->len != exthdr->len) { +- nft_reg_track_update(track, expr, priv->dreg, priv->len); +- return false; +- } +- +- if (!track->regs[priv->dreg].bitwise) +- return true; +- +- return nft_expr_reduce_bitwise(track, expr); +-} +- + static const struct nft_expr_ops nft_exthdr_ipv6_ops = { + .type = &nft_exthdr_type, + .size = NFT_EXPR_SIZE(sizeof(struct nft_exthdr)), + .eval = nft_exthdr_ipv6_eval, + .init = nft_exthdr_init, + .dump = nft_exthdr_dump, +- .reduce = nft_exthdr_reduce, + }; + + static const struct nft_expr_ops nft_exthdr_ipv4_ops = { +@@ -743,7 +715,6 @@ static const struct nft_expr_ops nft_ext + .eval = nft_exthdr_ipv4_eval, + .init = nft_exthdr_ipv4_init, + .dump = nft_exthdr_dump, +- .reduce = nft_exthdr_reduce, + }; + + static const struct nft_expr_ops nft_exthdr_tcp_ops = { +@@ -752,7 +723,6 @@ static const struct nft_expr_ops nft_ext + .eval = nft_exthdr_tcp_eval, + .init = nft_exthdr_init, + .dump = nft_exthdr_dump, +- .reduce = nft_exthdr_reduce, + }; + + static const struct nft_expr_ops nft_exthdr_tcp_set_ops = { +@@ -761,7 +731,6 @@ static const struct nft_expr_ops nft_ext + .eval = nft_exthdr_tcp_set_eval, + .init = nft_exthdr_tcp_set_init, + .dump = nft_exthdr_dump_set, +- .reduce = NFT_REDUCE_READONLY, + }; + + static const struct nft_expr_ops nft_exthdr_tcp_strip_ops = { +@@ -770,7 +739,6 @@ static const struct nft_expr_ops nft_ext + .eval = nft_exthdr_tcp_strip_eval, + .init = nft_exthdr_tcp_strip_init, + .dump = nft_exthdr_dump_strip, +- .reduce = NFT_REDUCE_READONLY, + }; + + static const struct nft_expr_ops nft_exthdr_sctp_ops = { +@@ -779,7 +747,6 @@ static const struct nft_expr_ops nft_ext + .eval = nft_exthdr_sctp_eval, + .init = nft_exthdr_init, + .dump = nft_exthdr_dump, +- .reduce = nft_exthdr_reduce, + }; + + static const struct nft_expr_ops nft_exthdr_dccp_ops = { +@@ -788,7 +755,6 @@ static const struct nft_expr_ops nft_ext + .eval = nft_exthdr_dccp_eval, + .init = nft_exthdr_dccp_init, + .dump = nft_exthdr_dump, +- .reduce = nft_exthdr_reduce, + }; + + static const struct nft_expr_ops * +--- a/net/netfilter/nft_fib.c ++++ b/net/netfilter/nft_fib.c +@@ -168,47 +168,5 @@ void nft_fib_store_result(void *reg, con + } + EXPORT_SYMBOL_GPL(nft_fib_store_result); + +-bool nft_fib_reduce(struct nft_regs_track *track, +- const struct nft_expr *expr) +-{ +- const struct nft_fib *priv = nft_expr_priv(expr); +- unsigned int len = NFT_REG32_SIZE; +- const struct nft_fib *fib; +- +- switch (priv->result) { +- case NFT_FIB_RESULT_OIF: +- break; +- case NFT_FIB_RESULT_OIFNAME: +- if (priv->flags & NFTA_FIB_F_PRESENT) +- len = NFT_REG32_SIZE; +- else +- len = IFNAMSIZ; +- break; +- case NFT_FIB_RESULT_ADDRTYPE: +- break; +- default: +- WARN_ON_ONCE(1); +- break; +- } +- +- if (!nft_reg_track_cmp(track, expr, priv->dreg)) { +- nft_reg_track_update(track, expr, priv->dreg, len); +- return false; +- } +- +- fib = nft_expr_priv(track->regs[priv->dreg].selector); +- if (priv->result != fib->result || +- priv->flags != fib->flags) { +- nft_reg_track_update(track, expr, priv->dreg, len); +- return false; +- } +- +- if (!track->regs[priv->dreg].bitwise) +- return true; +- +- return false; +-} +-EXPORT_SYMBOL_GPL(nft_fib_reduce); +- + MODULE_LICENSE("GPL"); + MODULE_AUTHOR("Florian Westphal "); +--- a/net/netfilter/nft_fib_inet.c ++++ b/net/netfilter/nft_fib_inet.c +@@ -49,7 +49,6 @@ static const struct nft_expr_ops nft_fib + .init = nft_fib_init, + .dump = nft_fib_dump, + .validate = nft_fib_validate, +- .reduce = nft_fib_reduce, + }; + + static struct nft_expr_type nft_fib_inet_type __read_mostly = { +--- a/net/netfilter/nft_fib_netdev.c ++++ b/net/netfilter/nft_fib_netdev.c +@@ -58,7 +58,6 @@ static const struct nft_expr_ops nft_fib + .init = nft_fib_init, + .dump = nft_fib_dump, + .validate = nft_fib_validate, +- .reduce = nft_fib_reduce, + }; + + static struct nft_expr_type nft_fib_netdev_type __read_mostly = { +--- a/net/netfilter/nft_flow_offload.c ++++ b/net/netfilter/nft_flow_offload.c +@@ -481,7 +481,6 @@ static const struct nft_expr_ops nft_flo + .destroy = nft_flow_offload_destroy, + .validate = nft_flow_offload_validate, + .dump = nft_flow_offload_dump, +- .reduce = NFT_REDUCE_READONLY, + }; + + static struct nft_expr_type nft_flow_offload_type __read_mostly = { +--- a/net/netfilter/nft_fwd_netdev.c ++++ b/net/netfilter/nft_fwd_netdev.c +@@ -228,7 +228,6 @@ static const struct nft_expr_ops nft_fwd + .init = nft_fwd_neigh_init, + .dump = nft_fwd_neigh_dump, + .validate = nft_fwd_validate, +- .reduce = NFT_REDUCE_READONLY, + }; + + static const struct nft_expr_ops nft_fwd_netdev_ops = { +@@ -238,7 +237,6 @@ static const struct nft_expr_ops nft_fwd + .init = nft_fwd_netdev_init, + .dump = nft_fwd_netdev_dump, + .validate = nft_fwd_validate, +- .reduce = NFT_REDUCE_READONLY, + .offload = nft_fwd_netdev_offload, + .offload_action = nft_fwd_netdev_offload_action, + }; +--- a/net/netfilter/nft_hash.c ++++ b/net/netfilter/nft_hash.c +@@ -165,16 +165,6 @@ nla_put_failure: + return -1; + } + +-static bool nft_jhash_reduce(struct nft_regs_track *track, +- const struct nft_expr *expr) +-{ +- const struct nft_jhash *priv = nft_expr_priv(expr); +- +- nft_reg_track_cancel(track, priv->dreg, sizeof(u32)); +- +- return false; +-} +- + static int nft_symhash_dump(struct sk_buff *skb, + const struct nft_expr *expr, bool reset) + { +@@ -195,30 +185,6 @@ nla_put_failure: + return -1; + } + +-static bool nft_symhash_reduce(struct nft_regs_track *track, +- const struct nft_expr *expr) +-{ +- struct nft_symhash *priv = nft_expr_priv(expr); +- struct nft_symhash *symhash; +- +- if (!nft_reg_track_cmp(track, expr, priv->dreg)) { +- nft_reg_track_update(track, expr, priv->dreg, sizeof(u32)); +- return false; +- } +- +- symhash = nft_expr_priv(track->regs[priv->dreg].selector); +- if (priv->offset != symhash->offset || +- priv->modulus != symhash->modulus) { +- nft_reg_track_update(track, expr, priv->dreg, sizeof(u32)); +- return false; +- } +- +- if (!track->regs[priv->dreg].bitwise) +- return true; +- +- return false; +-} +- + static struct nft_expr_type nft_hash_type; + static const struct nft_expr_ops nft_jhash_ops = { + .type = &nft_hash_type, +@@ -226,7 +192,6 @@ static const struct nft_expr_ops nft_jha + .eval = nft_jhash_eval, + .init = nft_jhash_init, + .dump = nft_jhash_dump, +- .reduce = nft_jhash_reduce, + }; + + static const struct nft_expr_ops nft_symhash_ops = { +@@ -235,7 +200,6 @@ static const struct nft_expr_ops nft_sym + .eval = nft_symhash_eval, + .init = nft_symhash_init, + .dump = nft_symhash_dump, +- .reduce = nft_symhash_reduce, + }; + + static const struct nft_expr_ops * +--- a/net/netfilter/nft_immediate.c ++++ b/net/netfilter/nft_immediate.c +@@ -320,17 +320,6 @@ static bool nft_immediate_offload_action + return false; + } + +-static bool nft_immediate_reduce(struct nft_regs_track *track, +- const struct nft_expr *expr) +-{ +- const struct nft_immediate_expr *priv = nft_expr_priv(expr); +- +- if (priv->dreg != NFT_REG_VERDICT) +- nft_reg_track_cancel(track, priv->dreg, priv->dlen); +- +- return false; +-} +- + static const struct nft_expr_ops nft_imm_ops = { + .type = &nft_imm_type, + .size = NFT_EXPR_SIZE(sizeof(struct nft_immediate_expr)), +@@ -341,7 +330,6 @@ static const struct nft_expr_ops nft_imm + .destroy = nft_immediate_destroy, + .dump = nft_immediate_dump, + .validate = nft_immediate_validate, +- .reduce = nft_immediate_reduce, + .offload = nft_immediate_offload, + .offload_action = nft_immediate_offload_action, + }; +--- a/net/netfilter/nft_last.c ++++ b/net/netfilter/nft_last.c +@@ -125,7 +125,6 @@ static const struct nft_expr_ops nft_las + .destroy = nft_last_destroy, + .clone = nft_last_clone, + .dump = nft_last_dump, +- .reduce = NFT_REDUCE_READONLY, + }; + + struct nft_expr_type nft_last_type __read_mostly = { +--- a/net/netfilter/nft_limit.c ++++ b/net/netfilter/nft_limit.c +@@ -243,7 +243,6 @@ static const struct nft_expr_ops nft_lim + .destroy = nft_limit_pkts_destroy, + .clone = nft_limit_pkts_clone, + .dump = nft_limit_pkts_dump, +- .reduce = NFT_REDUCE_READONLY, + }; + + static void nft_limit_bytes_eval(const struct nft_expr *expr, +@@ -299,7 +298,6 @@ static const struct nft_expr_ops nft_lim + .dump = nft_limit_bytes_dump, + .clone = nft_limit_bytes_clone, + .destroy = nft_limit_bytes_destroy, +- .reduce = NFT_REDUCE_READONLY, + }; + + static const struct nft_expr_ops * +--- a/net/netfilter/nft_log.c ++++ b/net/netfilter/nft_log.c +@@ -235,7 +235,6 @@ static const struct nft_expr_ops nft_log + .init = nft_log_init, + .destroy = nft_log_destroy, + .dump = nft_log_dump, +- .reduce = NFT_REDUCE_READONLY, + }; + + static struct nft_expr_type nft_log_type __read_mostly = { +--- a/net/netfilter/nft_lookup.c ++++ b/net/netfilter/nft_lookup.c +@@ -232,17 +232,6 @@ static int nft_lookup_validate(const str + return 0; + } + +-static bool nft_lookup_reduce(struct nft_regs_track *track, +- const struct nft_expr *expr) +-{ +- const struct nft_lookup *priv = nft_expr_priv(expr); +- +- if (priv->set->flags & NFT_SET_MAP) +- nft_reg_track_cancel(track, priv->dreg, priv->set->dlen); +- +- return false; +-} +- + static const struct nft_expr_ops nft_lookup_ops = { + .type = &nft_lookup_type, + .size = NFT_EXPR_SIZE(sizeof(struct nft_lookup)), +@@ -253,7 +242,6 @@ static const struct nft_expr_ops nft_loo + .destroy = nft_lookup_destroy, + .dump = nft_lookup_dump, + .validate = nft_lookup_validate, +- .reduce = nft_lookup_reduce, + }; + + struct nft_expr_type nft_lookup_type __read_mostly = { +--- a/net/netfilter/nft_masq.c ++++ b/net/netfilter/nft_masq.c +@@ -143,7 +143,6 @@ static const struct nft_expr_ops nft_mas + .destroy = nft_masq_ipv4_destroy, + .dump = nft_masq_dump, + .validate = nft_masq_validate, +- .reduce = NFT_REDUCE_READONLY, + }; + + static struct nft_expr_type nft_masq_ipv4_type __read_mostly = { +@@ -171,7 +170,6 @@ static const struct nft_expr_ops nft_mas + .destroy = nft_masq_ipv6_destroy, + .dump = nft_masq_dump, + .validate = nft_masq_validate, +- .reduce = NFT_REDUCE_READONLY, + }; + + static struct nft_expr_type nft_masq_ipv6_type __read_mostly = { +@@ -213,7 +211,6 @@ static const struct nft_expr_ops nft_mas + .destroy = nft_masq_inet_destroy, + .dump = nft_masq_dump, + .validate = nft_masq_validate, +- .reduce = NFT_REDUCE_READONLY, + }; + + static struct nft_expr_type nft_masq_inet_type __read_mostly = { +--- a/net/netfilter/nft_meta.c ++++ b/net/netfilter/nft_meta.c +@@ -742,60 +742,16 @@ static int nft_meta_get_offload(struct n + return 0; + } + +-bool nft_meta_get_reduce(struct nft_regs_track *track, +- const struct nft_expr *expr) +-{ +- const struct nft_meta *priv = nft_expr_priv(expr); +- const struct nft_meta *meta; +- +- if (!nft_reg_track_cmp(track, expr, priv->dreg)) { +- nft_reg_track_update(track, expr, priv->dreg, priv->len); +- return false; +- } +- +- meta = nft_expr_priv(track->regs[priv->dreg].selector); +- if (priv->key != meta->key || +- priv->dreg != meta->dreg) { +- nft_reg_track_update(track, expr, priv->dreg, priv->len); +- return false; +- } +- +- if (!track->regs[priv->dreg].bitwise) +- return true; +- +- return nft_expr_reduce_bitwise(track, expr); +-} +-EXPORT_SYMBOL_GPL(nft_meta_get_reduce); +- + static const struct nft_expr_ops nft_meta_get_ops = { + .type = &nft_meta_type, + .size = NFT_EXPR_SIZE(sizeof(struct nft_meta)), + .eval = nft_meta_get_eval, + .init = nft_meta_get_init, + .dump = nft_meta_get_dump, +- .reduce = nft_meta_get_reduce, + .validate = nft_meta_get_validate, + .offload = nft_meta_get_offload, + }; + +-static bool nft_meta_set_reduce(struct nft_regs_track *track, +- const struct nft_expr *expr) +-{ +- int i; +- +- for (i = 0; i < NFT_REG32_NUM; i++) { +- if (!track->regs[i].selector) +- continue; +- +- if (track->regs[i].selector->ops != &nft_meta_get_ops) +- continue; +- +- __nft_reg_track_cancel(track, i); +- } +- +- return false; +-} +- + static const struct nft_expr_ops nft_meta_set_ops = { + .type = &nft_meta_type, + .size = NFT_EXPR_SIZE(sizeof(struct nft_meta)), +@@ -803,7 +759,6 @@ static const struct nft_expr_ops nft_met + .init = nft_meta_set_init, + .destroy = nft_meta_set_destroy, + .dump = nft_meta_set_dump, +- .reduce = nft_meta_set_reduce, + .validate = nft_meta_set_validate, + }; + +--- a/net/netfilter/nft_nat.c ++++ b/net/netfilter/nft_nat.c +@@ -320,7 +320,6 @@ static const struct nft_expr_ops nft_nat + .destroy = nft_nat_destroy, + .dump = nft_nat_dump, + .validate = nft_nat_validate, +- .reduce = NFT_REDUCE_READONLY, + }; + + static struct nft_expr_type nft_nat_type __read_mostly = { +@@ -351,7 +350,6 @@ static const struct nft_expr_ops nft_nat + .destroy = nft_nat_destroy, + .dump = nft_nat_dump, + .validate = nft_nat_validate, +- .reduce = NFT_REDUCE_READONLY, + }; + + static struct nft_expr_type nft_inet_nat_type __read_mostly = { +--- a/net/netfilter/nft_numgen.c ++++ b/net/netfilter/nft_numgen.c +@@ -84,16 +84,6 @@ err: + return err; + } + +-static bool nft_ng_inc_reduce(struct nft_regs_track *track, +- const struct nft_expr *expr) +-{ +- const struct nft_ng_inc *priv = nft_expr_priv(expr); +- +- nft_reg_track_cancel(track, priv->dreg, NFT_REG32_SIZE); +- +- return false; +-} +- + static int nft_ng_dump(struct sk_buff *skb, enum nft_registers dreg, + u32 modulus, enum nft_ng_types type, u32 offset) + { +@@ -178,16 +168,6 @@ static int nft_ng_random_dump(struct sk_ + priv->offset); + } + +-static bool nft_ng_random_reduce(struct nft_regs_track *track, +- const struct nft_expr *expr) +-{ +- const struct nft_ng_random *priv = nft_expr_priv(expr); +- +- nft_reg_track_cancel(track, priv->dreg, NFT_REG32_SIZE); +- +- return false; +-} +- + static struct nft_expr_type nft_ng_type; + static const struct nft_expr_ops nft_ng_inc_ops = { + .type = &nft_ng_type, +@@ -196,7 +176,6 @@ static const struct nft_expr_ops nft_ng_ + .init = nft_ng_inc_init, + .destroy = nft_ng_inc_destroy, + .dump = nft_ng_inc_dump, +- .reduce = nft_ng_inc_reduce, + }; + + static const struct nft_expr_ops nft_ng_random_ops = { +@@ -205,7 +184,6 @@ static const struct nft_expr_ops nft_ng_ + .eval = nft_ng_random_eval, + .init = nft_ng_random_init, + .dump = nft_ng_random_dump, +- .reduce = nft_ng_random_reduce, + }; + + static const struct nft_expr_ops * +--- a/net/netfilter/nft_objref.c ++++ b/net/netfilter/nft_objref.c +@@ -123,7 +123,6 @@ static const struct nft_expr_ops nft_obj + .deactivate = nft_objref_deactivate, + .dump = nft_objref_dump, + .validate = nft_objref_validate, +- .reduce = NFT_REDUCE_READONLY, + }; + + struct nft_objref_map { +@@ -246,7 +245,6 @@ static const struct nft_expr_ops nft_obj + .destroy = nft_objref_map_destroy, + .dump = nft_objref_map_dump, + .validate = nft_objref_map_validate, +- .reduce = NFT_REDUCE_READONLY, + }; + + static const struct nft_expr_ops * +--- a/net/netfilter/nft_osf.c ++++ b/net/netfilter/nft_osf.c +@@ -136,30 +136,6 @@ static int nft_osf_validate(const struct + return nft_chain_validate_hooks(ctx->chain, hooks); + } + +-static bool nft_osf_reduce(struct nft_regs_track *track, +- const struct nft_expr *expr) +-{ +- struct nft_osf *priv = nft_expr_priv(expr); +- struct nft_osf *osf; +- +- if (!nft_reg_track_cmp(track, expr, priv->dreg)) { +- nft_reg_track_update(track, expr, priv->dreg, NFT_OSF_MAXGENRELEN); +- return false; +- } +- +- osf = nft_expr_priv(track->regs[priv->dreg].selector); +- if (priv->flags != osf->flags || +- priv->ttl != osf->ttl) { +- nft_reg_track_update(track, expr, priv->dreg, NFT_OSF_MAXGENRELEN); +- return false; +- } +- +- if (!track->regs[priv->dreg].bitwise) +- return true; +- +- return false; +-} +- + static struct nft_expr_type nft_osf_type; + static const struct nft_expr_ops nft_osf_op = { + .eval = nft_osf_eval, +@@ -168,7 +144,6 @@ static const struct nft_expr_ops nft_osf + .dump = nft_osf_dump, + .type = &nft_osf_type, + .validate = nft_osf_validate, +- .reduce = nft_osf_reduce, + }; + + static struct nft_expr_type nft_osf_type __read_mostly = { +--- a/net/netfilter/nft_payload.c ++++ b/net/netfilter/nft_payload.c +@@ -250,31 +250,6 @@ nla_put_failure: + return -1; + } + +-static bool nft_payload_reduce(struct nft_regs_track *track, +- const struct nft_expr *expr) +-{ +- const struct nft_payload *priv = nft_expr_priv(expr); +- const struct nft_payload *payload; +- +- if (!nft_reg_track_cmp(track, expr, priv->dreg)) { +- nft_reg_track_update(track, expr, priv->dreg, priv->len); +- return false; +- } +- +- payload = nft_expr_priv(track->regs[priv->dreg].selector); +- if (priv->base != payload->base || +- priv->offset != payload->offset || +- priv->len != payload->len) { +- nft_reg_track_update(track, expr, priv->dreg, priv->len); +- return false; +- } +- +- if (!track->regs[priv->dreg].bitwise) +- return true; +- +- return nft_expr_reduce_bitwise(track, expr); +-} +- + static bool nft_payload_offload_mask(struct nft_offload_reg *reg, + u32 priv_len, u32 field_len) + { +@@ -578,7 +553,6 @@ static const struct nft_expr_ops nft_pay + .eval = nft_payload_eval, + .init = nft_payload_init, + .dump = nft_payload_dump, +- .reduce = nft_payload_reduce, + .offload = nft_payload_offload, + }; + +@@ -588,7 +562,6 @@ const struct nft_expr_ops nft_payload_fa + .eval = nft_payload_eval, + .init = nft_payload_init, + .dump = nft_payload_dump, +- .reduce = nft_payload_reduce, + .offload = nft_payload_offload, + }; + +@@ -1008,32 +981,12 @@ nla_put_failure: + return -1; + } + +-static bool nft_payload_set_reduce(struct nft_regs_track *track, +- const struct nft_expr *expr) +-{ +- int i; +- +- for (i = 0; i < NFT_REG32_NUM; i++) { +- if (!track->regs[i].selector) +- continue; +- +- if (track->regs[i].selector->ops != &nft_payload_ops && +- track->regs[i].selector->ops != &nft_payload_fast_ops) +- continue; +- +- __nft_reg_track_cancel(track, i); +- } +- +- return false; +-} +- + static const struct nft_expr_ops nft_payload_set_ops = { + .type = &nft_payload_type, + .size = NFT_EXPR_SIZE(sizeof(struct nft_payload_set)), + .eval = nft_payload_set_eval, + .init = nft_payload_set_init, + .dump = nft_payload_set_dump, +- .reduce = nft_payload_set_reduce, + }; + + static const struct nft_expr_ops * +--- a/net/netfilter/nft_queue.c ++++ b/net/netfilter/nft_queue.c +@@ -191,7 +191,6 @@ static const struct nft_expr_ops nft_que + .init = nft_queue_init, + .dump = nft_queue_dump, + .validate = nft_queue_validate, +- .reduce = NFT_REDUCE_READONLY, + }; + + static const struct nft_expr_ops nft_queue_sreg_ops = { +@@ -201,7 +200,6 @@ static const struct nft_expr_ops nft_que + .init = nft_queue_sreg_init, + .dump = nft_queue_sreg_dump, + .validate = nft_queue_validate, +- .reduce = NFT_REDUCE_READONLY, + }; + + static const struct nft_expr_ops * +--- a/net/netfilter/nft_quota.c ++++ b/net/netfilter/nft_quota.c +@@ -265,7 +265,6 @@ static const struct nft_expr_ops nft_quo + .destroy = nft_quota_destroy, + .clone = nft_quota_clone, + .dump = nft_quota_dump, +- .reduce = NFT_REDUCE_READONLY, + }; + + static struct nft_expr_type nft_quota_type __read_mostly = { +--- a/net/netfilter/nft_range.c ++++ b/net/netfilter/nft_range.c +@@ -138,7 +138,6 @@ static const struct nft_expr_ops nft_ran + .eval = nft_range_eval, + .init = nft_range_init, + .dump = nft_range_dump, +- .reduce = NFT_REDUCE_READONLY, + }; + + struct nft_expr_type nft_range_type __read_mostly = { +--- a/net/netfilter/nft_redir.c ++++ b/net/netfilter/nft_redir.c +@@ -146,7 +146,6 @@ static const struct nft_expr_ops nft_red + .destroy = nft_redir_ipv4_destroy, + .dump = nft_redir_dump, + .validate = nft_redir_validate, +- .reduce = NFT_REDUCE_READONLY, + }; + + static struct nft_expr_type nft_redir_ipv4_type __read_mostly = { +@@ -174,7 +173,6 @@ static const struct nft_expr_ops nft_red + .destroy = nft_redir_ipv6_destroy, + .dump = nft_redir_dump, + .validate = nft_redir_validate, +- .reduce = NFT_REDUCE_READONLY, + }; + + static struct nft_expr_type nft_redir_ipv6_type __read_mostly = { +@@ -203,7 +201,6 @@ static const struct nft_expr_ops nft_red + .destroy = nft_redir_inet_destroy, + .dump = nft_redir_dump, + .validate = nft_redir_validate, +- .reduce = NFT_REDUCE_READONLY, + }; + + static struct nft_expr_type nft_redir_inet_type __read_mostly = { +--- a/net/netfilter/nft_reject_inet.c ++++ b/net/netfilter/nft_reject_inet.c +@@ -79,7 +79,6 @@ static const struct nft_expr_ops nft_rej + .init = nft_reject_init, + .dump = nft_reject_dump, + .validate = nft_reject_inet_validate, +- .reduce = NFT_REDUCE_READONLY, + }; + + static struct nft_expr_type nft_reject_inet_type __read_mostly = { +--- a/net/netfilter/nft_reject_netdev.c ++++ b/net/netfilter/nft_reject_netdev.c +@@ -158,7 +158,6 @@ static const struct nft_expr_ops nft_rej + .init = nft_reject_init, + .dump = nft_reject_dump, + .validate = nft_reject_netdev_validate, +- .reduce = NFT_REDUCE_READONLY, + }; + + static struct nft_expr_type nft_reject_netdev_type __read_mostly = { +--- a/net/netfilter/nft_rt.c ++++ b/net/netfilter/nft_rt.c +@@ -195,7 +195,6 @@ static const struct nft_expr_ops nft_rt_ + .init = nft_rt_get_init, + .dump = nft_rt_get_dump, + .validate = nft_rt_validate, +- .reduce = NFT_REDUCE_READONLY, + }; + + struct nft_expr_type nft_rt_type __read_mostly = { +--- a/net/netfilter/nft_socket.c ++++ b/net/netfilter/nft_socket.c +@@ -249,31 +249,6 @@ static int nft_socket_dump(struct sk_buf + return 0; + } + +-static bool nft_socket_reduce(struct nft_regs_track *track, +- const struct nft_expr *expr) +-{ +- const struct nft_socket *priv = nft_expr_priv(expr); +- const struct nft_socket *socket; +- +- if (!nft_reg_track_cmp(track, expr, priv->dreg)) { +- nft_reg_track_update(track, expr, priv->dreg, priv->len); +- return false; +- } +- +- socket = nft_expr_priv(track->regs[priv->dreg].selector); +- if (priv->key != socket->key || +- priv->dreg != socket->dreg || +- priv->level != socket->level) { +- nft_reg_track_update(track, expr, priv->dreg, priv->len); +- return false; +- } +- +- if (!track->regs[priv->dreg].bitwise) +- return true; +- +- return nft_expr_reduce_bitwise(track, expr); +-} +- + static int nft_socket_validate(const struct nft_ctx *ctx, + const struct nft_expr *expr) + { +@@ -296,7 +271,6 @@ static const struct nft_expr_ops nft_soc + .init = nft_socket_init, + .dump = nft_socket_dump, + .validate = nft_socket_validate, +- .reduce = nft_socket_reduce, + }; + + static struct nft_expr_type nft_socket_type __read_mostly = { +--- a/net/netfilter/nft_synproxy.c ++++ b/net/netfilter/nft_synproxy.c +@@ -290,7 +290,6 @@ static const struct nft_expr_ops nft_syn + .dump = nft_synproxy_dump, + .type = &nft_synproxy_type, + .validate = nft_synproxy_validate, +- .reduce = NFT_REDUCE_READONLY, + }; + + static struct nft_expr_type nft_synproxy_type __read_mostly = { +--- a/net/netfilter/nft_tproxy.c ++++ b/net/netfilter/nft_tproxy.c +@@ -331,7 +331,6 @@ static const struct nft_expr_ops nft_tpr + .init = nft_tproxy_init, + .destroy = nft_tproxy_destroy, + .dump = nft_tproxy_dump, +- .reduce = NFT_REDUCE_READONLY, + .validate = nft_tproxy_validate, + }; + +--- a/net/netfilter/nft_tunnel.c ++++ b/net/netfilter/nft_tunnel.c +@@ -124,31 +124,6 @@ nla_put_failure: + return -1; + } + +-static bool nft_tunnel_get_reduce(struct nft_regs_track *track, +- const struct nft_expr *expr) +-{ +- const struct nft_tunnel *priv = nft_expr_priv(expr); +- const struct nft_tunnel *tunnel; +- +- if (!nft_reg_track_cmp(track, expr, priv->dreg)) { +- nft_reg_track_update(track, expr, priv->dreg, priv->len); +- return false; +- } +- +- tunnel = nft_expr_priv(track->regs[priv->dreg].selector); +- if (priv->key != tunnel->key || +- priv->dreg != tunnel->dreg || +- priv->mode != tunnel->mode) { +- nft_reg_track_update(track, expr, priv->dreg, priv->len); +- return false; +- } +- +- if (!track->regs[priv->dreg].bitwise) +- return true; +- +- return false; +-} +- + static struct nft_expr_type nft_tunnel_type; + static const struct nft_expr_ops nft_tunnel_get_ops = { + .type = &nft_tunnel_type, +@@ -156,7 +131,6 @@ static const struct nft_expr_ops nft_tun + .eval = nft_tunnel_get_eval, + .init = nft_tunnel_get_init, + .dump = nft_tunnel_get_dump, +- .reduce = nft_tunnel_get_reduce, + }; + + static struct nft_expr_type nft_tunnel_type __read_mostly = { +--- a/net/netfilter/nft_xfrm.c ++++ b/net/netfilter/nft_xfrm.c +@@ -258,32 +258,6 @@ static int nft_xfrm_validate(const struc + return nft_chain_validate_hooks(ctx->chain, hooks); + } + +-static bool nft_xfrm_reduce(struct nft_regs_track *track, +- const struct nft_expr *expr) +-{ +- const struct nft_xfrm *priv = nft_expr_priv(expr); +- const struct nft_xfrm *xfrm; +- +- if (!nft_reg_track_cmp(track, expr, priv->dreg)) { +- nft_reg_track_update(track, expr, priv->dreg, priv->len); +- return false; +- } +- +- xfrm = nft_expr_priv(track->regs[priv->dreg].selector); +- if (priv->key != xfrm->key || +- priv->dreg != xfrm->dreg || +- priv->dir != xfrm->dir || +- priv->spnum != xfrm->spnum) { +- nft_reg_track_update(track, expr, priv->dreg, priv->len); +- return false; +- } +- +- if (!track->regs[priv->dreg].bitwise) +- return true; +- +- return nft_expr_reduce_bitwise(track, expr); +-} +- + static struct nft_expr_type nft_xfrm_type; + static const struct nft_expr_ops nft_xfrm_get_ops = { + .type = &nft_xfrm_type, +@@ -292,7 +266,6 @@ static const struct nft_expr_ops nft_xfr + .init = nft_xfrm_get_init, + .dump = nft_xfrm_get_dump, + .validate = nft_xfrm_validate, +- .reduce = nft_xfrm_reduce, + }; + + static struct nft_expr_type nft_xfrm_type __read_mostly = { diff --git a/queue-6.6/netfilter-nf_tables-remove-unused-nft_reduce_is_readonly.patch b/queue-6.6/netfilter-nf_tables-remove-unused-nft_reduce_is_readonly.patch new file mode 100644 index 0000000000..fa69670d13 --- /dev/null +++ b/queue-6.6/netfilter-nf_tables-remove-unused-nft_reduce_is_readonly.patch @@ -0,0 +1,39 @@ +From stable+bounces-288523-greg=kroah.com@vger.kernel.org Thu Jul 23 22:10:39 2026 +From: Sasha Levin +Date: Thu, 23 Jul 2026 15:58:40 -0400 +Subject: netfilter: nf_tables: Remove unused nft_reduce_is_readonly() +To: stable@vger.kernel.org +Cc: Yue Haibing , Simon Horman , Pablo Neira Ayuso , Sasha Levin +Message-ID: <20260723195842.3683768-1-sashal@kernel.org> + +From: Yue Haibing + +[ Upstream commit bf6788742b8d6c73de441e088a71de7154f0d4aa ] + +Since commit 9e539c5b6d9c ("netfilter: nf_tables: disable expression +reduction infra") this is unused. + +Signed-off-by: Yue Haibing +Reviewed-by: Simon Horman +Signed-off-by: Pablo Neira Ayuso +Stable-dep-of: d07955dd34ec ("netfilter: nft_fib: reject fib expression on the netdev egress hook") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + include/net/netfilter/nf_tables.h | 5 ----- + 1 file changed, 5 deletions(-) + +--- a/include/net/netfilter/nf_tables.h ++++ b/include/net/netfilter/nf_tables.h +@@ -1851,11 +1851,6 @@ static inline u64 nft_net_tstamp(const s + #define __NFT_REDUCE_READONLY 1UL + #define NFT_REDUCE_READONLY (void *)__NFT_REDUCE_READONLY + +-static inline bool nft_reduce_is_readonly(const struct nft_expr *expr) +-{ +- return expr->ops->reduce == NFT_REDUCE_READONLY; +-} +- + void nft_reg_track_update(struct nft_regs_track *track, + const struct nft_expr *expr, u8 dreg, u8 len); + void nft_reg_track_cancel(struct nft_regs_track *track, u8 dreg, u8 len); diff --git a/queue-6.6/netfilter-nft_fib-reject-fib-expression-on-the-netdev-egress-hook.patch b/queue-6.6/netfilter-nft_fib-reject-fib-expression-on-the-netdev-egress-hook.patch new file mode 100644 index 0000000000..bf2d34d951 --- /dev/null +++ b/queue-6.6/netfilter-nft_fib-reject-fib-expression-on-the-netdev-egress-hook.patch @@ -0,0 +1,100 @@ +From stable+bounces-288524-greg=kroah.com@vger.kernel.org Thu Jul 23 22:10:45 2026 +From: Sasha Levin +Date: Thu, 23 Jul 2026 15:58:42 -0400 +Subject: netfilter: nft_fib: reject fib expression on the netdev egress hook +To: stable@vger.kernel.org +Cc: Theodor Arsenij Larionov-Trichkine , Florian Westphal , Sasha Levin +Message-ID: <20260723195842.3683768-3-sashal@kernel.org> + +From: Theodor Arsenij Larionov-Trichkine + +[ Upstream commit d07955dd34ecae17d35d8c7d0a273a3fba653a8c ] + +A fib expression in a netdev egress base chain dereferences nft_in(pkt), +NULL on the transmit path, causing a NULL pointer dereference at eval. +nft_fib_validate() masks the hook with NF_INET_* values, but netdev hook +numbers are a separate enum that aliases them (NF_NETDEV_EGRESS == +NF_INET_LOCAL_IN), so an egress chain passes validation and then faults. + +Add nft_fib_netdev_validate() that limits each result/flag to the netdev +hook where the device it reads exists: the input-device cases (OIF, +OIFNAME, ADDRTYPE with F_IIF) to ingress, the output-device case (ADDRTYPE +with F_OIF) to egress, ADDRTYPE with no device flag to both. Also restrict +nft_fib_validate() to NFPROTO_IPV4/IPV6/INET so its NF_INET_* masks are +not applied to another family's hooks. + +Fixes: 42df6e1d221d ("netfilter: Introduce egress hook") +Cc: stable@vger.kernel.org +Link: https://lore.kernel.org/netfilter-devel/ajxsjcDOnwllMfoR@strlen.de/ +Signed-off-by: Theodor Arsenij Larionov-Trichkine +Signed-off-by: Florian Westphal +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + net/netfilter/nft_fib.c | 9 +++++++++ + net/netfilter/nft_fib_netdev.c | 29 ++++++++++++++++++++++++++++- + 2 files changed, 37 insertions(+), 1 deletion(-) + +--- a/net/netfilter/nft_fib.c ++++ b/net/netfilter/nft_fib.c +@@ -31,6 +31,15 @@ int nft_fib_validate(const struct nft_ct + const struct nft_fib *priv = nft_expr_priv(expr); + unsigned int hooks; + ++ switch (ctx->family) { ++ case NFPROTO_IPV4: ++ case NFPROTO_IPV6: ++ case NFPROTO_INET: ++ break; ++ default: ++ return -EOPNOTSUPP; ++ } ++ + switch (priv->result) { + case NFT_FIB_RESULT_OIF: + case NFT_FIB_RESULT_OIFNAME: +--- a/net/netfilter/nft_fib_netdev.c ++++ b/net/netfilter/nft_fib_netdev.c +@@ -50,6 +50,33 @@ static void nft_fib_netdev_eval(const st + regs->verdict.code = NFT_BREAK; + } + ++static int nft_fib_netdev_validate(const struct nft_ctx *ctx, ++ const struct nft_expr *expr) ++{ ++ const struct nft_fib *priv = nft_expr_priv(expr); ++ unsigned int hooks; ++ ++ switch (priv->result) { ++ case NFT_FIB_RESULT_OIF: ++ case NFT_FIB_RESULT_OIFNAME: ++ hooks = (1 << NF_NETDEV_INGRESS); ++ break; ++ case NFT_FIB_RESULT_ADDRTYPE: ++ if (priv->flags & NFTA_FIB_F_IIF) ++ hooks = (1 << NF_NETDEV_INGRESS); ++ else if (priv->flags & NFTA_FIB_F_OIF) ++ hooks = (1 << NF_NETDEV_EGRESS); ++ else ++ hooks = (1 << NF_NETDEV_INGRESS) | ++ (1 << NF_NETDEV_EGRESS); ++ break; ++ default: ++ return -EINVAL; ++ } ++ ++ return nft_chain_validate_hooks(ctx->chain, hooks); ++} ++ + static struct nft_expr_type nft_fib_netdev_type; + static const struct nft_expr_ops nft_fib_netdev_ops = { + .type = &nft_fib_netdev_type, +@@ -57,7 +84,7 @@ static const struct nft_expr_ops nft_fib + .eval = nft_fib_netdev_eval, + .init = nft_fib_init, + .dump = nft_fib_dump, +- .validate = nft_fib_validate, ++ .validate = nft_fib_netdev_validate, + }; + + static struct nft_expr_type nft_fib_netdev_type __read_mostly = { diff --git a/queue-6.6/netfilter-nft_set_pipapo-don-t-leak-bad-clone-into-future-transaction.patch b/queue-6.6/netfilter-nft_set_pipapo-don-t-leak-bad-clone-into-future-transaction.patch new file mode 100644 index 0000000000..8c2ae9807f --- /dev/null +++ b/queue-6.6/netfilter-nft_set_pipapo-don-t-leak-bad-clone-into-future-transaction.patch @@ -0,0 +1,164 @@ +From stable+bounces-288345-greg=kroah.com@vger.kernel.org Thu Jul 23 16:20:55 2026 +From: Sasha Levin +Date: Thu, 23 Jul 2026 10:18:55 -0400 +Subject: netfilter: nft_set_pipapo: don't leak bad clone into future transaction +To: stable@vger.kernel.org +Cc: Florian Westphal , Seesee , Stefano Brivio , Sasha Levin +Message-ID: <20260723141855.3217594-8-sashal@kernel.org> + +From: Florian Westphal + +[ Upstream commit 47e65eff50691f0a5b79d325e28d83ec1da43bcf ] + +On memory allocation failure the cloned nft_pipapo_match can enter a bad +state: + - some fields can have their lookup tables resized while others did + not + - bits might have been toggled + - scratch map can be undersized which also means m->bsize_max can be + lower than what is required + +This means that the next insertion in the same batch can trigger +out-of-bounds writes. + +Furthermore, a failure in the first can result in the bad clone to +leak into the next transaction because the abort callback is never +executed in this case (the upper layer saw an error and no attempt to +allocate a transactional request was made). + +Record a state for the nft_pipapo_match structure: +- NEW (pristine clone) +- MOD (modified clone with good state) +- ERR (potentially bogus content) + +Then make it so that deletes and insertions fail when the clone +entered ERR state. + +In case the very first insert attempt results in an error, free the +clone right away. + +Fixes: 3c4287f62044 ("nf_tables: Add set type for arbitrary concatenation of ranges") +Cc: stable@vger.kernel.org +Reported-and-tested-by: Seesee +Reviewed-by: Stefano Brivio +Signed-off-by: Florian Westphal +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + net/netfilter/nft_set_pipapo.c | 34 +++++++++++++++++++++++++++++----- + net/netfilter/nft_set_pipapo.h | 8 ++++++++ + 2 files changed, 37 insertions(+), 5 deletions(-) + +--- a/net/netfilter/nft_set_pipapo.c ++++ b/net/netfilter/nft_set_pipapo.c +@@ -342,6 +342,8 @@ + #include "nft_set_pipapo_avx2.h" + #include "nft_set_pipapo.h" + ++static void nft_pipapo_abort(const struct nft_set *set); ++ + /** + * pipapo_refill() - For each set bit, set bits from selected mapping table item + * @map: Bitmap to be scanned for set bits +@@ -1278,7 +1280,7 @@ static int nft_pipapo_insert(const struc + const u8 *start_p, *end_p; + int i, bsize_max, err = 0; + +- if (!m) ++ if (!m || m->state == NFT_PIPAPO_CLONE_ERR) + return -ENOMEM; + + if (nft_set_ext_exists(ext, NFT_SET_EXT_KEY_END)) +@@ -1351,8 +1353,10 @@ static int nft_pipapo_insert(const struc + else + ret = pipapo_expand(f, start, end, f->groups * f->bb); + +- if (ret < 0) +- return ret; ++ if (ret < 0) { ++ err = ret; ++ goto abort; ++ } + + if (f->bsize > bsize_max) + bsize_max = f->bsize; +@@ -1368,7 +1372,7 @@ static int nft_pipapo_insert(const struc + + err = pipapo_realloc_scratch(m, bsize_max); + if (err) +- return err; ++ goto abort; + + m->bsize_max = bsize_max; + } else { +@@ -1379,7 +1383,26 @@ static int nft_pipapo_insert(const struc + + pipapo_map(m, rulemap, e); + ++ m->state = NFT_PIPAPO_CLONE_MOD; + return 0; ++abort: ++ DEBUG_NET_WARN_ON_ONCE(m->state == NFT_PIPAPO_CLONE_ERR); ++ ++ /* Two rollback cases: ++ * 1) no previous changes. nft_pipapo_abort is not ++ * guaranteed to be invoked (there might be no further ++ * add/delete requests coming after this). ++ * ++ * 2) we had previous changes: there are transaction ++ * records pointing to this set. Leave the rollback to ++ * the transaction handling. ++ */ ++ if (m->state == NFT_PIPAPO_CLONE_NEW) ++ nft_pipapo_abort(set); /* releases m */ ++ else ++ m->state = NFT_PIPAPO_CLONE_ERR; ++ ++ return err; + } + + /** +@@ -1455,6 +1478,7 @@ static struct nft_pipapo_match *pipapo_c + dst++; + } + ++ new->state = NFT_PIPAPO_CLONE_NEW; + return new; + + out_mt: +@@ -1891,7 +1915,7 @@ static void *nft_pipapo_deactivate(const + /* removal must occur on priv->clone, if we are low on memory + * we have no choice and must fail the removal request. + */ +- if (!m) ++ if (!m || m->state == NFT_PIPAPO_CLONE_ERR) + return NULL; + + e = pipapo_get(net, set, m, (const u8 *)elem->key.val.data, +--- a/net/netfilter/nft_set_pipapo.h ++++ b/net/netfilter/nft_set_pipapo.h +@@ -142,9 +142,16 @@ struct nft_pipapo_scratch { + unsigned long map[]; + }; + ++enum nft_pipapo_clone_state { ++ NFT_PIPAPO_CLONE_NEW, ++ NFT_PIPAPO_CLONE_MOD, ++ NFT_PIPAPO_CLONE_ERR, ++}; ++ + /** + * struct nft_pipapo_match - Data used for lookup and matching + * @field_count Amount of fields in set ++ * @state: add/delete state; used from control plane + * @scratch: Preallocated per-CPU maps for partial matching results + * @bsize_max: Maximum lookup table bucket size of all fields, in longs + * @rcu Matching data is swapped on commits +@@ -152,6 +159,7 @@ struct nft_pipapo_scratch { + */ + struct nft_pipapo_match { + int field_count; ++ enum nft_pipapo_clone_state state:8; + struct nft_pipapo_scratch * __percpu *scratch; + size_t bsize_max; + struct rcu_head rcu; diff --git a/queue-6.6/netfilter-nft_set_pipapo-make-pipapo_clone-helper-return-null.patch b/queue-6.6/netfilter-nft_set_pipapo-make-pipapo_clone-helper-return-null.patch new file mode 100644 index 0000000000..fa963dca53 --- /dev/null +++ b/queue-6.6/netfilter-nft_set_pipapo-make-pipapo_clone-helper-return-null.patch @@ -0,0 +1,94 @@ +From stable+bounces-288340-greg=kroah.com@vger.kernel.org Thu Jul 23 16:28:39 2026 +From: Sasha Levin +Date: Thu, 23 Jul 2026 10:18:50 -0400 +Subject: netfilter: nft_set_pipapo: make pipapo_clone helper return NULL +To: stable@vger.kernel.org +Cc: Florian Westphal , Stefano Brivio , Pablo Neira Ayuso , Sasha Levin +Message-ID: <20260723141855.3217594-3-sashal@kernel.org> + +From: Florian Westphal + +[ Upstream commit 80efd2997fb9343a0283cf3cac5524a4595c8ff4 ] + +Currently it returns an error pointer, but the only possible failure +is ENOMEM. + +After a followup patch, we'd need to discard the errno code, i.e. + +x = pipapo_clone() +if (IS_ERR(x)) + return NULL + +or make more changes to fix up callers to expect IS_ERR() code +from set->ops->deactivate(). + +So simplify this and make it return ptr-or-null. + +Signed-off-by: Florian Westphal +Reviewed-by: Stefano Brivio +Signed-off-by: Pablo Neira Ayuso +Stable-dep-of: 47e65eff5069 ("netfilter: nft_set_pipapo: don't leak bad clone into future transaction") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + net/netfilter/nft_set_pipapo.c | 14 +++++++------- + 1 file changed, 7 insertions(+), 7 deletions(-) + +--- a/net/netfilter/nft_set_pipapo.c ++++ b/net/netfilter/nft_set_pipapo.c +@@ -1358,7 +1358,7 @@ static int nft_pipapo_insert(const struc + * pipapo_clone() - Clone matching data to create new working copy + * @old: Existing matching data + * +- * Return: copy of matching data passed as 'old', error pointer on failure ++ * Return: copy of matching data passed as 'old' or NULL. + */ + static struct nft_pipapo_match *pipapo_clone(struct nft_pipapo_match *old) + { +@@ -1368,7 +1368,7 @@ static struct nft_pipapo_match *pipapo_c + + new = kmalloc(struct_size(new, f, old->field_count), GFP_KERNEL_ACCOUNT); + if (!new) +- return ERR_PTR(-ENOMEM); ++ return NULL; + + new->field_count = old->field_count; + new->bsize_max = old->bsize_max; +@@ -1444,7 +1444,7 @@ out_scratch: + free_percpu(new->scratch); + kfree(new); + +- return ERR_PTR(-ENOMEM); ++ return NULL; + } + + /** +@@ -1798,7 +1798,7 @@ static void nft_pipapo_commit(struct nft + return; + + new_clone = pipapo_clone(priv->clone); +- if (IS_ERR(new_clone)) ++ if (!new_clone) + return; + + priv->dirty = false; +@@ -1824,7 +1824,7 @@ static void nft_pipapo_abort(const struc + m = rcu_dereference_protected(priv->match, nft_pipapo_transaction_mutex_held(set)); + + new_clone = pipapo_clone(m); +- if (IS_ERR(new_clone)) ++ if (!new_clone) + return; + + priv->dirty = false; +@@ -2265,8 +2265,8 @@ static int nft_pipapo_init(const struct + + /* Create an initial clone of matching data for next insertion */ + priv->clone = pipapo_clone(m); +- if (IS_ERR(priv->clone)) { +- err = PTR_ERR(priv->clone); ++ if (!priv->clone) { ++ err = -ENOMEM; + goto out_free; + } + diff --git a/queue-6.6/netfilter-nft_set_pipapo-merge-deactivate-helper-into-caller.patch b/queue-6.6/netfilter-nft_set_pipapo-merge-deactivate-helper-into-caller.patch new file mode 100644 index 0000000000..01e266a5dc --- /dev/null +++ b/queue-6.6/netfilter-nft_set_pipapo-merge-deactivate-helper-into-caller.patch @@ -0,0 +1,89 @@ +From stable+bounces-288342-greg=kroah.com@vger.kernel.org Thu Jul 23 16:28:43 2026 +From: Sasha Levin +Date: Thu, 23 Jul 2026 10:18:52 -0400 +Subject: netfilter: nft_set_pipapo: merge deactivate helper into caller +To: stable@vger.kernel.org +Cc: Florian Westphal , Stefano Brivio , Pablo Neira Ayuso , Sasha Levin +Message-ID: <20260723141855.3217594-5-sashal@kernel.org> + +From: Florian Westphal + +[ Upstream commit c5444786d0ea2417a5e2cee7bd67137fc8bad687 ] + +Its the only remaining call site so there is no need for this to +be separated anymore. + +Signed-off-by: Florian Westphal +Reviewed-by: Stefano Brivio +Signed-off-by: Pablo Neira Ayuso +Stable-dep-of: 47e65eff5069 ("netfilter: nft_set_pipapo: don't leak bad clone into future transaction") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + net/netfilter/nft_set_pipapo.c | 39 +++++++++------------------------------ + 1 file changed, 9 insertions(+), 30 deletions(-) + +--- a/net/netfilter/nft_set_pipapo.c ++++ b/net/netfilter/nft_set_pipapo.c +@@ -1854,52 +1854,31 @@ static void nft_pipapo_activate(const st + } + + /** +- * pipapo_deactivate() - Check that element is in set, mark as inactive ++ * nft_pipapo_deactivate() - Search for element and make it inactive + * @net: Network namespace + * @set: nftables API set representation +- * @data: Input key data +- * @ext: nftables API extension pointer, used to check for end element +- * +- * This is a convenience function that can be called from both +- * nft_pipapo_deactivate() and nft_pipapo_flush(), as they are in fact the same +- * operation. ++ * @elem: nftables API element representation containing key data + * + * Return: deactivated element if found, NULL otherwise. + */ +-static void *pipapo_deactivate(const struct net *net, const struct nft_set *set, +- const u8 *data, const struct nft_set_ext *ext) ++static void *nft_pipapo_deactivate(const struct net *net, ++ const struct nft_set *set, ++ const struct nft_set_elem *elem) + { + struct nft_pipapo_elem *e; + +- e = pipapo_get(net, set, data, nft_genmask_next(net), +- nft_net_tstamp(net), GFP_KERNEL); ++ e = pipapo_get(net, set, (const u8 *)elem->key.val.data, ++ nft_genmask_next(net), nft_net_tstamp(net), GFP_KERNEL); + if (IS_ERR(e)) + return NULL; + + nft_set_elem_change_active(net, set, &e->ext); + +- return e; +-} +- +-/** +- * nft_pipapo_deactivate() - Call pipapo_deactivate() to make element inactive +- * @net: Network namespace +- * @set: nftables API set representation +- * @elem: nftables API element representation containing key data +- * +- * Return: deactivated element if found, NULL otherwise. +- */ +-static void *nft_pipapo_deactivate(const struct net *net, +- const struct nft_set *set, +- const struct nft_set_elem *elem) +-{ +- const struct nft_set_ext *ext = nft_set_elem_ext(set, elem->priv); +- +- return pipapo_deactivate(net, set, (const u8 *)elem->key.val.data, ext); ++ return &e->priv; + } + + /** +- * nft_pipapo_flush() - Call pipapo_deactivate() to make element inactive ++ * nft_pipapo_flush() - make element inactive + * @net: Network namespace + * @set: nftables API set representation + * @elem: nftables API element representation containing key data diff --git a/queue-6.6/netfilter-nft_set_pipapo-move-cloning-of-match-info-to-insert-removal-path.patch b/queue-6.6/netfilter-nft_set_pipapo-move-cloning-of-match-info-to-insert-removal-path.patch new file mode 100644 index 0000000000..d53b0ae897 --- /dev/null +++ b/queue-6.6/netfilter-nft_set_pipapo-move-cloning-of-match-info-to-insert-removal-path.patch @@ -0,0 +1,184 @@ +From stable+bounces-288343-greg=kroah.com@vger.kernel.org Thu Jul 23 16:28:45 2026 +From: Sasha Levin +Date: Thu, 23 Jul 2026 10:18:54 -0400 +Subject: netfilter: nft_set_pipapo: move cloning of match info to insert/removal path +To: stable@vger.kernel.org +Cc: Florian Westphal , Stefano Brivio , Pablo Neira Ayuso , Sasha Levin +Message-ID: <20260723141855.3217594-7-sashal@kernel.org> + +From: Florian Westphal + +[ Upstream commit 3f1d886cc7c3525d4dbeee24bfa9bb3fe0d48ddc ] + +This set type keeps two copies of the sets' content, + priv->match (live version, used to match from packet path) + priv->clone (work-in-progress version of the 'future' priv->match). + +All additions and removals are done on priv->clone. When transaction +completes, priv->clone becomes priv->match and a new clone is allocated +for use by next transaction. + +Problem is that the cloning requires GFP_KERNEL allocations but we +cannot fail at either commit or abort time. + +This patch defers the clone until we get an insertion or removal +request. This allows us to handle OOM situations correctly. + +This also allows to remove ->dirty in a followup change: + +If ->clone exists, ->dirty is always true +If ->clone is NULL, ->dirty is always false, no elements were added +or removed (except catchall elements which are external to the specific +set backend). + +Signed-off-by: Florian Westphal +Reviewed-by: Stefano Brivio +Signed-off-by: Pablo Neira Ayuso +Stable-dep-of: 47e65eff5069 ("netfilter: nft_set_pipapo: don't leak bad clone into future transaction") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + net/netfilter/nft_set_pipapo.c | 70 ++++++++++++++++++++++++++++------------- + 1 file changed, 49 insertions(+), 21 deletions(-) + +--- a/net/netfilter/nft_set_pipapo.c ++++ b/net/netfilter/nft_set_pipapo.c +@@ -1230,6 +1230,29 @@ static bool nft_pipapo_transaction_mutex + #endif + } + ++static struct nft_pipapo_match *pipapo_clone(struct nft_pipapo_match *old); ++ ++/** ++ * pipapo_maybe_clone() - Build clone for pending data changes, if not existing ++ * @set: nftables API set representation ++ * ++ * Return: newly created or existing clone, if any. NULL on allocation failure ++ */ ++static struct nft_pipapo_match *pipapo_maybe_clone(const struct nft_set *set) ++{ ++ struct nft_pipapo *priv = nft_set_priv(set); ++ struct nft_pipapo_match *m; ++ ++ if (priv->clone) ++ return priv->clone; ++ ++ m = rcu_dereference_protected(priv->match, ++ nft_pipapo_transaction_mutex_held(set)); ++ priv->clone = pipapo_clone(m); ++ ++ return priv->clone; ++} ++ + /** + * nft_pipapo_insert() - Validate and insert ranged elements + * @net: Network namespace +@@ -1247,14 +1270,17 @@ static int nft_pipapo_insert(const struc + union nft_pipapo_map_bucket rulemap[NFT_PIPAPO_MAX_FIELDS]; + const u8 *start = (const u8 *)elem->key.val.data, *end; + struct nft_pipapo_elem *e = elem->priv, *dup; ++ struct nft_pipapo_match *m = pipapo_maybe_clone(set); + struct nft_pipapo *priv = nft_set_priv(set); +- struct nft_pipapo_match *m = priv->clone; + u8 genmask = nft_genmask_next(net); + u64 tstamp = nft_net_tstamp(net); + struct nft_pipapo_field *f; + const u8 *start_p, *end_p; + int i, bsize_max, err = 0; + ++ if (!m) ++ return -ENOMEM; ++ + if (nft_set_ext_exists(ext, NFT_SET_EXT_KEY_END)) + end = (const u8 *)nft_set_ext_key_end(ext)->data; + else +@@ -1791,7 +1817,10 @@ static void pipapo_reclaim_match(struct + static void nft_pipapo_commit(struct nft_set *set) + { + struct nft_pipapo *priv = nft_set_priv(set); +- struct nft_pipapo_match *new_clone, *old; ++ struct nft_pipapo_match *old; ++ ++ if (!priv->clone) ++ return; + + if (time_after_eq(jiffies, priv->last_gc + nft_set_gc_interval(set))) + pipapo_gc_scan(set, priv->clone); +@@ -1799,40 +1828,29 @@ static void nft_pipapo_commit(struct nft + if (!priv->dirty) + return; + +- new_clone = pipapo_clone(priv->clone); +- if (!new_clone) +- return; +- ++ old = rcu_replace_pointer(priv->match, priv->clone, ++ nft_pipapo_transaction_mutex_held(set)); ++ priv->clone = NULL; + priv->dirty = false; + +- old = rcu_access_pointer(priv->match); +- rcu_assign_pointer(priv->match, priv->clone); + if (old) + call_rcu(&old->rcu, pipapo_reclaim_match); + +- priv->clone = new_clone; +- + pipapo_gc_queue(set); + } + + static void nft_pipapo_abort(const struct nft_set *set) + { + struct nft_pipapo *priv = nft_set_priv(set); +- struct nft_pipapo_match *new_clone, *m; + + if (!priv->dirty) + return; + +- m = rcu_dereference_protected(priv->match, nft_pipapo_transaction_mutex_held(set)); +- +- new_clone = pipapo_clone(m); +- if (!new_clone) ++ if (!priv->clone) + return; +- + priv->dirty = false; +- + pipapo_free_match(priv->clone); +- priv->clone = new_clone; ++ priv->clone = NULL; + } + + /** +@@ -1867,10 +1885,15 @@ static void *nft_pipapo_deactivate(const + const struct nft_set *set, + const struct nft_set_elem *elem) + { +- const struct nft_pipapo *priv = nft_set_priv(set); +- struct nft_pipapo_match *m = priv->clone; ++ struct nft_pipapo_match *m = pipapo_maybe_clone(set); + struct nft_pipapo_elem *e; + ++ /* removal must occur on priv->clone, if we are low on memory ++ * we have no choice and must fail the removal request. ++ */ ++ if (!m) ++ return NULL; ++ + e = pipapo_get(net, set, m, (const u8 *)elem->key.val.data, + nft_genmask_next(net), nft_net_tstamp(net), GFP_KERNEL); + if (IS_ERR(e)) +@@ -2153,7 +2176,12 @@ static void nft_pipapo_walk(const struct + + switch (iter->type) { + case NFT_ITER_UPDATE: +- m = priv->clone; ++ m = pipapo_maybe_clone(set); ++ if (!m) { ++ iter->err = -ENOMEM; ++ return; ++ } ++ + nft_pipapo_do_walk(ctx, set, m, iter); + break; + case NFT_ITER_READ: diff --git a/queue-6.6/netfilter-nft_set_pipapo-move-prove_locking-helper-around.patch b/queue-6.6/netfilter-nft_set_pipapo-move-prove_locking-helper-around.patch new file mode 100644 index 0000000000..30808c231b --- /dev/null +++ b/queue-6.6/netfilter-nft_set_pipapo-move-prove_locking-helper-around.patch @@ -0,0 +1,63 @@ +From stable+bounces-288338-greg=kroah.com@vger.kernel.org Thu Jul 23 16:28:34 2026 +From: Sasha Levin +Date: Thu, 23 Jul 2026 10:18:49 -0400 +Subject: netfilter: nft_set_pipapo: move prove_locking helper around +To: stable@vger.kernel.org +Cc: Florian Westphal , Stefano Brivio , Pablo Neira Ayuso , Sasha Levin +Message-ID: <20260723141855.3217594-2-sashal@kernel.org> + +From: Florian Westphal + +[ Upstream commit a590f4760922acaa2d2b55a88004a38eecdd6412 ] + +Preparation patch, the helper will soon get called from insert +function too. + +Signed-off-by: Florian Westphal +Reviewed-by: Stefano Brivio +Signed-off-by: Pablo Neira Ayuso +Stable-dep-of: 47e65eff5069 ("netfilter: nft_set_pipapo: don't leak bad clone into future transaction") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + net/netfilter/nft_set_pipapo.c | 22 +++++++++++----------- + 1 file changed, 11 insertions(+), 11 deletions(-) + +--- a/net/netfilter/nft_set_pipapo.c ++++ b/net/netfilter/nft_set_pipapo.c +@@ -1217,6 +1217,17 @@ static int pipapo_realloc_scratch(struct + return 0; + } + ++static bool nft_pipapo_transaction_mutex_held(const struct nft_set *set) ++{ ++#ifdef CONFIG_PROVE_LOCKING ++ const struct net *net = read_pnet(&set->net); ++ ++ return lockdep_is_held(&nft_pernet(net)->commit_mutex); ++#else ++ return true; ++#endif ++} ++ + /** + * nft_pipapo_insert() - Validate and insert ranged elements + * @net: Network namespace +@@ -1802,17 +1813,6 @@ static void nft_pipapo_commit(struct nft + pipapo_gc_queue(set); + } + +-static bool nft_pipapo_transaction_mutex_held(const struct nft_set *set) +-{ +-#ifdef CONFIG_PROVE_LOCKING +- const struct net *net = read_pnet(&set->net); +- +- return lockdep_is_held(&nft_pernet(net)->commit_mutex); +-#else +- return true; +-#endif +-} +- + static void nft_pipapo_abort(const struct nft_set *set) + { + struct nft_pipapo *priv = nft_set_priv(set); diff --git a/queue-6.6/netfilter-nft_set_pipapo-prepare-pipapo_get-helper-for-on-demand-clone.patch b/queue-6.6/netfilter-nft_set_pipapo-prepare-pipapo_get-helper-for-on-demand-clone.patch new file mode 100644 index 0000000000..64d0af35ca --- /dev/null +++ b/queue-6.6/netfilter-nft_set_pipapo-prepare-pipapo_get-helper-for-on-demand-clone.patch @@ -0,0 +1,122 @@ +From stable+bounces-288344-greg=kroah.com@vger.kernel.org Thu Jul 23 16:31:22 2026 +From: Sasha Levin +Date: Thu, 23 Jul 2026 10:18:53 -0400 +Subject: netfilter: nft_set_pipapo: prepare pipapo_get helper for on-demand clone +To: stable@vger.kernel.org +Cc: Florian Westphal , Stefano Brivio , Pablo Neira Ayuso , Sasha Levin +Message-ID: <20260723141855.3217594-6-sashal@kernel.org> + +From: Florian Westphal + +[ Upstream commit a238106703ab4ae1090b86eba128815b8626d8f1 ] + +The helper uses priv->clone unconditionally which will fail once we do +the clone conditionally on first insert or removal. + +'nft get element' from userspace needs to use priv->match since this +runs from rcu read side lock section. + +Prepare for this by passing the match backend data as argument. + +Signed-off-by: Florian Westphal +Reviewed-by: Stefano Brivio +Signed-off-by: Pablo Neira Ayuso +Stable-dep-of: 47e65eff5069 ("netfilter: nft_set_pipapo: don't leak bad clone into future transaction") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + net/netfilter/nft_set_pipapo.c | 25 +++++++++++++++---------- + 1 file changed, 15 insertions(+), 10 deletions(-) + +--- a/net/netfilter/nft_set_pipapo.c ++++ b/net/netfilter/nft_set_pipapo.c +@@ -502,6 +502,7 @@ out: + * pipapo_get() - Get matching element reference given key data + * @net: Network namespace + * @set: nftables API set representation ++ * @m: storage containing active/existing elements + * @data: Key data to be matched against existing elements + * @genmask: If set, check that element is active in given genmask + * @tstamp: timestamp to check for expired elements +@@ -515,17 +516,15 @@ out: + */ + static struct nft_pipapo_elem *pipapo_get(const struct net *net, + const struct nft_set *set, ++ const struct nft_pipapo_match *m, + const u8 *data, u8 genmask, + u64 tstamp, gfp_t gfp) + { + struct nft_pipapo_elem *ret = ERR_PTR(-ENOENT); +- struct nft_pipapo *priv = nft_set_priv(set); + unsigned long *res_map, *fill_map = NULL; +- const struct nft_pipapo_match *m; + const struct nft_pipapo_field *f; + int i; + +- m = priv->clone; + if (m->bsize_max == 0) + return ret; + +@@ -609,7 +608,10 @@ out: + static void *nft_pipapo_get(const struct net *net, const struct nft_set *set, + const struct nft_set_elem *elem, unsigned int flags) + { +- return pipapo_get(net, set, (const u8 *)elem->key.val.data, ++ struct nft_pipapo *priv = nft_set_priv(set); ++ struct nft_pipapo_match *m = rcu_dereference(priv->match); ++ ++ return pipapo_get(net, set, m, (const u8 *)elem->key.val.data, + nft_genmask_cur(net), get_jiffies_64(), GFP_ATOMIC); + } + +@@ -1258,7 +1260,7 @@ static int nft_pipapo_insert(const struc + else + end = start; + +- dup = pipapo_get(net, set, start, genmask, tstamp, GFP_KERNEL); ++ dup = pipapo_get(net, set, m, start, genmask, tstamp, GFP_KERNEL); + if (!IS_ERR(dup)) { + /* Check if we already have the same exact entry */ + const struct nft_data *dup_key, *dup_end; +@@ -1280,7 +1282,7 @@ static int nft_pipapo_insert(const struc + + if (PTR_ERR(dup) == -ENOENT) { + /* Look for partially overlapping entries */ +- dup = pipapo_get(net, set, end, nft_genmask_next(net), tstamp, ++ dup = pipapo_get(net, set, m, end, nft_genmask_next(net), tstamp, + GFP_KERNEL); + } + +@@ -1865,16 +1867,18 @@ static void *nft_pipapo_deactivate(const + const struct nft_set *set, + const struct nft_set_elem *elem) + { ++ const struct nft_pipapo *priv = nft_set_priv(set); ++ struct nft_pipapo_match *m = priv->clone; + struct nft_pipapo_elem *e; + +- e = pipapo_get(net, set, (const u8 *)elem->key.val.data, ++ e = pipapo_get(net, set, m, (const u8 *)elem->key.val.data, + nft_genmask_next(net), nft_net_tstamp(net), GFP_KERNEL); + if (IS_ERR(e)) + return NULL; + + nft_set_elem_change_active(net, set, &e->ext); + +- return &e->priv; ++ return e; + } + + /** +@@ -1900,8 +1904,9 @@ static bool nft_pipapo_flush(const struc + { + struct nft_pipapo_elem *e = elem; + +- return pipapo_deactivate(net, set, (const u8 *)nft_set_ext_key(&e->ext), +- &e->ext); ++ nft_set_elem_change_active(net, set, &e->ext); ++ ++ return true; + } + + /** diff --git a/queue-6.6/netfilter-nft_set_pipapo-prepare-walk-function-for-on-demand-clone.patch b/queue-6.6/netfilter-nft_set_pipapo-prepare-walk-function-for-on-demand-clone.patch new file mode 100644 index 0000000000..4eb8afa4fe --- /dev/null +++ b/queue-6.6/netfilter-nft_set_pipapo-prepare-walk-function-for-on-demand-clone.patch @@ -0,0 +1,131 @@ +From stable+bounces-288341-greg=kroah.com@vger.kernel.org Thu Jul 23 16:31:07 2026 +From: Sasha Levin +Date: Thu, 23 Jul 2026 10:18:51 -0400 +Subject: netfilter: nft_set_pipapo: prepare walk function for on-demand clone +To: stable@vger.kernel.org +Cc: Florian Westphal , Stefano Brivio , Pablo Neira Ayuso , Sasha Levin +Message-ID: <20260723141855.3217594-4-sashal@kernel.org> + +From: Florian Westphal + +[ Upstream commit 6c108d9bee448a850b03e682836bfe91fca645cb ] + +The existing code uses iter->type to figure out what data is needed, the +live copy (READ) or clone (UPDATE). + +Without pending updates, priv->clone and priv->match will point to +different memory locations, but they have identical content. + +Future patch will make priv->clone == NULL if there are no pending changes, +in this case we must copy the live data for the UPDATE case. + +Currently this would require GFP_ATOMIC allocation. Split the walk +function in two parts: one that does the walk and one that decides which +data is needed. + +In the UPDATE case, callers hold the transaction mutex so we do not need +the rcu read lock. This allows to use GFP_KERNEL allocation while +cloning. + +Signed-off-by: Florian Westphal +Reviewed-by: Stefano Brivio +Signed-off-by: Pablo Neira Ayuso +Stable-dep-of: 47e65eff5069 ("netfilter: nft_set_pipapo: don't leak bad clone into future transaction") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + net/netfilter/nft_set_pipapo.c | 60 ++++++++++++++++++++++++++--------------- + 1 file changed, 39 insertions(+), 21 deletions(-) + +--- a/net/netfilter/nft_set_pipapo.c ++++ b/net/netfilter/nft_set_pipapo.c +@@ -2109,35 +2109,23 @@ static void nft_pipapo_remove(const stru + } + + /** +- * nft_pipapo_walk() - Walk over elements ++ * nft_pipapo_do_walk() - Walk over elements in m + * @ctx: nftables API context + * @set: nftables API set representation ++ * @m: matching data pointing to key mapping array + * @iter: Iterator + * + * As elements are referenced in the mapping array for the last field, directly + * scan that array: there's no need to follow rule mappings from the first +- * field. ++ * field. @m is protected either by RCU read lock or by transaction mutex. + */ +-static void nft_pipapo_walk(const struct nft_ctx *ctx, struct nft_set *set, +- struct nft_set_iter *iter) ++static void nft_pipapo_do_walk(const struct nft_ctx *ctx, struct nft_set *set, ++ const struct nft_pipapo_match *m, ++ struct nft_set_iter *iter) + { +- struct nft_pipapo *priv = nft_set_priv(set); +- const struct nft_pipapo_match *m; + const struct nft_pipapo_field *f; + int i, r; + +- WARN_ON_ONCE(iter->type != NFT_ITER_READ && +- iter->type != NFT_ITER_UPDATE); +- +- rcu_read_lock(); +- if (iter->type == NFT_ITER_READ) +- m = rcu_dereference(priv->match); +- else +- m = priv->clone; +- +- if (unlikely(!m)) +- goto out; +- + for (i = 0, f = m->f; i < m->field_count - 1; i++, f++) + ; + +@@ -2157,14 +2145,44 @@ static void nft_pipapo_walk(const struct + + iter->err = iter->fn(ctx, set, iter, &elem); + if (iter->err < 0) +- goto out; ++ return; + + cont: + iter->count++; + } ++} + +-out: +- rcu_read_unlock(); ++/** ++ * nft_pipapo_walk() - Walk over elements ++ * @ctx: nftables API context ++ * @set: nftables API set representation ++ * @iter: Iterator ++ * ++ * Test if destructive action is needed or not, clone active backend if needed ++ * and call the real function to work on the data. ++ */ ++static void nft_pipapo_walk(const struct nft_ctx *ctx, struct nft_set *set, ++ struct nft_set_iter *iter) ++{ ++ struct nft_pipapo *priv = nft_set_priv(set); ++ const struct nft_pipapo_match *m; ++ ++ switch (iter->type) { ++ case NFT_ITER_UPDATE: ++ m = priv->clone; ++ nft_pipapo_do_walk(ctx, set, m, iter); ++ break; ++ case NFT_ITER_READ: ++ rcu_read_lock(); ++ m = rcu_dereference(priv->match); ++ nft_pipapo_do_walk(ctx, set, m, iter); ++ rcu_read_unlock(); ++ break; ++ default: ++ iter->err = -EINVAL; ++ WARN_ON_ONCE(1); ++ break; ++ } + } + + /** diff --git a/queue-6.6/netfilter-nft_set_pipapo-use-gfp_kernel-for-insertions.patch b/queue-6.6/netfilter-nft_set_pipapo-use-gfp_kernel-for-insertions.patch new file mode 100644 index 0000000000..2050ed2ce2 --- /dev/null +++ b/queue-6.6/netfilter-nft_set_pipapo-use-gfp_kernel-for-insertions.patch @@ -0,0 +1,100 @@ +From stable+bounces-288339-greg=kroah.com@vger.kernel.org Thu Jul 23 16:31:05 2026 +From: Sasha Levin +Date: Thu, 23 Jul 2026 10:18:48 -0400 +Subject: netfilter: nft_set_pipapo: use GFP_KERNEL for insertions +To: stable@vger.kernel.org +Cc: Florian Westphal , Pablo Neira Ayuso , Sasha Levin +Message-ID: <20260723141855.3217594-1-sashal@kernel.org> + +From: Florian Westphal + +[ Upstream commit 5b651783d80b97167ecd27dc6a4408c694873902 ] + +An earlier attempt changed this to GFP_KERNEL, but the get helper is +also called for get requests from userspace, which uses rcu. + +Let the caller pass in the kmalloc flags to allow insertions +to schedule if needed. + +Suggested-by: Pablo Neira Ayuso +Signed-off-by: Florian Westphal +Stable-dep-of: 47e65eff5069 ("netfilter: nft_set_pipapo: don't leak bad clone into future transaction") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + net/netfilter/nft_set_pipapo.c | 17 ++++++++++------- + 1 file changed, 10 insertions(+), 7 deletions(-) + +--- a/net/netfilter/nft_set_pipapo.c ++++ b/net/netfilter/nft_set_pipapo.c +@@ -505,6 +505,7 @@ out: + * @data: Key data to be matched against existing elements + * @genmask: If set, check that element is active in given genmask + * @tstamp: timestamp to check for expired elements ++ * @gfp: the type of memory to allocate (see kmalloc). + * + * This is essentially the same as the lookup function, except that it matches + * key data against the uncommitted copy and doesn't use preallocated maps for +@@ -515,7 +516,7 @@ out: + static struct nft_pipapo_elem *pipapo_get(const struct net *net, + const struct nft_set *set, + const u8 *data, u8 genmask, +- u64 tstamp) ++ u64 tstamp, gfp_t gfp) + { + struct nft_pipapo_elem *ret = ERR_PTR(-ENOENT); + struct nft_pipapo *priv = nft_set_priv(set); +@@ -528,13 +529,13 @@ static struct nft_pipapo_elem *pipapo_ge + if (m->bsize_max == 0) + return ret; + +- res_map = kmalloc_array(m->bsize_max, sizeof(*res_map), GFP_ATOMIC); ++ res_map = kmalloc_array(m->bsize_max, sizeof(*res_map), gfp); + if (!res_map) { + ret = ERR_PTR(-ENOMEM); + goto out; + } + +- fill_map = kcalloc(m->bsize_max, sizeof(*res_map), GFP_ATOMIC); ++ fill_map = kcalloc(m->bsize_max, sizeof(*res_map), gfp); + if (!fill_map) { + ret = ERR_PTR(-ENOMEM); + goto out; +@@ -609,7 +610,7 @@ static void *nft_pipapo_get(const struct + const struct nft_set_elem *elem, unsigned int flags) + { + return pipapo_get(net, set, (const u8 *)elem->key.val.data, +- nft_genmask_cur(net), get_jiffies_64()); ++ nft_genmask_cur(net), get_jiffies_64(), GFP_ATOMIC); + } + + +@@ -1246,7 +1247,7 @@ static int nft_pipapo_insert(const struc + else + end = start; + +- dup = pipapo_get(net, set, start, genmask, tstamp); ++ dup = pipapo_get(net, set, start, genmask, tstamp, GFP_KERNEL); + if (!IS_ERR(dup)) { + /* Check if we already have the same exact entry */ + const struct nft_data *dup_key, *dup_end; +@@ -1268,7 +1269,8 @@ static int nft_pipapo_insert(const struc + + if (PTR_ERR(dup) == -ENOENT) { + /* Look for partially overlapping entries */ +- dup = pipapo_get(net, set, end, nft_genmask_next(net), tstamp); ++ dup = pipapo_get(net, set, end, nft_genmask_next(net), tstamp, ++ GFP_KERNEL); + } + + if (PTR_ERR(dup) != -ENOENT) { +@@ -1869,7 +1871,8 @@ static void *pipapo_deactivate(const str + { + struct nft_pipapo_elem *e; + +- e = pipapo_get(net, set, data, nft_genmask_next(net), nft_net_tstamp(net)); ++ e = pipapo_get(net, set, data, nft_genmask_next(net), ++ nft_net_tstamp(net), GFP_KERNEL); + if (IS_ERR(e)) + return NULL; + diff --git a/queue-6.6/nfs-charge-unstable-writes-by-request-size-not-folio-size.patch b/queue-6.6/nfs-charge-unstable-writes-by-request-size-not-folio-size.patch new file mode 100644 index 0000000000..e4bb6d1d42 --- /dev/null +++ b/queue-6.6/nfs-charge-unstable-writes-by-request-size-not-folio-size.patch @@ -0,0 +1,133 @@ +From stable+bounces-289593-greg=kroah.com@vger.kernel.org Mon Jul 27 17:49:19 2026 +From: Sasha Levin +Date: Mon, 27 Jul 2026 11:40:54 -0400 +Subject: NFS: Charge unstable writes by request size, not folio size +To: stable@vger.kernel.org +Cc: Benjamin Coddington , Benjamin Coddington , Claude Sonnet 5 , Anna Schumaker , Sasha Levin +Message-ID: <20260727154054.1513904-2-sashal@kernel.org> + +From: Benjamin Coddington + +[ Upstream commit 27934d02cbeb8a957dd11c985a579e58d30c5270 ] + +nfs_folio_mark_unstable() and nfs_folio_clear_commit() charge and +uncharge NR_WRITEBACK/WB_WRITEBACK by folio_nr_pages(folio) once per +*request* added to or removed from a commit list. This is correct only +when a folio has a single associated request. When pg_test splits a +folio into N sub-folio requests (e.g. pNFS flexfiles striping with a +stripe unit smaller than the folio size, or plain wsize-limited +splitting), each of the N requests independently charges the whole +folio's page count, inflating the accounting by a factor of N per +folio. With large folios and small stripe units this reaches multiple +orders of magnitude: a 2 MiB folio split into 512 4 KiB requests can +charge up to 512x its real size, pushing global dirty+writeback +accounting past the system's dirty threshold and forcing every +buffered writer on the host into the hard-throttle path, including +unrelated in-kernel NFS server threads sharing the box. + +Charge each request only for the pages it actually covers. + +Fixes: 0c493b5cf16e ("NFS: Convert buffered writes to use folios") +Cc: stable@vger.kernel.org +Signed-off-by: Benjamin Coddington +Assisted-By: Claude Sonnet 5 +Signed-off-by: Anna Schumaker +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + fs/nfs/internal.h | 12 +++++++----- + fs/nfs/pnfs_nfs.c | 2 +- + fs/nfs/write.c | 14 ++++++++------ + 3 files changed, 16 insertions(+), 12 deletions(-) + +--- a/fs/nfs/internal.h ++++ b/fs/nfs/internal.h +@@ -784,17 +784,19 @@ void nfs_super_set_maxbytes(struct super + } + + /* +- * Record the page as unstable (an extra writeback period) and mark its +- * inode as dirty. ++ * Record the request's range as unstable (an extra writeback period) and ++ * mark its inode as dirty. + */ +-static inline void nfs_folio_mark_unstable(struct folio *folio, ++static inline void nfs_folio_mark_unstable(struct nfs_page *req, + struct nfs_commit_info *cinfo) + { ++ struct folio *folio = nfs_page_to_folio(req); ++ + if (folio && !cinfo->dreq) { + struct inode *inode = folio->mapping->host; +- long nr = folio_nr_pages(folio); ++ long nr = DIV_ROUND_UP(req->wb_bytes, PAGE_SIZE); + +- /* This page is really still in write-back - just that the ++ /* This range is really still in write-back - just that the + * writeback is happening on the server now. + */ + node_stat_mod_folio(folio, NR_WRITEBACK, nr); +--- a/fs/nfs/pnfs_nfs.c ++++ b/fs/nfs/pnfs_nfs.c +@@ -1226,7 +1226,7 @@ pnfs_layout_mark_request_commit(struct n + + nfs_request_add_commit_list_locked(req, list, cinfo); + mutex_unlock(&NFS_I(cinfo->inode)->commit_mutex); +- nfs_folio_mark_unstable(nfs_page_to_folio(req), cinfo); ++ nfs_folio_mark_unstable(req, cinfo); + return; + out_resched: + mutex_unlock(&NFS_I(cinfo->inode)->commit_mutex); +--- a/fs/nfs/write.c ++++ b/fs/nfs/write.c +@@ -933,7 +933,7 @@ nfs_request_add_commit_list(struct nfs_p + mutex_lock(&NFS_I(cinfo->inode)->commit_mutex); + nfs_request_add_commit_list_locked(req, &cinfo->mds->list, cinfo); + mutex_unlock(&NFS_I(cinfo->inode)->commit_mutex); +- nfs_folio_mark_unstable(nfs_page_to_folio(req), cinfo); ++ nfs_folio_mark_unstable(req, cinfo); + } + EXPORT_SYMBOL_GPL(nfs_request_add_commit_list); + +@@ -992,10 +992,12 @@ nfs_mark_request_commit(struct nfs_page + nfs_request_add_commit_list(req, cinfo); + } + +-static void nfs_folio_clear_commit(struct folio *folio) ++static void nfs_folio_clear_commit(struct nfs_page *req) + { ++ struct folio *folio = nfs_page_to_folio(req); ++ + if (folio) { +- long nr = folio_nr_pages(folio); ++ long nr = DIV_ROUND_UP(req->wb_bytes, PAGE_SIZE); + + node_stat_mod_folio(folio, NR_WRITEBACK, -nr); + wb_stat_mod(&inode_to_bdi(folio_file_mapping(folio)->host)->wb, +@@ -1016,7 +1018,7 @@ static void nfs_clear_request_commit(str + nfs_request_remove_commit_list(req, cinfo); + } + mutex_unlock(&NFS_I(inode)->commit_mutex); +- nfs_folio_clear_commit(nfs_page_to_folio(req)); ++ nfs_folio_clear_commit(req); + } + } + +@@ -1834,7 +1836,7 @@ void nfs_retry_commit(struct list_head * + req = nfs_list_entry(page_list->next); + nfs_list_remove_request(req); + nfs_mark_request_commit(req, lseg, cinfo, ds_commit_idx); +- nfs_folio_clear_commit(nfs_page_to_folio(req)); ++ nfs_folio_clear_commit(req); + nfs_unlock_and_release_request(req); + } + } +@@ -1902,7 +1904,7 @@ static void nfs_commit_release_pages(str + req = nfs_list_entry(data->pages.next); + nfs_list_remove_request(req); + folio = nfs_page_to_folio(req); +- nfs_folio_clear_commit(folio); ++ nfs_folio_clear_commit(req); + + dprintk("NFS: commit (%s/%llu %d@%lld)", + nfs_req_openctx(req)->dentry->d_sb->s_id, diff --git a/queue-6.6/nfs-remove-dead-code-for-the-old-swap-over-nfs-implementation.patch b/queue-6.6/nfs-remove-dead-code-for-the-old-swap-over-nfs-implementation.patch new file mode 100644 index 0000000000..170a6180a8 --- /dev/null +++ b/queue-6.6/nfs-remove-dead-code-for-the-old-swap-over-nfs-implementation.patch @@ -0,0 +1,37 @@ +From stable+bounces-289592-greg=kroah.com@vger.kernel.org Mon Jul 27 17:52:01 2026 +From: Sasha Levin +Date: Mon, 27 Jul 2026 11:40:53 -0400 +Subject: nfs: remove dead code for the old swap over NFS implementation +To: stable@vger.kernel.org +Cc: Christoph Hellwig , Sagi Grimberg , Anna Schumaker , Sasha Levin +Message-ID: <20260727154054.1513904-1-sashal@kernel.org> + +From: Christoph Hellwig + +[ Upstream commit 7e8e78a0ba00c88f0ded86de64bdddc82e06b196 ] + +Remove the code testing folio_test_swapcache either explicitly or +implicitly in pagemap.h headers, as is now handled using the direct I/O +path and not the buffered I/O path that these helpers are located in. + +Signed-off-by: Christoph Hellwig +Reviewed-by: Sagi Grimberg +Signed-off-by: Anna Schumaker +Stable-dep-of: 27934d02cbeb ("NFS: Charge unstable writes by request size, not folio size") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + fs/nfs/internal.h | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +--- a/fs/nfs/internal.h ++++ b/fs/nfs/internal.h +@@ -791,7 +791,7 @@ static inline void nfs_folio_mark_unstab + struct nfs_commit_info *cinfo) + { + if (folio && !cinfo->dreq) { +- struct inode *inode = folio_file_mapping(folio)->host; ++ struct inode *inode = folio->mapping->host; + long nr = folio_nr_pages(folio); + + /* This page is really still in write-back - just that the diff --git a/queue-6.6/nfsd-pass-nfsd_file-to-nfsd_iter_read.patch b/queue-6.6/nfsd-pass-nfsd_file-to-nfsd_iter_read.patch new file mode 100644 index 0000000000..c98eec37c1 --- /dev/null +++ b/queue-6.6/nfsd-pass-nfsd_file-to-nfsd_iter_read.patch @@ -0,0 +1,116 @@ +From stable+bounces-288611-greg=kroah.com@vger.kernel.org Fri Jul 24 03:28:14 2026 +From: Sasha Levin +Date: Thu, 23 Jul 2026 21:25:11 -0400 +Subject: NFSD: pass nfsd_file to nfsd_iter_read() +To: stable@vger.kernel.org +Cc: Mike Snitzer , Jeff Layton , NeilBrown , Christoph Hellwig , Chuck Lever , Sasha Levin +Message-ID: <20260724012515.3833219-1-sashal@kernel.org> + +From: Mike Snitzer + +[ Upstream commit 803bc849f0039291f546ba0e2237faebeb5c073e ] + +Prepare for nfsd_iter_read() to use the DIO alignment stored in +nfsd_file by passing the nfsd_file to nfsd_iter_read() rather than +just the file which is associaed with the nfsd_file. + +This means nfsd4_encode_readv() now also needs the nfsd_file rather +than the file. Instead of changing the file arg to be the nfsd_file, +we discard the file arg as the nfsd_file (and indeed the file) is +already available via the "read" argument. + +Signed-off-by: Mike Snitzer +Reviewed-by: Jeff Layton +Reviewed-by: NeilBrown +Reviewed-by: Christoph Hellwig +Signed-off-by: Chuck Lever +Stable-dep-of: 18c1cc698861 ("SUNRPC: Return an error from xdr_buf_to_bvec() on overflow") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + fs/nfsd/nfs4xdr.c | 8 ++++---- + fs/nfsd/vfs.c | 7 ++++--- + fs/nfsd/vfs.h | 2 +- + 3 files changed, 9 insertions(+), 8 deletions(-) + +--- a/fs/nfsd/nfs4xdr.c ++++ b/fs/nfsd/nfs4xdr.c +@@ -4096,7 +4096,7 @@ out_err: + + static __be32 nfsd4_encode_readv(struct nfsd4_compoundres *resp, + struct nfsd4_read *read, +- struct file *file, unsigned long maxcount) ++ unsigned long maxcount) + { + struct xdr_stream *xdr = resp->xdr; + unsigned int base = xdr->buf->page_len & ~PAGE_MASK; +@@ -4107,7 +4107,7 @@ static __be32 nfsd4_encode_readv(struct + if (xdr_reserve_space_vec(xdr, maxcount) < 0) + return nfserr_resource; + +- nfserr = nfsd_iter_read(resp->rqstp, read->rd_fhp, file, ++ nfserr = nfsd_iter_read(resp->rqstp, read->rd_fhp, read->rd_nf, + read->rd_offset, &maxcount, base, + &read->rd_eof); + read->rd_length = maxcount; +@@ -4155,7 +4155,7 @@ nfsd4_encode_read(struct nfsd4_compoundr + if (file->f_op->splice_read && splice_ok) + nfserr = nfsd4_encode_splice_read(resp, read, file, maxcount); + else +- nfserr = nfsd4_encode_readv(resp, read, file, maxcount); ++ nfserr = nfsd4_encode_readv(resp, read, maxcount); + if (nfserr) { + xdr_truncate_encode(xdr, starting_len); + return nfserr; +@@ -4906,7 +4906,7 @@ nfsd4_encode_read_plus_data(struct nfsd4 + if (file->f_op->splice_read && splice_ok) + nfserr = nfsd4_encode_splice_read(resp, read, file, maxcount); + else +- nfserr = nfsd4_encode_readv(resp, read, file, maxcount); ++ nfserr = nfsd4_encode_readv(resp, read, maxcount); + if (nfserr) + return nfserr; + +--- a/fs/nfsd/vfs.c ++++ b/fs/nfsd/vfs.c +@@ -1061,7 +1061,7 @@ __be32 nfsd_splice_read(struct svc_rqst + * nfsd_iter_read - Perform a VFS read using an iterator + * @rqstp: RPC transaction context + * @fhp: file handle of file to be read +- * @file: opened struct file of file to be read ++ * @nf: opened struct nfsd_file of file to be read + * @offset: starting byte offset + * @count: IN: requested number of bytes; OUT: number of bytes read + * @base: offset in first page of read buffer +@@ -1074,9 +1074,10 @@ __be32 nfsd_splice_read(struct svc_rqst + * returned. + */ + __be32 nfsd_iter_read(struct svc_rqst *rqstp, struct svc_fh *fhp, +- struct file *file, loff_t offset, unsigned long *count, ++ struct nfsd_file *nf, loff_t offset, unsigned long *count, + unsigned int base, u32 *eof) + { ++ struct file *file = nf->nf_file; + unsigned long v, total; + struct iov_iter iter; + loff_t ppos = offset; +@@ -1258,7 +1259,7 @@ __be32 nfsd_read(struct svc_rqst *rqstp, + if (file->f_op->splice_read && test_bit(RQ_SPLICE_OK, &rqstp->rq_flags)) + err = nfsd_splice_read(rqstp, fhp, file, offset, count, eof); + else +- err = nfsd_iter_read(rqstp, fhp, file, offset, count, 0, eof); ++ err = nfsd_iter_read(rqstp, fhp, nf, offset, count, 0, eof); + + nfsd_file_put(nf); + trace_nfsd_read_done(rqstp, fhp, offset, *count); +--- a/fs/nfsd/vfs.h ++++ b/fs/nfsd/vfs.h +@@ -120,7 +120,7 @@ __be32 nfsd_splice_read(struct svc_rqst + unsigned long *count, + u32 *eof); + __be32 nfsd_iter_read(struct svc_rqst *rqstp, struct svc_fh *fhp, +- struct file *file, loff_t offset, ++ struct nfsd_file *nf, loff_t offset, + unsigned long *count, unsigned int base, + u32 *eof); + __be32 nfsd_read(struct svc_rqst *rqstp, struct svc_fh *fhp, diff --git a/queue-6.6/octeontx2-af-cn10k-restrict-vf-lmtline-sharing-to-its-own-pf.patch b/queue-6.6/octeontx2-af-cn10k-restrict-vf-lmtline-sharing-to-its-own-pf.patch new file mode 100644 index 0000000000..e26aad8924 --- /dev/null +++ b/queue-6.6/octeontx2-af-cn10k-restrict-vf-lmtline-sharing-to-its-own-pf.patch @@ -0,0 +1,78 @@ +From stable+bounces-290076-greg=kroah.com@vger.kernel.org Tue Jul 28 19:24:15 2026 +From: Sasha Levin +Date: Tue, 28 Jul 2026 12:10:46 -0400 +Subject: octeontx2-af: cn10k: restrict VF LMTLINE sharing to its own PF +To: stable@vger.kernel.org +Cc: Junrui Luo , Yuhao Jiang , Jakub Kicinski , Sasha Levin +Message-ID: <20260728161046.36627-1-sashal@kernel.org> + +From: Junrui Luo + +[ Upstream commit 8cdcf3d2caacdee7ddd363705fb4d93b0c1a0915 ] + +rvu_mbox_handler_lmtst_tbl_setup() uses req->base_pcifunc as a direct +index into the LMT map table to read another function's LMTLINE +physical base address and copy it into the caller's own LMT map table +entry. The mailbox dispatcher authenticates req->hdr.pcifunc from the +IRQ source, but req->base_pcifunc is a separate payload field and is +not sanitized. + +Reject the request with -EPERM when a VF caller's base_pcifunc is not a +valid function under its own PF. is_pf_func_valid() bounds the FUNC field +to the PF's configured VF count, keeping the computed index inside the +caller's own slot block. + +Fixes: 893ae97214c3 ("octeontx2-af: cn10k: Support configurable LMTST regions") +Reported-by: Yuhao Jiang +Cc: stable@vger.kernel.org +Signed-off-by: Junrui Luo +Link: https://patch.msgid.link/SYBPR01MB78811656934E713B77DA6CEDAFE62@SYBPR01MB7881.ausprd01.prod.outlook.com +Signed-off-by: Jakub Kicinski +[ Dropped `rvu->pdev` argument from `rvu_get_pf()` calls and folded in the `is_pf_func_valid()` de-static plus its `rvu.h` declaration from commit 2156a29aecff. ] +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/net/ethernet/marvell/octeontx2/af/rvu.c | 2 +- + drivers/net/ethernet/marvell/octeontx2/af/rvu.h | 1 + + drivers/net/ethernet/marvell/octeontx2/af/rvu_cn10k.c | 9 +++++++++ + 3 files changed, 11 insertions(+), 1 deletion(-) + +--- a/drivers/net/ethernet/marvell/octeontx2/af/rvu.c ++++ b/drivers/net/ethernet/marvell/octeontx2/af/rvu.c +@@ -440,7 +440,7 @@ struct rvu_pfvf *rvu_get_pfvf(struct rvu + return &rvu->pf[rvu_get_pf(pcifunc)]; + } + +-static bool is_pf_func_valid(struct rvu *rvu, u16 pcifunc) ++bool is_pf_func_valid(struct rvu *rvu, u16 pcifunc) + { + int pf, vf, nvfs; + u64 cfg; +--- a/drivers/net/ethernet/marvell/octeontx2/af/rvu.h ++++ b/drivers/net/ethernet/marvell/octeontx2/af/rvu.h +@@ -751,6 +751,7 @@ int rvu_get_pf(u16 pcifunc); + struct rvu_pfvf *rvu_get_pfvf(struct rvu *rvu, int pcifunc); + void rvu_get_pf_numvfs(struct rvu *rvu, int pf, int *numvfs, int *hwvf); + bool is_block_implemented(struct rvu_hwinfo *hw, int blkaddr); ++bool is_pf_func_valid(struct rvu *rvu, u16 pcifunc); + bool is_pffunc_map_valid(struct rvu *rvu, u16 pcifunc, int blktype); + int rvu_get_lf(struct rvu *rvu, struct rvu_block *block, u16 pcifunc, u16 slot); + int rvu_lf_reset(struct rvu *rvu, struct rvu_block *block, int lf); +--- a/drivers/net/ethernet/marvell/octeontx2/af/rvu_cn10k.c ++++ b/drivers/net/ethernet/marvell/octeontx2/af/rvu_cn10k.c +@@ -178,6 +178,15 @@ int rvu_mbox_handler_lmtst_tbl_setup(str + * pcifunc (will be the one who is calling this mailbox). + */ + if (req->base_pcifunc) { ++ /* A VF is untrusted and must not redirect its LMTLINE to ++ * another PF's region, so confine VF callers to their own PF. ++ */ ++ if (is_vf(req->hdr.pcifunc) && ++ (!is_pf_func_valid(rvu, req->base_pcifunc) || ++ rvu_get_pf(req->hdr.pcifunc) != ++ rvu_get_pf(req->base_pcifunc))) ++ return -EPERM; ++ + /* Calculating the LMT table index equivalent to primary + * pcifunc. + */ diff --git a/queue-6.6/octeontx2-annotate-mmio-regions-as-__iomem.patch b/queue-6.6/octeontx2-annotate-mmio-regions-as-__iomem.patch new file mode 100644 index 0000000000..339ff677a8 --- /dev/null +++ b/queue-6.6/octeontx2-annotate-mmio-regions-as-__iomem.patch @@ -0,0 +1,99 @@ +From stable+bounces-288093-greg=kroah.com@vger.kernel.org Thu Jul 23 01:26:48 2026 +From: Sasha Levin +Date: Wed, 22 Jul 2026 19:21:18 -0400 +Subject: octeontx2: Annotate mmio regions as __iomem +To: stable@vger.kernel.org +Cc: Subbaraya Sundeep , Jakub Kicinski , Sasha Levin +Message-ID: <20260722232119.2551122-1-sashal@kernel.org> + +From: Subbaraya Sundeep + +[ Upstream commit d0976b43956ee8c8bd093223df9115bfcf63dfe5 ] + +This patch removes unnecessary typecasts by marking the +mbox_regions array as __iomem since it is used to store +pointers to memory-mapped I/O (MMIO) regions. Also simplified +the call to readq() in PF driver by removing redundant type casts. + +Signed-off-by: Subbaraya Sundeep +Link: https://patch.msgid.link/1749484309-3434-1-git-send-email-sbhatta@marvell.com +Signed-off-by: Jakub Kicinski +Stable-dep-of: f918554fb724 ("octeontx2-pf: clear stale mailbox IRQ state before request_irq()") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/net/ethernet/marvell/octeontx2/af/rvu.c | 12 ++++++------ + drivers/net/ethernet/marvell/octeontx2/nic/otx2_pf.c | 3 +-- + 2 files changed, 7 insertions(+), 8 deletions(-) + +--- a/drivers/net/ethernet/marvell/octeontx2/af/rvu.c ++++ b/drivers/net/ethernet/marvell/octeontx2/af/rvu.c +@@ -2330,7 +2330,7 @@ static inline void rvu_afvf_mbox_up_hand + __rvu_mbox_up_handler(mwork, TYPE_AFVF); + } + +-static int rvu_get_mbox_regions(struct rvu *rvu, void **mbox_addr, ++static int rvu_get_mbox_regions(struct rvu *rvu, void __iomem **mbox_addr, + int num, int type, unsigned long *pf_bmap) + { + struct rvu_hwinfo *hw = rvu->hw; +@@ -2355,7 +2355,7 @@ static int rvu_get_mbox_regions(struct r + bar4 = rvupf_read64(rvu, RVU_PF_VF_BAR4_ADDR); + bar4 += region * MBOX_SIZE; + } +- mbox_addr[region] = (void *)ioremap_wc(bar4, MBOX_SIZE); ++ mbox_addr[region] = ioremap_wc(bar4, MBOX_SIZE); + if (!mbox_addr[region]) + goto error; + } +@@ -2378,7 +2378,7 @@ static int rvu_get_mbox_regions(struct r + RVU_AF_PF_BAR4_ADDR); + bar4 += region * MBOX_SIZE; + } +- mbox_addr[region] = (void *)ioremap_wc(bar4, MBOX_SIZE); ++ mbox_addr[region] = ioremap_wc(bar4, MBOX_SIZE); + if (!mbox_addr[region]) + goto error; + } +@@ -2386,7 +2386,7 @@ static int rvu_get_mbox_regions(struct r + + error: + while (region--) +- iounmap((void __iomem *)mbox_addr[region]); ++ iounmap(mbox_addr[region]); + return -ENOMEM; + } + +@@ -2396,10 +2396,10 @@ static int rvu_mbox_init(struct rvu *rvu + void (mbox_up_handler)(struct work_struct *)) + { + int err = -EINVAL, i, dir, dir_up; ++ void __iomem **mbox_regions; + void __iomem *reg_base; + struct rvu_work *mwork; + unsigned long *pf_bmap; +- void **mbox_regions; + const char *name; + u64 cfg; + +@@ -2422,7 +2422,7 @@ static int rvu_mbox_init(struct rvu *rvu + + mutex_init(&rvu->mbox_lock); + +- mbox_regions = kcalloc(num, sizeof(void *), GFP_KERNEL); ++ mbox_regions = kcalloc(num, sizeof(void __iomem *), GFP_KERNEL); + if (!mbox_regions) { + err = -ENOMEM; + goto free_bitmap; +--- a/drivers/net/ethernet/marvell/octeontx2/nic/otx2_pf.c ++++ b/drivers/net/ethernet/marvell/octeontx2/nic/otx2_pf.c +@@ -597,8 +597,7 @@ static int otx2_pfvf_mbox_init(struct ot + base = pci_resource_start(pf->pdev, PCI_MBOX_BAR_NUM) + + MBOX_SIZE; + else +- base = readq((void __iomem *)((u64)pf->reg_base + +- RVU_PF_VF_BAR4_ADDR)); ++ base = readq(pf->reg_base + RVU_PF_VF_BAR4_ADDR); + + hwbase = ioremap_wc(base, MBOX_SIZE * pf->total_vfs); + if (!hwbase) { diff --git a/queue-6.6/octeontx2-pf-clear-stale-mailbox-irq-state-before-request_irq.patch b/queue-6.6/octeontx2-pf-clear-stale-mailbox-irq-state-before-request_irq.patch new file mode 100644 index 0000000000..a9c71ded6f --- /dev/null +++ b/queue-6.6/octeontx2-pf-clear-stale-mailbox-irq-state-before-request_irq.patch @@ -0,0 +1,67 @@ +From stable+bounces-288094-greg=kroah.com@vger.kernel.org Thu Jul 23 01:21:39 2026 +From: Sasha Levin +Date: Wed, 22 Jul 2026 19:21:19 -0400 +Subject: octeontx2-pf: clear stale mailbox IRQ state before request_irq() +To: stable@vger.kernel.org +Cc: Runyu Xiao , Simon Horman , Ratheesh Kannoth , Jakub Kicinski , Sasha Levin +Message-ID: <20260722232119.2551122-2-sashal@kernel.org> + +From: Runyu Xiao + +[ Upstream commit f918554fb7246e89b98ef90abe80801f038258b3 ] + +otx2_register_mbox_intr() currently installs the PF mailbox IRQ handler +before clearing stale mailbox interrupt state. The function itself then +comments that the local interrupt bits must be cleared first to avoid +spurious interrupts, but that clear happens only after request_irq() has +already exposed the handler to irq delivery. + +A running system can reach this during PF mailbox interrupt registration +while stale or latched RVU_PF_INT state is still present. If delivery +happens in the request_irq()-to-clear window, +otx2_pfaf_mbox_intr_handler() can run before local quiesce and touch +the same pf->mbox and pf->mbox_wq carrier that probe and teardown later +reuse or destroy. + +Move the stale mailbox interrupt clear ahead of request_irq(), but keep +interrupt enabling after the handler is installed. This closes the +pre-clear early-IRQ window without creating a new enable-before-handler +window. + +Fixes: 5a6d7c9daef3 ("octeontx2-pf: Mailbox communication with AF") +Cc: stable@vger.kernel.org +Signed-off-by: Runyu Xiao +Reviewed-by: Simon Horman +Reviewed-by: Ratheesh Kannoth +Link: https://patch.msgid.link/20260611160014.3202224-2-runyu.xiao@seu.edu.cn +Signed-off-by: Jakub Kicinski +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/net/ethernet/marvell/octeontx2/nic/otx2_pf.c | 8 ++++---- + 1 file changed, 4 insertions(+), 4 deletions(-) + +--- a/drivers/net/ethernet/marvell/octeontx2/nic/otx2_pf.c ++++ b/drivers/net/ethernet/marvell/octeontx2/nic/otx2_pf.c +@@ -1027,6 +1027,9 @@ static int otx2_register_mbox_intr(struc + char *irq_name; + int err; + ++ /* Clear stale mailbox interrupt state before installing the handler. */ ++ otx2_write64(pf, RVU_PF_INT, BIT_ULL(0)); ++ + /* Register mailbox interrupt handler */ + irq_name = &hw->irq_name[RVU_PF_INT_VEC_AFPF_MBOX * NAME_SIZE]; + snprintf(irq_name, NAME_SIZE, "RVUPFAF Mbox"); +@@ -1038,10 +1041,7 @@ static int otx2_register_mbox_intr(struc + return err; + } + +- /* Enable mailbox interrupt for msgs coming from AF. +- * First clear to avoid spurious interrupts, if any. +- */ +- otx2_write64(pf, RVU_PF_INT, BIT_ULL(0)); ++ /* Enable mailbox interrupt for msgs coming from AF. */ + otx2_write64(pf, RVU_PF_INT_ENA_W1S, BIT_ULL(0)); + + if (!probe_af) diff --git a/queue-6.6/octeontx2-pf-fix-sqb-pointer-leak-on-init-failure.patch b/queue-6.6/octeontx2-pf-fix-sqb-pointer-leak-on-init-failure.patch new file mode 100644 index 0000000000..b825a97386 --- /dev/null +++ b/queue-6.6/octeontx2-pf-fix-sqb-pointer-leak-on-init-failure.patch @@ -0,0 +1,79 @@ +From stable+bounces-290231-greg=kroah.com@vger.kernel.org Wed Jul 29 04:39:02 2026 +From: Sasha Levin +Date: Tue, 28 Jul 2026 22:30:28 -0400 +Subject: octeontx2-pf: fix SQB pointer leak on init failure +To: stable@vger.kernel.org +Cc: Dawei Feng , Ratheesh Kannoth , Paolo Abeni , Sasha Levin +Message-ID: <20260729023028.1114866-1-sashal@kernel.org> + +From: Dawei Feng + +[ Upstream commit 62e7df6d042aeebd5efb581074e28865c04477be ] + +otx2_init_hw_resources() initializes SQ aura and pool resources before +several later setup steps. On failure, err_free_sq_ptrs only frees SQB +pages, leaving the per-SQ sqb_ptrs arrays behind. + +Use otx2_free_sq_res() for the SQ unwind path and let it free sqb_ptrs +even when sq->sqe has not been allocated yet. + +The bug was first flagged by an experimental analysis tool we are +developing for kernel memory-management bugs while analyzing +v6.13-rc1. The tool is still under development and is not yet publicly +available. Manual inspection confirms that the bug is still +present in v7.1.1. + +An x86_64 allyesconfig build showed no new warnings. As we do not have an +OcteonTX2 PF device and the corresponding AF mailbox setup to test with, +no runtime testing was able to be performed. + +Fixes: caa2da34fd25 ("octeontx2-pf: Initialize and config queues") +Cc: stable@vger.kernel.org +Reviewed-by: Ratheesh Kannoth +Signed-off-by: Dawei Feng +Link: https://patch.msgid.link/20260630071625.349996-1-dawei.feng@seu.edu.cn +Signed-off-by: Paolo Abeni +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/net/ethernet/marvell/octeontx2/nic/otx2_pf.c | 17 ++++++++--------- + 1 file changed, 8 insertions(+), 9 deletions(-) + +--- a/drivers/net/ethernet/marvell/octeontx2/nic/otx2_pf.c ++++ b/drivers/net/ethernet/marvell/octeontx2/nic/otx2_pf.c +@@ -1442,13 +1442,13 @@ static void otx2_free_sq_res(struct otx2 + otx2_sq_free_sqbs(pf); + for (qidx = 0; qidx < otx2_get_total_tx_queues(pf); qidx++) { + sq = &qset->sq[qidx]; +- /* Skip freeing Qos queues if they are not initialized */ +- if (!sq->sqe) +- continue; +- qmem_free(pf->dev, sq->sqe); +- qmem_free(pf->dev, sq->tso_hdrs); +- qmem_free(pf->dev, sq->timestamps); +- kfree(sq->sg); ++ /* sq->sqe is not initialized for unused QoS queues */ ++ if (sq->sqe) { ++ qmem_free(pf->dev, sq->sqe); ++ qmem_free(pf->dev, sq->tso_hdrs); ++ qmem_free(pf->dev, sq->timestamps); ++ kfree(sq->sg); ++ } + kfree(sq->sqb_ptrs); + } + } +@@ -1574,13 +1574,12 @@ static int otx2_init_hw_resources(struct + return err; + + err_free_nix_queues: +- otx2_free_sq_res(pf); + otx2_free_cq_res(pf); + otx2_ctx_disable(mbox, NIX_AQ_CTYPE_RQ, false); + err_free_txsch: + otx2_txschq_stop(pf); + err_free_sq_ptrs: +- otx2_sq_free_sqbs(pf); ++ otx2_free_sq_res(pf); + err_free_rq_ptrs: + otx2_free_aura_ptr(pf, AURA_NIX_RQ); + otx2_ctx_disable(mbox, NPA_AQ_CTYPE_POOL, true); diff --git a/queue-6.6/octeontx2-vf-clear-stale-mailbox-irq-state-before-request_irq.patch b/queue-6.6/octeontx2-vf-clear-stale-mailbox-irq-state-before-request_irq.patch new file mode 100644 index 0000000000..17084b0cba --- /dev/null +++ b/queue-6.6/octeontx2-vf-clear-stale-mailbox-irq-state-before-request_irq.patch @@ -0,0 +1,75 @@ +From stable+bounces-288096-greg=kroah.com@vger.kernel.org Thu Jul 23 01:27:11 2026 +From: Sasha Levin +Date: Wed, 22 Jul 2026 19:21:22 -0400 +Subject: octeontx2-vf: clear stale mailbox IRQ state before request_irq() +To: stable@vger.kernel.org +Cc: Runyu Xiao , Simon Horman , Ratheesh Kannoth , Jakub Kicinski , Sasha Levin +Message-ID: <20260722232122.2551218-2-sashal@kernel.org> + +From: Runyu Xiao + +[ Upstream commit 0b352f04b9be2c83c0240aa6dae7257fefa90464 ] + +otx2vf_register_mbox_intr() currently installs the VF mailbox IRQ +handler before clearing stale mailbox interrupt state. The code then says +that local interrupt bits should be cleared first to avoid spurious +interrupts, but that clear still happens only after request_irq() has +already made the handler reachable. + +A running system can reach this during VF mailbox interrupt registration +while stale or latched RVU_VF_INT state is still present. If delivery +happens in the request_irq()-to-clear window, +otx2vf_vfaf_mbox_intr_handler() can run before local quiesce and touch +the same vf->mbox and vf->mbox_wq carrier that probe and teardown later +reuse or destroy. + +Move the stale mailbox interrupt clear ahead of request_irq(), but keep +interrupt enabling after the handler is installed. This closes the +pre-clear early-IRQ window without creating a new enable-before-handler +window. + +Fixes: 3184fb5ba96e ("octeontx2-vf: Virtual function driver support") +Cc: stable@vger.kernel.org +Signed-off-by: Runyu Xiao +Reviewed-by: Simon Horman +Reviewed-by: Ratheesh Kannoth +Link: https://patch.msgid.link/20260611160014.3202224-3-runyu.xiao@seu.edu.cn +Signed-off-by: Jakub Kicinski +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/net/ethernet/marvell/octeontx2/nic/otx2_vf.c | 13 ++++++++----- + 1 file changed, 8 insertions(+), 5 deletions(-) + +--- a/drivers/net/ethernet/marvell/octeontx2/nic/otx2_vf.c ++++ b/drivers/net/ethernet/marvell/octeontx2/nic/otx2_vf.c +@@ -245,9 +245,15 @@ static int otx2vf_register_mbox_intr(str + { + struct otx2_hw *hw = &vf->hw; + struct msg_req *req; ++ u64 mbox_int_mask; + char *irq_name; + int err; + ++ mbox_int_mask = BIT_ULL(0); ++ ++ /* Clear stale mailbox interrupt state before installing the handler. */ ++ otx2_write64(vf, RVU_VF_INT, mbox_int_mask); ++ + /* Register mailbox interrupt handler */ + irq_name = &hw->irq_name[RVU_VF_INT_VEC_MBOX * NAME_SIZE]; + snprintf(irq_name, NAME_SIZE, "RVUVFAF Mbox"); +@@ -259,11 +265,8 @@ static int otx2vf_register_mbox_intr(str + return err; + } + +- /* Enable mailbox interrupt for msgs coming from PF. +- * First clear to avoid spurious interrupts, if any. +- */ +- otx2_write64(vf, RVU_VF_INT, BIT_ULL(0)); +- otx2_write64(vf, RVU_VF_INT_ENA_W1S, BIT_ULL(0)); ++ /* Enable mailbox interrupt for msgs coming from PF. */ ++ otx2_write64(vf, RVU_VF_INT_ENA_W1S, mbox_int_mask); + + if (!probe_pf) + return 0; diff --git a/queue-6.6/ovl-use-linked-upper-dentry-in-copy-up-tmpfile.patch b/queue-6.6/ovl-use-linked-upper-dentry-in-copy-up-tmpfile.patch new file mode 100644 index 0000000000..de76ee7be4 --- /dev/null +++ b/queue-6.6/ovl-use-linked-upper-dentry-in-copy-up-tmpfile.patch @@ -0,0 +1,105 @@ +From stable+bounces-289602-greg=kroah.com@vger.kernel.org Mon Jul 27 17:52:15 2026 +From: Sasha Levin +Date: Mon, 27 Jul 2026 11:41:17 -0400 +Subject: ovl: use linked upper dentry in copy-up tmpfile +To: stable@vger.kernel.org +Cc: Souvik Banerjee , Amir Goldstein , Miklos Szeredi , "Christian Brauner (Amutable)" , Sasha Levin +Message-ID: <20260727154117.1516979-1-sashal@kernel.org> + +From: Souvik Banerjee + +[ Upstream commit e348eecd4d8fa8d18a5157ff59f7be1dc59c5928 ] + +ovl_copy_up_tmpfile() stores the disconnected O_TMPFILE dentry as the +overlay's upper dentry reference via ovl_inode_update(). vfs_tmpfile() +allocated this dentry via d_alloc(parentpath->dentry, &slash_name), so +d_name is "/" and d_parent is c->workdir. Local upper filesystems +(ext4, btrfs, xfs, ...) immediately rename it to "#" via +d_mark_tmpfile() inside their ->tmpfile() op; FUSE and virtiofs do +not, so both fields stay that way. Neither identifies the destination +directory and filename where ovl_do_link() actually linked the file. + +When the upper filesystem implements ->d_revalidate() (e.g. FUSE or +virtiofs), ovl_revalidate_real() calls it with the dentry's parent +inode and a snapshot of d_name. The server tries to look up "/" inside +c->workdir, fails, and overlayfs reports -ESTALE. + +This causes persistent ESTALE errors for any file that was copied up via +the tmpfile path, breaking dpkg, apt, and other tools that do +rename-over-existing on overlayfs with a FUSE/virtiofs upper. + +Before commit 6b52243f633e ("ovl: fold copy-up helpers into callers"), +the tmpfile copy-up path used a dedicated helper ovl_link_tmpfile() +that captured the linked destination dentry returned by ovl_do_link(): + + err = ovl_do_link(temp, udir, upper); + ... + if (!err) + *newdentry = dget(upper); + +and published it via ovl_inode_update(d_inode(c->dentry), newdentry). +The fold inlined ovl_do_link() into ovl_copy_up_tmpfile() but dropped +the dget(upper) capture, and rewrote the publish line as +ovl_inode_update(d_inode(c->dentry), dget(temp)) — where temp is the +disconnected O_TMPFILE dentry. + +Fix by keeping a reference to the linked destination dentry after +ovl_do_link() succeeds, and publishing that dentry at the existing +ovl_inode_update() call site. The non-tmpfile/workdir path continues to +publish the renamed temporary dentry. + +Reproducer: + - Mount overlayfs with virtiofs (or a FUSE fs whose server advertises + FUSE_TMPFILE) as upper + - Run: dpkg -i + - Observe: "error installing new file '...': Stale file handle" + +Fixes: 6b52243f633e ("ovl: fold copy-up helpers into callers") +Cc: stable@vger.kernel.org # v4.20+ +Signed-off-by: Souvik Banerjee +Link: https://patch.msgid.link/20260501232735.2610824-1-souvik@amlalabs.com +Reviewed-by: Amir Goldstein +Reviewed-by: Miklos Szeredi +Signed-off-by: Christian Brauner (Amutable) +[ adapted scoped credential and creation helpers to explicit credential, locking, lookup, and cleanup handling ] +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + fs/overlayfs/copy_up.c | 12 ++++++++++-- + 1 file changed, 10 insertions(+), 2 deletions(-) + +--- a/fs/overlayfs/copy_up.c ++++ b/fs/overlayfs/copy_up.c +@@ -793,7 +793,7 @@ static int ovl_copy_up_tmpfile(struct ov + { + struct ovl_fs *ofs = OVL_FS(c->dentry->d_sb); + struct inode *udir = d_inode(c->destdir); +- struct dentry *temp, *upper; ++ struct dentry *temp, *upper, *newdentry = NULL; + struct file *tmpfile; + struct ovl_cu_creds cc; + int err; +@@ -826,6 +826,14 @@ static int ovl_copy_up_tmpfile(struct ov + err = PTR_ERR(upper); + if (!IS_ERR(upper)) { + err = ovl_do_link(ofs, temp, udir, upper); ++ if (!err) { ++ /* ++ * Record the linked dentry -- not the disconnected ++ * O_TMPFILE dentry -- so that ->d_revalidate() on ++ * the upper fs sees the real parent/name. ++ */ ++ newdentry = dget(upper); ++ } + dput(upper); + } + inode_unlock(udir); +@@ -841,7 +849,7 @@ static int ovl_copy_up_tmpfile(struct ov + + if (!c->metacopy) + ovl_set_upperdata(d_inode(c->dentry)); +- ovl_inode_update(d_inode(c->dentry), dget(temp)); ++ ovl_inode_update(d_inode(c->dentry), newdentry); + + out_fput: + fput(tmpfile); diff --git a/queue-6.6/pinctrl-remove-pinctrl_gpio_direction_output.patch b/queue-6.6/pinctrl-remove-pinctrl_gpio_direction_output.patch new file mode 100644 index 0000000000..0801db980d --- /dev/null +++ b/queue-6.6/pinctrl-remove-pinctrl_gpio_direction_output.patch @@ -0,0 +1,590 @@ +From stable+bounces-289948-greg=kroah.com@vger.kernel.org Tue Jul 28 14:18:24 2026 +From: Sasha Levin +Date: Tue, 28 Jul 2026 08:11:24 -0400 +Subject: pinctrl: remove pinctrl_gpio_direction_output() +To: stable@vger.kernel.org +Cc: Bartosz Golaszewski , Linus Walleij , Sasha Levin +Message-ID: <20260728121125.3703669-1-sashal@kernel.org> + +From: Bartosz Golaszewski + +[ Upstream commit 45d2055b0067739253883dc541f37c86aad45c92 ] + +There are no more users of pinctrl_gpio_direction_output() so remove it. + +Signed-off-by: Bartosz Golaszewski +Acked-by: Linus Walleij +Stable-dep-of: d3e91a95b2b0 ("gpio: tegra: do not call pinctrl for GPIO direction") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/gpio/gpio-mvebu.c | 4 ++-- + drivers/gpio/gpio-pxa.c | 4 ++-- + drivers/gpio/gpio-tegra.c | 4 ++-- + drivers/gpio/gpio-vf610.c | 4 ++-- + drivers/pinctrl/cirrus/pinctrl-cs42l43.c | 4 ++-- + drivers/pinctrl/cirrus/pinctrl-lochnagar.c | 2 +- + drivers/pinctrl/core.c | 14 ++++++++------ + drivers/pinctrl/intel/pinctrl-cherryview.c | 4 ++-- + drivers/pinctrl/intel/pinctrl-intel.c | 4 ++-- + drivers/pinctrl/intel/pinctrl-lynxpoint.c | 4 ++-- + drivers/pinctrl/mediatek/pinctrl-moore.c | 4 ++-- + drivers/pinctrl/mediatek/pinctrl-mtk-common.c | 4 ++-- + drivers/pinctrl/mediatek/pinctrl-paris.c | 4 ++-- + drivers/pinctrl/nuvoton/pinctrl-npcm7xx.c | 4 ++-- + drivers/pinctrl/pinctrl-as3722.c | 4 ++-- + drivers/pinctrl/pinctrl-axp209.c | 2 +- + drivers/pinctrl/pinctrl-cy8c95x0.c | 4 ++-- + drivers/pinctrl/pinctrl-ingenic.c | 11 ++++++++--- + drivers/pinctrl/pinctrl-ocelot.c | 4 ++-- + drivers/pinctrl/pinctrl-rk805.c | 4 ++-- + drivers/pinctrl/pinctrl-st.c | 4 ++-- + drivers/pinctrl/renesas/gpio.c | 4 ++-- + drivers/pinctrl/stm32/pinctrl-stm32.c | 4 ++-- + drivers/pinctrl/vt8500/pinctrl-wmt.c | 4 ++-- + include/linux/pinctrl/consumer.h | 13 +++++++++---- + 25 files changed, 67 insertions(+), 55 deletions(-) + +--- a/drivers/gpio/gpio-mvebu.c ++++ b/drivers/gpio/gpio-mvebu.c +@@ -345,7 +345,7 @@ static int mvebu_gpio_direction_input(st + * Check with the pinctrl driver whether this pin is usable as + * an input GPIO + */ +- ret = pinctrl_gpio_direction_input(chip->base + pin); ++ ret = pinctrl_gpio_direction_input(chip, pin); + if (ret) + return ret; + +@@ -365,7 +365,7 @@ static int mvebu_gpio_direction_output(s + * Check with the pinctrl driver whether this pin is usable as + * an output GPIO + */ +- ret = pinctrl_gpio_direction_output(chip->base + pin); ++ ret = pinctrl_gpio_direction_output(chip, pin); + if (ret) + return ret; + +--- a/drivers/gpio/gpio-pxa.c ++++ b/drivers/gpio/gpio-pxa.c +@@ -260,7 +260,7 @@ static int pxa_gpio_direction_input(stru + int ret; + + if (pxa_gpio_has_pinctrl()) { +- ret = pinctrl_gpio_direction_input(chip->base + offset); ++ ret = pinctrl_gpio_direction_input(chip, offset); + if (ret) + return ret; + } +@@ -289,7 +289,7 @@ static int pxa_gpio_direction_output(str + writel_relaxed(mask, base + (value ? GPSR_OFFSET : GPCR_OFFSET)); + + if (pxa_gpio_has_pinctrl()) { +- ret = pinctrl_gpio_direction_output(chip->base + offset); ++ ret = pinctrl_gpio_direction_output(chip, offset); + if (ret) + return ret; + } +--- a/drivers/gpio/gpio-tegra.c ++++ b/drivers/gpio/gpio-tegra.c +@@ -179,7 +179,7 @@ static int tegra_gpio_direction_input(st + tegra_gpio_mask_write(tgi, GPIO_MSK_OE(tgi, offset), offset, 0); + tegra_gpio_enable(tgi, offset); + +- ret = pinctrl_gpio_direction_input(chip->base + offset); ++ ret = pinctrl_gpio_direction_input(chip, offset); + if (ret < 0) + dev_err(tgi->dev, + "Failed to set pinctrl input direction of GPIO %d: %d", +@@ -199,7 +199,7 @@ static int tegra_gpio_direction_output(s + tegra_gpio_mask_write(tgi, GPIO_MSK_OE(tgi, offset), offset, 1); + tegra_gpio_enable(tgi, offset); + +- ret = pinctrl_gpio_direction_output(chip->base + offset); ++ ret = pinctrl_gpio_direction_output(chip, offset); + if (ret < 0) + dev_err(tgi->dev, + "Failed to set pinctrl output direction of GPIO %d: %d", +--- a/drivers/gpio/gpio-vf610.c ++++ b/drivers/gpio/gpio-vf610.c +@@ -116,7 +116,7 @@ static int vf610_gpio_direction_input(st + vf610_gpio_writel(val, port->gpio_base + GPIO_PDDR); + } + +- return pinctrl_gpio_direction_input(chip->base + gpio); ++ return pinctrl_gpio_direction_input(chip, gpio); + } + + static int vf610_gpio_direction_output(struct gpio_chip *chip, unsigned gpio, +@@ -134,7 +134,7 @@ static int vf610_gpio_direction_output(s + vf610_gpio_writel(val, port->gpio_base + GPIO_PDDR); + } + +- return pinctrl_gpio_direction_output(chip->base + gpio); ++ return pinctrl_gpio_direction_output(chip, gpio); + } + + static void vf610_gpio_irq_handler(struct irq_desc *desc) +--- a/drivers/pinctrl/cirrus/pinctrl-cs42l43.c ++++ b/drivers/pinctrl/cirrus/pinctrl-cs42l43.c +@@ -508,7 +508,7 @@ static void cs42l43_gpio_set(struct gpio + + static int cs42l43_gpio_direction_in(struct gpio_chip *chip, unsigned int offset) + { +- return pinctrl_gpio_direction_input(chip->base + offset); ++ return pinctrl_gpio_direction_input(chip, offset); + } + + static int cs42l43_gpio_direction_out(struct gpio_chip *chip, +@@ -516,7 +516,7 @@ static int cs42l43_gpio_direction_out(st + { + cs42l43_gpio_set(chip, offset, value); + +- return pinctrl_gpio_direction_output(chip->base + offset); ++ return pinctrl_gpio_direction_output(chip, offset); + } + + static int cs42l43_gpio_add_pin_ranges(struct gpio_chip *chip) +--- a/drivers/pinctrl/cirrus/pinctrl-lochnagar.c ++++ b/drivers/pinctrl/cirrus/pinctrl-lochnagar.c +@@ -1098,7 +1098,7 @@ static int lochnagar_gpio_direction_out( + { + lochnagar_gpio_set(chip, offset, value); + +- return pinctrl_gpio_direction_output(chip->base + offset); ++ return pinctrl_gpio_direction_output(chip, offset); + } + + static int lochnagar_fill_func_groups(struct lochnagar_pin_priv *priv) +--- a/drivers/pinctrl/core.c ++++ b/drivers/pinctrl/core.c +@@ -874,29 +874,31 @@ static int pinctrl_gpio_direction(unsign + + /** + * pinctrl_gpio_direction_input() - request a GPIO pin to go into input mode +- * @gpio: the GPIO pin number from the GPIO subsystem number space ++ * @gc: GPIO chip structure from the GPIO subsystem ++ * @offset: hardware offset of the GPIO relative to the controller + * + * This function should *ONLY* be used from gpiolib-based GPIO drivers, + * as part of their gpio_direction_input() semantics, platforms and individual + * drivers shall *NOT* touch pin control GPIO calls. + */ +-int pinctrl_gpio_direction_input(unsigned gpio) ++int pinctrl_gpio_direction_input(struct gpio_chip *gc, unsigned int offset) + { +- return pinctrl_gpio_direction(gpio, true); ++ return pinctrl_gpio_direction(gc->base + offset, true); + } + EXPORT_SYMBOL_GPL(pinctrl_gpio_direction_input); + + /** + * pinctrl_gpio_direction_output() - request a GPIO pin to go into output mode +- * @gpio: the GPIO pin number from the GPIO subsystem number space ++ * @gc: GPIO chip structure from the GPIO subsystem ++ * @offset: hardware offset of the GPIO relative to the controller + * + * This function should *ONLY* be used from gpiolib-based GPIO drivers, + * as part of their gpio_direction_output() semantics, platforms and individual + * drivers shall *NOT* touch pin control GPIO calls. + */ +-int pinctrl_gpio_direction_output(unsigned gpio) ++int pinctrl_gpio_direction_output(struct gpio_chip *gc, unsigned int offset) + { +- return pinctrl_gpio_direction(gpio, false); ++ return pinctrl_gpio_direction(gc->base + offset, false); + } + EXPORT_SYMBOL_GPL(pinctrl_gpio_direction_output); + +--- a/drivers/pinctrl/intel/pinctrl-cherryview.c ++++ b/drivers/pinctrl/intel/pinctrl-cherryview.c +@@ -1172,14 +1172,14 @@ static int chv_gpio_get_direction(struct + + static int chv_gpio_direction_input(struct gpio_chip *chip, unsigned int offset) + { +- return pinctrl_gpio_direction_input(chip->base + offset); ++ return pinctrl_gpio_direction_input(chip, offset); + } + + static int chv_gpio_direction_output(struct gpio_chip *chip, unsigned int offset, + int value) + { + chv_gpio_set(chip, offset, value); +- return pinctrl_gpio_direction_output(chip->base + offset); ++ return pinctrl_gpio_direction_output(chip, offset); + } + + static const struct gpio_chip chv_gpio_chip = { +--- a/drivers/pinctrl/intel/pinctrl-intel.c ++++ b/drivers/pinctrl/intel/pinctrl-intel.c +@@ -1026,14 +1026,14 @@ static int intel_gpio_get_direction(stru + + static int intel_gpio_direction_input(struct gpio_chip *chip, unsigned int offset) + { +- return pinctrl_gpio_direction_input(chip->base + offset); ++ return pinctrl_gpio_direction_input(chip, offset); + } + + static int intel_gpio_direction_output(struct gpio_chip *chip, unsigned int offset, + int value) + { + intel_gpio_set(chip, offset, value); +- return pinctrl_gpio_direction_output(chip->base + offset); ++ return pinctrl_gpio_direction_output(chip, offset); + } + + static const struct gpio_chip intel_gpio_chip = { +--- a/drivers/pinctrl/intel/pinctrl-lynxpoint.c ++++ b/drivers/pinctrl/intel/pinctrl-lynxpoint.c +@@ -545,7 +545,7 @@ static void lp_gpio_set(struct gpio_chip + + static int lp_gpio_direction_input(struct gpio_chip *chip, unsigned int offset) + { +- return pinctrl_gpio_direction_input(chip->base + offset); ++ return pinctrl_gpio_direction_input(chip, offset); + } + + static int lp_gpio_direction_output(struct gpio_chip *chip, unsigned int offset, +@@ -553,7 +553,7 @@ static int lp_gpio_direction_output(stru + { + lp_gpio_set(chip, offset, value); + +- return pinctrl_gpio_direction_output(chip->base + offset); ++ return pinctrl_gpio_direction_output(chip, offset); + } + + static int lp_gpio_get_direction(struct gpio_chip *chip, unsigned int offset) +--- a/drivers/pinctrl/mediatek/pinctrl-moore.c ++++ b/drivers/pinctrl/mediatek/pinctrl-moore.c +@@ -509,7 +509,7 @@ static void mtk_gpio_set(struct gpio_chi + + static int mtk_gpio_direction_input(struct gpio_chip *chip, unsigned int gpio) + { +- return pinctrl_gpio_direction_input(chip->base + gpio); ++ return pinctrl_gpio_direction_input(chip, gpio); + } + + static int mtk_gpio_direction_output(struct gpio_chip *chip, unsigned int gpio, +@@ -517,7 +517,7 @@ static int mtk_gpio_direction_output(str + { + mtk_gpio_set(chip, gpio, value); + +- return pinctrl_gpio_direction_output(chip->base + gpio); ++ return pinctrl_gpio_direction_output(chip, gpio); + } + + static int mtk_gpio_to_irq(struct gpio_chip *chip, unsigned int offset) +--- a/drivers/pinctrl/mediatek/pinctrl-mtk-common.c ++++ b/drivers/pinctrl/mediatek/pinctrl-mtk-common.c +@@ -811,14 +811,14 @@ static const struct pinmux_ops mtk_pmx_o + static int mtk_gpio_direction_input(struct gpio_chip *chip, + unsigned offset) + { +- return pinctrl_gpio_direction_input(chip->base + offset); ++ return pinctrl_gpio_direction_input(chip, offset); + } + + static int mtk_gpio_direction_output(struct gpio_chip *chip, + unsigned offset, int value) + { + mtk_gpio_set(chip, offset, value); +- return pinctrl_gpio_direction_output(chip->base + offset); ++ return pinctrl_gpio_direction_output(chip, offset); + } + + static int mtk_gpio_get_direction(struct gpio_chip *chip, unsigned offset) +--- a/drivers/pinctrl/mediatek/pinctrl-paris.c ++++ b/drivers/pinctrl/mediatek/pinctrl-paris.c +@@ -904,7 +904,7 @@ static int mtk_gpio_direction_input(stru + if (gpio >= hw->soc->npins) + return -EINVAL; + +- return pinctrl_gpio_direction_input(chip->base + gpio); ++ return pinctrl_gpio_direction_input(chip, gpio); + } + + static int mtk_gpio_direction_output(struct gpio_chip *chip, unsigned int gpio, +@@ -917,7 +917,7 @@ static int mtk_gpio_direction_output(str + + mtk_gpio_set(chip, gpio, value); + +- return pinctrl_gpio_direction_output(chip->base + gpio); ++ return pinctrl_gpio_direction_output(chip, gpio); + } + + static int mtk_gpio_to_irq(struct gpio_chip *chip, unsigned int offset) +--- a/drivers/pinctrl/nuvoton/pinctrl-npcm7xx.c ++++ b/drivers/pinctrl/nuvoton/pinctrl-npcm7xx.c +@@ -171,7 +171,7 @@ static int npcmgpio_direction_input(stru + struct npcm7xx_gpio *bank = gpiochip_get_data(chip); + int ret; + +- ret = pinctrl_gpio_direction_input(offset + chip->base); ++ ret = pinctrl_gpio_direction_input(chip, offset); + if (ret) + return ret; + +@@ -188,7 +188,7 @@ static int npcmgpio_direction_output(str + dev_dbg(chip->parent, "gpio_direction_output: offset%d = %x\n", offset, + value); + +- ret = pinctrl_gpio_direction_output(offset + chip->base); ++ ret = pinctrl_gpio_direction_output(chip, offset); + if (ret) + return ret; + +--- a/drivers/pinctrl/pinctrl-as3722.c ++++ b/drivers/pinctrl/pinctrl-as3722.c +@@ -502,14 +502,14 @@ static void as3722_gpio_set(struct gpio_ + + static int as3722_gpio_direction_input(struct gpio_chip *chip, unsigned offset) + { +- return pinctrl_gpio_direction_input(chip->base + offset); ++ return pinctrl_gpio_direction_input(chip, offset); + } + + static int as3722_gpio_direction_output(struct gpio_chip *chip, + unsigned offset, int value) + { + as3722_gpio_set(chip, offset, value); +- return pinctrl_gpio_direction_output(chip->base + offset); ++ return pinctrl_gpio_direction_output(chip, offset); + } + + static int as3722_gpio_to_irq(struct gpio_chip *chip, unsigned offset) +--- a/drivers/pinctrl/pinctrl-axp209.c ++++ b/drivers/pinctrl/pinctrl-axp209.c +@@ -126,7 +126,7 @@ static int axp20x_gpio_get_reg(unsigned + + static int axp20x_gpio_input(struct gpio_chip *chip, unsigned int offset) + { +- return pinctrl_gpio_direction_input(chip->base + offset); ++ return pinctrl_gpio_direction_input(chip, offset); + } + + static int axp20x_gpio_get(struct gpio_chip *chip, unsigned int offset) +--- a/drivers/pinctrl/pinctrl-cy8c95x0.c ++++ b/drivers/pinctrl/pinctrl-cy8c95x0.c +@@ -557,7 +557,7 @@ out: + + static int cy8c95x0_gpio_direction_input(struct gpio_chip *gc, unsigned int off) + { +- return pinctrl_gpio_direction_input(gc->base + off); ++ return pinctrl_gpio_direction_input(gc, off); + } + + static int cy8c95x0_gpio_direction_output(struct gpio_chip *gc, +@@ -574,7 +574,7 @@ static int cy8c95x0_gpio_direction_outpu + if (ret) + return ret; + +- return pinctrl_gpio_direction_output(gc->base + off); ++ return pinctrl_gpio_direction_output(gc, off); + } + + static int cy8c95x0_gpio_get_value(struct gpio_chip *gc, unsigned int off) +--- a/drivers/pinctrl/pinctrl-ingenic.c ++++ b/drivers/pinctrl/pinctrl-ingenic.c +@@ -133,6 +133,8 @@ struct ingenic_pinctrl { + struct pinctrl_pin_desc *pdesc; + + const struct ingenic_chip_info *info; ++ ++ struct gpio_chip *gc; + }; + + struct ingenic_gpio_chip { +@@ -3561,14 +3563,14 @@ static int ingenic_gpio_get(struct gpio_ + static int ingenic_gpio_direction_input(struct gpio_chip *gc, + unsigned int offset) + { +- return pinctrl_gpio_direction_input(gc->base + offset); ++ return pinctrl_gpio_direction_input(gc, offset); + } + + static int ingenic_gpio_direction_output(struct gpio_chip *gc, + unsigned int offset, int value) + { + ingenic_gpio_set(gc, offset, value); +- return pinctrl_gpio_direction_output(gc->base + offset); ++ return pinctrl_gpio_direction_output(gc, offset); + } + + static inline void ingenic_config_pin(struct ingenic_pinctrl *jzpc, +@@ -4052,7 +4054,8 @@ static int ingenic_pinconf_set(struct pi + break; + + case PIN_CONFIG_OUTPUT: +- ret = pinctrl_gpio_direction_output(pin); ++ ret = pinctrl_gpio_direction_output(jzpc->gc, ++ pin - jzpc->gc->base); + if (ret) + return ret; + +@@ -4172,6 +4175,8 @@ static int __init ingenic_gpio_probe(str + if (!jzgc) + return -ENOMEM; + ++ jzpc->gc = &jzgc->gc; ++ + jzgc->jzpc = jzpc; + jzgc->reg_base = bank * jzpc->info->reg_offset; + +--- a/drivers/pinctrl/pinctrl-ocelot.c ++++ b/drivers/pinctrl/pinctrl-ocelot.c +@@ -1779,7 +1779,7 @@ static int ocelot_gpio_get_direction(str + static int ocelot_gpio_direction_input(struct gpio_chip *chip, + unsigned int offset) + { +- return pinctrl_gpio_direction_input(chip->base + offset); ++ return pinctrl_gpio_direction_input(chip, offset); + } + + static int ocelot_gpio_direction_output(struct gpio_chip *chip, +@@ -1795,7 +1795,7 @@ static int ocelot_gpio_direction_output( + regmap_write(info->map, REG(OCELOT_GPIO_OUT_CLR, info, offset), + pin); + +- return pinctrl_gpio_direction_output(chip->base + offset); ++ return pinctrl_gpio_direction_output(chip, offset); + } + + static const struct gpio_chip ocelot_gpiolib_chip = { +--- a/drivers/pinctrl/pinctrl-rk805.c ++++ b/drivers/pinctrl/pinctrl-rk805.c +@@ -289,14 +289,14 @@ static void rk805_gpio_set(struct gpio_c + static int rk805_gpio_direction_input(struct gpio_chip *chip, + unsigned int offset) + { +- return pinctrl_gpio_direction_input(chip->base + offset); ++ return pinctrl_gpio_direction_input(chip, offset); + } + + static int rk805_gpio_direction_output(struct gpio_chip *chip, + unsigned int offset, int value) + { + rk805_gpio_set(chip, offset, value); +- return pinctrl_gpio_direction_output(chip->base + offset); ++ return pinctrl_gpio_direction_output(chip, offset); + } + + static int rk805_gpio_get_direction(struct gpio_chip *chip, unsigned int offset) +--- a/drivers/pinctrl/pinctrl-st.c ++++ b/drivers/pinctrl/pinctrl-st.c +@@ -719,7 +719,7 @@ static void st_gpio_set(struct gpio_chip + + static int st_gpio_direction_input(struct gpio_chip *chip, unsigned offset) + { +- pinctrl_gpio_direction_input(chip->base + offset); ++ pinctrl_gpio_direction_input(chip, offset); + + return 0; + } +@@ -730,7 +730,7 @@ static int st_gpio_direction_output(stru + struct st_gpio_bank *bank = gpiochip_get_data(chip); + + __st_gpio_set(bank, offset, value); +- pinctrl_gpio_direction_output(chip->base + offset); ++ pinctrl_gpio_direction_output(chip, offset); + + return 0; + } +--- a/drivers/pinctrl/renesas/gpio.c ++++ b/drivers/pinctrl/renesas/gpio.c +@@ -164,7 +164,7 @@ static void gpio_pin_set_value(struct sh + + static int gpio_pin_direction_input(struct gpio_chip *gc, unsigned offset) + { +- return pinctrl_gpio_direction_input(gc->base + offset); ++ return pinctrl_gpio_direction_input(gc, offset); + } + + static int gpio_pin_direction_output(struct gpio_chip *gc, unsigned offset, +@@ -172,7 +172,7 @@ static int gpio_pin_direction_output(str + { + gpio_pin_set_value(gpiochip_get_data(gc), offset, value); + +- return pinctrl_gpio_direction_output(gc->base + offset); ++ return pinctrl_gpio_direction_output(gc, offset); + } + + static int gpio_pin_get(struct gpio_chip *gc, unsigned offset) +--- a/drivers/pinctrl/stm32/pinctrl-stm32.c ++++ b/drivers/pinctrl/stm32/pinctrl-stm32.c +@@ -241,7 +241,7 @@ static void stm32_gpio_set(struct gpio_c + + static int stm32_gpio_direction_input(struct gpio_chip *chip, unsigned offset) + { +- return pinctrl_gpio_direction_input(chip->base + offset); ++ return pinctrl_gpio_direction_input(chip, offset); + } + + static int stm32_gpio_direction_output(struct gpio_chip *chip, +@@ -250,7 +250,7 @@ static int stm32_gpio_direction_output(s + struct stm32_gpio_bank *bank = gpiochip_get_data(chip); + + __stm32_gpio_set(bank, offset, value); +- pinctrl_gpio_direction_output(chip->base + offset); ++ pinctrl_gpio_direction_output(chip, offset); + + return 0; + } +--- a/drivers/pinctrl/vt8500/pinctrl-wmt.c ++++ b/drivers/pinctrl/vt8500/pinctrl-wmt.c +@@ -528,14 +528,14 @@ static void wmt_gpio_set_value(struct gp + + static int wmt_gpio_direction_input(struct gpio_chip *chip, unsigned offset) + { +- return pinctrl_gpio_direction_input(chip->base + offset); ++ return pinctrl_gpio_direction_input(chip, offset); + } + + static int wmt_gpio_direction_output(struct gpio_chip *chip, unsigned offset, + int value) + { + wmt_gpio_set_value(chip, offset, value); +- return pinctrl_gpio_direction_output(chip->base + offset); ++ return pinctrl_gpio_direction_output(chip, offset); + } + + static const struct gpio_chip wmt_gpio_chip = { +--- a/include/linux/pinctrl/consumer.h ++++ b/include/linux/pinctrl/consumer.h +@@ -17,6 +17,7 @@ + #include + + struct device; ++struct gpio_chip; + + /* This struct is private to the core and should be regarded as a cookie */ + struct pinctrl; +@@ -28,8 +29,10 @@ struct pinctrl_state; + extern bool pinctrl_gpio_can_use_line(unsigned gpio); + extern int pinctrl_gpio_request(unsigned gpio); + extern void pinctrl_gpio_free(unsigned gpio); +-extern int pinctrl_gpio_direction_input(unsigned gpio); +-extern int pinctrl_gpio_direction_output(unsigned gpio); ++extern int pinctrl_gpio_direction_input(struct gpio_chip *gc, ++ unsigned int offset); ++extern int pinctrl_gpio_direction_output(struct gpio_chip *gc, ++ unsigned int offset); + extern int pinctrl_gpio_set_config(unsigned gpio, unsigned long config); + + extern struct pinctrl * __must_check pinctrl_get(struct device *dev); +@@ -77,12 +80,14 @@ static inline void pinctrl_gpio_free(uns + { + } + +-static inline int pinctrl_gpio_direction_input(unsigned gpio) ++static inline int ++pinctrl_gpio_direction_input(struct gpio_chip *gc, unsigned int offset) + { + return 0; + } + +-static inline int pinctrl_gpio_direction_output(unsigned gpio) ++static inline int ++pinctrl_gpio_direction_output(struct gpio_chip *gc, unsigned int offset) + { + return 0; + } diff --git a/queue-6.6/platform-x86-dell-laptop-fix-missing-cleanups-in-init-error-path.patch b/queue-6.6/platform-x86-dell-laptop-fix-missing-cleanups-in-init-error-path.patch new file mode 100644 index 0000000000..039f5acf6e --- /dev/null +++ b/queue-6.6/platform-x86-dell-laptop-fix-missing-cleanups-in-init-error-path.patch @@ -0,0 +1,52 @@ +From stable+bounces-289363-greg=kroah.com@vger.kernel.org Sun Jul 26 23:28:44 2026 +From: Sasha Levin +Date: Sun, 26 Jul 2026 17:27:30 -0400 +Subject: platform/x86: dell-laptop: fix missing cleanups in init error path +To: stable@vger.kernel.org +Cc: "Haoxiang Li" , "Ilpo Järvinen" , "Sasha Levin" +Message-ID: <20260726212730.4016280-2-sashal@kernel.org> + +From: Haoxiang Li + +[ Upstream commit 6e9cab2247e5b243ae2d907ce7c948a8a9c8d61a ] + +dell_init() initializes several resources after dell_setup_rfkill(), +including the optional touchpad LED, keyboard backlight LED, battery +hook, debugfs directory and dell-laptop notifier. + +If a later LED or backlight registration fails, the error path only +tears down the battery hook and rfkill resources. This leaves the +notifier, debugfs directory, keyboard backlight LED and optional +touchpad LED registered after dell_init() returns an error. + +Add the missing cleanup calls before tearing down rfkill. + +Fixes: 9c656b07997f ("platform/x86: dell-*: Call new led hw_changed API on kbd brightness change") +Fixes: 037accfa14b2 ("dell-laptop: Add debugfs support") +Fixes: 2d8b90be4f1c ("dell-laptop: support Synaptics/Alps touchpad led") +Fixes: 6cff8d60aa0a ("platform: x86: dell-laptop: Add support for keyboard backlight") +Cc: stable@vger.kernel.org +Signed-off-by: Haoxiang Li +Link: https://patch.msgid.link/20260609081419.1995169-1-lihaoxiang@isrc.iscas.ac.cn +Reviewed-by: Ilpo Järvinen +Signed-off-by: Ilpo Järvinen +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/platform/x86/dell/dell-laptop.c | 5 +++++ + 1 file changed, 5 insertions(+) + +--- a/drivers/platform/x86/dell/dell-laptop.c ++++ b/drivers/platform/x86/dell/dell-laptop.c +@@ -2295,6 +2295,11 @@ fail_backlight: + if (mute_led_registered) + led_classdev_unregister(&mute_led_cdev); + fail_led: ++ dell_laptop_unregister_notifier(&dell_laptop_notifier); ++ debugfs_remove_recursive(dell_laptop_dir); ++ kbd_led_exit(); ++ if (quirks && quirks->touchpad_led) ++ touchpad_led_exit(); + dell_cleanup_rfkill(); + fail_rfkill: + platform_device_del(platform_device); diff --git a/queue-6.6/platform-x86-dell-smbios-move-request-functions-for-reuse.patch b/queue-6.6/platform-x86-dell-smbios-move-request-functions-for-reuse.patch new file mode 100644 index 0000000000..d1d1c67460 --- /dev/null +++ b/queue-6.6/platform-x86-dell-smbios-move-request-functions-for-reuse.patch @@ -0,0 +1,108 @@ +From stable+bounces-289362-greg=kroah.com@vger.kernel.org Sun Jul 26 23:28:48 2026 +From: Sasha Levin +Date: Sun, 26 Jul 2026 17:27:29 -0400 +Subject: platform/x86: dell-smbios: Move request functions for reuse +To: stable@vger.kernel.org +Cc: "Lyndon Sanche" , "Ilpo Järvinen" , "Sasha Levin" +Message-ID: <20260726212730.4016280-1-sashal@kernel.org> + +From: Lyndon Sanche + +[ Upstream commit 33245680ae565aaa114d726bf407c8ff38e75e71 ] + +Move the dell_send_request and dell_fill_request symbols into smbios-base +so that they can be used in multiple modules. + +Signed-off-by: Lyndon Sanche +Link: https://lore.kernel.org/r/20240529174843.13226-3-lsanche@lyndeno.ca +Reviewed-by: Ilpo Järvinen +Signed-off-by: Ilpo Järvinen +Stable-dep-of: 6e9cab2247e5 ("platform/x86: dell-laptop: fix missing cleanups in init error path") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/platform/x86/dell/dell-laptop.c | 23 ----------------------- + drivers/platform/x86/dell/dell-smbios-base.c | 25 +++++++++++++++++++++++++ + drivers/platform/x86/dell/dell-smbios.h | 5 +++++ + 3 files changed, 30 insertions(+), 23 deletions(-) + +--- a/drivers/platform/x86/dell/dell-laptop.c ++++ b/drivers/platform/x86/dell/dell-laptop.c +@@ -353,29 +353,6 @@ static const struct dmi_system_id dell_q + { } + }; + +-static void dell_fill_request(struct calling_interface_buffer *buffer, +- u32 arg0, u32 arg1, u32 arg2, u32 arg3) +-{ +- memset(buffer, 0, sizeof(struct calling_interface_buffer)); +- buffer->input[0] = arg0; +- buffer->input[1] = arg1; +- buffer->input[2] = arg2; +- buffer->input[3] = arg3; +-} +- +-static int dell_send_request(struct calling_interface_buffer *buffer, +- u16 class, u16 select) +-{ +- int ret; +- +- buffer->cmd_class = class; +- buffer->cmd_select = select; +- ret = dell_smbios_call(buffer); +- if (ret != 0) +- return ret; +- return dell_smbios_error(buffer->output[0]); +-} +- + /* + * Derived from information in smbios-wireless-ctl: + * +--- a/drivers/platform/x86/dell/dell-smbios-base.c ++++ b/drivers/platform/x86/dell/dell-smbios-base.c +@@ -320,6 +320,31 @@ out_smbios_call: + } + EXPORT_SYMBOL_GPL(dell_smbios_call); + ++void dell_fill_request(struct calling_interface_buffer *buffer, ++ u32 arg0, u32 arg1, u32 arg2, u32 arg3) ++{ ++ memset(buffer, 0, sizeof(struct calling_interface_buffer)); ++ buffer->input[0] = arg0; ++ buffer->input[1] = arg1; ++ buffer->input[2] = arg2; ++ buffer->input[3] = arg3; ++} ++EXPORT_SYMBOL_GPL(dell_fill_request); ++ ++int dell_send_request(struct calling_interface_buffer *buffer, ++ u16 class, u16 select) ++{ ++ int ret; ++ ++ buffer->cmd_class = class; ++ buffer->cmd_select = select; ++ ret = dell_smbios_call(buffer); ++ if (ret != 0) ++ return ret; ++ return dell_smbios_error(buffer->output[0]); ++} ++EXPORT_SYMBOL_GPL(dell_send_request); ++ + struct calling_interface_token *dell_smbios_find_token(int tokenid) + { + int i; +--- a/drivers/platform/x86/dell/dell-smbios.h ++++ b/drivers/platform/x86/dell/dell-smbios.h +@@ -64,6 +64,11 @@ int dell_smbios_call_filter(struct devic + struct calling_interface_buffer *buffer); + int dell_smbios_call(struct calling_interface_buffer *buffer); + ++void dell_fill_request(struct calling_interface_buffer *buffer, ++ u32 arg0, u32 arg1, u32 arg2, u32 arg3); ++int dell_send_request(struct calling_interface_buffer *buffer, ++ u16 class, u16 select); ++ + struct calling_interface_token *dell_smbios_find_token(int tokenid); + + enum dell_laptop_notifier_actions { diff --git a/queue-6.6/rxrpc-don-t-need-barrier-for-tx_bottom-and-acks_hard_ack.patch b/queue-6.6/rxrpc-don-t-need-barrier-for-tx_bottom-and-acks_hard_ack.patch new file mode 100644 index 0000000000..f799febb5d --- /dev/null +++ b/queue-6.6/rxrpc-don-t-need-barrier-for-tx_bottom-and-acks_hard_ack.patch @@ -0,0 +1,82 @@ +From stable+bounces-288015-greg=kroah.com@vger.kernel.org Wed Jul 22 19:36:30 2026 +From: Sasha Levin +Date: Wed, 22 Jul 2026 13:30:03 -0400 +Subject: rxrpc: Don't need barrier for ->tx_bottom and ->acks_hard_ack +To: stable@vger.kernel.org +Cc: David Howells , Marc Dionne , linux-afs@lists.infradead.org, Jakub Kicinski , Sasha Levin +Message-ID: <20260722173006.2069029-1-sashal@kernel.org> + +From: David Howells + +[ Upstream commit 6396b48ac0a77165f9c2c40ab03d6c8188c89739 ] + +We don't need a barrier for the ->tx_bottom value (which indicates the +lowest sequence still in the transmission queue) and the ->acks_hard_ack +value (which tracks the DATA packets hard-ack'd by the latest ACK packet +received and thus indicates which DATA packets can now be discarded) as the +app thread doesn't use either value as a reference to memory to access. +Rather, the app thread merely uses these as a guide to how much space is +available in the transmission queue + +Change the code to use READ/WRITE_ONCE() instead. + +Also, change rxrpc_check_tx_space() to use the same value for tx_bottom +throughout. + +Signed-off-by: David Howells +cc: Marc Dionne +cc: linux-afs@lists.infradead.org +Link: https://patch.msgid.link/20241204074710.990092-18-dhowells@redhat.com +Signed-off-by: Jakub Kicinski +Stable-dep-of: e66f8f32f501 ("rxrpc: Fix socket notification race") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + net/rxrpc/sendmsg.c | 8 +++++--- + net/rxrpc/txbuf.c | 4 ++-- + 2 files changed, 7 insertions(+), 5 deletions(-) + +--- a/net/rxrpc/sendmsg.c ++++ b/net/rxrpc/sendmsg.c +@@ -93,9 +93,11 @@ no_wait: + */ + static bool rxrpc_check_tx_space(struct rxrpc_call *call, rxrpc_seq_t *_tx_win) + { ++ rxrpc_seq_t tx_bottom = READ_ONCE(call->tx_bottom); ++ + if (_tx_win) +- *_tx_win = call->tx_bottom; +- return call->tx_prepared - call->tx_bottom < 256; ++ *_tx_win = tx_bottom; ++ return call->tx_prepared - tx_bottom < 256; + } + + /* +@@ -137,7 +139,7 @@ static int rxrpc_wait_for_tx_window_wait + rtt = 2; + + timeout = rtt; +- tx_start = smp_load_acquire(&call->acks_hard_ack); ++ tx_start = READ_ONCE(call->acks_hard_ack); + + for (;;) { + set_current_state(TASK_UNINTERRUPTIBLE); +--- a/net/rxrpc/txbuf.c ++++ b/net/rxrpc/txbuf.c +@@ -112,14 +112,14 @@ void rxrpc_shrink_call_tx_buffer(struct + + while ((txb = list_first_entry_or_null(&call->tx_buffer, + struct rxrpc_txbuf, call_link))) { +- hard_ack = smp_load_acquire(&call->acks_hard_ack); ++ hard_ack = call->acks_hard_ack; + if (before(hard_ack, txb->seq)) + break; + + if (txb->seq != call->tx_bottom + 1) + rxrpc_see_txbuf(txb, rxrpc_txbuf_see_out_of_step); + ASSERTCMP(txb->seq, ==, call->tx_bottom + 1); +- smp_store_release(&call->tx_bottom, call->tx_bottom + 1); ++ WRITE_ONCE(call->tx_bottom, call->tx_bottom + 1); + list_del_rcu(&txb->call_link); + + trace_rxrpc_txqueue(call, rxrpc_txqueue_dequeue); diff --git a/queue-6.6/rxrpc-fix-notification-vs-call-release-vs-recvmsg.patch b/queue-6.6/rxrpc-fix-notification-vs-call-release-vs-recvmsg.patch new file mode 100644 index 0000000000..6c5b24e050 --- /dev/null +++ b/queue-6.6/rxrpc-fix-notification-vs-call-release-vs-recvmsg.patch @@ -0,0 +1,138 @@ +From stable+bounces-288017-greg=kroah.com@vger.kernel.org Wed Jul 22 19:45:32 2026 +From: Sasha Levin +Date: Wed, 22 Jul 2026 13:30:05 -0400 +Subject: rxrpc: Fix notification vs call-release vs recvmsg +To: stable@vger.kernel.org +Cc: David Howells , Jeffrey Altman , Marc Dionne , "Junvyyang, Tencent Zhuque Lab" , LePremierHomme , Simon Horman , linux-afs@lists.infradead.org, Jakub Kicinski , Sasha Levin +Message-ID: <20260722173006.2069029-3-sashal@kernel.org> + +From: David Howells + +[ Upstream commit 2fd895842d49c23137ae48252dd211e5d6d8a3ed ] + +When a call is released, rxrpc takes the spinlock and removes it from +->recvmsg_q in an effort to prevent racing recvmsg() invocations from +seeing the same call. Now, rxrpc_recvmsg() only takes the spinlock when +actually removing a call from the queue; it doesn't, however, take it in +the lead up to that when it checks to see if the queue is empty. It *does* +hold the socket lock, which prevents a recvmsg/recvmsg race - but this +doesn't prevent sendmsg from ending the call because sendmsg() drops the +socket lock and relies on the call->user_mutex. + +Fix this by firstly removing the bit in rxrpc_release_call() that dequeues +the released call and, instead, rely on recvmsg() to simply discard +released calls (done in a preceding fix). + +Secondly, rxrpc_notify_socket() is abandoned if the call is already marked +as released rather than trying to be clever by setting both pointers in +call->recvmsg_link to NULL to trick list_empty(). This isn't perfect and +can still race, resulting in a released call on the queue, but recvmsg() +will now clean that up. + +Fixes: 17926a79320a ("[AF_RXRPC]: Provide secure RxRPC sockets for use by userspace and kernel both") +Signed-off-by: David Howells +Reviewed-by: Jeffrey Altman +cc: Marc Dionne +cc: Junvyyang, Tencent Zhuque Lab +cc: LePremierHomme +cc: Simon Horman +cc: linux-afs@lists.infradead.org +Link: https://patch.msgid.link/20250717074350.3767366-4-dhowells@redhat.com +Signed-off-by: Jakub Kicinski +Stable-dep-of: e66f8f32f501 ("rxrpc: Fix socket notification race") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + include/trace/events/rxrpc.h | 3 ++- + net/rxrpc/call_object.c | 28 ++++++++++++---------------- + net/rxrpc/recvmsg.c | 4 ++++ + 3 files changed, 18 insertions(+), 17 deletions(-) + +--- a/include/trace/events/rxrpc.h ++++ b/include/trace/events/rxrpc.h +@@ -271,10 +271,10 @@ + EM(rxrpc_call_put_poke, "PUT poke ") \ + EM(rxrpc_call_put_recvmsg, "PUT recvmsg ") \ + EM(rxrpc_call_put_recvmsg_peek_nowait, "PUT peek-nwt") \ ++ EM(rxrpc_call_put_release_recvmsg_q, "PUT rls-rcmq") \ + EM(rxrpc_call_put_release_sock, "PUT rls-sock") \ + EM(rxrpc_call_put_release_sock_tba, "PUT rls-sk-a") \ + EM(rxrpc_call_put_sendmsg, "PUT sendmsg ") \ +- EM(rxrpc_call_put_unnotify, "PUT unnotify") \ + EM(rxrpc_call_put_userid_exists, "PUT u-exists") \ + EM(rxrpc_call_put_userid, "PUT user-id ") \ + EM(rxrpc_call_see_accept, "SEE accept ") \ +@@ -287,6 +287,7 @@ + EM(rxrpc_call_see_disconnected, "SEE disconn ") \ + EM(rxrpc_call_see_distribute_error, "SEE dist-err") \ + EM(rxrpc_call_see_input, "SEE input ") \ ++ EM(rxrpc_call_see_notify_released, "SEE nfy-rlsd") \ + EM(rxrpc_call_see_recvmsg, "SEE recvmsg ") \ + EM(rxrpc_call_see_recvmsg_requeue, "SEE recv-rqu") \ + EM(rxrpc_call_see_recvmsg_requeue_first, "SEE recv-rqF") \ +--- a/net/rxrpc/call_object.c ++++ b/net/rxrpc/call_object.c +@@ -550,7 +550,7 @@ static void rxrpc_cleanup_ring(struct rx + void rxrpc_release_call(struct rxrpc_sock *rx, struct rxrpc_call *call) + { + struct rxrpc_connection *conn = call->conn; +- bool put = false, putu = false; ++ bool putu = false; + + _enter("{%d,%d}", call->debug_id, refcount_read(&call->ref)); + +@@ -562,23 +562,13 @@ void rxrpc_release_call(struct rxrpc_soc + + rxrpc_put_call_slot(call); + +- /* Make sure we don't get any more notifications */ ++ /* Note that at this point, the call may still be on or may have been ++ * added back on to the socket receive queue. recvmsg() must discard ++ * released calls. The CALL_RELEASED flag should prevent further ++ * notifications. ++ */ + spin_lock_irq(&rx->recvmsg_lock); +- +- if (!list_empty(&call->recvmsg_link)) { +- _debug("unlinking once-pending call %p { e=%lx f=%lx }", +- call, call->events, call->flags); +- list_del(&call->recvmsg_link); +- put = true; +- } +- +- /* list_empty() must return false in rxrpc_notify_socket() */ +- call->recvmsg_link.next = NULL; +- call->recvmsg_link.prev = NULL; +- + spin_unlock_irq(&rx->recvmsg_lock); +- if (put) +- rxrpc_put_call(call, rxrpc_call_put_unnotify); + + write_lock(&rx->call_lock); + +@@ -627,6 +617,12 @@ void rxrpc_release_calls_on_socket(struc + rxrpc_put_call(call, rxrpc_call_put_release_sock); + } + ++ while ((call = list_first_entry_or_null(&rx->recvmsg_q, ++ struct rxrpc_call, recvmsg_link))) { ++ list_del_init(&call->recvmsg_link); ++ rxrpc_put_call(call, rxrpc_call_put_release_recvmsg_q); ++ } ++ + _leave(""); + } + +--- a/net/rxrpc/recvmsg.c ++++ b/net/rxrpc/recvmsg.c +@@ -29,6 +29,10 @@ void rxrpc_notify_socket(struct rxrpc_ca + + if (!list_empty(&call->recvmsg_link)) + return; ++ if (test_bit(RXRPC_CALL_RELEASED, &call->flags)) { ++ rxrpc_see_call(call, rxrpc_call_see_notify_released); ++ return; ++ } + + rcu_read_lock(); + diff --git a/queue-6.6/rxrpc-fix-socket-notification-race.patch b/queue-6.6/rxrpc-fix-socket-notification-race.patch new file mode 100644 index 0000000000..39ddfe282f --- /dev/null +++ b/queue-6.6/rxrpc-fix-socket-notification-race.patch @@ -0,0 +1,47 @@ +From stable+bounces-288018-greg=kroah.com@vger.kernel.org Wed Jul 22 19:36:38 2026 +From: Sasha Levin +Date: Wed, 22 Jul 2026 13:30:06 -0400 +Subject: rxrpc: Fix socket notification race +To: stable@vger.kernel.org +Cc: David Howells , Marc Dionne , Jeffrey Altman , Simon Horman , linux-afs@lists.infradead.org, stable@kernel.org, Jakub Kicinski , Sasha Levin +Message-ID: <20260722173006.2069029-4-sashal@kernel.org> + +From: David Howells + +[ Upstream commit e66f8f32f50116670dbbee5bc9e692cd2cd0c8f8 ] + +There's a race between rxrpc_recvmsg() and rxrpc_notify_socket(), whereby +the latter's attempt to avoid disabling interrupts and taking the socket's +recvmsg_lock if the call is already queued may happen simultaneously with +the former's discarding of a call that has nothing queued. + +Fix this by removing the shortcut. Note that this only affects userspace's +use of AF_RXRPC; the AFS filesystem driver doesn't use the socket queue. + +Fixes: 248f219cb8bc ("rxrpc: Rewrite the data and ack handling code") +Link: https://sashiko.dev/#/patchset/20260616155749.2125907-1-dhowells%40redhat.com +Signed-off-by: David Howells +cc: Marc Dionne +cc: Jeffrey Altman +cc: Simon Horman +cc: linux-afs@lists.infradead.org +cc: stable@kernel.org +Link: https://patch.msgid.link/20260624163819.3017002-10-dhowells@redhat.com +Signed-off-by: Jakub Kicinski +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + net/rxrpc/recvmsg.c | 2 -- + 1 file changed, 2 deletions(-) + +--- a/net/rxrpc/recvmsg.c ++++ b/net/rxrpc/recvmsg.c +@@ -27,8 +27,6 @@ void rxrpc_notify_socket(struct rxrpc_ca + + _enter("%d", call->debug_id); + +- if (!list_empty(&call->recvmsg_link)) +- return; + if (test_bit(RXRPC_CALL_RELEASED, &call->flags)) { + rxrpc_see_call(call, rxrpc_call_see_notify_released); + return; diff --git a/queue-6.6/rxrpc-pull-out-certain-app-callback-funcs-into-an-ops-table.patch b/queue-6.6/rxrpc-pull-out-certain-app-callback-funcs-into-an-ops-table.patch new file mode 100644 index 0000000000..9695f33f61 --- /dev/null +++ b/queue-6.6/rxrpc-pull-out-certain-app-callback-funcs-into-an-ops-table.patch @@ -0,0 +1,294 @@ +From stable+bounces-287898-greg=kroah.com@vger.kernel.org Wed Jul 22 16:51:03 2026 +From: Sasha Levin +Date: Wed, 22 Jul 2026 10:48:35 -0400 +Subject: rxrpc: Pull out certain app callback funcs into an ops table +To: stable@vger.kernel.org +Cc: David Howells , Marc Dionne , Simon Horman , linux-afs@lists.infradead.org, Jakub Kicinski , Sasha Levin +Message-ID: <20260722144836.1601242-3-sashal@kernel.org> + +From: David Howells + +[ Upstream commit 23738cc8048322cf324f330cd697380fb3455da5 ] + +A number of functions separately furnish an AF_RXRPC socket with callback +function pointers into a kernel app (such as the AFS filesystem) that is +using it. Replace most of these with an ops table for the entire socket. +This makes it easier to add more callback functions. + +Note that the call incoming data processing callback is retaind as that +gets set to different things, depending on the type of op. + +Signed-off-by: David Howells +cc: Marc Dionne +cc: Simon Horman +cc: linux-afs@lists.infradead.org +Link: https://patch.msgid.link/20250411095303.2316168-3-dhowells@redhat.com +Signed-off-by: Jakub Kicinski +Stable-dep-of: dc175389b18c ("rxrpc: serialize kernel accept preallocation with socket teardown") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + fs/afs/rxrpc.c | 11 ++++++++--- + include/net/af_rxrpc.h | 25 +++++++++++++++---------- + net/rxrpc/af_rxrpc.c | 20 ++++++++------------ + net/rxrpc/ar-internal.h | 3 +-- + net/rxrpc/call_accept.c | 34 ++++++++++++++++------------------ + net/rxrpc/rxperf.c | 10 +++++++--- + 6 files changed, 55 insertions(+), 48 deletions(-) + +--- a/fs/afs/rxrpc.c ++++ b/fs/afs/rxrpc.c +@@ -23,8 +23,15 @@ static void afs_wake_up_async_call(struc + static void afs_process_async_call(struct work_struct *); + static void afs_rx_new_call(struct sock *, struct rxrpc_call *, unsigned long); + static void afs_rx_discard_new_call(struct rxrpc_call *, unsigned long); ++static void afs_rx_attach(struct rxrpc_call *rxcall, unsigned long user_call_ID); + static int afs_deliver_cm_op_id(struct afs_call *); + ++static const struct rxrpc_kernel_ops afs_rxrpc_callback_ops = { ++ .notify_new_call = afs_rx_new_call, ++ .discard_new_call = afs_rx_discard_new_call, ++ .user_attach_call = afs_rx_attach, ++}; ++ + /* asynchronous incoming call initial processing */ + static const struct afs_call_type afs_RXCMxxxx = { + .name = "CB.xxxx", +@@ -83,8 +90,7 @@ int afs_open_socket(struct afs_net *net) + * it sends back to us. + */ + +- rxrpc_kernel_new_call_notification(socket, afs_rx_new_call, +- afs_rx_discard_new_call); ++ rxrpc_kernel_set_notifications(socket, &afs_rxrpc_callback_ops); + + ret = kernel_listen(socket, INT_MAX); + if (ret < 0) +@@ -748,7 +754,6 @@ void afs_charge_preallocation(struct wor + + if (rxrpc_kernel_charge_accept(net->socket, + afs_wake_up_async_call, +- afs_rx_attach, + (unsigned long)call, + GFP_KERNEL, + call->debug_id) < 0) +--- a/include/net/af_rxrpc.h ++++ b/include/net/af_rxrpc.h +@@ -28,18 +28,23 @@ enum rxrpc_interruptibility { + */ + extern atomic_t rxrpc_debug_id; + ++/* ++ * Operations table for rxrpc to call out to a kernel application (e.g. kAFS). ++ */ ++struct rxrpc_kernel_ops { ++ void (*notify_new_call)(struct sock *sk, struct rxrpc_call *call, ++ unsigned long user_call_ID); ++ void (*discard_new_call)(struct rxrpc_call *call, unsigned long user_call_ID); ++ void (*user_attach_call)(struct rxrpc_call *call, unsigned long user_call_ID); ++}; ++ + typedef void (*rxrpc_notify_rx_t)(struct sock *, struct rxrpc_call *, + unsigned long); + typedef void (*rxrpc_notify_end_tx_t)(struct sock *, struct rxrpc_call *, + unsigned long); +-typedef void (*rxrpc_notify_new_call_t)(struct sock *, struct rxrpc_call *, +- unsigned long); +-typedef void (*rxrpc_discard_new_call_t)(struct rxrpc_call *, unsigned long); +-typedef void (*rxrpc_user_attach_call_t)(struct rxrpc_call *, unsigned long); + +-void rxrpc_kernel_new_call_notification(struct socket *, +- rxrpc_notify_new_call_t, +- rxrpc_discard_new_call_t); ++void rxrpc_kernel_set_notifications(struct socket *sock, ++ const struct rxrpc_kernel_ops *app_ops); + struct rxrpc_call *rxrpc_kernel_begin_call(struct socket *sock, + struct sockaddr_rxrpc *srx, + struct key *key, +@@ -63,9 +68,9 @@ void rxrpc_kernel_put_call(struct socket + void rxrpc_kernel_get_peer(struct socket *, struct rxrpc_call *, + struct sockaddr_rxrpc *); + bool rxrpc_kernel_get_srtt(struct socket *, struct rxrpc_call *, u32 *); +-int rxrpc_kernel_charge_accept(struct socket *, rxrpc_notify_rx_t, +- rxrpc_user_attach_call_t, unsigned long, gfp_t, +- unsigned int); ++int rxrpc_kernel_charge_accept(struct socket *sock, rxrpc_notify_rx_t notify_rx, ++ unsigned long user_call_ID, gfp_t gfp, ++ unsigned int debug_id); + void rxrpc_kernel_set_tx_length(struct socket *, struct rxrpc_call *, s64); + bool rxrpc_kernel_check_life(const struct socket *, const struct rxrpc_call *); + u32 rxrpc_kernel_get_epoch(struct socket *, struct rxrpc_call *); +--- a/net/rxrpc/af_rxrpc.c ++++ b/net/rxrpc/af_rxrpc.c +@@ -418,24 +418,20 @@ u32 rxrpc_kernel_get_epoch(struct socket + EXPORT_SYMBOL(rxrpc_kernel_get_epoch); + + /** +- * rxrpc_kernel_new_call_notification - Get notifications of new calls +- * @sock: The socket to intercept received messages on +- * @notify_new_call: Function to be called when new calls appear +- * @discard_new_call: Function to discard preallocated calls ++ * rxrpc_kernel_set_notifications - Set table of callback operations ++ * @sock: The socket to install table upon ++ * @app_ops: Callback operation table to set + * +- * Allow a kernel service to be given notifications about new calls. ++ * Allow a kernel service to set a table of event notifications on a socket. + */ +-void rxrpc_kernel_new_call_notification( +- struct socket *sock, +- rxrpc_notify_new_call_t notify_new_call, +- rxrpc_discard_new_call_t discard_new_call) ++void rxrpc_kernel_set_notifications(struct socket *sock, ++ const struct rxrpc_kernel_ops *app_ops) + { + struct rxrpc_sock *rx = rxrpc_sk(sock->sk); + +- rx->notify_new_call = notify_new_call; +- rx->discard_new_call = discard_new_call; ++ rx->app_ops = app_ops; + } +-EXPORT_SYMBOL(rxrpc_kernel_new_call_notification); ++EXPORT_SYMBOL(rxrpc_kernel_set_notifications); + + /** + * rxrpc_kernel_set_max_life - Set maximum lifespan on a call +--- a/net/rxrpc/ar-internal.h ++++ b/net/rxrpc/ar-internal.h +@@ -141,8 +141,7 @@ struct rxrpc_backlog { + struct rxrpc_sock { + /* WARNING: sk has to be the first member */ + struct sock sk; +- rxrpc_notify_new_call_t notify_new_call; /* Func to notify of new call */ +- rxrpc_discard_new_call_t discard_new_call; /* Func to discard a new call */ ++ const struct rxrpc_kernel_ops *app_ops; /* Table of kernel app notification funcs */ + struct rxrpc_local *local; /* local endpoint */ + struct rxrpc_backlog *backlog; /* Preallocation for services */ + spinlock_t incoming_lock; /* Incoming call vs service shutdown lock */ +--- a/net/rxrpc/call_accept.c ++++ b/net/rxrpc/call_accept.c +@@ -34,7 +34,6 @@ static void rxrpc_dummy_notify(struct so + static int rxrpc_service_prealloc_one(struct rxrpc_sock *rx, + struct rxrpc_backlog *b, + rxrpc_notify_rx_t notify_rx, +- rxrpc_user_attach_call_t user_attach_call, + unsigned long user_call_ID, gfp_t gfp, + unsigned int debug_id) + { +@@ -123,9 +122,10 @@ static int rxrpc_service_prealloc_one(st + + call->user_call_ID = user_call_ID; + call->notify_rx = notify_rx; +- if (user_attach_call) { ++ if (rx->app_ops && ++ rx->app_ops->user_attach_call) { + rxrpc_get_call(call, rxrpc_call_get_kernel_service); +- user_attach_call(call, user_call_ID); ++ rx->app_ops->user_attach_call(call, user_call_ID); + } + + rxrpc_get_call(call, rxrpc_call_get_userid); +@@ -221,9 +221,10 @@ void rxrpc_discard_prealloc(struct rxrpc + struct rxrpc_call *call = b->call_backlog[tail]; + rxrpc_see_call(call, rxrpc_call_see_discard); + rcu_assign_pointer(call->socket, rx); +- if (rx->discard_new_call) { ++ if (rx->app_ops && ++ rx->app_ops->discard_new_call) { + _debug("discard %lx", call->user_call_ID); +- rx->discard_new_call(call, call->user_call_ID); ++ rx->app_ops->discard_new_call(call, call->user_call_ID); + if (call->notify_rx) + call->notify_rx = rxrpc_dummy_notify; + rxrpc_put_call(call, rxrpc_call_put_kernel); +@@ -392,8 +393,9 @@ bool rxrpc_new_incoming_call(struct rxrp + rxrpc_incoming_call(rx, call, skb); + conn = call->conn; + +- if (rx->notify_new_call) +- rx->notify_new_call(&rx->sk, call, call->user_call_ID); ++ if (rx->app_ops && ++ rx->app_ops->notify_new_call) ++ rx->app_ops->notify_new_call(&rx->sk, call, call->user_call_ID); + + spin_lock(&conn->state_lock); + if (conn->state == RXRPC_CONN_SERVICE_UNSECURED) { +@@ -445,8 +447,7 @@ int rxrpc_user_charge_accept(struct rxrp + if (rx->sk.sk_state == RXRPC_CLOSE) + return -ESHUTDOWN; + +- return rxrpc_service_prealloc_one(rx, b, NULL, NULL, user_call_ID, +- GFP_KERNEL, ++ return rxrpc_service_prealloc_one(rx, b, NULL, user_call_ID, GFP_KERNEL, + atomic_inc_return(&rxrpc_debug_id)); + } + +@@ -454,20 +455,18 @@ int rxrpc_user_charge_accept(struct rxrp + * rxrpc_kernel_charge_accept - Charge up socket with preallocated calls + * @sock: The socket on which to preallocate + * @notify_rx: Event notification function for the call +- * @user_attach_call: Func to attach call to user_call_ID + * @user_call_ID: The tag to attach to the preallocated call + * @gfp: The allocation conditions. + * @debug_id: The tracing debug ID. + * +- * Charge up the socket with preallocated calls, each with a user ID. A +- * function should be provided to effect the attachment from the user's side. +- * The user is given a ref to hold on the call. ++ * Charge up the socket with preallocated calls, each with a user ID. The ++ * ->user_attach_call() callback function should be provided to effect the ++ * attachment from the user's side. The user is given a ref to hold on the ++ * call. + * + * Note that the call may be come connected before this function returns. + */ +-int rxrpc_kernel_charge_accept(struct socket *sock, +- rxrpc_notify_rx_t notify_rx, +- rxrpc_user_attach_call_t user_attach_call, ++int rxrpc_kernel_charge_accept(struct socket *sock, rxrpc_notify_rx_t notify_rx, + unsigned long user_call_ID, gfp_t gfp, + unsigned int debug_id) + { +@@ -477,8 +476,7 @@ int rxrpc_kernel_charge_accept(struct so + if (sock->sk->sk_state == RXRPC_CLOSE) + return -ESHUTDOWN; + +- return rxrpc_service_prealloc_one(rx, b, notify_rx, +- user_attach_call, user_call_ID, ++ return rxrpc_service_prealloc_one(rx, b, notify_rx, user_call_ID, + gfp, debug_id); + } + EXPORT_SYMBOL(rxrpc_kernel_charge_accept); +--- a/net/rxrpc/rxperf.c ++++ b/net/rxrpc/rxperf.c +@@ -136,6 +136,12 @@ static void rxperf_notify_end_reply_tx(s + RXPERF_CALL_SV_AWAIT_ACK); + } + ++static const struct rxrpc_kernel_ops rxperf_rxrpc_callback_ops = { ++ .notify_new_call = rxperf_rx_new_call, ++ .discard_new_call = rxperf_rx_discard_new_call, ++ .user_attach_call = rxperf_rx_attach, ++}; ++ + /* + * Charge the incoming call preallocation. + */ +@@ -161,7 +167,6 @@ static void rxperf_charge_preallocation( + + if (rxrpc_kernel_charge_accept(rxperf_socket, + rxperf_notify_rx, +- rxperf_rx_attach, + (unsigned long)call, + GFP_KERNEL, + call->debug_id) < 0) +@@ -209,8 +214,7 @@ static int rxperf_open_socket(void) + if (ret < 0) + goto error_2; + +- rxrpc_kernel_new_call_notification(socket, rxperf_rx_new_call, +- rxperf_rx_discard_new_call); ++ rxrpc_kernel_set_notifications(socket, &rxperf_rxrpc_callback_ops); + + ret = kernel_listen(socket, INT_MAX); + if (ret < 0) diff --git a/queue-6.6/rxrpc-serialize-kernel-accept-preallocation-with-socket-teardown.patch b/queue-6.6/rxrpc-serialize-kernel-accept-preallocation-with-socket-teardown.patch new file mode 100644 index 0000000000..d1f55221c1 --- /dev/null +++ b/queue-6.6/rxrpc-serialize-kernel-accept-preallocation-with-socket-teardown.patch @@ -0,0 +1,79 @@ +From stable+bounces-287899-greg=kroah.com@vger.kernel.org Wed Jul 22 16:51:13 2026 +From: Sasha Levin +Date: Wed, 22 Jul 2026 10:48:36 -0400 +Subject: rxrpc: serialize kernel accept preallocation with socket teardown +To: stable@vger.kernel.org +Cc: Li Daming , Yuan Tan , Yifan Wu , Juefei Pu , Xin Liu , Ren Wei , David Howells , Marc Dionne , Jeffrey Altman , Simon Horman , linux-afs@lists.infradead.org, stable@kernel.org, Jakub Kicinski , Sasha Levin +Message-ID: <20260722144836.1601242-4-sashal@kernel.org> + +From: Li Daming + +[ Upstream commit dc175389b18c29a5303ee83169ec653adfae3e17 ] + +rxrpc_kernel_charge_accept() reads rx->backlog without any +socket/backlog synchronization and passes that raw pointer into +rxrpc_service_prealloc_one(). A concurrent rxrpc_discard_prealloc() +sets rx->backlog = NULL and frees the backlog rings, so a kernel +preallocation worker can keep using a freed struct rxrpc_backlog +while updating *_backlog_head/tail and array slots. + +Serialize the state check and backlog lookup with the socket lock, +and reject kernel preallocation once teardown has disabled +listening or discarded the service backlog. + +Fixes: 00e907127e6f ("rxrpc: Preallocate peers, conns and calls for incoming service requests") +Reported-by: Yuan Tan +Reported-by: Yifan Wu +Reported-by: Juefei Pu +Reported-by: Xin Liu +Signed-off-by: Li Daming +Signed-off-by: Ren Wei +Signed-off-by: David Howells +cc: Marc Dionne +cc: Jeffrey Altman +cc: Simon Horman +cc: linux-afs@lists.infradead.org +cc: stable@kernel.org +Link: https://patch.msgid.link/20260609140911.838677-6-dhowells@redhat.com +Signed-off-by: Jakub Kicinski +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + net/rxrpc/call_accept.c | 25 +++++++++++++++++++------ + 1 file changed, 19 insertions(+), 6 deletions(-) + +--- a/net/rxrpc/call_accept.c ++++ b/net/rxrpc/call_accept.c +@@ -470,13 +470,26 @@ int rxrpc_kernel_charge_accept(struct so + unsigned long user_call_ID, gfp_t gfp, + unsigned int debug_id) + { +- struct rxrpc_sock *rx = rxrpc_sk(sock->sk); +- struct rxrpc_backlog *b = rx->backlog; ++ struct rxrpc_backlog *b; ++ struct rxrpc_sock *rx; ++ struct sock *sk; ++ int ret; + +- if (sock->sk->sk_state == RXRPC_CLOSE) +- return -ESHUTDOWN; ++ sk = sock->sk; ++ rx = rxrpc_sk(sk); + +- return rxrpc_service_prealloc_one(rx, b, notify_rx, user_call_ID, +- gfp, debug_id); ++ lock_sock(sk); ++ if (sk->sk_state != RXRPC_SERVER_LISTENING || !rx->backlog) { ++ ret = -ESHUTDOWN; ++ goto out; ++ } ++ ++ b = rx->backlog; ++ ret = rxrpc_service_prealloc_one(rx, b, notify_rx, user_call_ID, ++ gfp, debug_id); ++ ++out: ++ release_sock(sk); ++ return ret; + } + EXPORT_SYMBOL(rxrpc_kernel_charge_accept); diff --git a/queue-6.6/rxrpc-use-irq-disabling-spinlocks-between-app-and-i-o-thread.patch b/queue-6.6/rxrpc-use-irq-disabling-spinlocks-between-app-and-i-o-thread.patch new file mode 100644 index 0000000000..f1271f6e4e --- /dev/null +++ b/queue-6.6/rxrpc-use-irq-disabling-spinlocks-between-app-and-i-o-thread.patch @@ -0,0 +1,447 @@ +From stable+bounces-288016-greg=kroah.com@vger.kernel.org Wed Jul 22 19:36:35 2026 +From: Sasha Levin +Date: Wed, 22 Jul 2026 13:30:04 -0400 +Subject: rxrpc: Use irq-disabling spinlocks between app and I/O thread +To: stable@vger.kernel.org +Cc: David Howells , Marc Dionne , linux-afs@lists.infradead.org, Jakub Kicinski , Sasha Levin +Message-ID: <20260722173006.2069029-2-sashal@kernel.org> + +From: David Howells + +[ Upstream commit a2ea9a9072607c2fd6442bd1ffb4dbdbf882aed7 ] + +Where a spinlock is used by both the application thread and the I/O thread, +use irq-disabling locking so that an interrupt taken on the app thread +doesn't also slow down the I/O thread. + +Signed-off-by: David Howells +cc: Marc Dionne +cc: linux-afs@lists.infradead.org +Signed-off-by: Jakub Kicinski +Stable-dep-of: e66f8f32f501 ("rxrpc: Fix socket notification race") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + net/rxrpc/af_rxrpc.c | 4 ++-- + net/rxrpc/call_accept.c | 20 ++++++++++---------- + net/rxrpc/call_object.c | 14 +++++++------- + net/rxrpc/conn_client.c | 12 ++++++------ + net/rxrpc/conn_event.c | 14 +++++++------- + net/rxrpc/conn_object.c | 8 ++++---- + net/rxrpc/input.c | 5 +---- + net/rxrpc/peer_event.c | 8 ++++---- + net/rxrpc/peer_object.c | 1 + + net/rxrpc/recvmsg.c | 22 +++++++++++----------- + net/rxrpc/security.c | 4 ++-- + 11 files changed, 55 insertions(+), 57 deletions(-) + +--- a/net/rxrpc/af_rxrpc.c ++++ b/net/rxrpc/af_rxrpc.c +@@ -362,9 +362,9 @@ void rxrpc_kernel_shutdown_call(struct s + + /* Make sure we're not going to call back into a kernel service */ + if (call->notify_rx) { +- spin_lock(&call->notify_lock); ++ spin_lock_irq(&call->notify_lock); + call->notify_rx = rxrpc_dummy_notify_rx; +- spin_unlock(&call->notify_lock); ++ spin_unlock_irq(&call->notify_lock); + } + } + mutex_unlock(&call->user_mutex); +--- a/net/rxrpc/call_accept.c ++++ b/net/rxrpc/call_accept.c +@@ -189,8 +189,8 @@ void rxrpc_discard_prealloc(struct rxrpc + /* Make sure that there aren't any incoming calls in progress before we + * clear the preallocation buffers. + */ +- spin_lock(&rx->incoming_lock); +- spin_unlock(&rx->incoming_lock); ++ spin_lock_irq(&rx->incoming_lock); ++ spin_unlock_irq(&rx->incoming_lock); + + head = b->peer_backlog_head; + tail = b->peer_backlog_tail; +@@ -349,7 +349,7 @@ bool rxrpc_new_incoming_call(struct rxrp + if (sp->hdr.type != RXRPC_PACKET_TYPE_DATA) + return rxrpc_protocol_error(skb, rxrpc_eproto_no_service_call); + +- read_lock(&local->services_lock); ++ read_lock_irq(&local->services_lock); + + /* Weed out packets to services we're not offering. Packets that would + * begin a call are explicitly rejected and the rest are just +@@ -406,12 +406,12 @@ bool rxrpc_new_incoming_call(struct rxrp + spin_unlock(&conn->state_lock); + + spin_unlock(&rx->incoming_lock); +- read_unlock(&local->services_lock); ++ read_unlock_irq(&local->services_lock); + + if (hlist_unhashed(&call->error_link)) { +- spin_lock(&call->peer->lock); ++ spin_lock_irq(&call->peer->lock); + hlist_add_head(&call->error_link, &call->peer->error_targets); +- spin_unlock(&call->peer->lock); ++ spin_unlock_irq(&call->peer->lock); + } + + _leave(" = %p{%d}", call, call->debug_id); +@@ -420,20 +420,20 @@ bool rxrpc_new_incoming_call(struct rxrp + return true; + + unsupported_service: +- read_unlock(&local->services_lock); ++ read_unlock_irq(&local->services_lock); + return rxrpc_direct_abort(skb, rxrpc_abort_service_not_offered, + RX_INVALID_OPERATION, -EOPNOTSUPP); + unsupported_security: +- read_unlock(&local->services_lock); ++ read_unlock_irq(&local->services_lock); + return rxrpc_direct_abort(skb, rxrpc_abort_service_not_offered, + RX_INVALID_OPERATION, -EKEYREJECTED); + no_call: + spin_unlock(&rx->incoming_lock); +- read_unlock(&local->services_lock); ++ read_unlock_irq(&local->services_lock); + _leave(" = f [%u]", skb->mark); + return false; + discard: +- read_unlock(&local->services_lock); ++ read_unlock_irq(&local->services_lock); + return true; + } + +--- a/net/rxrpc/call_object.c ++++ b/net/rxrpc/call_object.c +@@ -48,7 +48,7 @@ void rxrpc_poke_call(struct rxrpc_call * + bool busy; + + if (!test_bit(RXRPC_CALL_DISCONNECTED, &call->flags)) { +- spin_lock_bh(&local->lock); ++ spin_lock_irq(&local->lock); + busy = !list_empty(&call->attend_link); + trace_rxrpc_poke_call(call, busy, what); + if (!busy && !rxrpc_try_get_call(call, rxrpc_call_get_poke)) +@@ -56,7 +56,7 @@ void rxrpc_poke_call(struct rxrpc_call * + if (!busy) { + list_add_tail(&call->attend_link, &local->call_attend_q); + } +- spin_unlock_bh(&local->lock); ++ spin_unlock_irq(&local->lock); + if (!busy) + rxrpc_wake_up_io_thread(local); + } +@@ -311,9 +311,9 @@ static int rxrpc_connect_call(struct rxr + + trace_rxrpc_client(NULL, -1, rxrpc_client_queue_new_call); + rxrpc_get_call(call, rxrpc_call_get_io_thread); +- spin_lock(&local->client_call_lock); ++ spin_lock_irq(&local->client_call_lock); + list_add_tail(&call->wait_link, &local->new_client_calls); +- spin_unlock(&local->client_call_lock); ++ spin_unlock_irq(&local->client_call_lock); + rxrpc_wake_up_io_thread(local); + return 0; + +@@ -439,7 +439,7 @@ error_attached_to_socket: + + /* + * Set up an incoming call. call->conn points to the connection. +- * This is called in BH context and isn't allowed to fail. ++ * This is called with interrupts disabled and isn't allowed to fail. + */ + void rxrpc_incoming_call(struct rxrpc_sock *rx, + struct rxrpc_call *call, +@@ -563,7 +563,7 @@ void rxrpc_release_call(struct rxrpc_soc + rxrpc_put_call_slot(call); + + /* Make sure we don't get any more notifications */ +- spin_lock(&rx->recvmsg_lock); ++ spin_lock_irq(&rx->recvmsg_lock); + + if (!list_empty(&call->recvmsg_link)) { + _debug("unlinking once-pending call %p { e=%lx f=%lx }", +@@ -576,7 +576,7 @@ void rxrpc_release_call(struct rxrpc_soc + call->recvmsg_link.next = NULL; + call->recvmsg_link.prev = NULL; + +- spin_unlock(&rx->recvmsg_lock); ++ spin_unlock_irq(&rx->recvmsg_lock); + if (put) + rxrpc_put_call(call, rxrpc_call_put_unnotify); + +--- a/net/rxrpc/conn_client.c ++++ b/net/rxrpc/conn_client.c +@@ -505,10 +505,10 @@ void rxrpc_connect_client_calls(struct r + ) { + struct rxrpc_bundle *bundle = call->bundle; + +- spin_lock(&local->client_call_lock); ++ spin_lock_irq(&local->client_call_lock); + list_move_tail(&call->wait_link, &bundle->waiting_calls); + rxrpc_see_call(call, rxrpc_call_see_waiting_call); +- spin_unlock(&local->client_call_lock); ++ spin_unlock_irq(&local->client_call_lock); + + if (rxrpc_bundle_has_space(bundle)) + rxrpc_activate_channels(bundle); +@@ -536,9 +536,9 @@ void rxrpc_expose_client_call(struct rxr + set_bit(RXRPC_CONN_DONT_REUSE, &conn->flags); + trace_rxrpc_client(conn, channel, rxrpc_client_exposed); + +- spin_lock(&call->peer->lock); ++ spin_lock_irq(&call->peer->lock); + hlist_add_head(&call->error_link, &call->peer->error_targets); +- spin_unlock(&call->peer->lock); ++ spin_unlock_irq(&call->peer->lock); + } + } + +@@ -579,9 +579,9 @@ void rxrpc_disconnect_client_call(struct + ASSERTCMP(call->call_id, ==, 0); + ASSERT(!test_bit(RXRPC_CALL_EXPOSED, &call->flags)); + /* May still be on ->new_client_calls. */ +- spin_lock(&local->client_call_lock); ++ spin_lock_irq(&local->client_call_lock); + list_del_init(&call->wait_link); +- spin_unlock(&local->client_call_lock); ++ spin_unlock_irq(&local->client_call_lock); + return; + } + +--- a/net/rxrpc/conn_event.c ++++ b/net/rxrpc/conn_event.c +@@ -26,7 +26,7 @@ static bool rxrpc_set_conn_aborted(struc + bool aborted = false; + + if (conn->state != RXRPC_CONN_ABORTED) { +- spin_lock(&conn->state_lock); ++ spin_lock_irq(&conn->state_lock); + if (conn->state != RXRPC_CONN_ABORTED) { + conn->abort_code = abort_code; + conn->error = err; +@@ -37,7 +37,7 @@ static bool rxrpc_set_conn_aborted(struc + set_bit(RXRPC_CONN_EV_ABORT_CALLS, &conn->events); + aborted = true; + } +- spin_unlock(&conn->state_lock); ++ spin_unlock_irq(&conn->state_lock); + } + + return aborted; +@@ -268,12 +268,12 @@ static int rxrpc_process_event(struct rx + return conn->security->respond_to_challenge(conn, skb); + + case RXRPC_PACKET_TYPE_RESPONSE: +- spin_lock(&conn->state_lock); ++ spin_lock_irq(&conn->state_lock); + if (conn->state != RXRPC_CONN_SERVICE_CHALLENGING) { +- spin_unlock(&conn->state_lock); ++ spin_unlock_irq(&conn->state_lock); + return 0; + } +- spin_unlock(&conn->state_lock); ++ spin_unlock_irq(&conn->state_lock); + + ret = rxrpc_verify_response(conn, skb); + if (ret < 0) +@@ -284,12 +284,12 @@ static int rxrpc_process_event(struct rx + if (ret < 0) + return ret; + +- spin_lock(&conn->state_lock); ++ spin_lock_irq(&conn->state_lock); + if (conn->state == RXRPC_CONN_SERVICE_CHALLENGING) { + conn->state = RXRPC_CONN_SERVICE; + secured = true; + } +- spin_unlock(&conn->state_lock); ++ spin_unlock_irq(&conn->state_lock); + + if (secured) { + /* Offload call state flipping to the I/O thread. As +--- a/net/rxrpc/conn_object.c ++++ b/net/rxrpc/conn_object.c +@@ -31,13 +31,13 @@ void rxrpc_poke_conn(struct rxrpc_connec + if (WARN_ON_ONCE(!local)) + return; + +- spin_lock_bh(&local->lock); ++ spin_lock_irq(&local->lock); + busy = !list_empty(&conn->attend_link); + if (!busy) { + rxrpc_get_connection(conn, why); + list_add_tail(&conn->attend_link, &local->conn_attend_q); + } +- spin_unlock_bh(&local->lock); ++ spin_unlock_irq(&local->lock); + rxrpc_wake_up_io_thread(local); + } + +@@ -196,9 +196,9 @@ void rxrpc_disconnect_call(struct rxrpc_ + call->peer->cong_ssthresh = call->cong_ssthresh; + + if (!hlist_unhashed(&call->error_link)) { +- spin_lock(&call->peer->lock); ++ spin_lock_irq(&call->peer->lock); + hlist_del_init(&call->error_link); +- spin_unlock(&call->peer->lock); ++ spin_unlock_irq(&call->peer->lock); + } + + if (rxrpc_is_client_call(call)) { +--- a/net/rxrpc/input.c ++++ b/net/rxrpc/input.c +@@ -366,7 +366,7 @@ static void rxrpc_input_queue_data(struc + struct rxrpc_skb_priv *sp = rxrpc_skb(skb); + bool last = sp->hdr.flags & RXRPC_LAST_PACKET; + +- __skb_queue_tail(&call->recvmsg_queue, skb); ++ skb_queue_tail(&call->recvmsg_queue, skb); + rxrpc_input_update_ack_window(call, window, wtop); + trace_rxrpc_receive(call, last ? why + 1 : why, sp->hdr.serial, sp->hdr.seq); + if (last) +@@ -443,7 +443,6 @@ static void rxrpc_input_data_one(struct + + rxrpc_get_skb(skb, rxrpc_skb_get_to_recvmsg); + +- spin_lock(&call->recvmsg_queue.lock); + rxrpc_input_queue_data(call, skb, window, wtop, rxrpc_receive_queue); + *_notify = true; + +@@ -465,8 +464,6 @@ static void rxrpc_input_data_one(struct + rxrpc_receive_queue_oos); + } + +- spin_unlock(&call->recvmsg_queue.lock); +- + call->ackr_sack_base = sack; + } else { + unsigned int slot; +--- a/net/rxrpc/peer_event.c ++++ b/net/rxrpc/peer_event.c +@@ -205,23 +205,23 @@ static void rxrpc_distribute_error(struc + struct rxrpc_call *call; + HLIST_HEAD(error_targets); + +- spin_lock(&peer->lock); ++ spin_lock_irq(&peer->lock); + hlist_move_list(&peer->error_targets, &error_targets); + + while (!hlist_empty(&error_targets)) { + call = hlist_entry(error_targets.first, + struct rxrpc_call, error_link); + hlist_del_init(&call->error_link); +- spin_unlock(&peer->lock); ++ spin_unlock_irq(&peer->lock); + + rxrpc_see_call(call, rxrpc_call_see_distribute_error); + rxrpc_set_call_completion(call, compl, 0, -err); + rxrpc_input_call_event(call, skb); + +- spin_lock(&peer->lock); ++ spin_lock_irq(&peer->lock); + } + +- spin_unlock(&peer->lock); ++ spin_unlock_irq(&peer->lock); + } + + /* +--- a/net/rxrpc/peer_object.c ++++ b/net/rxrpc/peer_object.c +@@ -302,6 +302,7 @@ static void rxrpc_free_peer(struct rxrpc + * Set up a new incoming peer. There shouldn't be any other matching peers + * since we've already done a search in the list from the non-reentrant context + * (the data_ready handler) that is the only place we can add new peers. ++ * Called with interrupts disabled. + */ + void rxrpc_new_incoming_peer(struct rxrpc_local *local, struct rxrpc_peer *peer) + { +--- a/net/rxrpc/recvmsg.c ++++ b/net/rxrpc/recvmsg.c +@@ -36,16 +36,16 @@ void rxrpc_notify_socket(struct rxrpc_ca + sk = &rx->sk; + if (rx && sk->sk_state < RXRPC_CLOSE) { + if (call->notify_rx) { +- spin_lock(&call->notify_lock); ++ spin_lock_irq(&call->notify_lock); + call->notify_rx(sk, call, call->user_call_ID); +- spin_unlock(&call->notify_lock); ++ spin_unlock_irq(&call->notify_lock); + } else { +- spin_lock(&rx->recvmsg_lock); ++ spin_lock_irq(&rx->recvmsg_lock); + if (list_empty(&call->recvmsg_link)) { + rxrpc_get_call(call, rxrpc_call_get_notify_socket); + list_add_tail(&call->recvmsg_link, &rx->recvmsg_q); + } +- spin_unlock(&rx->recvmsg_lock); ++ spin_unlock_irq(&rx->recvmsg_lock); + + if (!sock_flag(sk, SOCK_DEAD)) { + _debug("call %ps", sk->sk_data_ready); +@@ -379,14 +379,14 @@ try_again: + * We also want to weed out calls that got requeued whilst we were + * shovelling data out. + */ +- spin_lock(&rx->recvmsg_lock); ++ spin_lock_irq(&rx->recvmsg_lock); + l = rx->recvmsg_q.next; + call = list_entry(l, struct rxrpc_call, recvmsg_link); + + if (!rxrpc_call_is_complete(call) && + skb_queue_empty(&call->recvmsg_queue)) { + list_del_init(&call->recvmsg_link); +- spin_unlock(&rx->recvmsg_lock); ++ spin_unlock_irq(&rx->recvmsg_lock); + release_sock(&rx->sk); + trace_rxrpc_recvmsg(call->debug_id, rxrpc_recvmsg_unqueue, 0); + rxrpc_put_call(call, rxrpc_call_put_recvmsg); +@@ -407,7 +407,7 @@ try_again: + list_del_init(&call->recvmsg_link); + else + rxrpc_get_call(call, rxrpc_call_get_recvmsg); +- spin_unlock(&rx->recvmsg_lock); ++ spin_unlock_irq(&rx->recvmsg_lock); + + call_debug_id = call->debug_id; + trace_rxrpc_recvmsg(call_debug_id, rxrpc_recvmsg_dequeue, 0); +@@ -502,17 +502,17 @@ error_unlock_call: + + error_requeue_call: + if (!(flags & MSG_PEEK)) { +- spin_lock(&rx->recvmsg_lock); ++ spin_lock_irq(&rx->recvmsg_lock); + if (list_empty(&call->recvmsg_link)) { + list_add(&call->recvmsg_link, &rx->recvmsg_q); + rxrpc_see_call(call, rxrpc_call_see_recvmsg_requeue); +- spin_unlock(&rx->recvmsg_lock); ++ spin_unlock_irq(&rx->recvmsg_lock); + } else if (list_is_first(&call->recvmsg_link, &rx->recvmsg_q)) { +- spin_unlock(&rx->recvmsg_lock); ++ spin_unlock_irq(&rx->recvmsg_lock); + rxrpc_put_call(call, rxrpc_call_see_recvmsg_requeue_first); + } else { + list_move(&call->recvmsg_link, &rx->recvmsg_q); +- spin_unlock(&rx->recvmsg_lock); ++ spin_unlock_irq(&rx->recvmsg_lock); + rxrpc_put_call(call, rxrpc_call_see_recvmsg_requeue_move); + } + trace_rxrpc_recvmsg(call_debug_id, rxrpc_recvmsg_requeue, 0); +--- a/net/rxrpc/security.c ++++ b/net/rxrpc/security.c +@@ -114,10 +114,10 @@ found: + if (conn->state == RXRPC_CONN_CLIENT_UNSECURED) { + ret = conn->security->init_connection_security(conn, token); + if (ret == 0) { +- spin_lock(&conn->state_lock); ++ spin_lock_irq(&conn->state_lock); + if (conn->state == RXRPC_CONN_CLIENT_UNSECURED) + conn->state = RXRPC_CONN_CLIENT; +- spin_unlock(&conn->state_lock); ++ spin_unlock_irq(&conn->state_lock); + } + } + mutex_unlock(&conn->security_lock); diff --git a/queue-6.6/selinux-avoid-sk_socket-dereference-in-selinux_sctp_bind_connect.patch b/queue-6.6/selinux-avoid-sk_socket-dereference-in-selinux_sctp_bind_connect.patch new file mode 100644 index 0000000000..41426e1e50 --- /dev/null +++ b/queue-6.6/selinux-avoid-sk_socket-dereference-in-selinux_sctp_bind_connect.patch @@ -0,0 +1,112 @@ +From stable+bounces-288351-greg=kroah.com@vger.kernel.org Thu Jul 23 16:21:22 2026 +From: Sasha Levin +Date: Thu, 23 Jul 2026 10:19:20 -0400 +Subject: selinux: avoid sk_socket dereference in selinux_sctp_bind_connect() +To: stable@vger.kernel.org +Cc: Tristan Madani , Stephen Smalley , Paul Moore , Sasha Levin +Message-ID: <20260723141920.3218519-2-sashal@kernel.org> + +From: Tristan Madani + +[ Upstream commit 56acfeb10019e200ab6787d01f8d7cbe0f01526f ] + +selinux_sctp_bind_connect() dereferences sk->sk_socket to pass a +struct socket * to selinux_socket_bind() and +selinux_socket_connect_helper(). However, when the hook is invoked +from the ASCONF softirq path (sctp_process_asconf), there is no file +reference guaranteeing that sk->sk_socket is non-NULL. The setsockopt +callers (bindx, connectx, set_primary, sendmsg connect) hold a file +reference and are not affected. + +Both selinux_socket_bind() and selinux_socket_connect_helper() +immediately resolve sock->sk, never using the struct socket * for +anything else. Refactor the inner logic into helpers that take a +struct sock * directly so that selinux_sctp_bind_connect() never needs +to touch sk->sk_socket at all. + +Cc: stable@vger.kernel.org +Fixes: d452930fd3b9 ("selinux: Add SCTP support") +Suggested-by: Stephen Smalley +Signed-off-by: Tristan Madani +Reviewed-by: Stephen Smalley +Tested-by: Stephen Smalley +Signed-off-by: Paul Moore +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + security/selinux/hooks.c | 19 ++++++++++--------- + 1 file changed, 10 insertions(+), 9 deletions(-) + +--- a/security/selinux/hooks.c ++++ b/security/selinux/hooks.c +@@ -4777,9 +4777,8 @@ static int selinux_socket_socketpair(str + Need to determine whether we should perform a name_bind + permission check between the socket and the port number. */ + +-static int selinux_socket_bind(struct socket *sock, struct sockaddr *address, int addrlen) ++static int __selinux_socket_bind(struct sock *sk, struct sockaddr *address, int addrlen) + { +- struct sock *sk = sock->sk; + struct sk_security_struct *sksec = selinux_sock(sk); + u16 family; + int err; +@@ -4913,13 +4912,17 @@ err_af: + return -EAFNOSUPPORT; + } + ++static int selinux_socket_bind(struct socket *sock, struct sockaddr *address, int addrlen) ++{ ++ return __selinux_socket_bind(sock->sk, address, addrlen); ++} ++ + /* This supports connect(2) and SCTP connect services such as sctp_connectx(3) + * and sctp_sendmsg(3) as described in Documentation/security/SCTP.rst + */ +-static int selinux_socket_connect_helper(struct socket *sock, ++static int selinux_socket_connect_helper(struct sock *sk, + struct sockaddr *address, int addrlen) + { +- struct sock *sk = sock->sk; + struct sk_security_struct *sksec = selinux_sock(sk); + int err; + +@@ -5012,7 +5015,7 @@ static int selinux_socket_connect(struct + int err; + struct sock *sk = sock->sk; + +- err = selinux_socket_connect_helper(sock, address, addrlen); ++ err = selinux_socket_connect_helper(sk, address, addrlen); + if (err) + return err; + +@@ -5512,13 +5515,11 @@ static int selinux_sctp_bind_connect(str + int len, err = 0, walk_size = 0; + void *addr_buf; + struct sockaddr *addr; +- struct socket *sock; + + if (!selinux_policycap_extsockclass()) + return 0; + + /* Process one or more addresses that may be IPv4 or IPv6 */ +- sock = sk->sk_socket; + addr_buf = address; + + while (walk_size < addrlen) { +@@ -5547,14 +5548,14 @@ static int selinux_sctp_bind_connect(str + case SCTP_PRIMARY_ADDR: + case SCTP_SET_PEER_PRIMARY_ADDR: + case SCTP_SOCKOPT_BINDX_ADD: +- err = selinux_socket_bind(sock, addr, len); ++ err = __selinux_socket_bind(sk, addr, len); + break; + /* Connect checks */ + case SCTP_SOCKOPT_CONNECTX: + case SCTP_PARAM_SET_PRIMARY: + case SCTP_PARAM_ADD_IP: + case SCTP_SENDMSG_CONNECT: +- err = selinux_socket_connect_helper(sock, addr, len); ++ err = selinux_socket_connect_helper(sk, addr, len); + if (err) + return err; + diff --git a/queue-6.6/serial-max310x-implement-gpio_chip-get_direction.patch b/queue-6.6/serial-max310x-implement-gpio_chip-get_direction.patch new file mode 100644 index 0000000000..e5e83134fa --- /dev/null +++ b/queue-6.6/serial-max310x-implement-gpio_chip-get_direction.patch @@ -0,0 +1,64 @@ +From sashal@kernel.org Wed Jul 22 15:54:32 2026 +From: Sasha Levin +Date: Wed, 22 Jul 2026 09:54:28 -0400 +Subject: serial: max310x: implement gpio_chip::get_direction() +To: stable@vger.kernel.org +Cc: Tapio Reijonen , stable , Linus Walleij , Bartosz Golaszewski , Hugo Villeneuve , Greg Kroah-Hartman , Sasha Levin +Message-ID: <20260722135428.1365359-2-sashal@kernel.org> + +From: Tapio Reijonen + +[ Upstream commit a483b1a91b33b7533280e7c3efd2bc1275caef18 ] + +It's strongly recommended for GPIO drivers to always implement the +.get_direction() callback - even when the direction is tracked in +software. The GPIO core emits a warning when the callback is missing +and a user reads the direction of a line, e.g. via +/sys/kernel/debug/gpio. + +The MAX310X keeps the GPIO direction in the GPIOCFG register (a set bit +selects output), which the existing direction_input/output callbacks +already program, so the current direction can be read back directly. + +Fixes: f65444187a66 ("serial: New serial driver MAX310X") +Cc: stable +Signed-off-by: Tapio Reijonen +Reviewed-by: Linus Walleij +Reviewed-by: Bartosz Golaszewski +Reviewed-by: Hugo Villeneuve +Link: https://patch.msgid.link/20260615-b4-serial-max310x-gpio-get-direction-v2-1-4704ba2b181a@vaisala.com +Signed-off-by: Greg Kroah-Hartman +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/tty/serial/max310x.c | 12 ++++++++++++ + 1 file changed, 12 insertions(+) + +--- a/drivers/tty/serial/max310x.c ++++ b/drivers/tty/serial/max310x.c +@@ -1229,6 +1229,17 @@ static void max310x_gpio_set(struct gpio + value ? 1 << (offset % 4) : 0); + } + ++static int max310x_gpio_get_direction(struct gpio_chip *chip, unsigned int offset) ++{ ++ struct max310x_port *s = gpiochip_get_data(chip); ++ struct uart_port *port = &s->p[offset / 4].port; ++ unsigned int val; ++ ++ val = max310x_port_read(port, MAX310X_GPIOCFG_REG); ++ ++ return val & BIT(offset % 4) ? GPIO_LINE_DIRECTION_OUT : GPIO_LINE_DIRECTION_IN; ++} ++ + static int max310x_gpio_direction_input(struct gpio_chip *chip, unsigned int offset) + { + struct max310x_port *s = gpiochip_get_data(chip); +@@ -1442,6 +1453,7 @@ static int max310x_probe(struct device * + s->gpio.owner = THIS_MODULE; + s->gpio.parent = dev; + s->gpio.label = devtype->name; ++ s->gpio.get_direction = max310x_gpio_get_direction; + s->gpio.direction_input = max310x_gpio_direction_input; + s->gpio.get = max310x_gpio_get; + s->gpio.direction_output= max310x_gpio_direction_output; diff --git a/queue-6.6/serial-max310x-replace-bare-use-of-unsigned-with-unsigned-int-checkpatch.patch b/queue-6.6/serial-max310x-replace-bare-use-of-unsigned-with-unsigned-int-checkpatch.patch new file mode 100644 index 0000000000..8cdbd26c83 --- /dev/null +++ b/queue-6.6/serial-max310x-replace-bare-use-of-unsigned-with-unsigned-int-checkpatch.patch @@ -0,0 +1,68 @@ +From sashal@kernel.org Wed Jul 22 15:54:30 2026 +From: Sasha Levin +Date: Wed, 22 Jul 2026 09:54:27 -0400 +Subject: serial: max310x: replace bare use of 'unsigned' with 'unsigned int' (checkpatch) +To: stable@vger.kernel.org +Cc: Hugo Villeneuve , Andy Shevchenko , Greg Kroah-Hartman , Sasha Levin +Message-ID: <20260722135428.1365359-1-sashal@kernel.org> + +From: Hugo Villeneuve + +[ Upstream commit 79b69eb09cf5b6a77e621b2838b7e0d38113debb ] + +Fixes the following checkpatch warnings: + + WARNING: Prefer 'unsigned int' to bare use of 'unsigned' + +With this change, the affected functions now match the prototypes in +struct gpio_chip. + +Reviewed-by: Andy Shevchenko +Signed-off-by: Hugo Villeneuve +Link: https://lore.kernel.org/r/20240118152213.2644269-16-hugo@hugovil.com +Signed-off-by: Greg Kroah-Hartman +Stable-dep-of: a483b1a91b33 ("serial: max310x: implement gpio_chip::get_direction()") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/tty/serial/max310x.c | 8 ++++---- + 1 file changed, 4 insertions(+), 4 deletions(-) + +--- a/drivers/tty/serial/max310x.c ++++ b/drivers/tty/serial/max310x.c +@@ -1209,7 +1209,7 @@ static int __maybe_unused max310x_resume + static SIMPLE_DEV_PM_OPS(max310x_pm_ops, max310x_suspend, max310x_resume); + + #ifdef CONFIG_GPIOLIB +-static int max310x_gpio_get(struct gpio_chip *chip, unsigned offset) ++static int max310x_gpio_get(struct gpio_chip *chip, unsigned int offset) + { + unsigned int val; + struct max310x_port *s = gpiochip_get_data(chip); +@@ -1220,7 +1220,7 @@ static int max310x_gpio_get(struct gpio_ + return !!((val >> 4) & (1 << (offset % 4))); + } + +-static void max310x_gpio_set(struct gpio_chip *chip, unsigned offset, int value) ++static void max310x_gpio_set(struct gpio_chip *chip, unsigned int offset, int value) + { + struct max310x_port *s = gpiochip_get_data(chip); + struct uart_port *port = &s->p[offset / 4].port; +@@ -1229,7 +1229,7 @@ static void max310x_gpio_set(struct gpio + value ? 1 << (offset % 4) : 0); + } + +-static int max310x_gpio_direction_input(struct gpio_chip *chip, unsigned offset) ++static int max310x_gpio_direction_input(struct gpio_chip *chip, unsigned int offset) + { + struct max310x_port *s = gpiochip_get_data(chip); + struct uart_port *port = &s->p[offset / 4].port; +@@ -1240,7 +1240,7 @@ static int max310x_gpio_direction_input( + } + + static int max310x_gpio_direction_output(struct gpio_chip *chip, +- unsigned offset, int value) ++ unsigned int offset, int value) + { + struct max310x_port *s = gpiochip_get_data(chip); + struct uart_port *port = &s->p[offset / 4].port; diff --git a/queue-6.6/series b/queue-6.6/series index a5afa24d6f..9d855aaa4b 100644 --- a/queue-6.6/series +++ b/queue-6.6/series @@ -360,3 +360,117 @@ drm-amd-display-fix-dcn32-dtb-dto-update-breaking-li.patch io_uring-rw-fix-missing-erestartsys-conversion-in-re.patch net-pcs-xpcs-fix-sgmii-state-reading.patch bpf-drop-bpf_lsm_getselfattr-from-hook-list.patch +bpf-reject-bpf_map_type_inode_storage-creation-if-bpf-lsm-is-uninitialized.patch +iommu-amd-don-t-split-flush-for-amd_iommu_domain_flush_all.patch +udmabuf-do-not-create-malformed-scatterlists.patch +dma-buf-udmabuf-skip-redundant-cpu-sync-to-fix-cacheline-eexist-warning.patch +fpga-dfl-afu-validate-dma-mapping-length-in-afu_dma_map_region.patch +i2c-davinci-unregister-cpufreq-notifier-on-probe-failure.patch +vfs-audit-introduce-kern_path_parent-for-audit.patch +audit-widen-ino-fields-to-u64.patch +audit-use-unsigned-int-instead-of-unsigned.patch +audit-fix-recursive-locking-deadlock-in-audit_dupe_exe.patch +i2c-i801-fix-hardware-state-machine-corruption-in-error-path.patch +alsa-hda-conexant-remove-mic-bias-threshold-override.patch +alsa-hda-fix-cached-processing-coefficient-verbs.patch +serial-max310x-replace-bare-use-of-unsigned-with-unsigned-int-checkpatch.patch +serial-max310x-implement-gpio_chip-get_direction.patch +afs-annotate-struct-afs_addr_list-with-__counted_by.patch +afs-turn-the-afs_addr_list-address-array-into-an-array-of-structs.patch +rxrpc-pull-out-certain-app-callback-funcs-into-an-ops-table.patch +rxrpc-serialize-kernel-accept-preallocation-with-socket-teardown.patch +fbcon-rename-struct-fbcon_ops-to-struct-fbcon_par.patch +fbcon-use-correct-type-for-vc_resize-return-value.patch +tipc-restrict-socket-queue-dumps-in-enqueue-tracepoints.patch +rxrpc-don-t-need-barrier-for-tx_bottom-and-acks_hard_ack.patch +rxrpc-use-irq-disabling-spinlocks-between-app-and-i-o-thread.patch +rxrpc-fix-notification-vs-call-release-vs-recvmsg.patch +rxrpc-fix-socket-notification-race.patch +vduse-use-fixed-4kb-bounce-pages-for-non-4kb-page-size.patch +vduse-remove-unused-vaddr-parameter-of-vduse_domain_free_coherent.patch +vduse-take-out-allocations-from-vduse_dev_alloc_coherent.patch +vduse-avoid-leaking-information-to-userspace.patch +octeontx2-annotate-mmio-regions-as-__iomem.patch +octeontx2-pf-clear-stale-mailbox-irq-state-before-request_irq.patch +octeontx2-vf-clear-stale-mailbox-irq-state-before-request_irq.patch +fbdev-efifb-replace-references-to-global-screen_info-by-local-pointer.patch +fbdev-efifb-fix-memory-leak-in-efifb_probe.patch +asoc-mediatek-mt8195-remove-afe-dai-component-and-rework-codec-link.patch +asoc-mediatek-mt8192-afe-pcm-convert-to-devm_pm_runtime_enable.patch +asoc-mediatek-mt8192-afe-pcm-simplify-with-dev_err_probe.patch +asoc-mediatek-use-common-mtk_afe_pcm_platform-with-common-probe-cb.patch +asoc-mediatek-mt8192-afe-pcm-simplify-probe-with-local-dev-variable.patch +asoc-mediatek-mt8192-check-runtime-resume-during-probe.patch +asoc-mediatek-mt8183-check-runtime-resume-during-probe.patch +netfilter-nft_set_pipapo-use-gfp_kernel-for-insertions.patch +netfilter-nft_set_pipapo-move-prove_locking-helper-around.patch +netfilter-nft_set_pipapo-make-pipapo_clone-helper-return-null.patch +netfilter-nft_set_pipapo-prepare-walk-function-for-on-demand-clone.patch +netfilter-nft_set_pipapo-merge-deactivate-helper-into-caller.patch +netfilter-nft_set_pipapo-prepare-pipapo_get-helper-for-on-demand-clone.patch +netfilter-nft_set_pipapo-move-cloning-of-match-info-to-insert-removal-path.patch +netfilter-nft_set_pipapo-don-t-leak-bad-clone-into-future-transaction.patch +lsm-infrastructure-management-of-the-sock-security.patch +selinux-avoid-sk_socket-dereference-in-selinux_sctp_bind_connect.patch +netfilter-nf_conntrack_sip-remove-net-variable-shadowing.patch +netfilter-nf_conntrack_sip-validate-skb_dst-before-accessing-it.patch +netfilter-nf_tables-remove-unused-nft_reduce_is_readonly.patch +netfilter-nf_tables-remove-register-tracking-infrastructure.patch +netfilter-nft_fib-reject-fib-expression-on-the-netdev-egress-hook.patch +nfsd-pass-nfsd_file-to-nfsd_iter_read.patch +sunrpc-allocate-a-separate-bvec-array-for-socket-sends.patch +sunrpc-add-helpers-to-convert-xdr_buf-byte-ranges-to-scatterlists.patch +sunrpc-return-an-error-from-xdr_buf_to_bvec-on-overflow.patch +mm-mm_init-fix-pageblock-migratetype-for-zone_device-compound-pages.patch +mtd-maps-vmu-flash-fix-fault-in-unaligned-fixup.patch +mtd-rawnand-add-a-helper-for-calculating-a-page-index.patch +mtd-rawnand-ensure-all-continuous-terms-are-always-in-sync.patch +mtd-rawnand-pause-continuous-reads-at-block-boundaries.patch +taskstats-fill_stats_for_tgid-use-for_each_thread.patch +taskstats-retain-dead-thread-stats-in-tgid-queries.patch +dma-dw-edma-fix-build-warning-in-dw_edma_pcie_probe.patch +dmaengine-dw-edma-fix-confusing-cleanup.h-syntax.patch +dmaengine-dw-edma-pcie-reject-devices-without-driver-data.patch +thunderbolt-handle-lane-bonding-of-gen-4-xdomain-links-properly.patch +thunderbolt-remove-usage-of-the-deprecated-ida_simple_xx-api.patch +thunderbolt-update-property.c-function-documentation.patch +thunderbolt-keep-xdomain-reference-during-the-lifetime-of-a-service.patch +thunderbolt-remove-service-debugfs-entries-during-unregister.patch +thunderbolt-remove-xdomain-from-the-bus-without-holding-tb-lock.patch +thunderbolt-prevent-xdomain-delayed-work-use-after-free-on-disconnect.patch +platform-x86-dell-smbios-move-request-functions-for-reuse.patch +platform-x86-dell-laptop-fix-missing-cleanups-in-init-error-path.patch +i2c-imx-separate-atomic-dma-and-non-dma-use-case.patch +i2c-imx-fix-locked-bus-on-smbus-block-read-of-0-atomic.patch +nfs-remove-dead-code-for-the-old-swap-over-nfs-implementation.patch +nfs-charge-unstable-writes-by-request-size-not-folio-size.patch +ovl-use-linked-upper-dentry-in-copy-up-tmpfile.patch +dm-verity-avoid-double-increment-of-use_bh_wq_enabled.patch +dm-verity-make-error-counter-atomic.patch +firmware_loader-introduce-__free-cleanup-hanler.patch +input-ims-pcu-fix-firmware-leak-in-async-update.patch +workqueue-factor-out-init_cpu_worker_pool.patch +workqueue-add-system_percpu_wq-and-system_dfl_wq.patch +tracing-user_events-fix-use-after-free-in-user_event_mm_dup.patch +wifi-libertas_tf-fix-use-after-free-in-lbtf_free_adapter.patch +mmc-vub300-fix-use-after-free-on-disconnect.patch +mmc-vub300-rename-probe-error-labels.patch +mmc-vub300-fix-use-after-free-on-probe-failure.patch +locking-rt-fix-the-incorrect-rcu-protection-in-rt_spin_unlock.patch +net-mana-validate-the-packet-length-reported-by-the-nic.patch +pinctrl-remove-pinctrl_gpio_direction_output.patch +gpio-tegra-do-not-call-pinctrl-for-gpio-direction.patch +net-ip6_tunnel-require-cap_net_admin-in-the-device-netns-for-changelink.patch +gpio-mt7621-avoid-corruption-of-shared-interrupt-trigger-state.patch +octeontx2-af-cn10k-restrict-vf-lmtline-sharing-to-its-own-pf.patch +bootconfig-do-not-put-quotes-on-cmdline-items-unless-necessary.patch +bootconfig-move-xbc_snprint_cmdline-to-lib-bootconfig.c.patch +bootconfig-fix-null-pointer-arithmetic-in-xbc_snprint_cmdline.patch +ipmi-fix-refcount-leak-in-i_ipmi_request.patch +octeontx2-pf-fix-sqb-pointer-leak-on-init-failure.patch +ata-libata-core-reject-an-invalid-concurrent-positioning-ranges-count.patch +net-macb-drop-in-flight-tx-skbs-on-close.patch +net-ipa-fix-smem-state-handle-leaks-in-smp2p-init.patch +fs-resctrl-fix-double-add-of-pseudo-locked-region-s-rmid-to-free-list.patch +kvm-introduce-vcpu-wants_to_run.patch +kvm-x86-only-reset-tsc-deadline-timer-in-apic_timer_expired-on-kvm_run.patch diff --git a/queue-6.6/sunrpc-add-helpers-to-convert-xdr_buf-byte-ranges-to-scatterlists.patch b/queue-6.6/sunrpc-add-helpers-to-convert-xdr_buf-byte-ranges-to-scatterlists.patch new file mode 100644 index 0000000000..e3d9094ee4 --- /dev/null +++ b/queue-6.6/sunrpc-add-helpers-to-convert-xdr_buf-byte-ranges-to-scatterlists.patch @@ -0,0 +1,279 @@ +From stable+bounces-288613-greg=kroah.com@vger.kernel.org Fri Jul 24 03:28:23 2026 +From: Sasha Levin +Date: Thu, 23 Jul 2026 21:25:13 -0400 +Subject: SUNRPC: Add helpers to convert xdr_buf byte ranges to scatterlists +To: stable@vger.kernel.org +Cc: Chuck Lever , Jeff Layton , Anna Schumaker , Sasha Levin +Message-ID: <20260724012515.3833219-3-sashal@kernel.org> + +From: Chuck Lever + +[ Upstream commit e9be933959b581effd426f93b86654f5fbf0c574 ] + +The crypto/krb5 library accepts data in scatterlist form, but +the GSS-API layer presents RPC payloads as struct xdr_buf. +Bridge that gap with a pair of helper functions: + + xdr_buf_to_sg() - populate a caller-supplied scatterlist + array from a byte range + xdr_buf_to_sg_alloc() - populate a caller-supplied inline + scatterlist, chaining to a heap- + allocated overflow for large payloads + +The inline array (typically stack-allocated at eight entries) +covers the common case of small RPCs with no heap allocation +on the encrypt/decrypt path. Only buffers spanning many pages +incur a kmalloc for the chained extension. + +The segment-walking logic follows the same head, page array, +tail traversal as xdr_process_buf(), but populates a +scatterlist directly rather than invoking a per-segment +callback. sg_next() traversal makes the walker safe for +chained scatterlists. Once subsequent patches reroute all +per-message crypto operations through crypto/krb5, +xdr_process_buf() loses its last callers and is removed. + +Assisted-by: Claude:claude-opus-4-6 +Reviewed-by: Jeff Layton +Acked-by: Anna Schumaker +Signed-off-by: Chuck Lever +Stable-dep-of: 18c1cc698861 ("SUNRPC: Return an error from xdr_buf_to_bvec() on overflow") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + include/linux/sunrpc/xdr.h | 15 +++ + net/sunrpc/xdr.c | 199 +++++++++++++++++++++++++++++++++++++++++++++ + 2 files changed, 214 insertions(+) + +--- a/include/linux/sunrpc/xdr.h ++++ b/include/linux/sunrpc/xdr.h +@@ -141,6 +141,21 @@ int xdr_alloc_bvec(struct xdr_buf *buf, + void xdr_free_bvec(struct xdr_buf *buf); + unsigned int xdr_buf_to_bvec(struct bio_vec *bvec, unsigned int bvec_size, + const struct xdr_buf *xdr); ++int xdr_buf_to_sg(const struct xdr_buf *buf, unsigned int offset, ++ unsigned int len, struct scatterlist *sg, unsigned int nsg); ++int xdr_buf_to_sg_alloc(const struct xdr_buf *buf, unsigned int offset, ++ unsigned int len, struct scatterlist *sg_head, ++ unsigned int sg_head_nents, ++ struct scatterlist **sg_overflow, gfp_t gfp); ++ ++/* ++ * Inline scatterlist entries for xdr_buf_to_sg_alloc(). Sized to cover the ++ * head kvec, tail kvec, and a few page fragments without any heap allocation. ++ */ ++enum { ++ XDR_BUF_TO_SG_NENTS = 8, ++}; ++ + + static inline __be32 *xdr_encode_array(__be32 *p, const void *s, unsigned int len) + { +--- a/net/sunrpc/xdr.c ++++ b/net/sunrpc/xdr.c +@@ -219,6 +219,205 @@ bvec_overflow: + } + + /** ++ * xdr_buf_to_sg - Populate a scatterlist from an xdr_buf range ++ * @buf: xdr_buf to map ++ * @offset: starting byte offset within @buf ++ * @len: number of bytes to cover ++ * @sg: scatterlist array initialized with sg_init_table() ++ * @nsg: number of entries available in @sg ++ * ++ * @sg is traversed with sg_next(), so callers may pass a list ++ * assembled with sg_chain(). ++ * ++ * Return: on success, the number of scatterlist entries used; the ++ * last used entry is marked with sg_mark_end(). On failure, a ++ * negative errno. ++ */ ++int xdr_buf_to_sg(const struct xdr_buf *buf, unsigned int offset, ++ unsigned int len, struct scatterlist *sg, unsigned int nsg) ++{ ++ unsigned int page_len, thislen, page_offset; ++ struct scatterlist *cur = sg, *prev = NULL; ++ int nents = 0; ++ int i; ++ ++ if (len == 0) ++ return 0; ++ ++ if (offset >= buf->head[0].iov_len) { ++ offset -= buf->head[0].iov_len; ++ } else { ++ thislen = min_t(unsigned int, ++ buf->head[0].iov_len - offset, len); ++ if (nents >= nsg) ++ return -ENOSPC; ++ sg_set_buf(cur, buf->head[0].iov_base + offset, ++ thislen); ++ prev = cur; ++ cur = sg_next(cur); ++ nents++; ++ len -= thislen; ++ offset = 0; ++ } ++ if (len == 0) ++ goto done; ++ ++ if (offset >= buf->page_len) { ++ offset -= buf->page_len; ++ } else { ++ page_len = min(buf->page_len - offset, len); ++ len -= page_len; ++ page_offset = (offset + buf->page_base) & (PAGE_SIZE - 1); ++ i = (offset + buf->page_base) >> PAGE_SHIFT; ++ thislen = PAGE_SIZE - page_offset; ++ do { ++ if (thislen > page_len) ++ thislen = page_len; ++ if (nents >= nsg) ++ return -ENOSPC; ++ sg_set_page(cur, buf->pages[i], ++ thislen, page_offset); ++ prev = cur; ++ cur = sg_next(cur); ++ nents++; ++ page_len -= thislen; ++ i++; ++ page_offset = 0; ++ thislen = PAGE_SIZE; ++ } while (page_len != 0); ++ offset = 0; ++ } ++ if (len == 0) ++ goto done; ++ ++ if (offset < buf->tail[0].iov_len) { ++ thislen = min_t(unsigned int, ++ buf->tail[0].iov_len - offset, len); ++ if (nents >= nsg) ++ return -ENOSPC; ++ sg_set_buf(cur, buf->tail[0].iov_base + offset, ++ thislen); ++ prev = cur; ++ nents++; ++ len -= thislen; ++ } ++ if (len != 0) ++ return -EINVAL; ++ ++done: ++ if (prev) ++ sg_mark_end(prev); ++ return nents; ++} ++EXPORT_SYMBOL_GPL(xdr_buf_to_sg); ++ ++/* ++ * Count the scatterlist entries needed to cover [offset, offset + len) ++ * within @buf. Mirrors the walk in xdr_buf_to_sg() so the caller can ++ * size an allocation that matches the requested sub-range rather than ++ * the full xdr_buf. ++ */ ++static unsigned int xdr_buf_sg_nents(const struct xdr_buf *buf, ++ unsigned int offset, unsigned int len) ++{ ++ unsigned int nsg = 0, thislen, page_offset; ++ ++ if (len == 0) ++ return 0; ++ ++ if (offset < buf->head[0].iov_len) { ++ thislen = min_t(unsigned int, ++ buf->head[0].iov_len - offset, len); ++ nsg++; ++ len -= thislen; ++ offset = 0; ++ } else { ++ offset -= buf->head[0].iov_len; ++ } ++ if (len == 0) ++ return nsg; ++ ++ if (offset < buf->page_len) { ++ thislen = min(buf->page_len - offset, len); ++ page_offset = (offset + buf->page_base) & (PAGE_SIZE - 1); ++ nsg += DIV_ROUND_UP(page_offset + thislen, PAGE_SIZE); ++ len -= thislen; ++ offset = 0; ++ } else { ++ offset -= buf->page_len; ++ } ++ if (len == 0) ++ return nsg; ++ ++ if (offset < buf->tail[0].iov_len) ++ nsg++; ++ return nsg; ++} ++ ++/** ++ * xdr_buf_to_sg_alloc - Populate a scatterlist for an xdr_buf range ++ * @buf: xdr_buf to map ++ * @offset: starting byte offset within @buf ++ * @len: number of bytes to cover ++ * @sg_head: caller-provided scatterlist array (typically stack-allocated) ++ * @sg_head_nents: number of entries in @sg_head ++ * @sg_overflow: OUT: chained extension, or NULL when @sg_head sufficed ++ * @gfp: memory allocation flags for overflow ++ * ++ * Populates @sg_head directly when the xdr_buf fits. When more ++ * entries are needed, an overflow scatterlist is allocated and ++ * chained from @sg_head so that the result is traversable with ++ * sg_next(). ++ * ++ * Return: on success, the number of populated scatterlist entries ++ * (counting only data entries, not chain entries). @sg_head is ++ * the head of the resulting list. Caller must kfree @sg_overflow ++ * when done. On failure, a negative errno. ++ */ ++int xdr_buf_to_sg_alloc(const struct xdr_buf *buf, unsigned int offset, ++ unsigned int len, struct scatterlist *sg_head, ++ unsigned int sg_head_nents, ++ struct scatterlist **sg_overflow, gfp_t gfp) ++{ ++ unsigned int nsg; ++ int ret; ++ ++ *sg_overflow = NULL; ++ if (len == 0) ++ return 0; ++ ++ nsg = xdr_buf_sg_nents(buf, offset, len); ++ if (nsg == 0) ++ return -EINVAL; ++ ++ if (nsg <= sg_head_nents) { ++ sg_init_table(sg_head, nsg); ++ } else { ++ /* +1 replaces the slot sg_chain() consumes as the link. */ ++ unsigned int overflow_nents = nsg - sg_head_nents + 1; ++ struct scatterlist *overflow; ++ ++ overflow = kmalloc_array(overflow_nents, sizeof(*overflow), ++ gfp); ++ if (!overflow) ++ return -ENOMEM; ++ ++ sg_init_table(sg_head, sg_head_nents); ++ sg_init_table(overflow, overflow_nents); ++ sg_chain(sg_head, sg_head_nents, overflow); ++ *sg_overflow = overflow; ++ } ++ ++ ret = xdr_buf_to_sg(buf, offset, len, sg_head, nsg); ++ if (ret < 0) { ++ kfree(*sg_overflow); ++ *sg_overflow = NULL; ++ } ++ return ret; ++} ++EXPORT_SYMBOL_GPL(xdr_buf_to_sg_alloc); ++ ++/** + * xdr_inline_pages - Prepare receive buffer for a large reply + * @xdr: xdr_buf into which reply will be placed + * @offset: expected offset where data payload will start, in bytes diff --git a/queue-6.6/sunrpc-allocate-a-separate-bvec-array-for-socket-sends.patch b/queue-6.6/sunrpc-allocate-a-separate-bvec-array-for-socket-sends.patch new file mode 100644 index 0000000000..4b50588226 --- /dev/null +++ b/queue-6.6/sunrpc-allocate-a-separate-bvec-array-for-socket-sends.patch @@ -0,0 +1,140 @@ +From stable+bounces-288612-greg=kroah.com@vger.kernel.org Fri Jul 24 03:28:18 2026 +From: Sasha Levin +Date: Thu, 23 Jul 2026 21:25:12 -0400 +Subject: sunrpc: allocate a separate bvec array for socket sends +To: stable@vger.kernel.org +Cc: Jeff Layton , NeilBrown , Chuck Lever , Sasha Levin +Message-ID: <20260724012515.3833219-2-sashal@kernel.org> + +From: Jeff Layton + +[ Upstream commit 6b3b697d65d46a0f640216a3f6c72856c159c567 ] + +svc_tcp_sendmsg() calls xdr_buf_to_bvec() with the second slot of +rq_bvec as the start, but doesn't reduce the array length by one, which +could lead to an array overrun. Also, rq_bvec is always rq_maxpages in +length, which can be too short in some cases, since the TCP record +marker consumes a slot. + +Fix both problems by adding a separate bvec array to the svc_sock that +is specifically for sending. For TCP, make this array one slot longer +than rq_maxpages, to account for the record marker. For UDP, only +allocate as large an array as we need since it's limited to 64k of +payload. + +Signed-off-by: Jeff Layton +Reviewed-by: NeilBrown +Signed-off-by: Chuck Lever +Stable-dep-of: 18c1cc698861 ("SUNRPC: Return an error from xdr_buf_to_bvec() on overflow") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + include/linux/sunrpc/svcsock.h | 3 +++ + net/sunrpc/svcsock.c | 37 ++++++++++++++++++++++++++++--------- + 2 files changed, 31 insertions(+), 9 deletions(-) + +--- a/include/linux/sunrpc/svcsock.h ++++ b/include/linux/sunrpc/svcsock.h +@@ -26,6 +26,9 @@ struct svc_sock { + void (*sk_odata)(struct sock *); + void (*sk_owspace)(struct sock *); + ++ /* For sends (protected by xpt_mutex) */ ++ struct bio_vec *sk_bvec; ++ + /* private TCP part */ + /* On-the-wire fragment header: */ + __be32 sk_marker; +--- a/net/sunrpc/svcsock.c ++++ b/net/sunrpc/svcsock.c +@@ -68,6 +68,17 @@ + + #define RPCDBG_FACILITY RPCDBG_SVCXPRT + ++/* ++ * For UDP: ++ * 1 for header page ++ * enough pages for RPCSVC_MAXPAYLOAD_UDP ++ * 1 in case payload is not aligned ++ * 1 for tail page ++ */ ++enum { ++ SUNRPC_MAX_UDP_SENDPAGES = 1 + RPCSVC_MAXPAYLOAD_UDP / PAGE_SIZE + 1 + 1 ++}; ++ + /* To-do: to avoid tying up an nfsd thread while waiting for a + * handshake request, the request could instead be deferred. + */ +@@ -750,15 +761,14 @@ static int svc_udp_sendto(struct svc_rqs + if (svc_xprt_is_dead(xprt)) + goto out_notconn; + +- count = xdr_buf_to_bvec(rqstp->rq_bvec, +- ARRAY_SIZE(rqstp->rq_bvec), xdr); ++ count = xdr_buf_to_bvec(svsk->sk_bvec, SUNRPC_MAX_UDP_SENDPAGES, xdr); + +- iov_iter_bvec(&msg.msg_iter, ITER_SOURCE, rqstp->rq_bvec, ++ iov_iter_bvec(&msg.msg_iter, ITER_SOURCE, svsk->sk_bvec, + count, rqstp->rq_res.len); + err = sock_sendmsg(svsk->sk_sock, &msg); + if (err == -ECONNREFUSED) { + /* ICMP error on earlier request. */ +- iov_iter_bvec(&msg.msg_iter, ITER_SOURCE, rqstp->rq_bvec, ++ iov_iter_bvec(&msg.msg_iter, ITER_SOURCE, svsk->sk_bvec, + count, rqstp->rq_res.len); + err = sock_sendmsg(svsk->sk_sock, &msg); + } +@@ -1257,19 +1267,19 @@ static int svc_tcp_sendmsg(struct svc_so + *sentp = 0; + + /* The stream record marker is copied into a temporary page +- * fragment buffer so that it can be included in rq_bvec. ++ * fragment buffer so that it can be included in sk_bvec. + */ + buf = page_frag_alloc(&svsk->sk_frag_cache, sizeof(marker), + GFP_KERNEL); + if (!buf) + return -ENOMEM; + memcpy(buf, &marker, sizeof(marker)); +- bvec_set_virt(rqstp->rq_bvec, buf, sizeof(marker)); ++ bvec_set_virt(svsk->sk_bvec, buf, sizeof(marker)); + +- count = xdr_buf_to_bvec(rqstp->rq_bvec + 1, +- ARRAY_SIZE(rqstp->rq_bvec) - 1, &rqstp->rq_res); ++ count = xdr_buf_to_bvec(svsk->sk_bvec + 1, RPCSVC_MAXPAGES, ++ &rqstp->rq_res); + +- iov_iter_bvec(&msg.msg_iter, ITER_SOURCE, rqstp->rq_bvec, ++ iov_iter_bvec(&msg.msg_iter, ITER_SOURCE, svsk->sk_bvec, + 1 + count, sizeof(marker) + rqstp->rq_res.len); + ret = sock_sendmsg(svsk->sk_sock, &msg); + page_frag_free(buf); +@@ -1432,6 +1442,13 @@ static struct svc_sock *svc_setup_socket + if (!svsk) + return ERR_PTR(-ENOMEM); + ++ svsk->sk_bvec = kcalloc(RPCSVC_MAXPAGES + 1, sizeof(*svsk->sk_bvec), ++ GFP_KERNEL); ++ if (!svsk->sk_bvec) { ++ kfree(svsk); ++ return ERR_PTR(-ENOMEM); ++ } ++ + inet = sock->sk; + + if (pmap_register) { +@@ -1441,6 +1458,7 @@ static struct svc_sock *svc_setup_socket + inet->sk_protocol, + ntohs(inet_sk(inet)->inet_sport)); + if (err < 0) { ++ kfree(svsk->sk_bvec); + kfree(svsk); + return ERR_PTR(err); + } +@@ -1660,5 +1678,6 @@ static void svc_sock_free(struct svc_xpr + if (pfc->va) + __page_frag_cache_drain(virt_to_head_page(pfc->va), + pfc->pagecnt_bias); ++ kfree(svsk->sk_bvec); + kfree(svsk); + } diff --git a/queue-6.6/sunrpc-return-an-error-from-xdr_buf_to_bvec-on-overflow.patch b/queue-6.6/sunrpc-return-an-error-from-xdr_buf_to_bvec-on-overflow.patch new file mode 100644 index 0000000000..c08ef60ccf --- /dev/null +++ b/queue-6.6/sunrpc-return-an-error-from-xdr_buf_to_bvec-on-overflow.patch @@ -0,0 +1,142 @@ +From stable+bounces-288615-greg=kroah.com@vger.kernel.org Fri Jul 24 03:28:55 2026 +From: Sasha Levin +Date: Thu, 23 Jul 2026 21:25:15 -0400 +Subject: SUNRPC: Return an error from xdr_buf_to_bvec() on overflow +To: stable@vger.kernel.org +Cc: Chuck Lever , Chris Mason , Jeff Layton , Sasha Levin +Message-ID: <20260724012515.3833219-5-sashal@kernel.org> + +From: Chuck Lever + +[ Upstream commit 18c1cc69886192e33536498289d26dba6894e3d5 ] + +xdr_buf_to_bvec() returns a slot count even when the caller's bvec +budget is exhausted partway through the xdr_buf. Callers feed that +count into iov_iter_bvec() and continue as if the conversion had +succeeded, silently sending or writing fewer bytes than the data +length declares. For an NFS WRITE the server reports the truncated +transfer to the client as full success. + +The overflow represents an internal invariant violation: a higher +layer reserved a bvec budget too small for the xdr_buf it then +asked the encoder to convert. That is a server-side fault, not a +media I/O failure and not a malformed client argument. + +Change xdr_buf_to_bvec() to return a signed int and have the +overflow label return -ESERVERFAULT. Update the three callers to +detect the negative return and fail the request: nfsd_vfs_write() +folds the error into host_err, which nfserrno() translates to +nfserr_serverfault for the WRITE reply; svc_udp_sendto() and +svc_tcp_sendmsg() propagate the error out of the send path. + +Reported-by: Chris Mason +Fixes: 2eb2b9358181 ("SUNRPC: Convert svc_tcp_sendmsg to use bio_vecs directly") +Cc: stable@vger.kernel.org +Reviewed-by: Jeff Layton +Signed-off-by: Chuck Lever +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + include/linux/sunrpc/xdr.h | 4 ++-- + net/sunrpc/svcsock.c | 14 ++++++++++++-- + net/sunrpc/xdr.c | 11 ++++++----- + 3 files changed, 20 insertions(+), 9 deletions(-) + +--- a/include/linux/sunrpc/xdr.h ++++ b/include/linux/sunrpc/xdr.h +@@ -139,8 +139,8 @@ void xdr_terminate_string(const struct x + size_t xdr_buf_pagecount(const struct xdr_buf *buf); + int xdr_alloc_bvec(struct xdr_buf *buf, gfp_t gfp); + void xdr_free_bvec(struct xdr_buf *buf); +-unsigned int xdr_buf_to_bvec(struct bio_vec *bvec, unsigned int bvec_size, +- const struct xdr_buf *xdr); ++int xdr_buf_to_bvec(struct bio_vec *bvec, unsigned int bvec_size, ++ const struct xdr_buf *xdr); + int xdr_buf_to_sg(const struct xdr_buf *buf, unsigned int offset, + unsigned int len, struct scatterlist *sg, unsigned int nsg); + int xdr_buf_to_sg_alloc(const struct xdr_buf *buf, unsigned int offset, +--- a/net/sunrpc/svcsock.c ++++ b/net/sunrpc/svcsock.c +@@ -748,7 +748,7 @@ static int svc_udp_sendto(struct svc_rqs + .msg_flags = MSG_SPLICE_PAGES, + .msg_controllen = sizeof(buffer), + }; +- unsigned int count; ++ int count; + int err; + + svc_udp_release_ctxt(xprt, rqstp->rq_xprt_ctxt); +@@ -762,6 +762,10 @@ static int svc_udp_sendto(struct svc_rqs + goto out_notconn; + + count = xdr_buf_to_bvec(svsk->sk_bvec, SUNRPC_MAX_UDP_SENDPAGES, xdr); ++ if (count < 0) { ++ err = count; ++ goto out_trace; ++ } + + iov_iter_bvec(&msg.msg_iter, ITER_SOURCE, svsk->sk_bvec, + count, rqstp->rq_res.len); +@@ -773,6 +777,7 @@ static int svc_udp_sendto(struct svc_rqs + err = sock_sendmsg(svsk->sk_sock, &msg); + } + ++out_trace: + trace_svcsock_udp_send(xprt, err); + + mutex_unlock(&xprt->xpt_mutex); +@@ -1260,7 +1265,7 @@ static int svc_tcp_sendmsg(struct svc_so + struct msghdr msg = { + .msg_flags = MSG_SPLICE_PAGES, + }; +- unsigned int count; ++ int count; + void *buf; + int ret; + +@@ -1278,10 +1283,15 @@ static int svc_tcp_sendmsg(struct svc_so + + count = xdr_buf_to_bvec(svsk->sk_bvec + 1, RPCSVC_MAXPAGES, + &rqstp->rq_res); ++ if (count < 0) { ++ ret = count; ++ goto out; ++ } + + iov_iter_bvec(&msg.msg_iter, ITER_SOURCE, svsk->sk_bvec, + 1 + count, sizeof(marker) + rqstp->rq_res.len); + ret = sock_sendmsg(svsk->sk_sock, &msg); ++out: + page_frag_free(buf); + if (ret < 0) + return ret; +--- a/net/sunrpc/xdr.c ++++ b/net/sunrpc/xdr.c +@@ -167,13 +167,14 @@ xdr_free_bvec(struct xdr_buf *buf) + /** + * xdr_buf_to_bvec - Copy components of an xdr_buf into a bio_vec array + * @bvec: bio_vec array to populate +- * @bvec_size: element count of @bio_vec ++ * @bvec_size: element count of @bvec + * @xdr: xdr_buf to be copied + * +- * Returns the number of entries consumed in @bvec. ++ * Returns the number of entries consumed in @bvec on success, or ++ * -ESERVERFAULT when @xdr does not fit within @bvec_size entries. + */ +-unsigned int xdr_buf_to_bvec(struct bio_vec *bvec, unsigned int bvec_size, +- const struct xdr_buf *xdr) ++int xdr_buf_to_bvec(struct bio_vec *bvec, unsigned int bvec_size, ++ const struct xdr_buf *xdr) + { + const struct kvec *head = xdr->head; + const struct kvec *tail = xdr->tail; +@@ -215,7 +216,7 @@ unsigned int xdr_buf_to_bvec(struct bio_ + + bvec_overflow: + pr_warn_once("%s: bio_vec array overflow\n", __func__); +- return count; ++ return -ESERVERFAULT; + } + + /** diff --git a/queue-6.6/taskstats-fill_stats_for_tgid-use-for_each_thread.patch b/queue-6.6/taskstats-fill_stats_for_tgid-use-for_each_thread.patch new file mode 100644 index 0000000000..b617ed7341 --- /dev/null +++ b/queue-6.6/taskstats-fill_stats_for_tgid-use-for_each_thread.patch @@ -0,0 +1,54 @@ +From stable+bounces-289267-greg=kroah.com@vger.kernel.org Sun Jul 26 14:42:20 2026 +From: Sasha Levin +Date: Sun, 26 Jul 2026 08:42:06 -0400 +Subject: taskstats: fill_stats_for_tgid: use for_each_thread() +To: stable@vger.kernel.org +Cc: Oleg Nesterov , "Eric W. Biederman" , Andrew Morton , Sasha Levin +Message-ID: <20260726124207.101107-1-sashal@kernel.org> + +From: Oleg Nesterov + +[ Upstream commit ed5378a387fd7c382497f2abcf4605e030b64044 ] + +do/while_each_thread should be avoided when possible. + +Plus I _think_ this change allows to avoid lock_task_sighand() but I am +not sure, I forgot everything about taskstats. In any case, this code +does not look right in that the same thread can be accounted twice: +taskstats_exit() can account the exiting thread in signal->stats and drop +->siglock but this thread is still on the thread-group list, so +lock_task_sighand() can't help. + +Link: https://lkml.kernel.org/r/20230909214951.GA24274@redhat.com +Signed-off-by: Oleg Nesterov +Cc: Eric W. Biederman +Signed-off-by: Andrew Morton +Stable-dep-of: b3e4fbb04220 ("taskstats: retain dead thread stats in TGID queries") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + kernel/taskstats.c | 5 ++--- + 1 file changed, 2 insertions(+), 3 deletions(-) + +--- a/kernel/taskstats.c ++++ b/kernel/taskstats.c +@@ -233,9 +233,8 @@ static int fill_stats_for_tgid(pid_t tgi + else + memset(stats, 0, sizeof(*stats)); + +- tsk = first; + start_time = ktime_get_ns(); +- do { ++ for_each_thread(first, tsk) { + if (tsk->exit_state) + continue; + /* +@@ -258,7 +257,7 @@ static int fill_stats_for_tgid(pid_t tgi + + stats->nvcsw += tsk->nvcsw; + stats->nivcsw += tsk->nivcsw; +- } while_each_thread(first, tsk); ++ } + + unlock_task_sighand(first, &flags); + rc = 0; diff --git a/queue-6.6/taskstats-retain-dead-thread-stats-in-tgid-queries.patch b/queue-6.6/taskstats-retain-dead-thread-stats-in-tgid-queries.patch new file mode 100644 index 0000000000..29ed75f041 --- /dev/null +++ b/queue-6.6/taskstats-retain-dead-thread-stats-in-tgid-queries.patch @@ -0,0 +1,170 @@ +From stable+bounces-289268-greg=kroah.com@vger.kernel.org Sun Jul 26 14:42:14 2026 +From: Sasha Levin +Date: Sun, 26 Jul 2026 08:42:07 -0400 +Subject: taskstats: retain dead thread stats in TGID queries +To: stable@vger.kernel.org +Cc: Yiyang Chen , Balbir Singh , "Dr. Thomas Orgis" , Oleg Nesterov , Wang Yaxin , Yang Yang , Andrew Morton , Sasha Levin +Message-ID: <20260726124207.101107-2-sashal@kernel.org> + +From: Yiyang Chen + +[ Upstream commit b3e4fbb04220efc3bc022bcf31b5689d39c6b111 ] + +Patch series "taskstats: fix TGID dead-thread stat retention", v3. + +This series fixes a taskstats TGID aggregation bug where fields added in +the TGID query path were not preserved after thread exit, and adds a +kselftest covering the regression. + +The first patch keeps the cached TGID aggregate used for dead threads in +step with the fields already accumulated for live threads, and also fixes +the final TGID exit notification emitted when group_dead is true. + +The second patch adds a kselftest that verifies TGID CPU stats do not +regress after a worker thread exits and has been reaped. + +This patch (of 2): + +fill_stats_for_tgid() builds TGID stats from two sources: the cached +aggregate in signal->stats and a scan of the live threads in the group. + +However, fill_tgid_exit() only accumulates delay accounting into +signal->stats. This means that once a thread exits, TGID queries lose the +fields that fill_stats_for_tgid() adds for live threads. + +This gap was introduced incrementally by two earlier changes that extended +fill_stats_for_tgid() but did not make the corresponding update to +fill_tgid_exit(): + +- commit 8c733420bdd5 ("taskstats: add e/u/stime for TGID command") + added ac_etime, ac_utime, and ac_stime to the TGID query path. +- commit b663a79c1915 ("taskstats: add context-switch counters") + added nvcsw and nivcsw to the TGID query path. + +As a result, those fields were accounted for live threads in TGID queries, +but were dropped from the cached TGID aggregate after thread exit. The +final TGID exit notification emitted when group_dead is true also copies +that cached aggregate, so it loses the same fields. + +Factor the per-task TGID accumulation into tgid_stats_add_task() and use +it in both fill_stats_for_tgid() and fill_tgid_exit(). This keeps the +cached aggregate used for dead threads aligned with the live-thread +accumulation used by TGID queries. + +Link: https://lore.kernel.org/cover.1776094300.git.cyyzero16@gmail.com +Link: https://lore.kernel.org/abd2a15d33343636ab5ba43d540bcfe508bd66c7.1776094300.git.cyyzero16@gmail.com +Fixes: 8c733420bdd5 ("taskstats: add e/u/stime for TGID command") +Fixes: b663a79c1915 ("taskstats: add context-switch counters") +Signed-off-by: Yiyang Chen +Acked-by: Balbir Singh +Cc: Dr. Thomas Orgis +Cc: Oleg Nesterov +Cc: Wang Yaxin +Cc: Yang Yang +Cc: +Signed-off-by: Andrew Morton +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + kernel/taskstats.c | 62 ++++++++++++++++++++++++++++------------------------- + 1 file changed, 33 insertions(+), 29 deletions(-) + +--- a/kernel/taskstats.c ++++ b/kernel/taskstats.c +@@ -210,13 +210,39 @@ static int fill_stats_for_pid(pid_t pid, + return 0; + } + ++static void tgid_stats_add_task(struct taskstats *stats, ++ struct task_struct *tsk, u64 now_ns) ++{ ++ u64 delta, utime, stime; ++ ++ /* ++ * Each accounting subsystem calls its functions here to ++ * accumulate its per-task stats for tsk, into the per-tgid structure ++ * ++ * per-task-foo(stats, tsk); ++ */ ++ delayacct_add_tsk(stats, tsk); ++ ++ /* calculate task elapsed time in nsec */ ++ delta = now_ns - tsk->start_time; ++ /* Convert to micro seconds */ ++ do_div(delta, NSEC_PER_USEC); ++ stats->ac_etime += delta; ++ ++ task_cputime(tsk, &utime, &stime); ++ stats->ac_utime += div_u64(utime, NSEC_PER_USEC); ++ stats->ac_stime += div_u64(stime, NSEC_PER_USEC); ++ ++ stats->nvcsw += tsk->nvcsw; ++ stats->nivcsw += tsk->nivcsw; ++} ++ + static int fill_stats_for_tgid(pid_t tgid, struct taskstats *stats) + { + struct task_struct *tsk, *first; + unsigned long flags; + int rc = -ESRCH; +- u64 delta, utime, stime; +- u64 start_time; ++ u64 now_ns; + + /* + * Add additional stats from live tasks except zombie thread group +@@ -233,30 +259,12 @@ static int fill_stats_for_tgid(pid_t tgi + else + memset(stats, 0, sizeof(*stats)); + +- start_time = ktime_get_ns(); ++ now_ns = ktime_get_ns(); + for_each_thread(first, tsk) { + if (tsk->exit_state) + continue; +- /* +- * Accounting subsystem can call its functions here to +- * fill in relevant parts of struct taskstsats as follows +- * +- * per-task-foo(stats, tsk); +- */ +- delayacct_add_tsk(stats, tsk); +- +- /* calculate task elapsed time in nsec */ +- delta = start_time - tsk->start_time; +- /* Convert to micro seconds */ +- do_div(delta, NSEC_PER_USEC); +- stats->ac_etime += delta; +- +- task_cputime(tsk, &utime, &stime); +- stats->ac_utime += div_u64(utime, NSEC_PER_USEC); +- stats->ac_stime += div_u64(stime, NSEC_PER_USEC); + +- stats->nvcsw += tsk->nvcsw; +- stats->nivcsw += tsk->nivcsw; ++ tgid_stats_add_task(stats, tsk, now_ns); + } + + unlock_task_sighand(first, &flags); +@@ -275,18 +283,14 @@ out: + static void fill_tgid_exit(struct task_struct *tsk) + { + unsigned long flags; ++ u64 now_ns; + + spin_lock_irqsave(&tsk->sighand->siglock, flags); + if (!tsk->signal->stats) + goto ret; + +- /* +- * Each accounting subsystem calls its functions here to +- * accumalate its per-task stats for tsk, into the per-tgid structure +- * +- * per-task-foo(tsk->signal->stats, tsk); +- */ +- delayacct_add_tsk(tsk->signal->stats, tsk); ++ now_ns = ktime_get_ns(); ++ tgid_stats_add_task(tsk->signal->stats, tsk, now_ns); + ret: + spin_unlock_irqrestore(&tsk->sighand->siglock, flags); + return; diff --git a/queue-6.6/thunderbolt-handle-lane-bonding-of-gen-4-xdomain-links-properly.patch b/queue-6.6/thunderbolt-handle-lane-bonding-of-gen-4-xdomain-links-properly.patch new file mode 100644 index 0000000000..6f77eae0a2 --- /dev/null +++ b/queue-6.6/thunderbolt-handle-lane-bonding-of-gen-4-xdomain-links-properly.patch @@ -0,0 +1,110 @@ +From stable+bounces-289294-greg=kroah.com@vger.kernel.org Sun Jul 26 16:01:51 2026 +From: Sasha Levin +Date: Sun, 26 Jul 2026 09:59:49 -0400 +Subject: thunderbolt: Handle lane bonding of Gen 4 XDomain links properly +To: stable@vger.kernel.org +Cc: Gil Fine , Mika Westerberg , Sasha Levin +Message-ID: <20260726135955.668115-1-sashal@kernel.org> + +From: Gil Fine + +[ Upstream commit 36b6ad6ad0350554e611a8cb754ccd40857416a8 ] + +Gen 4 links come up as bonded already so we are not supposed to initiate +lane bonding on them. However, we should still update the port +structures accordingly. Split these into their own functions to make it +easier to follow. + +Signed-off-by: Gil Fine +Signed-off-by: Mika Westerberg +Stable-dep-of: 2c5d2d3c3f70 ("thunderbolt: Prevent XDomain delayed work use-after-free on disconnect") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/thunderbolt/tb.c | 2 - + drivers/thunderbolt/xdomain.c | 49 +++++++++++++++++++++++++++++++++++++++++- + 2 files changed, 48 insertions(+), 3 deletions(-) + +--- a/drivers/thunderbolt/tb.c ++++ b/drivers/thunderbolt/tb.c +@@ -539,8 +539,6 @@ static void tb_port_unconfigure_xdomain( + usb4_port_unconfigure_xdomain(port); + else + tb_lc_unconfigure_xdomain(port); +- +- tb_port_enable(port->dual_link_port); + } + + static void tb_scan_xdomain(struct tb_port *port) +--- a/drivers/thunderbolt/xdomain.c ++++ b/drivers/thunderbolt/xdomain.c +@@ -1903,6 +1903,50 @@ struct device_type tb_xdomain_type = { + }; + EXPORT_SYMBOL_GPL(tb_xdomain_type); + ++static void tb_xdomain_link_init(struct tb_xdomain *xd, struct tb_port *down) ++{ ++ if (!down->dual_link_port) ++ return; ++ ++ /* ++ * Gen 4 links come up already as bonded so only update the port ++ * structures here. ++ */ ++ if (tb_port_get_link_generation(down) >= 4) { ++ down->bonded = true; ++ down->dual_link_port->bonded = true; ++ } else { ++ xd->bonding_possible = true; ++ } ++} ++ ++static void tb_xdomain_link_exit(struct tb_xdomain *xd) ++{ ++ struct tb_port *down = tb_xdomain_downstream_port(xd); ++ ++ if (!down->dual_link_port) ++ return; ++ ++ if (tb_port_get_link_generation(down) >= 4) { ++ down->bonded = false; ++ down->dual_link_port->bonded = false; ++ } else if (xd->link_width > TB_LINK_WIDTH_SINGLE) { ++ /* ++ * Just return port structures back to way they were and ++ * update credits. No need to update userspace because ++ * the XDomain is removed soon anyway. ++ */ ++ tb_port_lane_bonding_disable(down); ++ tb_port_update_credits(down); ++ } else if (down->dual_link_port) { ++ /* ++ * Re-enable the lane 1 adapter we disabled at the end ++ * of tb_xdomain_get_properties(). ++ */ ++ tb_port_enable(down->dual_link_port); ++ } ++} ++ + /** + * tb_xdomain_alloc() - Allocate new XDomain object + * @tb: Domain where the XDomain belongs +@@ -1953,7 +1997,8 @@ struct tb_xdomain *tb_xdomain_alloc(stru + goto err_free_local_uuid; + } else { + xd->needs_uuid = true; +- xd->bonding_possible = !!down->dual_link_port; ++ ++ tb_xdomain_link_init(xd, down); + } + + device_initialize(&xd->dev); +@@ -2022,6 +2067,8 @@ void tb_xdomain_remove(struct tb_xdomain + + device_for_each_child_reverse(&xd->dev, xd, unregister_service); + ++ tb_xdomain_link_exit(xd); ++ + /* + * Undo runtime PM here explicitly because it is possible that + * the XDomain was never added to the bus and thus device_del() diff --git a/queue-6.6/thunderbolt-keep-xdomain-reference-during-the-lifetime-of-a-service.patch b/queue-6.6/thunderbolt-keep-xdomain-reference-during-the-lifetime-of-a-service.patch new file mode 100644 index 0000000000..9171c6bf94 --- /dev/null +++ b/queue-6.6/thunderbolt-keep-xdomain-reference-during-the-lifetime-of-a-service.patch @@ -0,0 +1,42 @@ +From stable+bounces-289297-greg=kroah.com@vger.kernel.org Sun Jul 26 16:00:04 2026 +From: Sasha Levin +Date: Sun, 26 Jul 2026 09:59:52 -0400 +Subject: thunderbolt: Keep XDomain reference during the lifetime of a service +To: stable@vger.kernel.org +Cc: Mika Westerberg , Sasha Levin +Message-ID: <20260726135955.668115-4-sashal@kernel.org> + +From: Mika Westerberg + +[ Upstream commit 8b4060998637f06975fceee9b73845d8672d411e ] + +This is needed because we release the service ID in tb_service_release() +and the ID array is owned by the parent XDomain. + +Signed-off-by: Mika Westerberg +Stable-dep-of: 2c5d2d3c3f70 ("thunderbolt: Prevent XDomain delayed work use-after-free on disconnect") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/thunderbolt/xdomain.c | 3 ++- + 1 file changed, 2 insertions(+), 1 deletion(-) + +--- a/drivers/thunderbolt/xdomain.c ++++ b/drivers/thunderbolt/xdomain.c +@@ -1008,6 +1008,7 @@ static void tb_service_release(struct de + ida_free(&xd->service_ids, svc->id); + kfree(svc->key); + kfree(svc); ++ tb_xdomain_put(xd); + } + + struct device_type tb_service_type = { +@@ -1116,7 +1117,7 @@ static void enumerate_services(struct tb + svc->id = id; + svc->dev.bus = &tb_bus_type; + svc->dev.type = &tb_service_type; +- svc->dev.parent = &xd->dev; ++ svc->dev.parent = get_device(&xd->dev); + dev_set_name(&svc->dev, "%s.%d", dev_name(&xd->dev), svc->id); + + tb_service_debugfs_init(svc); diff --git a/queue-6.6/thunderbolt-prevent-xdomain-delayed-work-use-after-free-on-disconnect.patch b/queue-6.6/thunderbolt-prevent-xdomain-delayed-work-use-after-free-on-disconnect.patch new file mode 100644 index 0000000000..21e9b75cdf --- /dev/null +++ b/queue-6.6/thunderbolt-prevent-xdomain-delayed-work-use-after-free-on-disconnect.patch @@ -0,0 +1,90 @@ +From stable+bounces-289300-greg=kroah.com@vger.kernel.org Sun Jul 26 16:00:09 2026 +From: Sasha Levin +Date: Sun, 26 Jul 2026 09:59:55 -0400 +Subject: thunderbolt: Prevent XDomain delayed work use-after-free on disconnect +To: stable@vger.kernel.org +Cc: Michael Bommarito , Mika Westerberg , Sasha Levin +Message-ID: <20260726135955.668115-7-sashal@kernel.org> + +From: Michael Bommarito + +[ Upstream commit 2c5d2d3c3f70cde2565d7b279b544893a2035842 ] + +tb_xdp_handle_request() runs on system_wq and queues +xd->state_work via queue_delayed_work() in three request handlers: +PROPERTIES_CHANGED_REQUEST, UUID_REQUEST (via start_handshake), +and LINK_STATE_CHANGE_REQUEST. Similarly, update_xdomain() queues +xd->properties_changed_work when local properties change. + +Concurrently, tb_xdomain_remove() calls stop_handshake() which does +cancel_delayed_work_sync() on both delayed works. Later, +tb_xdomain_unregister() calls device_unregister() which eventually +frees the xdomain. Since commit 559c1e1e0134 ("thunderbolt: Run +tb_xdp_handle_request() in system workqueue") moved the request +handler off tb->wq, the handler and the remove path are no longer +serialized. If queue_delayed_work() executes after +cancel_delayed_work_sync() but before the xdomain is freed, the +delayed work fires on a freed object. + +Add xd->removing that tb_xdomain_remove() sets under xd->lock +before calling stop_handshake(). Each external queue site holds +the same lock and checks removing before calling +queue_delayed_work(). This provides the mutual exclusion needed: +either the queue site acquires the lock first and queues work that +the subsequent cancel will see, or the remove path acquires the +lock first and the queue site observes removing == true and skips +the queue. + +Fixes: 559c1e1e0134 ("thunderbolt: Run tb_xdp_handle_request() in system workqueue") +Cc: stable@vger.kernel.org +Assisted-by: Claude:claude-opus-4-7 +Signed-off-by: Michael Bommarito +Signed-off-by: Mika Westerberg +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/thunderbolt/xdomain.c | 26 +++++++++++++------------- + 1 file changed, 13 insertions(+), 13 deletions(-) + +--- a/drivers/thunderbolt/xdomain.c ++++ b/drivers/thunderbolt/xdomain.c +@@ -905,6 +905,19 @@ void tb_unregister_service_driver(struct + } + EXPORT_SYMBOL_GPL(tb_unregister_service_driver); + ++static int update_xdomain(struct device *dev, void *data) ++{ ++ struct tb_xdomain *xd; ++ ++ xd = tb_to_xdomain(dev); ++ if (xd) { ++ queue_delayed_work(xd->tb->wq, &xd->properties_changed_work, ++ msecs_to_jiffies(50)); ++ } ++ ++ return 0; ++} ++ + static ssize_t key_show(struct device *dev, struct device_attribute *attr, + char *buf) + { +@@ -2475,19 +2488,6 @@ bool tb_xdomain_handle_request(struct tb + return ret > 0; + } + +-static int update_xdomain(struct device *dev, void *data) +-{ +- struct tb_xdomain *xd; +- +- xd = tb_to_xdomain(dev); +- if (xd) { +- queue_delayed_work(xd->tb->wq, &xd->properties_changed_work, +- msecs_to_jiffies(50)); +- } +- +- return 0; +-} +- + static void update_all_xdomains(void) + { + bus_for_each_dev(&tb_bus_type, NULL, NULL, update_xdomain); diff --git a/queue-6.6/thunderbolt-remove-service-debugfs-entries-during-unregister.patch b/queue-6.6/thunderbolt-remove-service-debugfs-entries-during-unregister.patch new file mode 100644 index 0000000000..ecf78726bc --- /dev/null +++ b/queue-6.6/thunderbolt-remove-service-debugfs-entries-during-unregister.patch @@ -0,0 +1,76 @@ +From stable+bounces-289298-greg=kroah.com@vger.kernel.org Sun Jul 26 16:00:05 2026 +From: Sasha Levin +Date: Sun, 26 Jul 2026 09:59:53 -0400 +Subject: thunderbolt: Remove service debugfs entries during unregister +To: stable@vger.kernel.org +Cc: Mika Westerberg , Sasha Levin +Message-ID: <20260726135955.668115-5-sashal@kernel.org> + +From: Mika Westerberg + +[ Upstream commit 4d5fc3f4068568dfcb8cbe2852b4adc56394aa26 ] + +We add them as part of the register path so to keep it symmetric remove +them as part of the unregister path. This also removes them even if the +service itself is not yet released (but is unregistered), thus allowing +new register with the same service name to happen. + +Signed-off-by: Mika Westerberg +Stable-dep-of: 2c5d2d3c3f70 ("thunderbolt: Prevent XDomain delayed work use-after-free on disconnect") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/thunderbolt/xdomain.c | 14 +++++++++++--- + 1 file changed, 11 insertions(+), 3 deletions(-) + +--- a/drivers/thunderbolt/xdomain.c ++++ b/drivers/thunderbolt/xdomain.c +@@ -1004,7 +1004,6 @@ static void tb_service_release(struct de + struct tb_service *svc = container_of(dev, struct tb_service, dev); + struct tb_xdomain *xd = tb_service_parent(svc); + +- tb_service_debugfs_remove(svc); + ida_free(&xd->service_ids, svc->id); + kfree(svc->key); + kfree(svc); +@@ -1019,6 +1018,14 @@ struct device_type tb_service_type = { + }; + EXPORT_SYMBOL_GPL(tb_service_type); + ++static void __unregister_service(struct device *dev) ++{ ++ struct tb_service *svc = tb_to_service(dev); ++ ++ tb_service_debugfs_remove(svc); ++ device_unregister(&svc->dev); ++} ++ + static int remove_missing_service(struct device *dev, void *data) + { + struct tb_xdomain *xd = data; +@@ -1030,7 +1037,7 @@ static int remove_missing_service(struct + + if (!tb_property_find(xd->remote_properties, svc->key, + TB_PROPERTY_TYPE_DIRECTORY)) +- device_unregister(dev); ++ __unregister_service(dev); + + return 0; + } +@@ -1123,6 +1130,7 @@ static void enumerate_services(struct tb + tb_service_debugfs_init(svc); + + if (device_register(&svc->dev)) { ++ tb_service_debugfs_remove(svc); + put_device(&svc->dev); + break; + } +@@ -2048,7 +2056,7 @@ void tb_xdomain_add(struct tb_xdomain *x + + static int unregister_service(struct device *dev, void *data) + { +- device_unregister(dev); ++ __unregister_service(dev); + return 0; + } + diff --git a/queue-6.6/thunderbolt-remove-usage-of-the-deprecated-ida_simple_xx-api.patch b/queue-6.6/thunderbolt-remove-usage-of-the-deprecated-ida_simple_xx-api.patch new file mode 100644 index 0000000000..bbe56395cd --- /dev/null +++ b/queue-6.6/thunderbolt-remove-usage-of-the-deprecated-ida_simple_xx-api.patch @@ -0,0 +1,159 @@ +From stable+bounces-289295-greg=kroah.com@vger.kernel.org Sun Jul 26 16:02:04 2026 +From: Sasha Levin +Date: Sun, 26 Jul 2026 09:59:50 -0400 +Subject: thunderbolt: Remove usage of the deprecated ida_simple_xx() API +To: stable@vger.kernel.org +Cc: Christophe JAILLET , Mika Westerberg , Sasha Levin +Message-ID: <20260726135955.668115-2-sashal@kernel.org> + +From: Christophe JAILLET + +[ Upstream commit dec6a613574cd3dea799170b7aaa8fd76e22f176 ] + +ida_alloc() and ida_free() should be preferred to the deprecated +ida_simple_get() and ida_simple_remove(). + +Note that the upper limit of ida_simple_get() is exclusive, but the one of +ida_alloc_range()/ida_alloc_max() is inclusive. So a -1 has been added +when needed. + +Signed-off-by: Christophe JAILLET +Signed-off-by: Mika Westerberg +Stable-dep-of: 2c5d2d3c3f70 ("thunderbolt: Prevent XDomain delayed work use-after-free on disconnect") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/thunderbolt/domain.c | 6 +++--- + drivers/thunderbolt/nhi.c | 6 +++--- + drivers/thunderbolt/nvm.c | 4 ++-- + drivers/thunderbolt/switch.c | 6 +++--- + drivers/thunderbolt/xdomain.c | 4 ++-- + 5 files changed, 13 insertions(+), 13 deletions(-) + +--- a/drivers/thunderbolt/domain.c ++++ b/drivers/thunderbolt/domain.c +@@ -321,7 +321,7 @@ static void tb_domain_release(struct dev + + tb_ctl_free(tb->ctl); + destroy_workqueue(tb->wq); +- ida_simple_remove(&tb_domain_ida, tb->index); ++ ida_free(&tb_domain_ida, tb->index); + mutex_destroy(&tb->lock); + kfree(tb); + } +@@ -389,7 +389,7 @@ struct tb *tb_domain_alloc(struct tb_nhi + tb->nhi = nhi; + mutex_init(&tb->lock); + +- tb->index = ida_simple_get(&tb_domain_ida, 0, 0, GFP_KERNEL); ++ tb->index = ida_alloc(&tb_domain_ida, GFP_KERNEL); + if (tb->index < 0) + goto err_free; + +@@ -413,7 +413,7 @@ struct tb *tb_domain_alloc(struct tb_nhi + err_destroy_wq: + destroy_workqueue(tb->wq); + err_remove_ida: +- ida_simple_remove(&tb_domain_ida, tb->index); ++ ida_free(&tb_domain_ida, tb->index); + err_free: + kfree(tb); + +--- a/drivers/thunderbolt/nhi.c ++++ b/drivers/thunderbolt/nhi.c +@@ -465,7 +465,7 @@ static int ring_request_msix(struct tb_r + if (!nhi->pdev->msix_enabled) + return 0; + +- ret = ida_simple_get(&nhi->msix_ida, 0, MSIX_MAX_VECS, GFP_KERNEL); ++ ret = ida_alloc_max(&nhi->msix_ida, MSIX_MAX_VECS - 1, GFP_KERNEL); + if (ret < 0) + return ret; + +@@ -485,7 +485,7 @@ static int ring_request_msix(struct tb_r + return 0; + + err_ida_remove: +- ida_simple_remove(&nhi->msix_ida, ring->vector); ++ ida_free(&nhi->msix_ida, ring->vector); + + return ret; + } +@@ -496,7 +496,7 @@ static void ring_release_msix(struct tb_ + return; + + free_irq(ring->irq, ring); +- ida_simple_remove(&ring->nhi->msix_ida, ring->vector); ++ ida_free(&ring->nhi->msix_ida, ring->vector); + ring->vector = 0; + ring->irq = 0; + } +--- a/drivers/thunderbolt/nvm.c ++++ b/drivers/thunderbolt/nvm.c +@@ -330,7 +330,7 @@ struct tb_nvm *tb_nvm_alloc(struct devic + if (!nvm) + return ERR_PTR(-ENOMEM); + +- ret = ida_simple_get(&nvm_ida, 0, 0, GFP_KERNEL); ++ ret = ida_alloc(&nvm_ida, GFP_KERNEL); + if (ret < 0) { + kfree(nvm); + return ERR_PTR(ret); +@@ -528,7 +528,7 @@ void tb_nvm_free(struct tb_nvm *nvm) + nvmem_unregister(nvm->non_active); + nvmem_unregister(nvm->active); + vfree(nvm->buf); +- ida_simple_remove(&nvm_ida, nvm->id); ++ ida_free(&nvm_ida, nvm->id); + } + kfree(nvm); + } +--- a/drivers/thunderbolt/switch.c ++++ b/drivers/thunderbolt/switch.c +@@ -777,7 +777,7 @@ static int tb_port_alloc_hopid(struct tb + if (max_hopid < 0 || max_hopid > port_max_hopid) + max_hopid = port_max_hopid; + +- return ida_simple_get(ida, min_hopid, max_hopid + 1, GFP_KERNEL); ++ return ida_alloc_range(ida, min_hopid, max_hopid, GFP_KERNEL); + } + + /** +@@ -815,7 +815,7 @@ int tb_port_alloc_out_hopid(struct tb_po + */ + void tb_port_release_in_hopid(struct tb_port *port, int hopid) + { +- ida_simple_remove(&port->in_hopids, hopid); ++ ida_free(&port->in_hopids, hopid); + } + + /** +@@ -825,7 +825,7 @@ void tb_port_release_in_hopid(struct tb_ + */ + void tb_port_release_out_hopid(struct tb_port *port, int hopid) + { +- ida_simple_remove(&port->out_hopids, hopid); ++ ida_free(&port->out_hopids, hopid); + } + + static inline bool tb_switch_is_reachable(const struct tb_switch *parent, +--- a/drivers/thunderbolt/xdomain.c ++++ b/drivers/thunderbolt/xdomain.c +@@ -1005,7 +1005,7 @@ static void tb_service_release(struct de + struct tb_xdomain *xd = tb_service_parent(svc); + + tb_service_debugfs_remove(svc); +- ida_simple_remove(&xd->service_ids, svc->id); ++ ida_free(&xd->service_ids, svc->id); + kfree(svc->key); + kfree(svc); + } +@@ -1107,7 +1107,7 @@ static void enumerate_services(struct tb + break; + } + +- id = ida_simple_get(&xd->service_ids, 0, 0, GFP_KERNEL); ++ id = ida_alloc(&xd->service_ids, GFP_KERNEL); + if (id < 0) { + kfree(svc->key); + kfree(svc); diff --git a/queue-6.6/thunderbolt-remove-xdomain-from-the-bus-without-holding-tb-lock.patch b/queue-6.6/thunderbolt-remove-xdomain-from-the-bus-without-holding-tb-lock.patch new file mode 100644 index 0000000000..f41c1de7ae --- /dev/null +++ b/queue-6.6/thunderbolt-remove-xdomain-from-the-bus-without-holding-tb-lock.patch @@ -0,0 +1,337 @@ +From stable+bounces-289299-greg=kroah.com@vger.kernel.org Sun Jul 26 16:00:09 2026 +From: Sasha Levin +Date: Sun, 26 Jul 2026 09:59:54 -0400 +Subject: thunderbolt: Remove XDomain from the bus without holding tb->lock +To: stable@vger.kernel.org +Cc: Mika Westerberg , Sasha Levin +Message-ID: <20260726135955.668115-6-sashal@kernel.org> + +From: Mika Westerberg + +[ Upstream commit a8937f35cf39c39c64325aa84d0463d866850857 ] + +Currently we call device_unregister() for services and the XDomain +itself with tb->lock held. This prevents the service drivers from +calling any functions that may take it. For this reason separate +removing the XDomain from the topology data structures (where we need +the lock) from unregistering the device from the bus (where remove +callbacks of the drivers are being called). + +Signed-off-by: Mika Westerberg +Stable-dep-of: 2c5d2d3c3f70 ("thunderbolt: Prevent XDomain delayed work use-after-free on disconnect") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/thunderbolt/debugfs.c | 2 + + drivers/thunderbolt/domain.c | 30 +++++++++++++++++++++ + drivers/thunderbolt/icm.c | 5 +++ + drivers/thunderbolt/switch.c | 14 +++++++++ + drivers/thunderbolt/tb.c | 59 ++++++++++++++++++++---------------------- + drivers/thunderbolt/tb.h | 2 + + drivers/thunderbolt/xdomain.c | 53 +++++++++++++++++++++++-------------- + 7 files changed, 115 insertions(+), 50 deletions(-) + +--- a/drivers/thunderbolt/debugfs.c ++++ b/drivers/thunderbolt/debugfs.c +@@ -956,6 +956,8 @@ static void margining_port_remove(struct + + if (!port->usb4) + return; ++ if (!port->usb4->margining) ++ return; + + snprintf(dir_name, sizeof(dir_name), "port%d", port->port); + parent = debugfs_lookup(dir_name, port->sw->debugfs_dir); +--- a/drivers/thunderbolt/domain.c ++++ b/drivers/thunderbolt/domain.c +@@ -867,6 +867,36 @@ int tb_domain_disconnect_all_paths(struc + return bus_for_each_dev(&tb_bus_type, NULL, tb, disconnect_xdomain); + } + ++struct unregister_context { ++ const struct tb *tb; ++ int n; ++}; ++ ++static int unregister_unplugged_xdomain(struct device *dev, void *data) ++{ ++ struct unregister_context *ctx = data; ++ struct tb_xdomain *xd; ++ ++ xd = tb_to_xdomain(dev); ++ if (xd && xd->tb == ctx->tb && xd->is_unplugged) { ++ tb_xdomain_unregister(xd); ++ ctx->n++; ++ } ++ return 0; ++} ++ ++int tb_domain_unregister_unplugged_xdomains(struct tb *tb) ++{ ++ struct unregister_context ctx; ++ ++ ctx.tb = tb_domain_get(tb); ++ ctx.n = 0; ++ bus_for_each_dev(&tb_bus_type, NULL, &ctx, unregister_unplugged_xdomain); ++ tb_domain_put(tb); ++ ++ return ctx.n; ++} ++ + int tb_domain_init(void) + { + int ret; +--- a/drivers/thunderbolt/icm.c ++++ b/drivers/thunderbolt/icm.c +@@ -713,6 +713,7 @@ static void remove_xdomain(struct tb_xdo + + sw = tb_to_switch(xd->dev.parent); + tb_port_at(xd->route, sw)->xdomain = NULL; ++ xd->is_unplugged = true; + tb_xdomain_remove(xd); + } + +@@ -1728,6 +1729,8 @@ static void icm_handle_notification(stru + + kfree(n->pkg); + kfree(n); ++ ++ tb_domain_unregister_unplugged_xdomains(tb); + } + + static void icm_handle_event(struct tb *tb, enum tb_cfg_pkg_type type, +@@ -2078,6 +2081,8 @@ static void icm_rescan_work(struct work_ + if (tb->root_switch) + icm_free_unplugged_children(tb->root_switch); + mutex_unlock(&tb->lock); ++ ++ tb_domain_unregister_unplugged_xdomains(tb); + } + + static void icm_complete(struct tb *tb) +--- a/drivers/thunderbolt/switch.c ++++ b/drivers/thunderbolt/switch.c +@@ -3556,6 +3556,20 @@ int tb_switch_resume(struct tb_switch *s + tb_port_warn(port, + "lost during suspend, disconnecting\n"); + tb_sw_set_unplugged(port->remote->sw); ++ } else if (port->xdomain) { ++ /* ++ * If the user replaced the XDomain with ++ * another router, this will succeed in ++ * which case we must remove the XDomain ++ * before adding the new router. ++ */ ++ err = tb_cfg_get_upstream_port(sw->tb->ctl, ++ port->xdomain->route); ++ if (err > 0) { ++ tb_port_warn(port, ++ "XDomain was disconnected\n"); ++ port->xdomain->is_unplugged = true; ++ } + } + } + } +--- a/drivers/thunderbolt/tb.c ++++ b/drivers/thunderbolt/tb.c +@@ -2319,6 +2319,8 @@ put_sw: + out: + mutex_unlock(&tb->lock); + ++ tb_domain_unregister_unplugged_xdomains(tb); ++ + pm_runtime_mark_last_busy(&tb->dev); + pm_runtime_put_autosuspend(&tb->dev); + +@@ -2787,6 +2789,24 @@ static void tb_restore_children(struct t + } + } + ++static void tb_free_unplugged_xdomains(struct tb_switch *sw) ++{ ++ struct tb_port *port; ++ ++ tb_switch_for_each_port(sw, port) { ++ if (tb_is_upstream_port(port)) ++ continue; ++ if (port->xdomain && port->xdomain->is_unplugged) { ++ tb_retimer_remove_all(port); ++ tb_xdomain_remove(port->xdomain); ++ tb_port_unconfigure_xdomain(port); ++ port->xdomain = NULL; ++ } else if (port->remote) { ++ tb_free_unplugged_xdomains(port->remote->sw); ++ } ++ } ++} ++ + static int tb_resume_noirq(struct tb *tb) + { + struct tb_cm *tcm = tb_priv(tb); +@@ -2806,6 +2826,7 @@ static int tb_resume_noirq(struct tb *tb + tb_switch_resume(tb->root_switch, false); + tb_free_invalid_tunnels(tb); + tb_free_unplugged_children(tb->root_switch); ++ tb_free_unplugged_xdomains(tb->root_switch); + tb_restore_children(tb->root_switch); + + /* +@@ -2848,28 +2869,6 @@ static int tb_resume_noirq(struct tb *tb + return 0; + } + +-static int tb_free_unplugged_xdomains(struct tb_switch *sw) +-{ +- struct tb_port *port; +- int ret = 0; +- +- tb_switch_for_each_port(sw, port) { +- if (tb_is_upstream_port(port)) +- continue; +- if (port->xdomain && port->xdomain->is_unplugged) { +- tb_retimer_remove_all(port); +- tb_xdomain_remove(port->xdomain); +- tb_port_unconfigure_xdomain(port); +- port->xdomain = NULL; +- ret++; +- } else if (port->remote) { +- ret += tb_free_unplugged_xdomains(port->remote->sw); +- } +- } +- +- return ret; +-} +- + static int tb_freeze_noirq(struct tb *tb) + { + struct tb_cm *tcm = tb_priv(tb); +@@ -2889,14 +2888,14 @@ static int tb_thaw_noirq(struct tb *tb) + static void tb_complete(struct tb *tb) + { + /* +- * Release any unplugged XDomains and if there is a case where ++ * Unregister unplugged XDomains and if there is a case where + * another domain is swapped in place of unplugged XDomain we + * need to run another rescan. + */ +- mutex_lock(&tb->lock); +- if (tb_free_unplugged_xdomains(tb->root_switch)) +- tb_scan_switch(tb->root_switch); +- mutex_unlock(&tb->lock); ++ if (tb_domain_unregister_unplugged_xdomains(tb)) { ++ scoped_guard(mutex, &tb->lock) ++ tb_scan_switch(tb->root_switch); ++ } + } + + static int tb_runtime_suspend(struct tb *tb) +@@ -2923,11 +2922,11 @@ static void tb_remove_work(struct work_s + struct tb *tb = tcm_to_tb(tcm); + + mutex_lock(&tb->lock); +- if (tb->root_switch) { ++ if (tb->root_switch) + tb_free_unplugged_children(tb->root_switch); +- tb_free_unplugged_xdomains(tb->root_switch); +- } + mutex_unlock(&tb->lock); ++ ++ tb_free_unplugged_xdomains(tb->root_switch); + } + + static int tb_runtime_resume(struct tb *tb) +--- a/drivers/thunderbolt/tb.h ++++ b/drivers/thunderbolt/tb.h +@@ -752,6 +752,7 @@ int tb_domain_disconnect_xdomain_paths(s + int transmit_path, int transmit_ring, + int receive_path, int receive_ring); + int tb_domain_disconnect_all_paths(struct tb *tb); ++int tb_domain_unregister_unplugged_xdomains(struct tb *tb); + + static inline struct tb *tb_domain_get(struct tb *tb) + { +@@ -1200,6 +1201,7 @@ struct tb_xdomain *tb_xdomain_alloc(stru + const uuid_t *remote_uuid); + void tb_xdomain_add(struct tb_xdomain *xd); + void tb_xdomain_remove(struct tb_xdomain *xd); ++void tb_xdomain_unregister(struct tb_xdomain *xd); + struct tb_xdomain *tb_xdomain_find_by_link_depth(struct tb *tb, u8 link, + u8 depth); + +--- a/drivers/thunderbolt/xdomain.c ++++ b/drivers/thunderbolt/xdomain.c +@@ -2061,41 +2061,54 @@ static int unregister_service(struct dev + } + + /** +- * tb_xdomain_remove() - Remove XDomain from the bus ++ * tb_xdomain_remove() - Remove XDomain + * @xd: XDomain to remove + * +- * This will stop all ongoing configuration work and remove the XDomain +- * along with any services from the bus. When the last reference to @xd +- * is released the object will be released as well. ++ * This will stop all ongoing configuration work. XDomain is not removed ++ * from the bus if it was added. That needs to be done separately by ++ * calling tb_xdomain_unregister(). ++ * ++ * Called with @tb->lock held. + */ + void tb_xdomain_remove(struct tb_xdomain *xd) + { + tb_xdomain_debugfs_remove(xd); +- + stop_handshake(xd); +- +- device_for_each_child_reverse(&xd->dev, xd, unregister_service); +- + tb_xdomain_link_exit(xd); + +- /* +- * Undo runtime PM here explicitly because it is possible that +- * the XDomain was never added to the bus and thus device_del() +- * is not called for it (device_del() would handle this otherwise). +- */ +- pm_runtime_disable(&xd->dev); +- pm_runtime_put_noidle(&xd->dev); +- pm_runtime_set_suspended(&xd->dev); +- + if (!device_is_registered(&xd->dev)) { ++ /* ++ * Undo runtime PM here explicitly because it is ++ * possible that the XDomain was never added to the bus ++ * and thus device_del() is not called for it ++ * (device_del() would handle this otherwise). ++ */ ++ pm_runtime_disable(&xd->dev); ++ pm_runtime_put_noidle(&xd->dev); ++ pm_runtime_set_suspended(&xd->dev); + put_device(&xd->dev); +- } else { +- dev_info(&xd->dev, "host disconnected\n"); +- device_unregister(&xd->dev); + } + } + + /** ++ * tb_xdomain_unregister() - Unregister XDomain ++ * @xd: XDomain to unregister ++ * ++ * This will unregister the XDomain along with any services from the ++ * bus. When the last reference to @xd is released the object will be ++ * released as well. ++ */ ++void tb_xdomain_unregister(struct tb_xdomain *xd) ++{ ++ lockdep_assert_not_held(&xd->tb->lock); ++ ++ device_for_each_child_reverse(&xd->dev, xd, unregister_service); ++ ++ dev_info(&xd->dev, "host disconnected\n"); ++ device_unregister(&xd->dev); ++} ++ ++/** + * tb_xdomain_lane_bonding_enable() - Enable lane bonding on XDomain + * @xd: XDomain connection + * diff --git a/queue-6.6/thunderbolt-update-property.c-function-documentation.patch b/queue-6.6/thunderbolt-update-property.c-function-documentation.patch new file mode 100644 index 0000000000..f01daf2095 --- /dev/null +++ b/queue-6.6/thunderbolt-update-property.c-function-documentation.patch @@ -0,0 +1,137 @@ +From stable+bounces-289296-greg=kroah.com@vger.kernel.org Sun Jul 26 16:00:04 2026 +From: Sasha Levin +Date: Sun, 26 Jul 2026 09:59:51 -0400 +Subject: thunderbolt: Update property.c function documentation +To: stable@vger.kernel.org +Cc: Alan Borzeszkowski , Mika Westerberg , Sasha Levin +Message-ID: <20260726135955.668115-3-sashal@kernel.org> + +From: Alan Borzeszkowski + +[ Upstream commit d015642ad36d78e6eba12d8ab96cea6fd4602b49 ] + +Make property.c function documentation compliant with current kernel-doc +standards. No functional changes. + +Signed-off-by: Alan Borzeszkowski +Signed-off-by: Mika Westerberg +Stable-dep-of: 2c5d2d3c3f70 ("thunderbolt: Prevent XDomain delayed work use-after-free on disconnect") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/thunderbolt/property.c | 38 ++++++++++++++++++++++++++++---------- + 1 file changed, 28 insertions(+), 10 deletions(-) + +--- a/drivers/thunderbolt/property.c ++++ b/drivers/thunderbolt/property.c +@@ -231,11 +231,13 @@ static struct tb_property_dir *__tb_prop + * + * This function parses the XDomain properties data block into format that + * can be traversed using the helper functions provided by this module. +- * Upon success returns the parsed directory. In case of error returns +- * %NULL. The resulting &struct tb_property_dir needs to be released by ++ * ++ * The resulting &struct tb_property_dir needs to be released by + * calling tb_property_free_dir() when not needed anymore. + * + * The @block is expected to be root directory. ++ * ++ * Return: Pointer to &struct tb_property_dir, %NULL in case of failure. + */ + struct tb_property_dir *tb_property_parse_dir(const u32 *block, + size_t block_len) +@@ -258,6 +260,8 @@ struct tb_property_dir *tb_property_pars + * + * Creates new, empty property directory. If @uuid is %NULL then the + * directory is assumed to be root directory. ++ * ++ * Return: Pointer to &struct tb_property_dir, %NULL in case of failure. + */ + struct tb_property_dir *tb_property_create_dir(const uuid_t *uuid) + { +@@ -501,9 +505,11 @@ static ssize_t __tb_property_format_dir( + * @block_len: Length of the property block + * + * This function formats the directory to the packed format that can be +- * then send over the thunderbolt fabric to receiving host. Returns %0 in +- * case of success and negative errno on faulure. Passing %NULL in @block +- * returns number of entries the block takes. ++ * then sent over the thunderbolt fabric to receiving host. ++ * ++ * Passing %NULL in @block returns number of entries the block takes. ++ * ++ * Return: %0 on success, negative errno otherwise. + */ + ssize_t tb_property_format_dir(const struct tb_property_dir *dir, u32 *block, + size_t block_len) +@@ -525,9 +531,9 @@ ssize_t tb_property_format_dir(const str + * tb_property_copy_dir() - Take a deep copy of directory + * @dir: Directory to copy + * +- * This function takes a deep copy of @dir and returns back the copy. In +- * case of error returns %NULL. The resulting directory needs to be +- * released by calling tb_property_free_dir(). ++ * The resulting directory needs to be released by calling tb_property_free_dir(). ++ * ++ * Return: Pointer to &struct tb_property_dir, %NULL in case of failure. + */ + struct tb_property_dir *tb_property_copy_dir(const struct tb_property_dir *dir) + { +@@ -597,6 +603,8 @@ err_free: + * @parent: Directory to add the property + * @key: Key for the property + * @value: Immediate value to store with the property ++ * ++ * Return: %0 on success, negative errno otherwise. + */ + int tb_property_add_immediate(struct tb_property_dir *parent, const char *key, + u32 value) +@@ -626,6 +634,8 @@ EXPORT_SYMBOL_GPL(tb_property_add_immedi + * @buflen: Number of bytes in the data buffer + * + * Function takes a copy of @buf and adds it to the directory. ++ * ++ * Return: %0 on success, negative errno otherwise. + */ + int tb_property_add_data(struct tb_property_dir *parent, const char *key, + const void *buf, size_t buflen) +@@ -662,6 +672,8 @@ EXPORT_SYMBOL_GPL(tb_property_add_data); + * @text: String to add + * + * Function takes a copy of @text and adds it to the directory. ++ * ++ * Return: %0 on success, negative errno otherwise. + */ + int tb_property_add_text(struct tb_property_dir *parent, const char *key, + const char *text) +@@ -696,6 +708,8 @@ EXPORT_SYMBOL_GPL(tb_property_add_text); + * @parent: Directory to add the property + * @key: Key for the property + * @dir: Directory to add ++ * ++ * Return: %0 on success, negative errno otherwise. + */ + int tb_property_add_dir(struct tb_property_dir *parent, const char *key, + struct tb_property_dir *dir) +@@ -736,8 +750,10 @@ EXPORT_SYMBOL_GPL(tb_property_remove); + * @key: Key to look for + * @type: Type of the property + * +- * Finds and returns property from the given directory. Does not recurse +- * into sub-directories. Returns %NULL if the property was not found. ++ * Finds and returns property from the given directory. Does not ++ * recurse into sub-directories. ++ * ++ * Return: Pointer to &struct tb_property, %NULL if the property was not found. + */ + struct tb_property *tb_property_find(struct tb_property_dir *dir, + const char *key, enum tb_property_type type) +@@ -757,6 +773,8 @@ EXPORT_SYMBOL_GPL(tb_property_find); + * tb_property_get_next() - Get next property from directory + * @dir: Directory holding properties + * @prev: Previous property in the directory (%NULL returns the first) ++ * ++ * Return: Pointer to &struct tb_property, %NULL if property was not found. + */ + struct tb_property *tb_property_get_next(struct tb_property_dir *dir, + struct tb_property *prev) diff --git a/queue-6.6/tipc-restrict-socket-queue-dumps-in-enqueue-tracepoints.patch b/queue-6.6/tipc-restrict-socket-queue-dumps-in-enqueue-tracepoints.patch new file mode 100644 index 0000000000..9f61b5ac49 --- /dev/null +++ b/queue-6.6/tipc-restrict-socket-queue-dumps-in-enqueue-tracepoints.patch @@ -0,0 +1,97 @@ +From stable+bounces-288004-greg=kroah.com@vger.kernel.org Wed Jul 22 19:34:35 2026 +From: Sasha Levin +Date: Wed, 22 Jul 2026 13:29:40 -0400 +Subject: tipc: restrict socket queue dumps in enqueue tracepoints +To: stable@vger.kernel.org +Cc: Li Xiasong , Tung Nguyen , Jakub Kicinski , Sasha Levin +Message-ID: <20260722172940.2067866-1-sashal@kernel.org> + +From: Li Xiasong + +[ Upstream commit acd7df8d955480a6f6e5bb809da67b1500cc3cf4 ] + +tipc_sk_enqueue() runs with sk->sk_lock.slock held while the socket is +owned by user context. The spinlock protects the backlog queue in this +path, but it does not serialize against the socket owner consuming or +purging sk_receive_queue. + +KASAN reported: + + CPU: 14 UID: 0 PID: 1050 Comm: tipc3 Not tainted 7.1.0-rc6+ #126 PREEMPT(lazy) + Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.15.0-1 04/01/2014 + Call Trace: + + dump_stack_lvl+0x76/0xa0 lib/dump_stack.c:123 + print_report+0xce/0x5b0 mm/kasan/report.c:482 + kasan_report+0xc6/0x100 mm/kasan/report.c:597 + __asan_report_load4_noabort+0x14/0x30 mm/kasan/report_generic.c:380 + tipc_skb_dump+0x1327/0x16f0 net/tipc/trace.c:73 + tipc_list_dump+0x208/0x2e0 net/tipc/trace.c:187 + tipc_sk_dump+0xaf6/0xd60 net/tipc/socket.c:3996 + trace_event_raw_event_tipc_sk_class+0x312/0x5a0 net/tipc/trace.h:188 + tipc_sk_rcv+0xb1d/0x1d50 net/tipc/socket.c:2497 + tipc_node_xmit+0x1c3/0x1440 net/tipc/node.c:1689 + __tipc_sendmsg+0x97a/0x1440 net/tipc/socket.c:1512 + tipc_sendmsg+0x52/0x80 net/tipc/socket.c:1400 + sock_sendmsg+0x2f6/0x3e0 net/socket.c:825 + splice_to_socket+0x7f9/0x1010 fs/splice.c:884 + do_splice+0xe21/0x2330 fs/splice.c:936 + __do_splice+0x153/0x260 fs/splice.c:1431 + __x64_sys_splice+0x150/0x230 fs/splice.c:1616 + x64_sys_call+0xeb5/0x2790 arch/x86/entry/syscall_64.c:41 + do_syscall_64+0xf3/0x620 arch/x86/entry/syscall_64.c:63 + entry_SYSCALL_64_after_hwframe+0x76/0x7e arch/x86/entry/entry_64.S:130 + RIP: 0033:0x71624e8aafe2 + Code: 08 0f 85 71 3a ff ff 49 89 fb 48 89 f0 48 89 d7 48 89 ce 4c 89 c2 4d 89 ca 4c 8b 44 24 08 4c 8b 4c 24 10 4c 89 5c 24 08 0f 05 66 2e 0f 1f 84 00 00 00 00 00 66 2e 0f 1f 84 00 00 00 00 00 66 + RSP: 002b:0000716157ffed68 EFLAGS: 00000246 ORIG_RAX: 0000000000000113 + RAX: ffffffffffffffda RBX: 0000716157fff6c0 RCX: 000071624e8aafe2 + RDX: 000000000000005f RSI: 0000000000000000 RDI: 0000000000000066 + RBP: 0000716157ffed90 R08: 0000000000008000 R09: 0000000000000001 + R10: 0000000000000000 R11: 0000000000000246 R12: ffffffffffffff00 + R13: 0000000000000021 R14: 0000000000000000 R15: 00007fff89799c40 + + +The TIPC_DUMP_ALL tracepoints in tipc_sk_enqueue() also dump +sk_receive_queue and can therefore dereference skbs that the socket +owner has already dequeued or freed. Restrict these dumps to +TIPC_DUMP_SK_BKLGQ, which matches the queue protected by the held +spinlock. + +Keep the change limited to the enqueue path, where the unsafe queue dump +is reachable while the socket is owned by user context. + +Fixes: 01e661ebfbad ("tipc: add trace_events for tipc socket") +Cc: stable@vger.kernel.org +Signed-off-by: Li Xiasong +Reviewed-by: Tung Nguyen +Link: https://patch.msgid.link/20260611135647.3666727-1-lixiasong1@huawei.com +Signed-off-by: Jakub Kicinski +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + net/tipc/socket.c | 6 +++--- + 1 file changed, 3 insertions(+), 3 deletions(-) + +--- a/net/tipc/socket.c ++++ b/net/tipc/socket.c +@@ -2461,17 +2461,17 @@ static void tipc_sk_enqueue(struct sk_bu + atomic_set(dcnt, 0); + lim = rcvbuf_limit(sk, skb) + atomic_read(dcnt); + if (likely(!sk_add_backlog(sk, skb, lim))) { +- trace_tipc_sk_overlimit1(sk, skb, TIPC_DUMP_ALL, ++ trace_tipc_sk_overlimit1(sk, skb, TIPC_DUMP_SK_BKLGQ, + "bklg & rcvq >90% allocated!"); + continue; + } + +- trace_tipc_sk_dump(sk, skb, TIPC_DUMP_ALL, "err_overload!"); ++ trace_tipc_sk_dump(sk, skb, TIPC_DUMP_SK_BKLGQ, "err_overload!"); + /* Overload => reject message back to sender */ + onode = tipc_own_addr(sock_net(sk)); + atomic_inc(&sk->sk_drops); + if (tipc_msg_reverse(onode, &skb, TIPC_ERR_OVERLOAD)) { +- trace_tipc_sk_rej_msg(sk, skb, TIPC_DUMP_ALL, ++ trace_tipc_sk_rej_msg(sk, skb, TIPC_DUMP_SK_BKLGQ, + "@sk_enqueue!"); + __skb_queue_tail(xmitq, skb); + } diff --git a/queue-6.6/tracing-user_events-fix-use-after-free-in-user_event_mm_dup.patch b/queue-6.6/tracing-user_events-fix-use-after-free-in-user_event_mm_dup.patch new file mode 100644 index 0000000000..eab56495d7 --- /dev/null +++ b/queue-6.6/tracing-user_events-fix-use-after-free-in-user_event_mm_dup.patch @@ -0,0 +1,148 @@ +From stable+bounces-289751-greg=kroah.com@vger.kernel.org Tue Jul 28 01:40:14 2026 +From: Sasha Levin +Date: Mon, 27 Jul 2026 19:37:50 -0400 +Subject: tracing/user_events: Fix use-after-free in user_event_mm_dup() +To: stable@vger.kernel.org +Cc: Michael Bommarito , XIAO WU , Beau Belgrave , Steven Rostedt , Sasha Levin +Message-ID: <20260727233750.2439387-3-sashal@kernel.org> + +From: Michael Bommarito + +[ Upstream commit 50fd6dd755c6e48a38af2fa4621167eea56829c2 ] + +user_event_mm_dup() walks the parent mm's enabler list locklessly under +rcu_read_lock() during fork() (from copy_process()); it does not take +event_mutex: + + rcu_read_lock(); + list_for_each_entry_rcu(enabler, &old_mm->enablers, mm_enablers_link) + enabler->event = user_event_get(orig->event); + +user_event_enabler_destroy() removes an enabler from that list with +list_del_rcu() and then, without waiting for a grace period, drops the +enabler's user_event reference with user_event_put() and frees the enabler +with kfree(). A reader that loaded the enabler before the list_del_rcu() +can still be walking it, which leads to two use-after-frees: + + - kfree(enabler) frees the enabler while that reader dereferences + enabler->event. + + - user_event_put() may drop the last reference to the user_event, which + is then freed (via delayed_destroy_user_event() on a work queue), while + the same reader does user_event_get(orig->event) on it. + +Both are reachable by an unprivileged task that can open user_events_data: +one multithreaded process that registers an enabler and then concurrently +unregisters it and calls fork() triggers the race. KASAN reports a +slab-use-after-free in user_event_mm_dup() during clone(), with a +"refcount_t: addition on 0" warning when the user_event is freed. + +The enabler use-after-free was found first; the user_event one was reported +by XIAO WU, and the earlier enabler-only fix did not address it. + +Defer both the user_event_put() and the kfree(enabler) to a work item +queued with queue_rcu_work(), so they run only after an RCU grace period, +once all readers walking the enabler list have finished. The put must run +in process context because user_event_put() takes event_mutex on the last +reference, so a work queue is used rather than call_rcu(). The now-unlocked +put lets the locked argument of user_event_enabler_destroy() be removed; +all callers are updated. + +Fixes: 7235759084a4 ("tracing/user_events: Use remote writes for event enablement") +Cc: stable@vger.kernel.org +Link: https://patch.msgid.link/20260707165912.2560537-2-michael.bommarito@gmail.com +Reported-by: XIAO WU +Closes: https://lore.kernel.org/all/tencent_89647CE40DC452B891C65C94D1B271DE8E07@qq.com/ +Suggested-by: Beau Belgrave +Assisted-by: Claude:claude-opus-4-8 +Signed-off-by: Michael Bommarito +Signed-off-by: Steven Rostedt +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + kernel/trace/trace_events_user.c | 39 ++++++++++++++++++++++++++++++++------- + 1 file changed, 32 insertions(+), 7 deletions(-) + +--- a/kernel/trace/trace_events_user.c ++++ b/kernel/trace/trace_events_user.c +@@ -104,6 +104,9 @@ struct user_event_enabler { + + /* Track enable bit, flags, etc. Aligned for bitops. */ + unsigned long values; ++ ++ /* Defer the event put and enabler free past an RCU grace period. */ ++ struct rcu_work put_rwork; + }; + + /* Bits 0-5 are for the bit to update upon enable/disable (0-63 allowed) */ +@@ -378,17 +381,39 @@ error: + return NULL; + }; + +-static void user_event_enabler_destroy(struct user_event_enabler *enabler, +- bool locked) ++static void delayed_user_event_enabler_put(struct work_struct *work) + { +- list_del_rcu(&enabler->mm_enablers_link); ++ struct user_event_enabler *enabler = container_of(to_rcu_work(work), ++ struct user_event_enabler, put_rwork); + + /* No longer tracking the event via the enabler */ +- user_event_put(enabler->event, locked); ++ user_event_put(enabler->event, false); + ++ /* Run from queue_rcu_work(), the RCU grace period has elapsed */ + kfree(enabler); + } + ++static void user_event_enabler_destroy(struct user_event_enabler *enabler) ++{ ++ list_del_rcu(&enabler->mm_enablers_link); ++ ++ /* ++ * The enabler is removed from an RCU-traversed list ++ * (user_event_mm_dup() walks mm->enablers under rcu_read_lock() only), ++ * and readers there dereference enabler->event and take a new ref on ++ * it. Both the put of that event reference and the free of the enabler ++ * therefore have to wait for a grace period so no reader can be looking ++ * at the enabler or racing the last put of its event. ++ * ++ * The put itself must not run in RCU context: when it drops the last ++ * reference user_event_put() takes event_mutex, which cannot be taken ++ * from a softirq/RCU callback. Defer both to a work item scheduled ++ * after a grace period via queue_rcu_work(). ++ */ ++ INIT_RCU_WORK(&enabler->put_rwork, delayed_user_event_enabler_put); ++ queue_rcu_work(system_percpu_wq, &enabler->put_rwork); ++} ++ + static int user_event_mm_fault_in(struct user_event_mm *mm, unsigned long uaddr, + int attempt) + { +@@ -446,7 +471,7 @@ static void user_event_enabler_fault_fix + + /* User asked for enabler to be removed during fault */ + if (test_bit(ENABLE_VAL_FREEING_BIT, ENABLE_BITOPS(enabler))) { +- user_event_enabler_destroy(enabler, true); ++ user_event_enabler_destroy(enabler); + goto out; + } + +@@ -746,7 +771,7 @@ static void user_event_mm_destroy(struct + struct user_event_enabler *enabler, *next; + + list_for_each_entry_safe(enabler, next, &mm->enablers, mm_enablers_link) +- user_event_enabler_destroy(enabler, false); ++ user_event_enabler_destroy(enabler); + + mmdrop(mm->mm); + kfree(mm); +@@ -2579,7 +2604,7 @@ static long user_events_ioctl_unreg(unsi + flags |= enabler->values & ENABLE_VAL_COMPAT_MASK; + + if (!test_bit(ENABLE_VAL_FAULTING_BIT, ENABLE_BITOPS(enabler))) +- user_event_enabler_destroy(enabler, true); ++ user_event_enabler_destroy(enabler); + + /* Removed at least one */ + ret = 0; diff --git a/queue-6.6/udmabuf-do-not-create-malformed-scatterlists.patch b/queue-6.6/udmabuf-do-not-create-malformed-scatterlists.patch new file mode 100644 index 0000000000..55aa4ee9d0 --- /dev/null +++ b/queue-6.6/udmabuf-do-not-create-malformed-scatterlists.patch @@ -0,0 +1,59 @@ +From stable+bounces-281368-greg=kroah.com@vger.kernel.org Tue Jul 21 23:37:19 2026 +From: Sasha Levin +Date: Tue, 21 Jul 2026 14:28:02 -0400 +Subject: udmabuf: Do not create malformed scatterlists +To: stable@vger.kernel.org +Cc: Jason Gunthorpe , Julian Orth , Vivek Kasireddy , Sasha Levin +Message-ID: <20260721182803.332607-1-sashal@kernel.org> + +From: Jason Gunthorpe + +[ Upstream commit 5bf888673e0dda5a53220fa0c4956271a46c353c ] + +Using a sg_set_folio() loop for every 4K results in a malformed scatterlist +because sg_set_folio() has an issue with offsets > PAGE_SIZE and because +scatterlist expects the creator to build a list which consolidates any +physical contiguity. + +sg_alloc_table_from_pages() creates a valid scatterlist directly from a +struct page array, so go back to that. + +Remove the offsets allocation and just store an array of tail pages as it +did before the below commit. Everything wants that anyhow. + +Fixes: 0c8b91ef5100 ("udmabuf: add back support for mapping hugetlb pages") +Reported-by: Julian Orth +Closes: https://lore.kernel.org/all/20260308-scatterlist-v1-1-39c4566b0bba@gmail.com/ +Signed-off-by: Jason Gunthorpe +Reviewed-by: Vivek Kasireddy +Signed-off-by: Vivek Kasireddy +Link: https://patch.msgid.link/0-v1-42779f29381a+4b9-udmabuf_sg_jgg@nvidia.com +Stable-dep-of: 504e2b4ab97a ("dma-buf/udmabuf: skip redundant cpu sync to fix cacheline EEXIST warning") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/dma-buf/udmabuf.c | 8 +++++--- + 1 file changed, 5 insertions(+), 3 deletions(-) + +--- a/drivers/dma-buf/udmabuf.c ++++ b/drivers/dma-buf/udmabuf.c +@@ -100,14 +100,16 @@ static struct sg_table *get_sg_table(str + 0, ubuf->pagecount << PAGE_SHIFT, + GFP_KERNEL); + if (ret < 0) +- goto err; ++ goto err_alloc; ++ + ret = dma_map_sgtable(dev, sg, direction, 0); + if (ret < 0) +- goto err; ++ goto err_map; + return sg; + +-err: ++err_map: + sg_free_table(sg); ++err_alloc: + kfree(sg); + return ERR_PTR(ret); + } diff --git a/queue-6.6/vduse-avoid-leaking-information-to-userspace.patch b/queue-6.6/vduse-avoid-leaking-information-to-userspace.patch new file mode 100644 index 0000000000..a872007c7a --- /dev/null +++ b/queue-6.6/vduse-avoid-leaking-information-to-userspace.patch @@ -0,0 +1,53 @@ +From stable+bounces-288035-greg=kroah.com@vger.kernel.org Wed Jul 22 20:08:36 2026 +From: Sasha Levin +Date: Wed, 22 Jul 2026 14:00:52 -0400 +Subject: VDUSE: avoid leaking information to userspace +To: stable@vger.kernel.org +Cc: "Jason Wang" , "Xie Yongji" , "Eugenio Pérez" , "Michael S. Tsirkin" , "Sasha Levin" +Message-ID: <20260722180052.2184339-4-sashal@kernel.org> + +From: Jason Wang + +[ Upstream commit 9c1523803445ee0348f62b77793266dd981596e0 ] + +The bounceing is not necessarily page aligned, so current VDUSE can +leak kernel information through mapping bounce pages to +userspace. Allocate bounce pages with __GFP_ZERO to avoid leaking +information to userspace. + +Fixes: 8c773d53fb7b ("vduse: Implement an MMU-based software IOTLB") +Cc: stable@vger.kernel.org +Signed-off-by: Jason Wang +Reviewed-by: Xie Yongji +Reviewed-by: Eugenio Pérez +Signed-off-by: Michael S. Tsirkin +Message-ID: <20260130050750.4050-1-jasowang@redhat.com> +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/vdpa/vdpa_user/iova_domain.c | 2 +- + drivers/vdpa/vdpa_user/vduse_dev.c | 2 +- + 2 files changed, 2 insertions(+), 2 deletions(-) + +--- a/drivers/vdpa/vdpa_user/iova_domain.c ++++ b/drivers/vdpa/vdpa_user/iova_domain.c +@@ -124,7 +124,7 @@ static int vduse_domain_map_bounce_page( + if (!map->bounce_page) { + head_map = &domain->bounce_maps[(iova & PAGE_MASK) >> BOUNCE_MAP_SHIFT]; + if (!head_map->bounce_page) { +- tmp_page = alloc_page(GFP_ATOMIC); ++ tmp_page = alloc_page(GFP_ATOMIC | __GFP_ZERO); + if (!tmp_page) + return -ENOMEM; + if (cmpxchg(&head_map->bounce_page, NULL, tmp_page)) +--- a/drivers/vdpa/vdpa_user/vduse_dev.c ++++ b/drivers/vdpa/vdpa_user/vduse_dev.c +@@ -855,7 +855,7 @@ static void *vduse_dev_alloc_coherent(st + + *dma_addr = DMA_MAPPING_ERROR; + +- addr = alloc_pages_exact(size, flag); ++ addr = alloc_pages_exact(size, flag | __GFP_ZERO); + if (!addr) + return NULL; + diff --git a/queue-6.6/vduse-remove-unused-vaddr-parameter-of-vduse_domain_free_coherent.patch b/queue-6.6/vduse-remove-unused-vaddr-parameter-of-vduse_domain_free_coherent.patch new file mode 100644 index 0000000000..d7178f03da --- /dev/null +++ b/queue-6.6/vduse-remove-unused-vaddr-parameter-of-vduse_domain_free_coherent.patch @@ -0,0 +1,61 @@ +From stable+bounces-288032-greg=kroah.com@vger.kernel.org Wed Jul 22 20:08:17 2026 +From: Sasha Levin +Date: Wed, 22 Jul 2026 14:00:50 -0400 +Subject: vduse: remove unused vaddr parameter of vduse_domain_free_coherent +To: stable@vger.kernel.org +Cc: "Eugenio Pérez" , "Michael S. Tsirkin" , "Sasha Levin" +Message-ID: <20260722180052.2184339-2-sashal@kernel.org> + +From: Eugenio Pérez + +[ Upstream commit 766e1749c0ef6a09651be9b8a8283d508c322b58 ] + +We will modify the function in next patches so let's clean it first. + +Signed-off-by: Eugenio Pérez +Signed-off-by: Michael S. Tsirkin +Message-Id: <20260119143306.1818855-9-eperezma@redhat.com> +Stable-dep-of: 9c1523803445 ("VDUSE: avoid leaking information to userspace") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/vdpa/vdpa_user/iova_domain.c | 3 +-- + drivers/vdpa/vdpa_user/iova_domain.h | 3 +-- + drivers/vdpa/vdpa_user/vduse_dev.c | 2 +- + 3 files changed, 3 insertions(+), 5 deletions(-) + +--- a/drivers/vdpa/vdpa_user/iova_domain.c ++++ b/drivers/vdpa/vdpa_user/iova_domain.c +@@ -514,8 +514,7 @@ err: + } + + void vduse_domain_free_coherent(struct vduse_iova_domain *domain, size_t size, +- void *vaddr, dma_addr_t dma_addr, +- unsigned long attrs) ++ dma_addr_t dma_addr, unsigned long attrs) + { + struct iova_domain *iovad = &domain->consistent_iovad; + struct vhost_iotlb_map *map; +--- a/drivers/vdpa/vdpa_user/iova_domain.h ++++ b/drivers/vdpa/vdpa_user/iova_domain.h +@@ -63,8 +63,7 @@ void *vduse_domain_alloc_coherent(struct + gfp_t flag, unsigned long attrs); + + void vduse_domain_free_coherent(struct vduse_iova_domain *domain, size_t size, +- void *vaddr, dma_addr_t dma_addr, +- unsigned long attrs); ++ dma_addr_t dma_addr, unsigned long attrs); + + void vduse_domain_reset_bounce_map(struct vduse_iova_domain *domain); + +--- a/drivers/vdpa/vdpa_user/vduse_dev.c ++++ b/drivers/vdpa/vdpa_user/vduse_dev.c +@@ -872,7 +872,7 @@ static void vduse_dev_free_coherent(stru + struct vduse_dev *vdev = dev_to_vduse(dev); + struct vduse_iova_domain *domain = vdev->domain; + +- vduse_domain_free_coherent(domain, size, vaddr, dma_addr, attrs); ++ vduse_domain_free_coherent(domain, size, dma_addr, attrs); + } + + static size_t vduse_dev_max_mapping_size(struct device *dev) diff --git a/queue-6.6/vduse-take-out-allocations-from-vduse_dev_alloc_coherent.patch b/queue-6.6/vduse-take-out-allocations-from-vduse_dev_alloc_coherent.patch new file mode 100644 index 0000000000..887642cf72 --- /dev/null +++ b/queue-6.6/vduse-take-out-allocations-from-vduse_dev_alloc_coherent.patch @@ -0,0 +1,147 @@ +From stable+bounces-288033-greg=kroah.com@vger.kernel.org Wed Jul 22 20:08:27 2026 +From: Sasha Levin +Date: Wed, 22 Jul 2026 14:00:51 -0400 +Subject: vduse: take out allocations from vduse_dev_alloc_coherent +To: stable@vger.kernel.org +Cc: "Eugenio Pérez" , "Jason Wang" , "Michael S. Tsirkin" , "Sasha Levin" +Message-ID: <20260722180052.2184339-3-sashal@kernel.org> + +From: Eugenio Pérez + +[ Upstream commit 489d76520612abf9a4ede4344349105406c91a73 ] + +The function vduse_dev_alloc_coherent will be called under rwlock in +next patches. Make it out of the lock to avoid increasing its fail +rate. + +Acked-by: Jason Wang +Signed-off-by: Eugenio Pérez +Signed-off-by: Michael S. Tsirkin +Message-Id: <20260119143306.1818855-10-eperezma@redhat.com> +Stable-dep-of: 9c1523803445 ("VDUSE: avoid leaking information to userspace") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/vdpa/vdpa_user/iova_domain.c | 24 +++++++----------------- + drivers/vdpa/vdpa_user/iova_domain.h | 5 ++--- + drivers/vdpa/vdpa_user/vduse_dev.c | 14 ++++++++++---- + 3 files changed, 19 insertions(+), 24 deletions(-) + +--- a/drivers/vdpa/vdpa_user/iova_domain.c ++++ b/drivers/vdpa/vdpa_user/iova_domain.c +@@ -479,17 +479,15 @@ void vduse_domain_unmap_page(struct vdus + vduse_domain_free_iova(iovad, dma_addr, size); + } + +-void *vduse_domain_alloc_coherent(struct vduse_iova_domain *domain, +- size_t size, dma_addr_t *dma_addr, +- gfp_t flag, unsigned long attrs) ++dma_addr_t vduse_domain_alloc_coherent(struct vduse_iova_domain *domain, ++ size_t size, void *orig) + { + struct iova_domain *iovad = &domain->consistent_iovad; + unsigned long limit = domain->iova_limit; + dma_addr_t iova = vduse_domain_alloc_iova(iovad, size, limit); +- void *orig = alloc_pages_exact(size, flag); + +- if (!iova || !orig) +- goto err; ++ if (!iova) ++ return DMA_MAPPING_ERROR; + + spin_lock(&domain->iotlb_lock); + if (vduse_iotlb_add_range(domain, (u64)iova, (u64)iova + size - 1, +@@ -500,17 +498,12 @@ void *vduse_domain_alloc_coherent(struct + } + spin_unlock(&domain->iotlb_lock); + +- *dma_addr = iova; ++ return iova; + +- return orig; + err: +- *dma_addr = DMA_MAPPING_ERROR; +- if (orig) +- free_pages_exact(orig, size); +- if (iova) +- vduse_domain_free_iova(iovad, iova, size); ++ vduse_domain_free_iova(iovad, iova, size); + +- return NULL; ++ return DMA_MAPPING_ERROR; + } + + void vduse_domain_free_coherent(struct vduse_iova_domain *domain, size_t size, +@@ -519,7 +512,6 @@ void vduse_domain_free_coherent(struct v + struct iova_domain *iovad = &domain->consistent_iovad; + struct vhost_iotlb_map *map; + struct vdpa_map_file *map_file; +- phys_addr_t pa; + + spin_lock(&domain->iotlb_lock); + map = vhost_iotlb_itree_first(domain->iotlb, (u64)dma_addr, +@@ -531,12 +523,10 @@ void vduse_domain_free_coherent(struct v + map_file = (struct vdpa_map_file *)map->opaque; + fput(map_file->file); + kfree(map_file); +- pa = map->addr; + vhost_iotlb_map_free(domain->iotlb, map); + spin_unlock(&domain->iotlb_lock); + + vduse_domain_free_iova(iovad, dma_addr, size); +- free_pages_exact(phys_to_virt(pa), size); + } + + static vm_fault_t vduse_domain_mmap_fault(struct vm_fault *vmf) +--- a/drivers/vdpa/vdpa_user/iova_domain.h ++++ b/drivers/vdpa/vdpa_user/iova_domain.h +@@ -58,9 +58,8 @@ void vduse_domain_unmap_page(struct vdus + dma_addr_t dma_addr, size_t size, + enum dma_data_direction dir, unsigned long attrs); + +-void *vduse_domain_alloc_coherent(struct vduse_iova_domain *domain, +- size_t size, dma_addr_t *dma_addr, +- gfp_t flag, unsigned long attrs); ++dma_addr_t vduse_domain_alloc_coherent(struct vduse_iova_domain *domain, ++ size_t size, void *orig); + + void vduse_domain_free_coherent(struct vduse_iova_domain *domain, size_t size, + dma_addr_t dma_addr, unsigned long attrs); +--- a/drivers/vdpa/vdpa_user/vduse_dev.c ++++ b/drivers/vdpa/vdpa_user/vduse_dev.c +@@ -851,18 +851,23 @@ static void *vduse_dev_alloc_coherent(st + { + struct vduse_dev *vdev = dev_to_vduse(dev); + struct vduse_iova_domain *domain = vdev->domain; +- unsigned long iova; + void *addr; + + *dma_addr = DMA_MAPPING_ERROR; +- addr = vduse_domain_alloc_coherent(domain, size, +- (dma_addr_t *)&iova, flag, attrs); ++ ++ addr = alloc_pages_exact(size, flag); + if (!addr) + return NULL; + +- *dma_addr = (dma_addr_t)iova; ++ *dma_addr = vduse_domain_alloc_coherent(domain, size, addr); ++ if (*dma_addr == DMA_MAPPING_ERROR) ++ goto err; + + return addr; ++ ++err: ++ free_pages_exact(addr, size); ++ return NULL; + } + + static void vduse_dev_free_coherent(struct device *dev, size_t size, +@@ -873,6 +878,7 @@ static void vduse_dev_free_coherent(stru + struct vduse_iova_domain *domain = vdev->domain; + + vduse_domain_free_coherent(domain, size, dma_addr, attrs); ++ free_pages_exact(vaddr, size); + } + + static size_t vduse_dev_max_mapping_size(struct device *dev) diff --git a/queue-6.6/vduse-use-fixed-4kb-bounce-pages-for-non-4kb-page-size.patch b/queue-6.6/vduse-use-fixed-4kb-bounce-pages-for-non-4kb-page-size.patch new file mode 100644 index 0000000000..1e1e140d18 --- /dev/null +++ b/queue-6.6/vduse-use-fixed-4kb-bounce-pages-for-non-4kb-page-size.patch @@ -0,0 +1,297 @@ +From stable+bounces-288031-greg=kroah.com@vger.kernel.org Wed Jul 22 20:08:19 2026 +From: Sasha Levin +Date: Wed, 22 Jul 2026 14:00:49 -0400 +Subject: vduse: Use fixed 4KB bounce pages for non-4KB page size +To: stable@vger.kernel.org +Cc: Sheng Zhao , "Michael S. Tsirkin" , Sasha Levin +Message-ID: <20260722180052.2184339-1-sashal@kernel.org> + +From: Sheng Zhao + +[ Upstream commit 3fc3068e7247c94dec08e93fea422a1bb649bfe5 ] + +The allocation granularity of bounce pages is PAGE_SIZE. This may cause +even small IO requests to occupy an entire bounce page exclusively. The +kind of memory waste will be more significant when PAGE_SIZE is larger +than 4KB (e.g. arm64 with 64KB pages). + +So, optimize it by using fixed 4KB bounce maps and iova allocation +granularity. A single IO request occupies at least a 4KB bounce page +instead of the entire memory page of PAGE_SIZE. + +Signed-off-by: Sheng Zhao +Message-Id: <20250925113516.60305-1-sheng.zhao@bytedance.com> +Signed-off-by: Michael S. Tsirkin +Stable-dep-of: 9c1523803445 ("VDUSE: avoid leaking information to userspace") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/vdpa/vdpa_user/iova_domain.c | 134 +++++++++++++++++++++++++---------- + drivers/vdpa/vdpa_user/iova_domain.h | 5 + + 2 files changed, 101 insertions(+), 38 deletions(-) + +--- a/drivers/vdpa/vdpa_user/iova_domain.c ++++ b/drivers/vdpa/vdpa_user/iova_domain.c +@@ -103,19 +103,38 @@ void vduse_domain_clear_map(struct vduse + static int vduse_domain_map_bounce_page(struct vduse_iova_domain *domain, + u64 iova, u64 size, u64 paddr) + { +- struct vduse_bounce_map *map; ++ struct vduse_bounce_map *map, *head_map; ++ struct page *tmp_page; + u64 last = iova + size - 1; + + while (iova <= last) { +- map = &domain->bounce_maps[iova >> PAGE_SHIFT]; ++ /* ++ * When PAGE_SIZE is larger than 4KB, multiple adjacent bounce_maps will ++ * point to the same memory page of PAGE_SIZE. Since bounce_maps originate ++ * from IO requests, we may not be able to guarantee that the orig_phys ++ * values of all IO requests within the same 64KB memory page are contiguous. ++ * Therefore, we need to store them separately. ++ * ++ * Bounce pages are allocated on demand. As a result, it may occur that ++ * multiple bounce pages corresponding to the same 64KB memory page attempt ++ * to allocate memory simultaneously, so we use cmpxchg to handle this ++ * concurrency. ++ */ ++ map = &domain->bounce_maps[iova >> BOUNCE_MAP_SHIFT]; + if (!map->bounce_page) { +- map->bounce_page = alloc_page(GFP_ATOMIC); +- if (!map->bounce_page) +- return -ENOMEM; ++ head_map = &domain->bounce_maps[(iova & PAGE_MASK) >> BOUNCE_MAP_SHIFT]; ++ if (!head_map->bounce_page) { ++ tmp_page = alloc_page(GFP_ATOMIC); ++ if (!tmp_page) ++ return -ENOMEM; ++ if (cmpxchg(&head_map->bounce_page, NULL, tmp_page)) ++ __free_page(tmp_page); ++ } ++ map->bounce_page = head_map->bounce_page; + } + map->orig_phys = paddr; +- paddr += PAGE_SIZE; +- iova += PAGE_SIZE; ++ paddr += BOUNCE_MAP_SIZE; ++ iova += BOUNCE_MAP_SIZE; + } + return 0; + } +@@ -127,12 +146,17 @@ static void vduse_domain_unmap_bounce_pa + u64 last = iova + size - 1; + + while (iova <= last) { +- map = &domain->bounce_maps[iova >> PAGE_SHIFT]; ++ map = &domain->bounce_maps[iova >> BOUNCE_MAP_SHIFT]; + map->orig_phys = INVALID_PHYS_ADDR; +- iova += PAGE_SIZE; ++ iova += BOUNCE_MAP_SIZE; + } + } + ++static unsigned int offset_in_bounce_page(dma_addr_t addr) ++{ ++ return (addr & ~BOUNCE_MAP_MASK); ++} ++ + static void do_bounce(phys_addr_t orig, void *addr, size_t size, + enum dma_data_direction dir) + { +@@ -162,7 +186,7 @@ static void vduse_domain_bounce(struct v + enum dma_data_direction dir) + { + struct vduse_bounce_map *map; +- unsigned int offset; ++ unsigned int offset, head_offset; + void *addr; + size_t sz; + +@@ -170,16 +194,17 @@ static void vduse_domain_bounce(struct v + return; + + while (size) { +- map = &domain->bounce_maps[iova >> PAGE_SHIFT]; +- offset = offset_in_page(iova); +- sz = min_t(size_t, PAGE_SIZE - offset, size); ++ map = &domain->bounce_maps[iova >> BOUNCE_MAP_SHIFT]; ++ head_offset = offset_in_page(iova); ++ offset = offset_in_bounce_page(iova); ++ sz = min_t(size_t, BOUNCE_MAP_SIZE - offset, size); + + if (WARN_ON(!map->bounce_page || + map->orig_phys == INVALID_PHYS_ADDR)) + return; + + addr = kmap_local_page(map->bounce_page); +- do_bounce(map->orig_phys + offset, addr + offset, sz, dir); ++ do_bounce(map->orig_phys + offset, addr + head_offset, sz, dir); + kunmap_local(addr); + size -= sz; + iova += sz; +@@ -214,7 +239,7 @@ vduse_domain_get_bounce_page(struct vdus + struct page *page = NULL; + + read_lock(&domain->bounce_lock); +- map = &domain->bounce_maps[iova >> PAGE_SHIFT]; ++ map = &domain->bounce_maps[iova >> BOUNCE_MAP_SHIFT]; + if (domain->user_bounce_pages || !map->bounce_page) + goto out; + +@@ -232,7 +257,7 @@ vduse_domain_free_kernel_bounce_pages(st + struct vduse_bounce_map *map; + unsigned long pfn, bounce_pfns; + +- bounce_pfns = domain->bounce_size >> PAGE_SHIFT; ++ bounce_pfns = domain->bounce_size >> BOUNCE_MAP_SHIFT; + + for (pfn = 0; pfn < bounce_pfns; pfn++) { + map = &domain->bounce_maps[pfn]; +@@ -242,7 +267,8 @@ vduse_domain_free_kernel_bounce_pages(st + if (!map->bounce_page) + continue; + +- __free_page(map->bounce_page); ++ if (!((pfn << BOUNCE_MAP_SHIFT) & ~PAGE_MASK)) ++ __free_page(map->bounce_page); + map->bounce_page = NULL; + } + } +@@ -250,8 +276,12 @@ vduse_domain_free_kernel_bounce_pages(st + int vduse_domain_add_user_bounce_pages(struct vduse_iova_domain *domain, + struct page **pages, int count) + { +- struct vduse_bounce_map *map; +- int i, ret; ++ struct vduse_bounce_map *map, *head_map; ++ int i, j, ret; ++ int inner_pages = PAGE_SIZE / BOUNCE_MAP_SIZE; ++ int bounce_pfns = domain->bounce_size >> BOUNCE_MAP_SHIFT; ++ struct page *head_page = NULL; ++ bool need_copy; + + /* Now we don't support partial mapping */ + if (count != (domain->bounce_size >> PAGE_SHIFT)) +@@ -263,17 +293,25 @@ int vduse_domain_add_user_bounce_pages(s + goto out; + + for (i = 0; i < count; i++) { +- map = &domain->bounce_maps[i]; +- if (map->bounce_page) { ++ need_copy = false; ++ head_map = &domain->bounce_maps[(i * inner_pages)]; ++ head_page = head_map->bounce_page; ++ for (j = 0; j < inner_pages; j++) { ++ if ((i * inner_pages + j) >= bounce_pfns) ++ break; ++ map = &domain->bounce_maps[(i * inner_pages + j)]; + /* Copy kernel page to user page if it's in use */ +- if (map->orig_phys != INVALID_PHYS_ADDR) +- memcpy_to_page(pages[i], 0, +- page_address(map->bounce_page), +- PAGE_SIZE); +- __free_page(map->bounce_page); ++ if ((head_page) && (map->orig_phys != INVALID_PHYS_ADDR)) ++ need_copy = true; ++ map->bounce_page = pages[i]; + } +- map->bounce_page = pages[i]; + get_page(pages[i]); ++ if ((head_page) && (need_copy)) ++ memcpy_to_page(pages[i], 0, ++ page_address(head_page), ++ PAGE_SIZE); ++ if (head_page) ++ __free_page(head_page); + } + domain->user_bounce_pages = true; + ret = 0; +@@ -285,8 +323,12 @@ out: + + void vduse_domain_remove_user_bounce_pages(struct vduse_iova_domain *domain) + { +- struct vduse_bounce_map *map; +- unsigned long i, count; ++ struct vduse_bounce_map *map, *head_map; ++ unsigned long i, j, count; ++ int inner_pages = PAGE_SIZE / BOUNCE_MAP_SIZE; ++ int bounce_pfns = domain->bounce_size >> BOUNCE_MAP_SHIFT; ++ struct page *head_page = NULL; ++ bool need_copy; + + write_lock(&domain->bounce_lock); + if (!domain->user_bounce_pages) +@@ -296,18 +338,34 @@ void vduse_domain_remove_user_bounce_pag + for (i = 0; i < count; i++) { + struct page *page = NULL; + +- map = &domain->bounce_maps[i]; +- if (WARN_ON(!map->bounce_page)) ++ need_copy = false; ++ head_map = &domain->bounce_maps[(i * inner_pages)]; ++ if (WARN_ON(!head_map->bounce_page)) + continue; ++ head_page = head_map->bounce_page; + +- /* Copy user page to kernel page if it's in use */ +- if (map->orig_phys != INVALID_PHYS_ADDR) { ++ for (j = 0; j < inner_pages; j++) { ++ if ((i * inner_pages + j) >= bounce_pfns) ++ break; ++ map = &domain->bounce_maps[(i * inner_pages + j)]; ++ if (WARN_ON(!map->bounce_page)) ++ continue; ++ /* Copy user page to kernel page if it's in use */ ++ if (map->orig_phys != INVALID_PHYS_ADDR) ++ need_copy = true; ++ } ++ if (need_copy) { + page = alloc_page(GFP_ATOMIC | __GFP_NOFAIL); + memcpy_from_page(page_address(page), +- map->bounce_page, 0, PAGE_SIZE); ++ head_page, 0, PAGE_SIZE); ++ } ++ for (j = 0; j < inner_pages; j++) { ++ if ((i * inner_pages + j) >= bounce_pfns) ++ break; ++ map = &domain->bounce_maps[(i * inner_pages + j)]; ++ map->bounce_page = page; + } +- put_page(map->bounce_page); +- map->bounce_page = page; ++ put_page(head_page); + } + domain->user_bounce_pages = false; + out: +@@ -557,7 +615,7 @@ vduse_domain_create(unsigned long iova_l + unsigned long pfn, bounce_pfns; + int ret; + +- bounce_pfns = PAGE_ALIGN(bounce_size) >> PAGE_SHIFT; ++ bounce_pfns = PAGE_ALIGN(bounce_size) >> BOUNCE_MAP_SHIFT; + if (iova_limit <= bounce_size) + return NULL; + +@@ -589,7 +647,7 @@ vduse_domain_create(unsigned long iova_l + rwlock_init(&domain->bounce_lock); + spin_lock_init(&domain->iotlb_lock); + init_iova_domain(&domain->stream_iovad, +- PAGE_SIZE, IOVA_START_PFN); ++ BOUNCE_MAP_SIZE, IOVA_START_PFN); + ret = iova_domain_init_rcaches(&domain->stream_iovad); + if (ret) + goto err_iovad_stream; +--- a/drivers/vdpa/vdpa_user/iova_domain.h ++++ b/drivers/vdpa/vdpa_user/iova_domain.h +@@ -19,6 +19,11 @@ + + #define INVALID_PHYS_ADDR (~(phys_addr_t)0) + ++#define BOUNCE_MAP_SHIFT 12 ++#define BOUNCE_MAP_SIZE (1 << BOUNCE_MAP_SHIFT) ++#define BOUNCE_MAP_MASK (~(BOUNCE_MAP_SIZE - 1)) ++#define BOUNCE_MAP_ALIGN(addr) (((addr) + BOUNCE_MAP_SIZE - 1) & ~(BOUNCE_MAP_SIZE - 1)) ++ + struct vduse_bounce_map { + struct page *bounce_page; + u64 orig_phys; diff --git a/queue-6.6/vfs-audit-introduce-kern_path_parent-for-audit.patch b/queue-6.6/vfs-audit-introduce-kern_path_parent-for-audit.patch new file mode 100644 index 0000000000..ee698e9cd0 --- /dev/null +++ b/queue-6.6/vfs-audit-introduce-kern_path_parent-for-audit.patch @@ -0,0 +1,191 @@ +From stable+bounces-287676-greg=kroah.com@vger.kernel.org Wed Jul 22 04:46:08 2026 +From: Sasha Levin +Date: Tue, 21 Jul 2026 20:51:12 -0400 +Subject: VFS/audit: introduce kern_path_parent() for audit +To: stable@vger.kernel.org +Cc: NeilBrown , Christian Brauner , Sasha Levin +Message-ID: <20260722005116.605624-1-sashal@kernel.org> + +From: NeilBrown + +[ Upstream commit 76a53de6f7ff0641570364234fb4489f4d4fc8e9 ] + +audit_alloc_mark() and audit_get_nd() both need to perform a path +lookup getting the parent dentry (which must exist) and the final +target (following a LAST_NORM name) which sometimes doesn't need to +exist. + +They don't need the parent to be locked, but use kern_path_locked() or +kern_path_locked_negative() anyway. This is somewhat misleading to the +casual reader. + +This patch introduces a more targeted function, kern_path_parent(), +which returns not holding locks. On success the "path" will +be set to the parent, which must be found, and the return value is the +dentry of the target, which might be negative. + +This will clear the way to rename kern_path_locked() which is +otherwise only used to prepare for removing something. + +It also allows us to remove kern_path_locked_negative(), which is +transformed into the new kern_path_parent(). + +Signed-off-by: NeilBrown +Signed-off-by: Christian Brauner +Stable-dep-of: 81905b5acbe7 ("audit: fix recursive locking deadlock in audit_dupe_exe()") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + fs/namei.c | 43 +++++++++++++++++++++++++++++++++++++++++++ + include/linux/namei.h | 1 + + kernel/audit.h | 4 ++-- + kernel/audit_fsnotify.c | 9 +++------ + kernel/audit_watch.c | 9 ++++++--- + 5 files changed, 55 insertions(+), 11 deletions(-) + +--- a/fs/namei.c ++++ b/fs/namei.c +@@ -2603,6 +2603,49 @@ static struct dentry *__kern_path_locked + return d; + } + ++/** ++ * kern_path_parent: lookup path returning parent and target ++ * @name: path name ++ * @path: path to store parent in ++ * ++ * The path @name should end with a normal component, not "." or ".." or "/". ++ * A lookup is performed and if successful the parent information ++ * is store in @parent and the dentry is returned. ++ * ++ * The dentry maybe negative, the parent will be positive. ++ * ++ * Returns: dentry or error. ++ */ ++struct dentry *kern_path_parent(const char *name, struct path *path) ++{ ++ struct filename *filename = getname_kernel(name); ++ struct path parent_path; ++ struct dentry *d; ++ struct qstr last; ++ int type, error; ++ ++ error = filename_parentat(AT_FDCWD, filename, 0, &parent_path, &last, &type); ++ if (error) { ++ d = ERR_PTR(error); ++ goto out; ++ } ++ if (unlikely(type != LAST_NORM)) { ++ path_put(&parent_path); ++ d = ERR_PTR(-EINVAL); ++ goto out; ++ } ++ ++ d = lookup_one_len_unlocked(last.name, parent_path.dentry, last.len); ++ if (IS_ERR(d)) { ++ path_put(&parent_path); ++ goto out; ++ } ++ *path = parent_path; ++out: ++ putname(filename); ++ return d; ++} ++ + struct dentry *kern_path_locked(const char *name, struct path *path) + { + struct filename *filename = getname_kernel(name); +--- a/include/linux/namei.h ++++ b/include/linux/namei.h +@@ -61,6 +61,7 @@ struct dentry *lookup_one_qstr_excl(cons + struct dentry *base, + unsigned int flags); + extern int kern_path(const char *, unsigned, struct path *); ++struct dentry *kern_path_parent(const char *name, struct path *parent); + + extern struct dentry *kern_path_create(int, const char *, struct path *, unsigned int); + extern struct dentry *user_path_create(int, const char __user *, struct path *, unsigned int); +--- a/kernel/audit.h ++++ b/kernel/audit.h +@@ -279,8 +279,8 @@ extern struct audit_fsnotify_mark *audit + extern char *audit_mark_path(struct audit_fsnotify_mark *mark); + extern void audit_remove_mark(struct audit_fsnotify_mark *audit_mark); + extern void audit_remove_mark_rule(struct audit_krule *krule); +-extern int audit_mark_compare(struct audit_fsnotify_mark *mark, +- unsigned long ino, dev_t dev); ++extern int audit_mark_compare(struct audit_fsnotify_mark *mark, u64 ino, ++ dev_t dev); + extern int audit_dupe_exe(struct audit_krule *new, struct audit_krule *old); + extern int audit_exe_compare(struct task_struct *tsk, + struct audit_fsnotify_mark *mark); +--- a/kernel/audit_fsnotify.c ++++ b/kernel/audit_fsnotify.c +@@ -57,7 +57,7 @@ char *audit_mark_path(struct audit_fsnot + return mark->path; + } + +-int audit_mark_compare(struct audit_fsnotify_mark *mark, unsigned long ino, dev_t dev) ++int audit_mark_compare(struct audit_fsnotify_mark *mark, u64 ino, dev_t dev) + { + if (mark->ino == AUDIT_INO_UNSET) + return 0; +@@ -76,17 +76,14 @@ struct audit_fsnotify_mark *audit_alloc_ + struct audit_fsnotify_mark *audit_mark; + struct path path; + struct dentry *dentry; +- struct inode *inode; + int ret; + + if (pathname[0] != '/' || pathname[len-1] == '/') + return ERR_PTR(-EINVAL); + +- dentry = kern_path_locked(pathname, &path); ++ dentry = kern_path_parent(pathname, &path); + if (IS_ERR(dentry)) + return ERR_CAST(dentry); /* returning an error */ +- inode = path.dentry->d_inode; +- inode_unlock(inode); + + audit_mark = kzalloc(sizeof(*audit_mark), GFP_KERNEL); + if (unlikely(!audit_mark)) { +@@ -100,7 +97,7 @@ struct audit_fsnotify_mark *audit_alloc_ + audit_update_mark(audit_mark, dentry->d_inode); + audit_mark->rule = krule; + +- ret = fsnotify_add_inode_mark(&audit_mark->mark, inode, 0); ++ ret = fsnotify_add_inode_mark(&audit_mark->mark, path.dentry->d_inode, 0); + if (ret < 0) { + audit_mark->path = NULL; + fsnotify_put_mark(&audit_mark->mark); +--- a/kernel/audit_watch.c ++++ b/kernel/audit_watch.c +@@ -244,7 +244,7 @@ static void audit_watch_log_rule_change( + /* Update inode info in audit rules based on filesystem event. */ + static void audit_update_watch(struct audit_parent *parent, + const struct qstr *dname, dev_t dev, +- unsigned long ino, unsigned invalidating) ++ u64 ino, unsigned int invalidating) + { + struct audit_watch *owatch, *nwatch, *nextw; + struct audit_krule *r, *nextr; +@@ -347,15 +347,18 @@ static void audit_remove_parent_watches( + /* Get path information necessary for adding watches. */ + static int audit_get_nd(struct audit_watch *watch, struct path *parent) + { +- struct dentry *d = kern_path_locked(watch->path, parent); ++ struct dentry *d; ++ ++ d = kern_path_parent(watch->path, parent); + if (IS_ERR(d)) + return PTR_ERR(d); ++ + if (d_is_positive(d)) { + /* update watch filter fields */ + watch->dev = d->d_sb->s_dev; + watch->ino = d_backing_inode(d)->i_ino; + } +- inode_unlock(d_backing_inode(parent->dentry)); ++ + dput(d); + return 0; + } diff --git a/queue-6.6/wifi-libertas_tf-fix-use-after-free-in-lbtf_free_adapter.patch b/queue-6.6/wifi-libertas_tf-fix-use-after-free-in-lbtf_free_adapter.patch new file mode 100644 index 0000000000..acd8556069 --- /dev/null +++ b/queue-6.6/wifi-libertas_tf-fix-use-after-free-in-lbtf_free_adapter.patch @@ -0,0 +1,49 @@ +From stable+bounces-289770-greg=kroah.com@vger.kernel.org Tue Jul 28 03:03:28 2026 +From: Sasha Levin +Date: Mon, 27 Jul 2026 20:57:42 -0400 +Subject: wifi: libertas_tf: fix use-after-free in lbtf_free_adapter() +To: stable@vger.kernel.org +Cc: Maoyi Xie , Johannes Berg , Sasha Levin +Message-ID: <20260728005742.2689126-1-sashal@kernel.org> + +From: Maoyi Xie + +[ Upstream commit aa6dcd5c8dd9ba1d7d0f60093bcda41c0d6d438d ] + +lbtf_free_adapter() calls timer_delete(&priv->command_timer), which does +not wait for a running command_timer_fn() callback. lbtf_free_adapter() +runs on the teardown path right before ieee80211_free_hw() frees priv, +both in lbtf_remove_card() and in the probe error path. command_timer is +armed by mod_timer() in lbtf_cmd() whenever a firmware command is sent. +command_timer_fn() dereferences priv. If a command times out as the +device is removed, command_timer_fn() runs concurrently with teardown and +dereferences priv after it has been freed. + +This is the same use-after-free that commit 03cc8f90d053 ("wifi: libertas: +fix use-after-free in lbs_free_adapter()") fixed in the sibling libertas +driver. The libertas_tf variant has the identical pattern and was left +unchanged. Use timer_delete_sync() so any in-flight callback completes +before priv is freed. + +Fixes: 06b16ae53192 ("libertas_tf: main.c, data paths and mac80211 handlers") +Cc: stable@vger.kernel.org +Signed-off-by: Maoyi Xie +Link: https://patch.msgid.link/178211481807.2212567.8773346114561900100@maoyixie.com +Signed-off-by: Johannes Berg +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/net/wireless/marvell/libertas_tf/main.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +--- a/drivers/net/wireless/marvell/libertas_tf/main.c ++++ b/drivers/net/wireless/marvell/libertas_tf/main.c +@@ -174,7 +174,7 @@ static void lbtf_free_adapter(struct lbt + { + lbtf_deb_enter(LBTF_DEB_MAIN); + lbtf_free_cmd_buffer(priv); +- del_timer(&priv->command_timer); ++ timer_delete_sync(&priv->command_timer); + lbtf_deb_leave(LBTF_DEB_MAIN); + } + diff --git a/queue-6.6/workqueue-add-system_percpu_wq-and-system_dfl_wq.patch b/queue-6.6/workqueue-add-system_percpu_wq-and-system_dfl_wq.patch new file mode 100644 index 0000000000..a741b34008 --- /dev/null +++ b/queue-6.6/workqueue-add-system_percpu_wq-and-system_dfl_wq.patch @@ -0,0 +1,119 @@ +From stable+bounces-289750-greg=kroah.com@vger.kernel.org Tue Jul 28 01:37:58 2026 +From: Sasha Levin +Date: Mon, 27 Jul 2026 19:37:49 -0400 +Subject: workqueue: Add system_percpu_wq and system_dfl_wq +To: stable@vger.kernel.org +Cc: Marco Crivellari , Tejun Heo , Sasha Levin +Message-ID: <20260727233750.2439387-2-sashal@kernel.org> + +From: Marco Crivellari + +[ Upstream commit 128ea9f6ccfb6960293ae4212f4f97165e42222d ] + +Currently, if a user enqueue a work item using schedule_delayed_work() the +used wq is "system_wq" (per-cpu wq) while queue_delayed_work() use +WORK_CPU_UNBOUND (used when a cpu is not specified). The same applies to +schedule_work() that is using system_wq and queue_work(), that makes use +again of WORK_CPU_UNBOUND. + +This lack of consistentcy cannot be addressed without refactoring the API. + +system_wq is a per-CPU worqueue, yet nothing in its name tells about that +CPU affinity constraint, which is very often not required by users. Make it +clear by adding a system_percpu_wq. + +system_unbound_wq should be the default workqueue so as not to enforce +locality constraints for random work whenever it's not required. + +Adding system_dfl_wq to encourage its use when unbound work should be used. + +Suggested-by: Tejun Heo +Signed-off-by: Marco Crivellari +Signed-off-by: Tejun Heo +Stable-dep-of: 50fd6dd755c6 ("tracing/user_events: Fix use-after-free in user_event_mm_dup()") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + include/linux/workqueue.h | 8 +++++--- + kernel/workqueue.c | 14 ++++++++++---- + 2 files changed, 15 insertions(+), 7 deletions(-) + +--- a/include/linux/workqueue.h ++++ b/include/linux/workqueue.h +@@ -410,7 +410,7 @@ enum { + /* + * System-wide workqueues which are always present. + * +- * system_wq is the one used by schedule[_delayed]_work[_on](). ++ * system_percpu_wq is the one used by schedule[_delayed]_work[_on](). + * Multi-CPU multi-threaded. There are users which expect relatively + * short queue flush time. Don't queue works which can run for too + * long. +@@ -421,7 +421,7 @@ enum { + * system_long_wq is similar to system_wq but may host long running + * works. Queue flushing might take relatively long. + * +- * system_unbound_wq is unbound workqueue. Workers are not bound to ++ * system_dfl_wq is unbound workqueue. Workers are not bound to + * any specific CPU, not concurrency managed, and all queued works are + * executed immediately as long as max_active limit is not reached and + * resources are available. +@@ -435,10 +435,12 @@ enum { + * system_power_efficient_wq is identical to system_wq if + * 'wq_power_efficient' is disabled. See WQ_POWER_EFFICIENT for more info. + */ +-extern struct workqueue_struct *system_wq; ++extern struct workqueue_struct *system_wq; /* use system_percpu_wq, this will be removed */ ++extern struct workqueue_struct *system_percpu_wq; + extern struct workqueue_struct *system_highpri_wq; + extern struct workqueue_struct *system_long_wq; + extern struct workqueue_struct *system_unbound_wq; ++extern struct workqueue_struct *system_dfl_wq; + extern struct workqueue_struct *system_freezable_wq; + extern struct workqueue_struct *system_power_efficient_wq; + extern struct workqueue_struct *system_freezable_power_efficient_wq; +--- a/kernel/workqueue.c ++++ b/kernel/workqueue.c +@@ -425,12 +425,16 @@ static struct kthread_worker *pwq_releas + + struct workqueue_struct *system_wq __read_mostly; + EXPORT_SYMBOL(system_wq); ++struct workqueue_struct *system_percpu_wq __read_mostly; ++EXPORT_SYMBOL(system_percpu_wq); + struct workqueue_struct *system_highpri_wq __read_mostly; + EXPORT_SYMBOL_GPL(system_highpri_wq); + struct workqueue_struct *system_long_wq __read_mostly; + EXPORT_SYMBOL_GPL(system_long_wq); + struct workqueue_struct *system_unbound_wq __read_mostly; + EXPORT_SYMBOL_GPL(system_unbound_wq); ++struct workqueue_struct *system_dfl_wq __read_mostly; ++EXPORT_SYMBOL_GPL(system_dfl_wq); + struct workqueue_struct *system_freezable_wq __read_mostly; + EXPORT_SYMBOL_GPL(system_freezable_wq); + struct workqueue_struct *system_power_efficient_wq __read_mostly; +@@ -6696,10 +6700,11 @@ void __init workqueue_init_early(void) + } + + system_wq = alloc_workqueue("events", 0, 0); ++ system_percpu_wq = alloc_workqueue("events", 0, 0); + system_highpri_wq = alloc_workqueue("events_highpri", WQ_HIGHPRI, 0); + system_long_wq = alloc_workqueue("events_long", 0, 0); +- system_unbound_wq = alloc_workqueue("events_unbound", WQ_UNBOUND, +- WQ_MAX_ACTIVE); ++ system_unbound_wq = alloc_workqueue("events_unbound", WQ_UNBOUND, WQ_MAX_ACTIVE); ++ system_dfl_wq = alloc_workqueue("events_unbound", WQ_UNBOUND, WQ_MAX_ACTIVE); + system_freezable_wq = alloc_workqueue("events_freezable", + WQ_FREEZABLE, 0); + system_power_efficient_wq = alloc_workqueue("events_power_efficient", +@@ -6707,8 +6712,9 @@ void __init workqueue_init_early(void) + system_freezable_power_efficient_wq = alloc_workqueue("events_freezable_power_efficient", + WQ_FREEZABLE | WQ_POWER_EFFICIENT, + 0); +- BUG_ON(!system_wq || !system_highpri_wq || !system_long_wq || +- !system_unbound_wq || !system_freezable_wq || ++ BUG_ON(!system_wq || !system_percpu_wq || !system_highpri_wq || ++ !system_long_wq || !system_unbound_wq || !system_dfl_wq || ++ !system_freezable_wq || + !system_power_efficient_wq || + !system_freezable_power_efficient_wq); + } diff --git a/queue-6.6/workqueue-factor-out-init_cpu_worker_pool.patch b/queue-6.6/workqueue-factor-out-init_cpu_worker_pool.patch new file mode 100644 index 0000000000..aa7d86d4a9 --- /dev/null +++ b/queue-6.6/workqueue-factor-out-init_cpu_worker_pool.patch @@ -0,0 +1,72 @@ +From stable+bounces-289749-greg=kroah.com@vger.kernel.org Tue Jul 28 01:40:07 2026 +From: Sasha Levin +Date: Mon, 27 Jul 2026 19:37:48 -0400 +Subject: workqueue: Factor out init_cpu_worker_pool() +To: stable@vger.kernel.org +Cc: Tejun Heo , Allen Pais , Sasha Levin +Message-ID: <20260727233750.2439387-1-sashal@kernel.org> + +From: Tejun Heo + +[ Upstream commit 2fcdb1b44491e08f5334a92c50e8f362e0d46f91 ] + +Factor out init_cpu_worker_pool() from workqueue_init_early(). This is pure +reorganization in preparation of BH workqueue support. + +Signed-off-by: Tejun Heo +Tested-by: Allen Pais +Stable-dep-of: 50fd6dd755c6 ("tracing/user_events: Fix use-after-free in user_event_mm_dup()") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + kernel/workqueue.c | 32 ++++++++++++++++++-------------- + 1 file changed, 18 insertions(+), 14 deletions(-) + +--- a/kernel/workqueue.c ++++ b/kernel/workqueue.c +@@ -6609,6 +6609,22 @@ static void __init restrict_unbound_cpum + cpumask_and(wq_unbound_cpumask, wq_unbound_cpumask, mask); + } + ++static void __init init_cpu_worker_pool(struct worker_pool *pool, int cpu, int nice) ++{ ++ BUG_ON(init_worker_pool(pool)); ++ pool->cpu = cpu; ++ cpumask_copy(pool->attrs->cpumask, cpumask_of(cpu)); ++ cpumask_copy(pool->attrs->__pod_cpumask, cpumask_of(cpu)); ++ pool->attrs->nice = nice; ++ pool->attrs->affn_strict = true; ++ pool->node = cpu_to_node(cpu); ++ ++ /* alloc pool ID */ ++ mutex_lock(&wq_pool_mutex); ++ BUG_ON(worker_pool_assign_id(pool)); ++ mutex_unlock(&wq_pool_mutex); ++} ++ + /** + * workqueue_init_early - early init for workqueue subsystem + * +@@ -6657,20 +6673,8 @@ void __init workqueue_init_early(void) + struct worker_pool *pool; + + i = 0; +- for_each_cpu_worker_pool(pool, cpu) { +- BUG_ON(init_worker_pool(pool)); +- pool->cpu = cpu; +- cpumask_copy(pool->attrs->cpumask, cpumask_of(cpu)); +- cpumask_copy(pool->attrs->__pod_cpumask, cpumask_of(cpu)); +- pool->attrs->nice = std_nice[i++]; +- pool->attrs->affn_strict = true; +- pool->node = cpu_to_node(cpu); +- +- /* alloc pool ID */ +- mutex_lock(&wq_pool_mutex); +- BUG_ON(worker_pool_assign_id(pool)); +- mutex_unlock(&wq_pool_mutex); +- } ++ for_each_cpu_worker_pool(pool, cpu) ++ init_cpu_worker_pool(pool, cpu, std_nice[i++]); + } + + /* create default unbound and ordered wq attrs */