Skip to contentSkip to conversation

6.12.105-rc1 review

PATCH Applied Greg Kroah-Hartman 14 participants 245 messages Aug 20 – Aug 31 Page 1 of 2

Subsystems (3)
lore
Share & tools
lore

Greg Kroah-Hartman

author maintainer
#0 lore raw

cover 6.12.105-rc1 review

This is the start of the stable review cycle for the 6.12.105 release. There are 220 patches in this series, all will be posted as a response to this one. If anyone has any issues with these being applied, please let me know.

Responses should be made by Sat, 22 Aug 2026 14:51:11 +0000. Anything received after that time might be too late.

The whole patch series can be found in one patch at:

	https://www.kernel.org/pub/linux/kernel/v6.x/stable-review/patch-6.12.105-rc1.gz

or in the git tree and branch at:
git://git.kernel.org/pub/scm/linux/kernel/git/stable/linux-stable-rc.git linux-6.12.y
and the diffstat can be found below.

thanks,

greg k-h

-------------
Pseudo-Shortlog of commits:

Greg Kroah-Hartman <[email protected]>

    Linux 6.12.105-rc1

Eric Dumazet <[email protected]>
ip_tunnel: adapt iptunnel_xmit_stats() to NETDEV_PCPU_STAT_DSTATS

Paolo Abeni <[email protected]>
net: harmonize tstats and dstats

Christian Brauner <[email protected]>
binfmt_misc: use exe_file_deny_write_access() for the interpreter clone

Darrick J. Wong <[email protected]>
fs: unlock the superblock during iterate_supers_type

Aditya Chillara <[email protected]>
perf/core: Fix group leader use-after-free after sibling detach

Peter Zijlstra <[email protected]>
perf: Unify perf_event_free_task() / perf_event_exit_task_context()

Gao Xiang <[email protected]>
erofs: fix EROFS_FS_ZIP_LZMA_DEFAULT_MAX_STREAMS on some UP platforms

Linmao Li <[email protected]>
drm/xe/oa: Fix sync entry leak on OA config emit failure

Jamal Hadi Salim <[email protected]>
net/sched: cls_bpf: reject dev-bound programs bound to a different device

Siddharth Vadapalli <[email protected]>
net: ethernet: ti: am65-cpsw-nuss: Fix port_id extraction from SRC TAG

Uwe Kleine-König <[email protected]>
m68k: Define NR_CPUS to 1

Zhang Changzhong <[email protected]>
net/sched: cls_u32: skip hash tables in u32_bind_class()

Jamal Hadi Salim <[email protected]>

    net/sched: act_api: fix TOCTOU NULL deref on a->goto_chain

Eric Dumazet <[email protected]>
af_packet: Don't send zero-byte data in tpacket_snd().

Rosen Penev <[email protected]>
ASoC: xilinx: formatter_pcm: pass aud_drv_data to irq handlers

Chuck Lever <[email protected]>
net/tls: Fail tls_sw_splice_read() after a failed async decrypt

Wei Fang <[email protected]>
net: packet: fix wrong transport_header when sending VLAN-tagged frame

Jiayuan Chen <[email protected]>
tcp: fix icsk_ack.ato bitfield overflow

Jonas Köppeler <[email protected]>
veth: fix queue index used to wake the peer txq in veth_poll

Eric Dumazet <[email protected]>
macvlan: inherit needed_headroom and needed_tailroom from lowerdev

Eric Dumazet <[email protected]>
ipvlan: inherit needed_headroom and needed_tailroom from phy_dev

Florian Westphal <[email protected]>
netfilter: ipset: let destroy callbacks adjust ext mem size

Florian Westphal <[email protected]>
netfilter: ipset: fix list type element drift bug

Jérémy Jean <[email protected]>
netfilter: flowtable: publish GC-visible tuple last

Alexey Velichayshiy <[email protected]>
netfilter: nf_tables_offload: suppress WARN_ON_ONCE for ENOMEM in abort path

Xiang Mei (Microsoft) <[email protected]>
netfilter: ipset: fix refcount race between list:set GC and swap

Vladislav Dronov <[email protected]>

    crypto: tegra - fix rctx->cryptlen calculation in tegra_gcm_do_one_req()

Herbert Xu <[email protected]>
crypto: ccm - Set rfc4309 maxauthsize from child

Jon Hunter <[email protected]>
arm64: tegra: Add EL2 virtual timer interrupt for Tegra194

Baul Lee <[email protected]>
net/x25: fix use-after-free of the socket by its timers

Peter Ujfalusi <[email protected]>
ASoC: SOF: ipc4-topology: Refresh copier IPC payload before widget setup

Eric Dumazet <[email protected]>
ipv4: start using dst_dev_rcu()

Breno Leitao <[email protected]>
x86/mce: Set up the polling timer before CMCI discovery

Yazen Ghannam <[email protected]>
x86/mce: Set CR4.MCE last during init

Yazen Ghannam <[email protected]>

    x86/mce: Remove __mcheck_cpu_init_early()

Vincent Donnefort <[email protected]>
ring-buffer: Prevent resizing of persistent ring buffer

Steven Rostedt <[email protected]>
ring-buffer: Make ring_buffer_{un}map() simpler with guard(mutex)

Steven Rostedt <[email protected]>
ring-buffer: Simplify ring_buffer_read_page() with guard()

Paolo Bonzini <[email protected]>
KVM: SVM: Serialize accesses to the owner and mirror list with separate lock

Lorenzo Stoakes (ARM) <[email protected]>
mm/ptdump: always stabilise against page table freeing using init_mm

Lorenzo Stoakes (ARM) <[email protected]>
mm/vmalloc: acquire init_mm lock on huge vmap to avoid ptdump UAF

Lorenzo Stoakes <[email protected]>
mm/pagewalk: split walk_page_range_novma() into kernel/user parts

Vincent Donnefort <[email protected]>
ring-buffer: Initialise reader page order in rb_allocate_cpu_buffer()

Steven Rostedt <[email protected]>
ring-buffer: Simplify functions with __free(kfree) to free allocations

Zack Rusin <[email protected]>

    drm/vmwgfx: take fman->lock around fence list mutation in fifo_down

Dongli Zhang <[email protected]>

    net: tap: set skb->dev before parsing virtio net header in tap_get_user_xdp()

Yang Wang <[email protected]>
drm/amd/pm: fix pptable use-after-free

Yang Wang <[email protected]>
drm/amd/pm: adjust the visibility of pp_table sysfs node

Pierre-Eric Pelloux-Prayer <[email protected]>
drm/amdgpu: move debug_vm handling to amdgpu_cs_parser_fini

Yunxiang Li <[email protected]>
drm/amdgpu: remove unused function parameter

Yang Wang <[email protected]>
drm/amd/pm: fix torn gpu metrics reads

Lijo Lazar <[email protected]>
drm/amd/pm: Use macro to initialize metrics table

Asad Kamal <[email protected]>
drm/amd/pm: Use same metric table for APU

Tu Nguyen <[email protected]>
can: rcar_canfd: change the initializing flow for clocks and resets

Frank Sorenson <[email protected]>
cifs: add fscache_resize_cookie() to cifs_setsize()

Paulo Alcantara <[email protected]>
smb: client: fix race with fallocate(2) and AIO+DIO

Max Kellermann <[email protected]>
include/linux/fs.h: add inode_lock_killable()

Dawid Osuchowski <[email protected]>
ice: fix VF interrupts cleanup

Eric Dumazet <[email protected]>
vxlan: use pskb_network_may_pull() for transmit path header pulls

Guillaume Nault <[email protected]>
vxlan: Handle stats using NETDEV_PCPU_STAT_DSTATS.

Guillaume Nault <[email protected]>
vrf: Make pcpu_dstats update functions available to other modules.

Christian Brauner <[email protected]>
binfmt_misc: restore write access when removing an entry

Amir Goldstein <[email protected]>
fs: don't block write during exec on pre-content watched files

Amir Goldstein <[email protected]>
fsnotify: opt-in for permission events at file open time

Song Liu <[email protected]>
fsnotify, lsm: Decouple fsnotify from lsm

Christian Brauner <[email protected]>
binfmt_misc: don't leak the user namespace when the mount fails

Chengfeng Ye <[email protected]>
net: pktgen: fix proc entry use-after-free

Peter Seiderer <[email protected]>
net: pktgen: fix code style (WARNING: Block comments)

Namjae Jeon <[email protected]>
ksmbd: reject repeated SMB2 NEGOTIATE requests

Namjae Jeon <[email protected]>
ksmbd: conn lock to serialize smb2 negotiate

David Carlier <[email protected]>
igc: remove napi_synchronize() in igc_down()

Damien Le Moal <[email protected]>
ata: libata-scsi: terminate deferred commands on time out

Mark Brown <[email protected]>
ASoC: tas2562: Validate values for volume writes

Weiming Shi <[email protected]>
KVM: x86: Cancel delayed I/O APIC EOI handling before destroying vCPUs

Guanghui Yang <[email protected]>
btrfs: zoned: fix missing chunk metadata reservation

Filipe Manana <[email protected]>
btrfs: remove fs_info argument from btrfs_zoned_activate_one_bg()

Naohiro Aota <[email protected]>
btrfs: add space_info argument to btrfs_chunk_alloc()

David Sterba <[email protected]>
btrfs: add debug build only WARN

Peiyang He <[email protected]>
iommufd: Fix wrong hwpt passed to iommufd_auto_response_faults on replace

Yuxiang Yang <[email protected]>
tcp: challenge ACK for non-exact RST in SYN-RECEIVED

Chia-Yu Chang <[email protected]>
tcp: reorganize tcp_sock_write_txrx group for variables later

Ilpo Järvinen <[email protected]>
tcp: fast path functions later

Ilpo Järvinen <[email protected]>
tcp: Pass flags to __tcp_send_ack

Guidong Han <[email protected]>
eventpoll: pin files while checking reverse paths

Namjae Jeon <[email protected]>
ksmbd: validate minimum PDU size for transform requests

ChenXiaoSong <[email protected]>
smb/server: fix minimum SMB2 PDU size

ChenXiaoSong <[email protected]>
smb/server: fix minimum SMB1 PDU size

Namjae Jeon <[email protected]>
ksmbd: rename smb2_get_msg to smb_get_msg

ChenXiaoSong <[email protected]>
smb/server: rename include guard in smb_common.h

ZhangGuoDong <[email protected]>
smb: move get_rfc1002_len() to common/smbglob.h

ZhangGuoDong <[email protected]>
smb: move smb_version_values to common/smbglob.h

Christian Brauner <[email protected]>
super: fix emergency thaw deadlock on frozen block devices

Chen Changcheng <[email protected]>
fs/super: fix emergency thaw double-unlock of s_umount

Christian Brauner <[email protected]>
super: use common iterator (Part 2)

Christian Brauner <[email protected]>
super: use a common iterator (Part 1)

Christian Brauner <[email protected]>
super: skip dying superblocks early

Christian Brauner <[email protected]>
super: remove pointless s_root checks

Aldo Ariel Panzardo <[email protected]>
net/sched: serialize qdisc_rtab_list against concurrent get/put

Pavitra Jha <[email protected]>
libceph: fix two unsafe bare decodes in decode_lockers()

Mike Rapoport (Microsoft) <[email protected]>
userfaultfd: prevent registration of special VMAs

Lance Yang <[email protected]>
mm/khugepaged: guard is_zero_pfn() calls with pte_present()

Xiang Mei <[email protected]>
libceph: bound pg_{temp,upmap,upmap_items} length to CEPH_PG_MAX_SIZE

Andy Shevchenko <[email protected]>
libceph: Amend checking to fix make W=1 build breakage

Max Kellermann <[email protected]>
ceph: fix hanging __ceph_get_caps() with stale mds_wanted

Max Kellermann <[email protected]>

    ceph: avoid fs reclaim while using current->journal_info

Wentao Liang <[email protected]>
ceph: fix writeback_count leak in write_folio_nounlock()

Matthew Wilcox (Oracle) <[email protected]>
ceph: Convert writepage_nounlock() to write_folio_nounlock()

Matthew Wilcox (Oracle) <[email protected]>
ceph: Convert ceph_find_incompatible() to take a folio

Matthew Wilcox (Oracle) <[email protected]>
ceph: Use a folio in ceph_page_mkwrite()

Matthew Wilcox (Oracle) <[email protected]>
ceph: Remove ceph_writepage()

Christoph Hellwig <[email protected]>
xfs: check v5 superblock features early

Darrick J. Wong <[email protected]>
xfs: check xfarray iteration errors when committing unlinked inode lists

Long Li <[email protected]>
xfs: don't swallow dquot recovery verification errors

Lin Jiapeng <[email protected]>
xfs: fix exchange-range reflink flag clearing issue with INO1_WRITTEN

Darrick J. Wong <[email protected]>

    xfs: avoid UAF on sc->tempip in xrep_tempfile_create

Darrick J. Wong <[email protected]>
xfs: don't return EFSCORRUPTED when scrubbing corrupt parent pointers

Darrick J. Wong <[email protected]>
xfs: fix another iunlink infinite loop bug in online fsck

Darrick J. Wong <[email protected]>
xfs: fix allocated inodes that show up in the unlinked list

Darrick J. Wong <[email protected]>

    xfs: don't walk off the end of a null sc->sa.agi_bp in AGI repair

Darrick J. Wong <[email protected]>
xfs: don't zap the attr fork on repair when there are queued pptr updates

Long Li <[email protected]>
xfs: fix ilock leak on error in xfs_dq_get_next_id

Darrick J. Wong <[email protected]>
xfs: load next_agino from the correct xfarray in xrep_iunlink_relink_prev

Darrick J. Wong <[email protected]>
xfs: nlink scrub must take IOLOCK before determining ILOCK state

Darrick J. Wong <[email protected]>
xfs: pass runtime errors from xrep_iunlink_mark_ondisk_rec up to callers

Darrick J. Wong <[email protected]>
xfs: set the prev pointer when reinserting an inode on the unlinked list

Darrick J. Wong <[email protected]>
xfs: don't double-lock when deleting a self-referential directory

Darrick J. Wong <[email protected]>
xfs: only check mergeability of bnobt records

Darrick J. Wong <[email protected]>
xfs: zero i_nlink before repair puts inode on unlinked list

Qiang Ma <[email protected]>
xfs: clear zapped attr fork state when bmap repair finds no attr fork

Junrui Luo <[email protected]>
drm/amdgpu: disallow multiple FENCE chunks in one submit

David Rosca <[email protected]>
drm/amdgpu: Fix UVD decode image min size calculation

David Rosca <[email protected]>
drm/amdgpu: Fix UVD dpb min size calculation for H264

David Rosca <[email protected]>
drm/amdgpu: Fix UVD min buffer sizes

David Rosca <[email protected]>
drm/amdgpu: Implement insert_end for VCE 3

David Rosca <[email protected]>
drm/amdgpu: Reject UVD message with dimensions above 4096

Candice Li <[email protected]>
drm/amdgpu: validate GEM_CREATE domain combinations

Yang Wang <[email protected]>
drm/amdgpu: check ASPM on the dGPU host link

Yang Wang <[email protected]>
drm/amdgpu: fix nbif 6.3.1 l1 low power not functional

David Rosca <[email protected]>
drm/amdgpu: Reject UVD message with invalid number of h265 refs

Nathan Lucas <[email protected]>
drm/amd/display: fix BT.2020 YCbCr output CSC matrices for DCE

Nathan Lucas <[email protected]>
drm/amd/display: fix BT.2020 YCbCr limited output CSC matrix

Eric Farman <[email protected]>
s390/vfio_ccw: Implement a crw lock

Eric Farman <[email protected]>
s390/vfio_ccw: Calculate idal length based on idaw type

Eric Farman <[email protected]>
s390/vfio_ccw: Selectively expand io_mutex

Eric Farman <[email protected]>
s390/vfio_ccw: Move cp cleanup out of not operational

Eric Farman <[email protected]>
s390/vfio_ccw: Fix out of bounds check on CCW array

Eric Farman <[email protected]>
s390/vfio_ccw: Ensure first IDAW remains constant

Eric Farman <[email protected]>
s390/vfio_ccw: Ensure index for read/write regions are within range

Eric Farman <[email protected]>
s390/vfio_ccw: Cancel existing workqueues

Eric Farman <[email protected]>
s390/vfio_ccw: Limit the number of channel program segments

Eric Farman <[email protected]>
s390/vfio_ccw: Free all memory if cp_init() fails

Guangshuo Li <[email protected]>
drm/radeon: fix autosuspend cleanup during teardown

Matthew Brost <[email protected]>
drm/xe: Order ring writes before ring tail updates

John Harrison <[email protected]>
drm/connector/hdmi: Fix out of bounds memory read

Pei Xiao <[email protected]>
mmc: atmel-mci: Fix use-after-free in atmci_remove due to race condition

Haibo Chen <[email protected]>
mmc: sdhci: make tuning_err a signed int

Myeonghun Pak <[email protected]>
mmc: sdhci: unmap the bounce buffer before device release

Zhan Xusheng <[email protected]>
mmc: omap_hsmmc: fix busy_timeout overflow in ns conversion on 32-bit

Kefu Chai <[email protected]>
libceph: tolerate addrvecs with multiple entries of the same type

Yiming Zhu <[email protected]>
ceph: fix MDS random selection readiness predicate

Raphael Zimmer <[email protected]>
libceph: Avoid using invalid osd indices from primary_temp

Dmitry Torokhov <[email protected]>
Input: sur40 - fix V4L error path cleanup

Dmitry Torokhov <[email protected]>
Input: sur40 - fix input device registration ordering

Ali Ahmet Memis <[email protected]>
openrisc: signal: do not restore privileged SR bits on sigreturn

Josh Poimboeuf <[email protected]>
ftrace: Fix off-by-one fentry site disable in ftrace_free_mem()

Leon Hwang <[email protected]>
ftrace: Protect direct_functions in ftrace_find_rec_direct

Pavitra Jha <[email protected]>
libceph: fix multiple unsafe decodes in decode_locker()

Praveen Talari <[email protected]>
pmdomain: arm: Fix -EINVAL from scmi_pd_set_perf_state() on state 0

Cengiz Can <[email protected]>
gpio: sloppy-logic-analyzer: fix use-after-free via debugfs trigger on unbind

Thorsten Blum <[email protected]>
crypto: qce - fix error path in devm_qce_register_algs

Thorsten Blum <[email protected]>
crypto: starfive - use scatterlist length before DMA mapping

Jianing Li <[email protected]>
Input: hynitron_cstxxx - validate touch count and finger IDs

Dmitry Torokhov <[email protected]>
Input: synaptics-rmi4 - propagate F54 worker errors to V4L2 queue

Dmitry Torokhov <[email protected]>
Input: synaptics-rmi4 - block s_input when F54 queue is busy

Bryam Vargas <[email protected]>
Input: synaptics-rmi4 - bound the F54 report size to the allocated buffer

Dmitry Torokhov <[email protected]>
Input: synaptics-rmi4 - zero report size on F54 work error

George Wilson <[email protected]>

    powerpc/pseries: lparcfg - fix kbuf[] underflow

Pengpeng Hou <[email protected]>
Input: iforce - validate input packet lengths

Zhefu Zhang <[email protected]>
Input: atkbd - skip deactivate for Xiaomi Book Pro 14's internal keyboard

Linmao Li <[email protected]>
Input: psxpad-spi - set driver data before use

Richard Davies <[email protected]>
Input: focaltech - fix array out-of-bounds in focaltech_process_rel_packet

Dmitry Torokhov <[email protected]>
Input: synaptics-rmi4 - fix F55 transmitter electrode count typo

George Wilson <[email protected]>
powerpc/pseries: pci - logic bug

HyeongJun An <[email protected]>
Input: cs40l50-vibra - validate custom data from user space

Kyohei Kadota <[email protected]>
Input: xpad - add support for ZENAIM LEVERLESS

Bard Liao <[email protected]>
ASoC: SOF: topology: Use acpi mach from the machine driver

Asad Kamal <[email protected]>
drm/amdgpu: fix aperture iounmap skipped on device removal

Qiang Yu <[email protected]>
drm/amdgpu: read TRUNCATE_COORD_MODE on gfx12

Candice Li <[email protected]>
drm/amdgpu: reject oversized IBs with per-ring packet limits

Osama Abdelkader <[email protected]>
drm/panthor: skip zero-sized firmware sections

Mingyu Wang <[email protected]>
fbdev: core: Fix pointer desynchronization in fb_io_read()

Dawid Wróbel <[email protected]>
ASoC: codecs: lpass-wsa-macro: Fix enum kcontrol accesses

Peter Ujfalusi <[email protected]>
ASoC: cs35l41: sort the register default table

Peter Ujfalusi <[email protected]>
ASoC: cs35l45: sort the register default table

Peter Ujfalusi <[email protected]>
ASoC: cs4265: sort the register default table

Peter Ujfalusi <[email protected]>
ASoC: SOF: sof-audio: Fix error path in sof_widget_setup_unlocked()

Hidayath Khan <[email protected]>
s390/qeth: validate user buffer length in SNMP and ARP query ioctls

Wyatt Feng <[email protected]>
mptcp: fastopen: only mark MPTFO subflows with SYN data

Matthieu Baerts (NGI0) <[email protected]>
mptcp: options: reset DSS fields in case of unexpected size

Matthieu Baerts (NGI0) <[email protected]>
mptcp: avoid combining some incoming suboptions

Gang Yan <[email protected]>
selftests: mptcp: join: mark tests with data corruption as failed

Bryam Vargas <[email protected]>
selinux: reject an unclaimed class value in security_get_classes()

Bryam Vargas <[email protected]>
selinux: do not cancel a policy conversion that never started

Bryam Vargas <[email protected]>
selinux: reject a class permission count below its inherited common

Bryam Vargas <[email protected]>
selinux: require every boolean value to be defined

Sean Christopherson <[email protected]>
KVM: SVM: Ensure PSP module is initialized if KVM module is built-in

Sean Christopherson <[email protected]>
crypto: ccp: Add external API interface for PSP module initialization

Erni Sri Satya Vennela <[email protected]>
net: mana: Fix EQ leak in mana_remove on NULL port

Yizhou Zhao <[email protected]>
ipvs: separate destination availability state

Philipp David <[email protected]>
igc: fix netdev not re-attached after resume if interface is down

SJ Park <[email protected]>

    mm/damon: adjust isolated pages stat for DAMOS_MIGRATE_{HOT,COLD}

liyouhong <[email protected]>
mm/damon/ops-common: putback folios on invalid migrate nid

Liyuan Pang <[email protected]>

    ubi: fastmap: fix ubi->fm memory leak

Cheng Ming Lin <[email protected]>
mtd: ubi: skip programming unused bits in ubi headers

Chao Yu <[email protected]>
f2fs: fix UAF issue in f2fs_merge_page_bio()

Daniel Borkmann <[email protected]>
selftests/bpf: Add tests for stale delta leaking through id reassignment

Daniel Borkmann <[email protected]>

    selftests/bpf: Add tests for delta tracking when src_reg == dst_reg

Daniel Borkmann <[email protected]>
bpf: Clear delta when clearing reg id for non-{add,sub} ops

Daniel Borkmann <[email protected]>

    bpf: Fix linked reg delta tracking when src_reg == dst_reg

Chao Shi <[email protected]>
block: stop the timeout timer when releasing a never added disk

-------------

Diffstat:

 Makefile                                           |   4 +-
 arch/arm64/boot/dts/nvidia/tegra194.dtsi           |   2 +
 arch/loongarch/mm/pageattr.c                       |   2 +-
 arch/m68k/Kconfig.cpu                              |   4 +
 arch/openrisc/include/asm/processor.h              |   2 +
 arch/openrisc/kernel/dma.c                         |   4 +-
 arch/openrisc/kernel/signal.c                      |   5 +-
 arch/powerpc/platforms/pseries/lparcfg.c           |   2 +-
 arch/powerpc/platforms/pseries/pci.c               |   2 +-
 arch/riscv/mm/pageattr.c                           |   8 +-
 arch/x86/kernel/cpu/mce/amd.c                      |   4 +
 arch/x86/kernel/cpu/mce/core.c                     |  21 +--
 arch/x86/kvm/svm/sev.c                             |  49 ++++--
 arch/x86/kvm/svm/svm.h                             |   1 +
 arch/x86/kvm/x86.c                                 |  13 ++
 block/genhd.c                                      |  12 +-
 crypto/ccm.c                                       |   2 +-
 drivers/ata/libata-eh.c                            |  29 +---
 drivers/ata/libata-scsi.c                          | 115 +++++++++++++--
 drivers/ata/libata.h                               |   3 +-
 drivers/crypto/ccp/sp-dev.c                        |  14 ++
 drivers/crypto/qce/core.c                          |   2 +-
 drivers/crypto/starfive/jh7110-aes.c               |   2 +-
 drivers/crypto/tegra/tegra-se-aes.c                |  22 +--
 drivers/gpio/gpio-sloppy-logic-analyzer.c          |   2 +-
 drivers/gpu/drm/amd/amdgpu/amdgpu_cs.c             |  63 ++++++--
 drivers/gpu/drm/amd/amdgpu/amdgpu_device.c         |  50 ++++++-
 drivers/gpu/drm/amd/amdgpu/amdgpu_dma_buf.c        |   2 +-
 drivers/gpu/drm/amd/amdgpu/amdgpu_gem.c            |  24 ++-
 drivers/gpu/drm/amd/amdgpu/amdgpu_object.c         |   3 +-
 drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.c            |  10 +-
 drivers/gpu/drm/amd/amdgpu/amdgpu_uvd.c            |  85 +++--------
 drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c             |   4 +-
 drivers/gpu/drm/amd/amdgpu/amdgpu_vm.h             |   3 +-
 drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c             |   5 +
 drivers/gpu/drm/amd/amdgpu/nbif_v6_3_1.c           |  42 +++---
 drivers/gpu/drm/amd/amdgpu/vce_v3_0.c              |  26 +++-
 .../gpu/drm/amd/display/dc/core/dc_hw_sequencer.c  |  31 ++--
 drivers/gpu/drm/amd/display/dc/dce/dce_transform.c |   7 +-
 .../drm/amd/display/dc/dce110/dce110_opp_csc_v.c   |   7 +-
 drivers/gpu/drm/amd/pm/amdgpu_dpm.c                |  36 ++++-
 drivers/gpu/drm/amd/pm/amdgpu_pm.c                 |  26 ++--
 drivers/gpu/drm/amd/pm/inc/amdgpu_dpm.h            |   6 +-
 drivers/gpu/drm/amd/pm/powerplay/amd_powerplay.c   |   5 +-
 drivers/gpu/drm/amd/pm/swsmu/amdgpu_smu.c          |   2 +-
 .../gpu/drm/amd/pm/swsmu/smu13/smu_v13_0_6_ppt.c   | 102 +++++++------
 drivers/gpu/drm/amd/pm/swsmu/smu_cmn.c             |  58 --------
 drivers/gpu/drm/amd/pm/swsmu/smu_cmn.h             |  14 +-
 drivers/gpu/drm/display/drm_hdmi_state_helper.c    |   3 +-
 drivers/gpu/drm/panthor/panthor_fw.c               |   3 +
 drivers/gpu/drm/radeon/radeon_kms.c                |   1 +
 drivers/gpu/drm/vmwgfx/vmwgfx_fence.c              |  10 +-
 drivers/gpu/drm/xe/xe_lrc.c                        |   7 +
 drivers/gpu/drm/xe/xe_oa.c                         |   4 +
 drivers/input/joystick/iforce/iforce-packets.c     |  11 +-
 drivers/input/joystick/iforce/iforce-usb.c         |   3 +
 drivers/input/joystick/psxpad-spi.c                |   1 +
 drivers/input/joystick/xpad.c                      |   2 +
 drivers/input/keyboard/atkbd.c                     |   8 +
 drivers/input/misc/cs40l50-vibra.c                 |  10 +-
 drivers/input/mouse/focaltech.c                    |   2 +-
 drivers/input/rmi4/rmi_f54.c                       |  43 ++++--
 drivers/input/rmi4/rmi_f55.c                       |   4 +-
 drivers/input/touchscreen/hynitron_cstxxx.c        |  13 +-
 drivers/input/touchscreen/sur40.c                  |  23 +--
 drivers/iommu/iommufd/device.c                     |   2 +-
 drivers/mmc/host/atmel-mci.c                       |   2 +
 drivers/mmc/host/omap_hsmmc.c                      |   2 +-
 drivers/mmc/host/sdhci.c                           |  16 ++
 drivers/mmc/host/sdhci.h                           |   2 +-
 drivers/mtd/ubi/attach.c                           |   4 +-
 drivers/mtd/ubi/fastmap-wl.c                       |   8 +-
 drivers/mtd/ubi/io.c                               |  10 ++
 drivers/mtd/ubi/ubi.h                              |  12 ++
 drivers/net/can/rcar/rcar_canfd.c                  |  28 ++--
 drivers/net/ethernet/intel/ice/ice_vf_lib.c        |  27 ++++
 .../net/ethernet/intel/ice/ice_vf_lib_private.h    |   1 +
 drivers/net/ethernet/intel/ice/ice_virtchnl.c      |  21 +++
 drivers/net/ethernet/intel/igc/igc_main.c          |   9 +-
 drivers/net/ethernet/microsoft/mana/mana_en.c      |   4 +-
 drivers/net/ethernet/ti/am65-cpsw-nuss.c           |   2 +
 drivers/net/ipvlan/ipvlan_main.c                   |   4 +
 drivers/net/macvlan.c                              |   4 +
 drivers/net/tap.c                                  |  24 +--
 drivers/net/veth.c                                 |   2 +-
 drivers/net/vrf.c                                  |  49 ++----
 drivers/net/vxlan/vxlan_core.c                     |  34 ++---
 drivers/net/vxlan/vxlan_mdb.c                      |   4 +-
 drivers/pmdomain/arm/scmi_perf_domain.c            |   2 +-
 drivers/s390/cio/vfio_ccw_async.c                  |  16 ++
 drivers/s390/cio/vfio_ccw_chp.c                    |  31 +++-
 drivers/s390/cio/vfio_ccw_cp.c                     |  74 +++++++---
 drivers/s390/cio/vfio_ccw_cp.h                     |  10 ++
 drivers/s390/cio/vfio_ccw_drv.c                    |  21 ++-
 drivers/s390/cio/vfio_ccw_fsm.c                    |   8 +-
 drivers/s390/cio/vfio_ccw_ops.c                    |  39 ++++-
 drivers/s390/cio/vfio_ccw_private.h                |  10 +-
 drivers/s390/net/qeth_core_main.c                  |   3 +
 drivers/s390/net/qeth_l3_main.c                    |   5 +
 drivers/video/fbdev/core/fb_io_fops.c              |   8 +
 fs/binfmt_elf.c                                    |   4 +-
 fs/binfmt_elf_fdpic.c                              |   4 +-
 fs/binfmt_misc.c                                   |  46 +++---
 fs/btrfs/block-group.c                             |  61 ++++----
 fs/btrfs/block-group.h                             |   3 +-
 fs/btrfs/extent-tree.c                             |   6 +-
 fs/btrfs/messages.h                                |   7 +
 fs/btrfs/space-info.c                              |   2 +-
 fs/btrfs/transaction.c                             |   5 +-
 fs/btrfs/zoned.c                                   |   5 +-
 fs/btrfs/zoned.h                                   |   6 +-
 fs/ceph/addr.c                                     | 164 ++++++++++-----------
 fs/ceph/caps.c                                     |  17 ++-
 fs/ceph/file.c                                     |   9 +-
 fs/ceph/mds_client.c                               |  11 ++
 fs/ceph/mds_client.h                               |   1 +
 fs/ceph/mdsmap.c                                   |   2 +-
 fs/erofs/Kconfig                                   |   3 +-
 fs/eventpoll.c                                     |  17 ++-
 fs/exec.c                                          |   8 +-
 fs/f2fs/data.c                                     |   2 +-
 fs/notify/fanotify/Kconfig                         |   1 -
 fs/notify/fsnotify.c                               |  38 +++++
 fs/open.c                                          |  12 +-
 fs/smb/client/cifsfs.c                             |  22 ++-
 fs/smb/client/cifsglob.h                           |  27 ----
 fs/smb/client/cifssmb.c                            |   6 +-
 fs/smb/client/connect.c                            |   2 +-
 fs/smb/client/inode.c                              |   2 +
 fs/smb/client/smb2ops.c                            |  18 +--
 fs/smb/client/transport.c                          |   8 +-
 fs/smb/common/cifsglob.h                           |  36 +++++
 fs/smb/server/auth.c                               |   4 +-
 fs/smb/server/connection.c                         |  23 ++-
 fs/smb/server/connection.h                         |   5 +
 fs/smb/server/oplock.c                             |   8 +-
 fs/smb/server/smb2misc.c                           |   2 +-
 fs/smb/server/smb2ops.c                            |  32 ++--
 fs/smb/server/smb2pdu.c                            |  88 ++++++-----
 fs/smb/server/smb2pdu.h                            |   9 --
 fs/smb/server/smb_common.c                         |  43 ++++--
 fs/smb/server/smb_common.h                         |  43 ++----
 fs/super.c                                         | 137 +++++++++--------
 fs/xfs/libxfs/xfs_exchmaps.c                       |  10 ++
 fs/xfs/libxfs/xfs_sb.c                             |   4 +-
 fs/xfs/scrub/agheader_repair.c                     | 120 ++++++++++++---
 fs/xfs/scrub/alloc.c                               |   5 +-
 fs/xfs/scrub/attr_repair.c                         |   3 +-
 fs/xfs/scrub/bmap.c                                |   5 +
 fs/xfs/scrub/dirtree_repair.c                      |  22 ++-
 fs/xfs/scrub/nlinks.c                              |  13 +-
 fs/xfs/scrub/nlinks_repair.c                       |   7 +-
 fs/xfs/scrub/parent.c                              |   2 +-
 fs/xfs/scrub/tempfile.c                            |   1 +
 fs/xfs/scrub/trace.h                               |   2 +
 fs/xfs/xfs_dquot.c                                 |   3 +-
 fs/xfs/xfs_dquot_item_recover.c                    |   2 +-
 include/linux/fs.h                                 |  77 +++++++++-
 include/linux/fsnotify.h                           |  39 +++--
 include/linux/libata.h                             |   2 +
 include/linux/netdevice.h                          |  45 +++++-
 include/linux/pagewalk.h                           |   7 +-
 include/linux/psp-sev.h                            |   9 ++
 include/linux/tcp.h                                |   4 +-
 include/linux/userfaultfd_k.h                      |   6 +
 include/net/ip_tunnels.h                           |  28 +++-
 include/net/ip_vs.h                                |   7 +
 include/net/tcp.h                                  |  58 ++++----
 include/uapi/linux/ip_vs.h                         |   6 -
 kernel/bpf/verifier.c                              |  51 ++++---
 kernel/events/core.c                               | 159 ++++++++------------
 kernel/fork.c                                      |  12 +-
 kernel/trace/ftrace.c                              |   9 +-
 kernel/trace/ring_buffer.c                         |  93 +++++-------
 mm/damon/paddr.c                                   |  15 +-
 mm/hugetlb_vmemmap.c                               |   2 +-
 mm/internal.h                                      |   6 +
 mm/khugepaged.c                                    |  35 +++--
 mm/pagewalk.c                                      |  97 ++++++++----
 mm/ptdump.c                                        |  10 +-
 mm/vmalloc.c                                       |  60 +++++++-
 net/ceph/cls_lock_client.c                         |  21 ++-
 net/ceph/decode.c                                  |  18 +--
 net/ceph/osdmap.c                                  |   9 +-
 net/core/dev.c                                     |  14 ++
 net/core/pktgen.c                                  |  43 +++---
 net/ipv4/bpf_tcp_ca.c                              |   2 +-
 net/ipv4/icmp.c                                    |   6 +-
 net/ipv4/ip_fragment.c                             |   6 +-
 net/ipv4/ipmr.c                                    |   4 +-
 net/ipv4/route.c                                   |   4 +-
 net/ipv4/tcp.c                                     |   4 +-
 net/ipv4/tcp_dctcp.h                               |   2 +-
 net/ipv4/tcp_input.c                               |  62 ++++++--
 net/ipv4/tcp_minisocks.c                           |  12 +-
 net/ipv4/tcp_output.c                              |   6 +-
 net/ipv4/tcp_timer.c                               |   4 +-
 net/mptcp/fastopen.c                               |   7 +-
 net/mptcp/options.c                                |  67 ++++++++-
 net/mptcp/protocol.h                               |   1 +
 net/netfilter/ipset/ip_set_bitmap_gen.h            |   2 +-
 net/netfilter/ipset/ip_set_core.c                  |  19 ++-
 net/netfilter/ipset/ip_set_list_set.c              |  10 +-
 net/netfilter/ipvs/ip_vs_conn.c                    |   4 +-
 net/netfilter/ipvs/ip_vs_core.c                    |   6 +-
 net/netfilter/ipvs/ip_vs_ctl.c                     |   4 +-
 net/netfilter/ipvs/ip_vs_dh.c                      |   4 +-
 net/netfilter/ipvs/ip_vs_lblc.c                    |   2 +-
 net/netfilter/ipvs/ip_vs_lblcr.c                   |   8 +-
 net/netfilter/nf_flow_table_core.c                 |   7 +-
 net/netfilter/nf_tables_offload.c                  |   2 +-
 net/packet/af_packet.c                             |   7 +-
 net/sched/act_api.c                                |  10 +-
 net/sched/cls_bpf.c                                |  18 ++-
 net/sched/cls_u32.c                                |   3 +
 net/sched/sch_api.c                                |  24 ++-
 net/tls/tls_sw.c                                   |   5 +
 net/x25/af_x25.c                                   |   4 +-
 net/x25/x25_timer.c                                |  25 ++--
 security/security.c                                |   9 +-
 security/selinux/ss/policydb.c                     |  31 ++++
 security/selinux/ss/services.c                     |  28 +++-
 sound/soc/codecs/cs35l41-lib.c                     |   2 +-
 sound/soc/codecs/cs35l45-tables.c                  |  32 ++--
 sound/soc/codecs/cs4265.c                          |   4 +-
 sound/soc/codecs/lpass-wsa-macro.c                 |   8 +-
 sound/soc/codecs/tas2562.c                         |   9 +-
 sound/soc/intel/common/sof-function-topology-lib.c |   7 +-
 sound/soc/sof/ipc4-topology.c                      |  33 +++++
 sound/soc/sof/sof-audio.c                          |   9 +-
 sound/soc/xilinx/xlnx_formatter_pcm.c              |  10 +-
 .../selftests/bpf/progs/verifier_linked_scalars.c  | 112 ++++++++++++++
 tools/testing/selftests/net/mptcp/mptcp_join.sh    |   4 +-
 233 files changed, 2826 insertions(+), 1496 deletions(-)
author maintainer
#1 lore raw

PATCH 1/220 block: stop the timeout timer when releasing a never added disk

6.12-stable review patch. If anyone has any objections, please let me know.

------------------

From: Chao Shi <[email protected]>

[ Upstream commit 26cb8ebbfaf713c82e142d08828d4d765057633b ]

disk_release() undoes blk_mq_init_allocated_queue() for a disk whose probe failed before add_disk(), but it only calls blk_mq_exit_queue(). Nothing there stops q->timeout, and that timer rolls forward: it stays pending until it next expires, not until the last request completes. So if the driver issued any I/O before adding the disk, the request_queue is freed while still linked into a timer wheel bucket.

Commit 6f8191fdf41d ("block: simplify disk shutdown") dropped the blk_cleanup_queue() call that used to stop it. __del_gendisk() and blk_mq_destroy_queue() still do; only the probe failure path lost it.

nvme gets there because nvme_update_ns_info() submits Report Zones or FDP io-mgmt-recv on ns->queue before the disk is added, so a later failure - a concurrent reset setting NVME_CTRL_FROZEN, or device_add_disk() failing - lands in put_disk() with the timer armed:

  BUG: KASAN: slab-use-after-free in detach_if_pending+0x30c/0x340
  Write of size 8 at addr ffff888004d71310 by task kworker/u8:2/37
   __timer_delete_sync+0x156/0x240 kernel/time/timer.c:1621
   blk_sync_queue+0x22/0x40 block/blk-core.c:222
   nvme_sync_queues+0x100/0x150 drivers/nvme/host/core.c:5362
   nvme_reset_work+0x138/0x930 drivers/nvme/host/pci.c:3264

  Allocated by task 34:
   __blk_mq_alloc_disk+0x33/0x100 block/blk-mq.c:4462
   nvme_alloc_ns+0x290/0x3870 drivers/nvme/host/core.c:4146

  Freed by task 0:
   blk_free_queue_rcu+0x3a/0x50 block/blk-core.c:254
   rcu_core+0xc10/0x1730 kernel/rcu/tree.c:2857

The queue being synced there is ctrl->admin_q, only a victim sharing a timer wheel bucket with the freed queue's dangling entry; other runs tripped in enqueue_timer(), __run_timers() or blk_mq_timeout_work(). Failing nvme_alloc_ns() with a debug patch makes it deterministic: one leaked timer trips KASAN within seconds, while 1987 patched releases produced no splat.

Stop the timer and the queue work items before blk_mq_exit_queue(), like blk_mq_destroy_queue() does.

Found by FuzzNvme.

---
 block/genhd.c | 12 ++++++++----
 1 file changed, 8 insertions(+), 4 deletions(-)
block/genhd.c +8 -4
index 31e1539e6e6a..a909817bf1c8 100644
@@ -1192,14 +1192,18 @@ static void disk_release(struct device *dev)
 	/*
 	 * To undo the all initialization from blk_mq_init_allocated_queue in
 	 * case of a probe failure where add_disk is never called we have to
-	 * call blk_mq_exit_queue here. We can't do this for the more common
-	 * teardown case (yet) as the tagset can be gone by the time the disk
-	 * is released once it was added.
+	 * call blk_mq_exit_queue here, after stopping the timer and work items
+	 * that I/O issued before add_disk may have left pending.  We can't do
+	 * this for the more common teardown case (yet) as the tagset can be
+	 * gone by the time the disk is released once it was added.
 	 */
 	if (queue_is_mq(disk->queue) &&
 	    test_bit(GD_OWNS_QUEUE, &disk->state) &&
-	    !test_bit(GD_ADDED, &disk->state))
+	    !test_bit(GD_ADDED, &disk->state)) {
+		blk_sync_queue(disk->queue);
+		blk_mq_cancel_work_sync(disk->queue);
 		blk_mq_exit_queue(disk->queue);
+	}
 
 	blkcg_exit_disk(disk);
 
Signature · 1 line
2.53.0
author maintainer
#2 lore raw

PATCH 2/220 bpf: Fix linked reg delta tracking when src_reg == dst_reg

In reply to Greg Kroah-Hartman

6.12-stable review patch. If anyone has any objections, please let me know.

------------------

From: Daniel Borkmann <[email protected]>

commit d7f14173c0d5866c3cae759dee560ad1bed10d2e upstream.

Consider the case of rX += rX where src_reg and dst_reg are pointers to the same bpf_reg_state in adjust_reg_min_max_vals(). The latter first modifies the dst_reg in-place, and later in the delta tracking, the subsequent is_reg_const(src_reg)/reg_const_value(src_reg) reads the post-{add,sub} value instead of the original source.

This is problematic since it sets an incorrect delta, which sync_linked_regs() then propagates to linked registers, thus creating a verifier-vs-runtime mismatch. Fix it by just skipping this corner case.

Fixes 98d7ca374ba4 ("bpf: Track delta between "linked" registers.")

[shung-hsi.yu: contextual difference due to commit 7a433e519364 ("bpf: Support negative offsets, BPF_SUB, and alu32 for linked register tracking") not backported. ]

---
 kernel/bpf/verifier.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)
kernel/bpf/verifier.c +2 -1
index 275c9fabc955..582be2211ffc 100644
@@ -14544,7 +14544,8 @@ static int adjust_reg_min_max_vals(struct bpf_verifier_env *env,
 	 */
 	if (env->bpf_capable &&
 	    BPF_OP(insn->code) == BPF_ADD && !alu32 &&
-	    dst_reg->id && is_reg_const(src_reg, false)) {
+	    dst_reg->id && is_reg_const(src_reg, false) &&
+	    !(BPF_SRC(insn->code) == BPF_X && insn->src_reg == insn->dst_reg)) {
 		u64 val = reg_const_value(src_reg, false);
 
 		if ((dst_reg->id & BPF_ADD_CONST) ||
Signature · 1 line
2.53.0
author maintainer
#3 lore raw

PATCH 3/220 bpf: Clear delta when clearing reg id for non-{add,sub} ops

In reply to Greg Kroah-Hartman

6.12-stable review patch. If anyone has any objections, please let me know.

------------------

From: Daniel Borkmann <[email protected]>

commit 1b327732c84640c1e3da487eefe9d00cc9f2dd34 upstream.

When a non-{add,sub} alu op such as xor is performed on a scalar register that previously had a BPF_ADD_CONST delta, the else path in adjust_reg_min_max_vals() only clears dst_reg->id but leaves dst_reg->delta unchanged.

This stale delta can propagate via assign_scalar_id_before_mov() when the register is later used in a mov. It gets a fresh id but keeps the stale delta from the old (now-cleared) BPF_ADD_CONST. This stale delta can later propagate leading to a verifier-vs- runtime value mismatch.

The clear_id label already correctly clears both delta and id. Make the else path consistent by also zeroing the delta when id is cleared. More generally, this introduces a helper clear_scalar_id() which internally takes care of zeroing. There are various other locations in the verifier where only the id is cleared. By using the helper we catch all current and future locations.

Fixes 98d7ca374ba4 ("bpf: Track delta between "linked" registers.")

[shung-hsi.yu:
- reverse 'off' -> 'delta' renaming done later in commit 3d91c618aca4 ("bpf:
rename bpf_reg_state->off to bpf_reg_state->delta")
- kept "dst_reg->live |= REG_LIVE_WRITTEN", which got removed in commit 107e16979905 ("bpf: disable and remove registers chain based liveness")
- dropped hunk in scalar_byte_swap(), which was added later in commit 9d2119984224 ("bpf: Add bitwise tracking for BPF_END")
- dropped hunk in clear_singular_ids(), which was added later in commit b2a0aa3a8739 ("bpf: Clear singular ids for scalars in is_state_visited()")]

---
 kernel/bpf/verifier.c | 48 ++++++++++++++++++++++---------------------
 1 file changed, 25 insertions(+), 23 deletions(-)
kernel/bpf/verifier.c +25 -23
index 582be2211ffc..99b3f539e431 100644
@@ -4635,27 +4635,30 @@ static bool __is_pointer_value(bool allow_ptr_leaks,
 	return reg->type != SCALAR_VALUE;
 }
 
+static void clear_scalar_id(struct bpf_reg_state *reg)
+{
+	reg->id = 0;
+	reg->off = 0;
+}
+
 static void assign_scalar_id_before_mov(struct bpf_verifier_env *env,
 					struct bpf_reg_state *src_reg)
 {
 	if (src_reg->type != SCALAR_VALUE)
 		return;
-
-	if (src_reg->id & BPF_ADD_CONST) {
-		/*
-		 * The verifier is processing rX = rY insn and
-		 * rY->id has special linked register already.
-		 * Cleared it, since multiple rX += const are not supported.
-		 */
-		src_reg->id = 0;
-		src_reg->off = 0;
-	}
-
+	/*
+	 * The verifier is processing rX = rY insn and
+	 * rY->id has special linked register already.
+	 * Cleared it, since multiple rX += const are not supported.
+	 */
+	if (src_reg->id & BPF_ADD_CONST)
+		clear_scalar_id(src_reg);
+	/*
+	 * Ensure that src_reg has a valid ID that will be copied to
+	 * dst_reg and then will be used by sync_linked_regs() to
+	 * propagate min/max range.
+	 */
 	if (!src_reg->id && !tnum_is_const(src_reg->var_off))
-		/* Ensure that src_reg has a valid ID that will be copied to
-		 * dst_reg and then will be used by sync_linked_regs() to
-		 * propagate min/max range.
-		 */
 		src_reg->id = ++env->id_gen;
 }
 
@@ -5093,7 +5096,7 @@ static int check_stack_read_fixed_off(struct bpf_verifier_env *env,
 				 * coerce_reg_to_size will adjust the boundaries.
 				 */
 				if (get_reg_width(reg) > size * BITS_PER_BYTE)
-					state->regs[dst_regno].id = 0;
+					clear_scalar_id(&state->regs[dst_regno]);
 			} else {
 				int spill_cnt = 0, zero_cnt = 0;
 
@@ -14555,8 +14558,7 @@ static int adjust_reg_min_max_vals(struct bpf_verifier_env *env,
 			 * If the register already went through rX += val
 			 * we cannot accumulate another val into rx->off.
 			 */
-			dst_reg->off = 0;
-			dst_reg->id = 0;
+			clear_scalar_id(dst_reg);
 		} else {
 			dst_reg->id |= BPF_ADD_CONST;
 			dst_reg->off = val;
@@ -14566,7 +14568,7 @@ static int adjust_reg_min_max_vals(struct bpf_verifier_env *env,
 		 * Make sure ID is cleared otherwise dst_reg min/max could be
 		 * incorrectly propagated into other registers by sync_linked_regs()
 		 */
-		dst_reg->id = 0;
+		clear_scalar_id(dst_reg);
 	}
 	return 0;
 }
@@ -14690,7 +14692,7 @@ static int check_alu_op(struct bpf_verifier_env *env, struct bpf_insn *insn)
 							assign_scalar_id_before_mov(env, src_reg);
 						copy_register_state(dst_reg, src_reg);
 						if (!no_sext)
-							dst_reg->id = 0;
+							clear_scalar_id(dst_reg);
 						coerce_reg_to_size_sx(dst_reg, insn->off >> 3);
 						dst_reg->live |= REG_LIVE_WRITTEN;
 						dst_reg->subreg_def = DEF_NOT_SUBREG;
@@ -14717,7 +14719,7 @@ static int check_alu_op(struct bpf_verifier_env *env, struct bpf_insn *insn)
 						 * propagated into src_reg by sync_linked_regs()
 						 */
 						if (!is_src_reg_u32)
-							dst_reg->id = 0;
+							clear_scalar_id(dst_reg);
 						dst_reg->live |= REG_LIVE_WRITTEN;
 						dst_reg->subreg_def = env->insn_idx + 1;
 					} else {
@@ -14728,7 +14730,7 @@ static int check_alu_op(struct bpf_verifier_env *env, struct bpf_insn *insn)
 							assign_scalar_id_before_mov(env, src_reg);
 						copy_register_state(dst_reg, src_reg);
 						if (!no_sext)
-							dst_reg->id = 0;
+							clear_scalar_id(dst_reg);
 						dst_reg->live |= REG_LIVE_WRITTEN;
 						dst_reg->subreg_def = env->insn_idx + 1;
 						coerce_subreg_to_size_sx(dst_reg, insn->off >> 3);
@@ -15559,7 +15561,7 @@ static void __collect_linked_regs(struct linked_regs *reg_set, struct bpf_reg_st
 		e->is_reg = is_reg;
 		e->regno = spi_or_reg;
 	} else {
-		reg->id = 0;
+		clear_scalar_id(reg);
 	}
 }
 
Signature · 1 line
2.53.0
author maintainer
#4 lore raw

PATCH 4/220 selftests/bpf: Add tests for delta tracking when src_reg == dst_reg

In reply to Greg Kroah-Hartman

6.12-stable review patch. If anyone has any objections, please let me know.

------------------

From: Daniel Borkmann <[email protected]>

commit ed2eecdc0c6613353bc1565e900d2b23237713da upstream.

Extend the verifier_linked_scalars BPF selftest with a rX += rX test such that the div-by-zero path is rejected in the fixed case.

# LDLIBS=-static PKG_CONFIG='pkg-config --static' ./vmtest.sh -- ./test_progs -t verifier_linked_scalars [...]
./test_progs -t verifier_linked_scalars

  #612/1   verifier_linked_scalars/scalars: find linked scalars:OK
  #612/2   verifier_linked_scalars/sync_linked_regs_preserves_id:OK
  #612/3   verifier_linked_scalars/scalars_neg:OK
  #612/4   verifier_linked_scalars/scalars_neg_sub:OK
  #612/5   verifier_linked_scalars/scalars_neg_alu32_add:OK
  #612/6   verifier_linked_scalars/scalars_neg_alu32_sub:OK
  #612/7   verifier_linked_scalars/scalars_pos:OK
  #612/8   verifier_linked_scalars/scalars_sub_neg_imm:OK
  #612/9   verifier_linked_scalars/scalars_double_add:OK
  #612/10  verifier_linked_scalars/scalars_sync_delta_overflow:OK
  #612/11  verifier_linked_scalars/scalars_sync_delta_overflow_large_range:OK
  #612/12  verifier_linked_scalars/scalars_alu32_big_offset:OK
  #612/13  verifier_linked_scalars/scalars_alu32_basic:OK
  #612/14  verifier_linked_scalars/scalars_alu32_wrap:OK
  #612/15  verifier_linked_scalars/scalars_alu32_zext_linked_reg:OK
  #612/16  verifier_linked_scalars/scalars_alu32_alu64_cross_type:OK
  #612/17  verifier_linked_scalars/scalars_alu32_alu64_regsafe_pruning:OK
  #612/18  verifier_linked_scalars/alu32_negative_offset:OK
  #612/19  verifier_linked_scalars/spurious_precision_marks:OK
  #612/20  verifier_linked_scalars/scalars_self_add_clears_id:OK
  #612/21  verifier_linked_scalars/scalars_self_add_alu32_clears_id:OK
  #612     verifier_linked_scalars:OK

Summary: 1/21 PASSED, 0 SKIPPED, 0 FAILED

---
 .../bpf/progs/verifier_linked_scalars.c       | 57 +++++++++++++++++++
 1 file changed, 57 insertions(+)
tools/testing/selftests/bpf/progs/verifier_linked_scalars.c +57 -0
index 8f755d2464cf..1b3b44f5f910 100644
@@ -31,4 +31,61 @@ l1:						\
 "	::: __clobber_all);
 }
 
+/*
+ * Test that r += r (self-add, src_reg == dst_reg) clears the scalar ID
+ * so that sync_linked_regs() does not propagate an incorrect delta.
+ */
+SEC("socket")
+__failure
+__msg("div by zero")
+__naked void scalars_self_add_clears_id(void)
+{
+	asm volatile ("						\
+	call %[bpf_get_prandom_u32];				\
+	r6 = r0;		/* r6 unknown, id A */		\
+	r7 = r6;		/* r7 linked to r6, id A */	\
+	call %[bpf_get_prandom_u32];				\
+	r8 = r0;		/* r8 unknown, id B */		\
+	r9 = r8;		/* r9 linked to r8, id B */	\
+	if r7 != 1 goto l_exit_%=;				\
+	/* r7 == 1; sync propagates: r6 = 1 (known, id A) */	\
+	r6 += r6;		/* r6 = 2; should clear id */	\
+	if r7 == r9 goto l_exit_%=;				\
+	/* Bug: r6 synced to r7(1)+delta(2)=3; Fix: r6 = 2 */	\
+	if r6 == 3 goto l_exit_%=;				\
+	r0 /= 0;						\
+l_exit_%=:							\
+	r0 = 0;							\
+	exit;							\
+"	:
+	: __imm(bpf_get_prandom_u32)
+	: __clobber_all);
+}
+
+/* Same as above but with alu32 such that w6 += w6 also clears id. */
+SEC("socket")
+__failure
+__msg("div by zero")
+__naked void scalars_self_add_alu32_clears_id(void)
+{
+	asm volatile ("						\
+	call %[bpf_get_prandom_u32];				\
+	w6 = w0;						\
+	w7 = w6;						\
+	call %[bpf_get_prandom_u32];				\
+	w8 = w0;						\
+	w9 = w8;						\
+	if w7 != 1 goto l_exit_%=;				\
+	w6 += w6;						\
+	if w7 == w9 goto l_exit_%=;				\
+	if w6 == 3 goto l_exit_%=;				\
+	r0 /= 0;						\
+l_exit_%=:							\
+	r0 = 0;							\
+	exit;							\
+"	:
+	: __imm(bpf_get_prandom_u32)
+	: __clobber_all);
+}
+
 char _license[] SEC("license") = "GPL";
Signature · 1 line
2.53.0
author maintainer
#5 lore raw

PATCH 5/220 selftests/bpf: Add tests for stale delta leaking through id reassignment

In reply to Greg Kroah-Hartman

6.12-stable review patch. If anyone has any objections, please let me know.

------------------

From: Daniel Borkmann <[email protected]>

commit cac16ce1e3786bd98cec0c108e3bc06ed3d3c6a9 upstream.

Extend the verifier_linked_scalars BPF selftest with a stale delta test such that the div-by-zero path is rejected in the fixed case.

# LDLIBS=-static PKG_CONFIG='pkg-config --static' ./vmtest.sh -- ./test_progs -t verifier_linked_scalars [...]
./test_progs -t verifier_linked_scalars

  #612/1   verifier_linked_scalars/scalars: find linked scalars:OK
  #612/2   verifier_linked_scalars/sync_linked_regs_preserves_id:OK
  #612/3   verifier_linked_scalars/scalars_neg:OK
  #612/4   verifier_linked_scalars/scalars_neg_sub:OK
  #612/5   verifier_linked_scalars/scalars_neg_alu32_add:OK
  #612/6   verifier_linked_scalars/scalars_neg_alu32_sub:OK
  #612/7   verifier_linked_scalars/scalars_pos:OK
  #612/8   verifier_linked_scalars/scalars_sub_neg_imm:OK
  #612/9   verifier_linked_scalars/scalars_double_add:OK
  #612/10  verifier_linked_scalars/scalars_sync_delta_overflow:OK
  #612/11  verifier_linked_scalars/scalars_sync_delta_overflow_large_range:OK
  #612/12  verifier_linked_scalars/scalars_alu32_big_offset:OK
  #612/13  verifier_linked_scalars/scalars_alu32_basic:OK
  #612/14  verifier_linked_scalars/scalars_alu32_wrap:OK
  #612/15  verifier_linked_scalars/scalars_alu32_zext_linked_reg:OK
  #612/16  verifier_linked_scalars/scalars_alu32_alu64_cross_type:OK
  #612/17  verifier_linked_scalars/scalars_alu32_alu64_regsafe_pruning:OK
  #612/18  verifier_linked_scalars/alu32_negative_offset:OK
  #612/19  verifier_linked_scalars/spurious_precision_marks:OK
  #612/20  verifier_linked_scalars/scalars_self_add_clears_id:OK
  #612/21  verifier_linked_scalars/scalars_self_add_alu32_clears_id:OK
  #612/22  verifier_linked_scalars/scalars_stale_delta_from_cleared_id:OK
  #612/23  verifier_linked_scalars/scalars_stale_delta_from_cleared_id_alu32:OK
  #612     verifier_linked_scalars:OK

Summary: 1/23 PASSED, 0 SKIPPED, 0 FAILED

---
 .../bpf/progs/verifier_linked_scalars.c       | 55 +++++++++++++++++++
 1 file changed, 55 insertions(+)
tools/testing/selftests/bpf/progs/verifier_linked_scalars.c +55 -0
index 1b3b44f5f910..fb44f1770dab 100644
@@ -88,4 +88,59 @@ l_exit_%=:							\
 	: __clobber_all);
 }
 
+/*
+ * Test that stale delta from a cleared BPF_ADD_CONST does not leak
+ * through assign_scalar_id_before_mov() into a new id, causing
+ * sync_linked_regs() to compute an incorrect offset.
+ */
+SEC("socket")
+__failure
+__msg("div by zero")
+__naked void scalars_stale_delta_from_cleared_id(void)
+{
+	asm volatile ("						\
+	call %[bpf_get_prandom_u32];				\
+	r6 = r0;		/* r6 unknown, gets id A */	\
+	r6 += 5;		/* id A|ADD_CONST, delta 5 */	\
+	r6 ^= 0;		/* id cleared; delta stays 5 */	\
+	r8 = r6;		/* new id B, stale delta 5 */	\
+	r8 += 3;		/* id B|ADD_CONST, delta 3 */	\
+	r9 = r6;		/* id B, stale delta 5 */	\
+	if r9 != 10 goto l_exit_%=;				\
+	/* Bug: r8 = 10+(3-5) = 8; Fix: r8 = 10+(3-0) = 13 */	\
+	if r8 == 8 goto l_exit_%=;				\
+	r0 /= 0;						\
+l_exit_%=:							\
+	r0 = 0;							\
+	exit;							\
+"	:
+	: __imm(bpf_get_prandom_u32)
+	: __clobber_all);
+}
+
+/* Same as above but with alu32. */
+SEC("socket")
+__failure
+__msg("div by zero")
+__naked void scalars_stale_delta_from_cleared_id_alu32(void)
+{
+	asm volatile ("						\
+	call %[bpf_get_prandom_u32];				\
+	w6 = w0;						\
+	w6 += 5;						\
+	w6 ^= 0;						\
+	w8 = w6;						\
+	w8 += 3;						\
+	w9 = w6;						\
+	if w9 != 10 goto l_exit_%=;				\
+	if w8 == 8 goto l_exit_%=;				\
+	r0 /= 0;						\
+l_exit_%=:							\
+	r0 = 0;							\
+	exit;							\
+"	:
+	: __imm(bpf_get_prandom_u32)
+	: __clobber_all);
+}
+
 char _license[] SEC("license") = "GPL";
Signature · 1 line
2.53.0
author maintainer
#6 lore raw

PATCH 6/220 f2fs: fix UAF issue in f2fs_merge_page_bio()

In reply to Greg Kroah-Hartman

6.12-stable review patch. If anyone has any objections, please let me know.

------------------

From: Chao Yu <[email protected]>

commit edf7e9040fc52c922db947f9c6c36f07377c52ea upstream.

As JY reported in bugzilla [1],

Unable to handle kernel NULL pointer dereference at virtual address 0000000000000000
pc : [0xffffffe51d249484] f2fs_is_cp_guaranteed+0x70/0x98
lr : [0xffffffe51d24adbc] f2fs_merge_page_bio+0x520/0x6d4
CPU: 3 UID: 0 PID: 6790 Comm: kworker/u16:3 Tainted: P    B   W  OE      6.12.30-android16-5-maybe-dirty-4k #1 5f7701c9cbf727d1eebe77c89bbbeb3371e895e5
Tainted: [P]=PROPRIETARY_MODULE, [B]=BAD_PAGE, [W]=WARN, [O]=OOT_MODULE, [E]=UNSIGNED_MODULE
Workqueue: writeback wb_workfn (flush-254:49)
Call trace:
 f2fs_is_cp_guaranteed+0x70/0x98
 f2fs_inplace_write_data+0x174/0x2f4
 f2fs_do_write_data_page+0x214/0x81c
 f2fs_write_single_data_page+0x28c/0x764
 f2fs_write_data_pages+0x78c/0xce4
 do_writepages+0xe8/0x2fc
 __writeback_single_inode+0x4c/0x4b4
 writeback_sb_inodes+0x314/0x540
 __writeback_inodes_wb+0xa4/0xf4
 wb_writeback+0x160/0x448
 wb_workfn+0x2f0/0x5dc
 process_scheduled_works+0x1c8/0x458
 worker_thread+0x334/0x3f0
 kthread+0x118/0x1ac
 ret_from_fork+0x10/0x20

[1] https://bugzilla.kernel.org/show_bug.cgi?id=220575

The panic was caused by UAF issue w/ below race condition:

kworker
- writepages
- f2fs_write_cache_pages
- f2fs_write_single_data_page
- f2fs_do_write_data_page
- f2fs_inplace_write_data
- f2fs_merge_page_bio
- add_inu_page
: cache page #1 into bio & cache bio in
io->bio_list
- f2fs_write_single_data_page
- f2fs_do_write_data_page
- f2fs_inplace_write_data
- f2fs_merge_page_bio
- add_inu_page
: cache page #2 into bio which is linked
in io->bio_list
write
- f2fs_write_begin
: write page #1
- f2fs_folio_wait_writeback
- f2fs_submit_merged_ipu_write
- f2fs_submit_write_bio
: submit bio which inclues page #1 and #2

software IRQ
- f2fs_write_end_io
- fscrypt_free_bounce_page
: freed bounced page which belongs to page #2
- inc_page_count( , WB_DATA_TYPE(data_folio), false)
: data_folio points to fio->encrypted_page
the bounced page can be freed before
accessing it in f2fs_is_cp_guarantee()

It can reproduce w/ below testcase:
Run below script in shell #1:
for ((i=1;i>0;i++)) do xfs_io -f /mnt/f2fs/enc/file \
-c "pwrite 0 32k" -c "fdatasync"

Run below script in shell #2:
for ((i=1;i>0;i++)) do xfs_io -f /mnt/f2fs/enc/file \
-c "pwrite 0 32k" -c "fdatasync"

So, in f2fs_merge_page_bio(), let's avoid using fio->encrypted_page after commit page into internal ipu cache.

Fixes 0b20fcec8651 ("f2fs: cache global IPU bio")
[Jiucheng Xu: backport to 6.12.y]
---
 fs/f2fs/data.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
fs/f2fs/data.c +1 -1
index 68891c086b38..16c15360605b 100644
@@ -950,7 +950,7 @@ int f2fs_merge_page_bio(struct f2fs_io_info *fio)
 		wbc_account_cgroup_owner(fio->io_wbc, page_folio(fio->page),
 					 PAGE_SIZE);
 
-	inc_page_count(fio->sbi, WB_DATA_TYPE(page, false));
+	inc_page_count(fio->sbi, WB_DATA_TYPE(fio->page, false));
 
 	*fio->last_block = fio->new_blkaddr;
 	*fio->bio = bio;
Signature · 1 line
2.53.0
author maintainer
#7 lore raw

PATCH 7/220 mtd: ubi: skip programming unused bits in ubi headers

In reply to Greg Kroah-Hartman

6.12-stable review patch. If anyone has any objections, please let me know.

------------------

From: Cheng Ming Lin <[email protected]>

[ Upstream commit 77530d1a78ca5c274e37d6494a965223672630b2 ]

This patch prevents unnecessary programming of bits in ec_hdr and vid_hdr that are not used or read during normal UBI operation. These unused bits are typcially already set to 1 in erased flash and do not need to be explicitly programmed to 0 if they are not used.

Programming such unused areas offers no functional benefit and may result in unnecessary flash wear, reducing the overall lifetime of the device. By skipping these writes, we preserve the flash state as much as possible and minimize wear caused by redundant operations.

This change ensures that only necessary fields are written when preparing UBI headers, improving flash efficiency without affecting functionality.

Additionally, the Kioxia TC58NVG1S3HTA00 datasheet (page 63) also notes that continuous program/erase cycling with a high percentage of '0' bits in the data pattern can accelerate block endurance degradation. This further supports avoiding large 0x00 patterns.

---
 drivers/mtd/ubi/io.c | 10 ++++++++++
 1 file changed, 10 insertions(+)
drivers/mtd/ubi/io.c +10 -0
index a4999bce435f..915eb64cb001 100644
@@ -868,6 +868,8 @@ int ubi_io_write_ec_hdr(struct ubi_device *ubi, int pnum,
 		return -EROFS;
 	}
 
+	memset((char *)ec_hdr + UBI_EC_HDR_SIZE, 0xFF, ubi->ec_hdr_alsize - UBI_EC_HDR_SIZE);
+
 	err = ubi_io_write(ubi, ec_hdr, pnum, 0, ubi->ec_hdr_alsize);
 	return err;
 }
@@ -1150,6 +1152,14 @@ int ubi_io_write_vid_hdr(struct ubi_device *ubi, int pnum,
 		return -EROFS;
 	}
 
+	if (ubi->vid_hdr_shift) {
+		memset((char *)p, 0xFF, ubi->vid_hdr_shift);
+		memset((char *)p + ubi->vid_hdr_shift + UBI_VID_HDR_SIZE, 0xFF,
+		       ubi->vid_hdr_alsize - (ubi->vid_hdr_shift + UBI_VID_HDR_SIZE));
+	} else {
+		memset((char *)p + UBI_VID_HDR_SIZE, 0xFF, ubi->vid_hdr_alsize - UBI_VID_HDR_SIZE);
+	}
+
 	err = ubi_io_write(ubi, p, pnum, ubi->vid_hdr_aloffset,
 			   ubi->vid_hdr_alsize);
 	return err;
Signature · 1 line
2.53.0
author maintainer
#8 lore raw

PATCH 8/220 ubi: fastmap: fix ubi->fm memory leak

In reply to Greg Kroah-Hartman

6.12-stable review patch. If anyone has any objections, please let me know.

------------------

From: Liyuan Pang <[email protected]>

[ Upstream commit d133e30aabc7c8eb8206827f8fbe0f3679adb911 ]

The problem is that scan_fast() allocate memory for ubi->fm and ubi->fm->e[x], but if the following attach process fails in ubi_wl_init or ubi_read_volume_table, the whole attach process will fail without executing ubi_wl_close to free the memory under ubi->fm.

Fix this by add a new ubi_free_fastmap function in fastmap.c to free the memory allocated for fm.

If SLUB_DEBUG and KUNIT are enabled, the following warning messages will show:
ubi0: detaching mtd0
ubi0: mtd0 is detached
ubi0: default fastmap pool size: 200
ubi0: default fastmap WL pool size: 100
ubi0: attaching mtd0
ubi0: attached by fastmap
ubi0: fastmap pool size: 200
ubi0: fastmap WL pool size: 100
ubi0 error: ubi_wl_init [ubi]: no enough physical eraseblocks (4, need 203) ubi0 error: ubi_attach_mtd_dev [ubi]: failed to attach mtd0, error -28 UBI error: cannot attach mtd0
=================================================================
BUG ubi_wl_entry_slab (Tainted: G B O L ): Objects remaining in ubi_wl_entry_slab on __kmem_cache_shutdown()
-----------------------------------------------------------------------------

Slab 0xffff2fd23a40cd00 objects=22 used=1 fp=0xffff2fd1d0334fd8 flags=0x883fffc010200(slab|head|section=34|node=0|zone=1|lastcpupid=0x7fff)

CPU: 0 PID: 5884 Comm: insmod Tainted: G    B      O L    5.10.0 #1
Hardware name: LS1043A RDB Board (DT)
Call trace:
 dump_backtrace+0x0/0x198
 show_stack+0x18/0x28
 dump_stack+0xe8/0x15c
 slab_err+0x94/0xc0
 __kmem_cache_shutdown+0x1fc/0x39c
 kmem_cache_destroy+0x48/0x138
 ubi_init+0x1d4/0xf34 [ubi]
 do_one_initcall+0xb4/0x24c
 do_init_module+0x4c/0x1dc
 load_module+0x212c/0x2260
 __se_sys_finit_module+0xb4/0xd8
 __arm64_sys_finit_module+0x18/0x28
 el0_svc_common.constprop.0+0x78/0x1a0
 do_el0_svc+0x78/0x90
 el0_svc+0x20/0x38
 el0_sync_handler+0xf0/0x140
 normal+0x3d8/0x400
Object 0xffff2fd1d0334e68 @offset=3688
Allocated in ubi_scan_fastmap+0xf04/0xf40 [ubi] age=80 cpu=0 pid=5884
	__slab_alloc.isra.21+0x6c/0xb4
	kmem_cache_alloc+0x1e4/0x80c
	ubi_scan_fastmap+0xf04/0xf40 [ubi]
	ubi_attach+0x1f0/0x3a8 [ubi]
	ubi_attach_mtd_dev+0x810/0xbc8 [ubi]
	ubi_init+0x238/0xf34 [ubi]
	do_one_initcall+0xb4/0x24c
	do_init_module+0x4c/0x1dc
	load_module+0x212c/0x2260
	__se_sys_finit_module+0xb4/0xd8
	__arm64_sys_finit_module+0x18/0x28
	el0_svc_common.constprop.0+0x78/0x1a0
	do_el0_svc+0x78/0x90
	el0_svc+0x20/0x38
	el0_sync_handler+0xf0/0x140
	normal+0x3d8/0x400
---
 drivers/mtd/ubi/attach.c     |  4 +++-
 drivers/mtd/ubi/fastmap-wl.c |  8 +-------
 drivers/mtd/ubi/ubi.h        | 12 ++++++++++++
 3 files changed, 16 insertions(+), 8 deletions(-)
drivers/mtd/ubi/attach.c +3 -1
index adc47b87b38a..884171871d0e 100644
@@ -1600,7 +1600,7 @@ int ubi_attach(struct ubi_device *ubi, int force_scan)
 
 	err = ubi_read_volume_table(ubi, ai);
 	if (err)
-		goto out_ai;
+		goto out_fm;
 
 	err = ubi_wl_init(ubi, ai);
 	if (err)
@@ -1642,6 +1642,8 @@ int ubi_attach(struct ubi_device *ubi, int force_scan)
 out_vtbl:
 	ubi_free_all_volumes(ubi);
 	vfree(ubi->vtbl);
+out_fm:
+	ubi_free_fastmap(ubi);
 out_ai:
 	destroy_ai(ai);
 	return err;
drivers/mtd/ubi/fastmap-wl.c +1 -7
index 9bdb6525f128..e2bc1122bfd3 100644
@@ -530,8 +530,6 @@ int ubi_is_erase_work(struct ubi_work *wrk)
 
 static void ubi_fastmap_close(struct ubi_device *ubi)
 {
-	int i;
-
 	return_unused_pool_pebs(ubi, &ubi->fm_pool);
 	return_unused_pool_pebs(ubi, &ubi->fm_wl_pool);
 
@@ -540,11 +538,7 @@ static void ubi_fastmap_close(struct ubi_device *ubi)
 		ubi->fm_anchor = NULL;
 	}
 
-	if (ubi->fm) {
-		for (i = 0; i < ubi->fm->used_blocks; i++)
-			kfree(ubi->fm->e[i]);
-	}
-	kfree(ubi->fm);
+	ubi_free_fastmap(ubi);
 }
 
 /**
drivers/mtd/ubi/ubi.h +12 -0
index 1c9e874e8ede..450a7b9a5bd7 100644
@@ -970,10 +970,22 @@ int ubi_scan_fastmap(struct ubi_device *ubi, struct ubi_attach_info *ai,
 		     struct ubi_attach_info *scan_ai);
 int ubi_fastmap_init_checkmap(struct ubi_volume *vol, int leb_count);
 void ubi_fastmap_destroy_checkmap(struct ubi_volume *vol);
+static inline void ubi_free_fastmap(struct ubi_device *ubi)
+{
+	if (ubi->fm) {
+		int i;
+
+		for (i = 0; i < ubi->fm->used_blocks; i++)
+			kmem_cache_free(ubi_wl_entry_slab, ubi->fm->e[i]);
+		kfree(ubi->fm);
+		ubi->fm = NULL;
+	}
+}
 #else
 static inline int ubi_update_fastmap(struct ubi_device *ubi) { return 0; }
 static inline int ubi_fastmap_init_checkmap(struct ubi_volume *vol, int leb_count) { return 0; }
 static inline void ubi_fastmap_destroy_checkmap(struct ubi_volume *vol) {}
+static inline void ubi_free_fastmap(struct ubi_device *ubi) { }
 #endif
 
 /* block.c */
Signature · 1 line
2.53.0
author maintainer
#9 lore raw

PATCH 9/220 mm/damon/ops-common: putback folios on invalid migrate nid

In reply to Greg Kroah-Hartman

6.12-stable review patch. If anyone has any objections, please let me know.

------------------

From: liyouhong <[email protected]>

[ Upstream commit 5deb65c34e682e7c5f5df417a70e223e8fcc5f5a ]

damon_pa_migrate() and damos_va_migrate() isolate folios into a local list and then call damon_migrate_pages(). When target_nid is invalid (including the scheme default NUMA_NO_NODE / -1), damon_migrate_pages() returns early without putting the folios back to the LRU.

Callers then discard the list head while those folios remain isolated with an extra reference taken by folio_isolate_lru(). The pages stay off the LRU for as long as the mapping exists (anon active+inactive counts drop while RSS does not), and the leftover references can pin the pages after the mapping is gone.

Put the folios back on the invalid-nid path so ignored migration requests still return them to the LRU.

Fixes 7e6c3130690a ("mm/damon/ops-common: ignore migration request to invalid nodes")
Assisted-by Cursor:grok-4.5
---
 mm/damon/paddr.c | 9 ++++++++-
 1 file changed, 8 insertions(+), 1 deletion(-)
mm/damon/paddr.c +8 -1
index 4120a73f4933..884042856f7e 100644
@@ -432,8 +432,15 @@ static unsigned long damon_pa_migrate_pages(struct list_head *folio_list,
 		return nr_migrated;
 
 	if (target_nid < 0 || target_nid >= MAX_NUMNODES ||
-			!node_state(target_nid, N_MEMORY))
+			!node_state(target_nid, N_MEMORY)) {
+		while (!list_empty(folio_list)) {
+			struct folio *folio = lru_to_folio(folio_list);
+
+			list_del(&folio->lru);
+			folio_putback_lru(folio);
+		}
 		return nr_migrated;
+	}
 
 	noreclaim_flag = memalloc_noreclaim_save();
 
Signature · 1 line
2.53.0
author maintainer
#10 lore raw

PATCH 10/220 mm/damon: adjust isolated pages stat for DAMOS_MIGRATE_{HOT,COLD}

In reply to Greg Kroah-Hartman

6.12-stable review patch. If anyone has any objections, please let me know.

------------------

From: SJ Park <[email protected]>

[ Upstream commit 1ec0e6b6f7321feb769f50d2f094a0aa6c2eda63 ]

Callers of migrate_pages() should adjust NR_MIGRATED_{ANON,FILE} for isolations and putback of the folios. That for migration succeeded folios is done by migrate_pages(), in migrate_folio_done(). That for MR_DEMOTION reason is an exception though.

DAMOS_MIGRATE_{HOT,COLD} call migrate_pages() but mistakenly not doing the stat adjustment. As a result, use of DAMOS_MIGRATE_{HOT,COLD} could corrupt the stat. It could confuse too_many_isolated(), make compaction and reclaim to behave in unexpected ways. The stat corruption can be reproduced and confirmed using DAMON user-space tool [1] on NUMA systems, like below.

    $ numactl --hardware
    available: 2 nodes (0-1)
    [...]
    $ sudo ./damo start --damos_action migrate_hot 1
    $ sudo cat /proc/sys/vm/stat_refresh
    $ sudo dmesg
    [...]
    [   80.215554] vmstat_refresh: nr_isolated_anon -5578
    [   80.216842] vmstat_refresh: nr_isolated_file -34400

This issue was discovered [2] by Sashiko.

Fixes b51820ebea65 ("mm/damon/paddr: introduce DAMOS_MIGRATE_COLD action for demotion")
Cc Honggyu Kim <[email protected]>
Cc Hyeongtak Ji <[email protected]>
Cc <[email protected]> # 6.11.x
---
 mm/damon/paddr.c | 6 ++++++
 1 file changed, 6 insertions(+)
mm/damon/paddr.c +6 -0
index 884042856f7e..d4a4d51750f5 100644
@@ -414,6 +414,8 @@ static unsigned int damon_pa_migrate_folio_list(struct list_head *folio_list,
 	while (!list_empty(folio_list)) {
 		folio = lru_to_folio(folio_list);
 		list_del(&folio->lru);
+		node_stat_sub_folio(folio, NR_ISOLATED_ANON +
+				folio_is_file_lru(folio));
 		folio_putback_lru(folio);
 	}
 
@@ -437,6 +439,8 @@ static unsigned long damon_pa_migrate_pages(struct list_head *folio_list,
 			struct folio *folio = lru_to_folio(folio_list);
 
 			list_del(&folio->lru);
+			node_stat_sub_folio(folio, NR_ISOLATED_ANON +
+					folio_is_file_lru(folio));
 			folio_putback_lru(folio);
 		}
 		return nr_migrated;
@@ -487,6 +491,8 @@ static unsigned long damon_pa_migrate(struct damon_region *r, struct damos *s)
 
 		if (!folio_isolate_lru(folio))
 			goto put_folio;
+		node_stat_add_folio(folio, NR_ISOLATED_ANON +
+				folio_is_file_lru(folio));
 		list_add(&folio->lru, &folio_list);
 put_folio:
 		addr += folio_size(folio);
Signature · 1 line
2.53.0
author maintainer
#11 lore raw

PATCH 11/220 igc: fix netdev not re-attached after resume if interface is down

In reply to Greg Kroah-Hartman

6.12-stable review patch. If anyone has any objections, please let me know.

------------------

From: Philipp David <[email protected]>

[ Upstream commit b0ce5fd9fabe7c79463cf4602217d4dfeff5b1fd ]

__igc_resume() calls netif_device_attach() only inside the netif_running() branch, so an interface that was down during suspend is never re-attached on resume. It then stays in the not-present state that __igc_shutdown() set via netif_device_detach(): ethtool reports ENODEV and every attempt to bring the interface up fails the netif_device_present() check in __dev_open() with -ENODEV, silently, since __igc_resume() returns 0. Only reloading the driver recovers the device.

This is easy to hit in practice because NetworkManager brings managed interfaces down before sleep unless Wake-on-LAN is configured, making the adapter unusable after every suspend/resume cycle with WoL disabled.

Re-attach the netdev on every successful resume, as igb and e1000e do.

---
 drivers/net/ethernet/intel/igc/igc_main.c | 8 +++++---
 1 file changed, 5 insertions(+), 3 deletions(-)
drivers/net/ethernet/intel/igc/igc_main.c +5 -3
index 6fcf4fd7ee19..ecc33f797e37 100644
@@ -7405,11 +7405,13 @@ static int igc_resume(struct device *dev)
 
 	if (netif_running(netdev)) {
 		err = __igc_open(netdev, true);
-		if (!err)
-			netif_device_attach(netdev);
+		if (err)
+			return err;
 	}
 
-	return err;
+	netif_device_attach(netdev);
+
+	return 0;
 }
 
 static int igc_runtime_resume(struct device *dev)
Signature · 1 line
2.53.0
author maintainer
#12 lore raw

PATCH 12/220 ipvs: separate destination availability state

In reply to Greg Kroah-Hartman

6.12-stable review patch. If anyone has any objections, please let me know.

------------------

From: Yizhou Zhao <[email protected]>

commit cdcc4e46180df8161f4d2f3c6fd6beaf6990133d upstream.

IPVS configuration paths update destination availability while connection accounting updates destination overload state. The two independent states share dest->flags, so their read-modify-write updates can race and lose one another.

Keep OVERLOAD in flags, where the preceding patch serializes its updates with dst_lock, and move AVAILABLE to cflags. This keeps configuration- controlled availability out of the scheduler hot cacheline until a scheduler needs to check it. It also prevents availability updates from clobbering overload state.

The destination status bits are not exposed through the IPVS sockopt or netlink interfaces, so keep their definitions in the internal IPVS header.

Readers can still observe stale destination state; this does not provide a cross-field snapshot.

Fixes 1da177e4c3f4 ("Linux-2.6.12-rc2")
Assisted-by Claude-Code:GLM-5.2
(cherry picked from commit cdcc4e46180df8161f4d2f3c6fd6beaf6990133d)
[ Julian: Backport by removing the hunks from ip_vs_xmit.c ]
---
 include/net/ip_vs.h              | 7 +++++++
 include/uapi/linux/ip_vs.h       | 6 ------
 net/netfilter/ipvs/ip_vs_conn.c  | 4 ++--
 net/netfilter/ipvs/ip_vs_core.c  | 6 +++---
 net/netfilter/ipvs/ip_vs_ctl.c   | 4 ++--
 net/netfilter/ipvs/ip_vs_dh.c    | 4 ++--
 net/netfilter/ipvs/ip_vs_lblc.c  | 2 +-
 net/netfilter/ipvs/ip_vs_lblcr.c | 8 ++++----
 8 files changed, 21 insertions(+), 20 deletions(-)
include/net/ip_vs.h +7 -0
index e0077fd28ad1c..35f012f620150 100644
@@ -34,6 +34,12 @@
 #define IP_VS_HDR_INVERSE	1
 #define IP_VS_HDR_ICMP		2
 
+/* Destination Server Flags */
+#define IP_VS_DEST_F_OVERLOAD	0x0002		/* server is overloaded */
+
+/* Destination Server Config Flags */
+#define IP_VS_DEST_CF_AVAILABLE	0x0001		/* server is available */
+
 /* Generic access of ipvs struct */
 static inline struct netns_ipvs *net_ipvs(struct net* net)
 {
@@ -725,6 +731,7 @@ struct ip_vs_dest {
 	volatile unsigned int	flags;		/* dest status flags */
 	atomic_t		conn_flags;	/* flags to copy to conn */
 	atomic_t		weight;		/* server weight */
+	unsigned long		cflags;		/* config flags */
 	atomic_t		last_weight;	/* server latest weight */
 	__u16			tun_type;	/* tunnel type */
 	__be16			tun_port;	/* tunnel port */
include/uapi/linux/ip_vs.h +0 -6
index 1ed234e7f2513..2c37c6ac7525a 100644
@@ -28,12 +28,6 @@
 #define IP_VS_SVC_F_SCHED_SH_FALLBACK	IP_VS_SVC_F_SCHED1 /* SH fallback */
 #define IP_VS_SVC_F_SCHED_SH_PORT	IP_VS_SVC_F_SCHED2 /* SH use port */
 
-/*
- *      Destination Server Flags
- */
-#define IP_VS_DEST_F_AVAILABLE	0x0001		/* server is available */
-#define IP_VS_DEST_F_OVERLOAD	0x0002		/* server is overloaded */
-
 /*
  *      IPVS sync daemon states
  */
net/netfilter/ipvs/ip_vs_conn.c +2 -2
index f50fbcb13f89c..d7a7e26e9c697 100644
@@ -759,7 +759,7 @@ int ip_vs_check_template(struct ip_vs_conn *ct, struct ip_vs_dest *cdest)
 	 * Checking the dest server status.
 	 */
 	if ((dest == NULL) ||
-	    !(dest->flags & IP_VS_DEST_F_AVAILABLE) ||
+	    !(dest->cflags & IP_VS_DEST_CF_AVAILABLE) ||
 	    expire_quiescent_template(ipvs, dest) ||
 	    (cdest && (dest != cdest))) {
 		IP_VS_DBG_BUF(9, "check_template: dest not available for "
@@ -1402,7 +1402,7 @@ void ip_vs_expire_nodest_conn_flush(struct netns_ipvs *ipvs)
 				continue;
 
 			dest = cp->dest;
-			if (!dest || (dest->flags & IP_VS_DEST_F_AVAILABLE))
+			if (!dest || (dest->cflags & IP_VS_DEST_CF_AVAILABLE))
 				continue;
 
 			if (atomic_read(&cp->n_control))
net/netfilter/ipvs/ip_vs_core.c +3 -3
index d121ea3d16bc9..1f62ca2f73309 100644
@@ -124,7 +124,7 @@ ip_vs_in_stats(struct ip_vs_conn *cp, struct sk_buff *skb)
 	struct ip_vs_dest *dest = cp->dest;
 	struct netns_ipvs *ipvs = cp->ipvs;
 
-	if (dest && (dest->flags & IP_VS_DEST_F_AVAILABLE)) {
+	if (dest && (dest->cflags & IP_VS_DEST_CF_AVAILABLE)) {
 		struct ip_vs_cpu_stats *s;
 		struct ip_vs_service *svc;
 
@@ -160,7 +160,7 @@ ip_vs_out_stats(struct ip_vs_conn *cp, struct sk_buff *skb)
 	struct ip_vs_dest *dest = cp->dest;
 	struct netns_ipvs *ipvs = cp->ipvs;
 
-	if (dest && (dest->flags & IP_VS_DEST_F_AVAILABLE)) {
+	if (dest && (dest->cflags & IP_VS_DEST_CF_AVAILABLE)) {
 		struct ip_vs_cpu_stats *s;
 		struct ip_vs_service *svc;
 
@@ -2030,7 +2030,7 @@ ip_vs_in_hook(void *priv, struct sk_buff *skb, const struct nf_hook_state *state
 	}
 
 	/* Check the server status */
-	if (cp && cp->dest && !(cp->dest->flags & IP_VS_DEST_F_AVAILABLE)) {
+	if (cp && cp->dest && !(cp->dest->cflags & IP_VS_DEST_CF_AVAILABLE)) {
 		/* the destination server is not available */
 		if (sysctl_expire_nodest_conn(ipvs)) {
 			bool old_ct = ip_vs_conn_uses_old_conntrack(cp, skb);
net/netfilter/ipvs/ip_vs_ctl.c +2 -2
index 8aa29596b36fd..1aec6a078a525 100644
@@ -1058,7 +1058,7 @@ __ip_vs_update_dest(struct ip_vs_service *svc, struct ip_vs_dest *dest,
 	}
 
 	/* set the dest status flags */
-	dest->flags |= IP_VS_DEST_F_AVAILABLE;
+	dest->cflags |= IP_VS_DEST_CF_AVAILABLE;
 
 	if (READ_ONCE(dest->u_threshold) != udest->u_threshold ||
 	    READ_ONCE(dest->l_threshold) != udest->l_threshold) {
@@ -1325,7 +1325,7 @@ static void __ip_vs_unlink_dest(struct ip_vs_service *svc,
 				struct ip_vs_dest *dest,
 				int svcupd)
 {
-	dest->flags &= ~IP_VS_DEST_F_AVAILABLE;
+	dest->cflags &= ~IP_VS_DEST_CF_AVAILABLE;
 
 	/*
 	 *  Remove it from the d-linked destination list.
net/netfilter/ipvs/ip_vs_dh.c +2 -2
index 75f4c231f4a02..f04d2530a093a 100644
@@ -220,8 +220,8 @@ ip_vs_dh_schedule(struct ip_vs_service *svc, const struct sk_buff *skb,
 
 	s = (struct ip_vs_dh_state *) svc->sched_data;
 	dest = ip_vs_dh_get(svc->af, s, &iph->daddr);
-	if (!dest
-	    || !(dest->flags & IP_VS_DEST_F_AVAILABLE)
+	if (!dest ||
+	    !(dest->cflags & IP_VS_DEST_CF_AVAILABLE)
 	    || atomic_read(&dest->weight) <= 0
 	    || is_overloaded(dest)) {
 		ip_vs_scheduler_err(svc, "no destination available");
net/netfilter/ipvs/ip_vs_lblc.c +1 -1
index 2423513d701d4..d22f8154ff949 100644
@@ -502,7 +502,7 @@ ip_vs_lblc_schedule(struct ip_vs_service *svc, const struct sk_buff *skb,
 		 */
 
 		dest = en->dest;
-		if ((dest->flags & IP_VS_DEST_F_AVAILABLE) &&
+		if ((dest->cflags & IP_VS_DEST_CF_AVAILABLE) &&
 		    atomic_read(&dest->weight) > 0 && !is_overloaded(dest, svc))
 			goto out;
 	}
net/netfilter/ipvs/ip_vs_lblcr.c +4 -4
index cdb1d4bf6761c..abe9fd0764958 100644
@@ -170,8 +170,8 @@ static inline struct ip_vs_dest *ip_vs_dest_set_min(struct ip_vs_dest_set *set)
 		if (least->flags & IP_VS_DEST_F_OVERLOAD)
 			continue;
 
-		if ((atomic_read(&least->weight) > 0)
-		    && (least->flags & IP_VS_DEST_F_AVAILABLE)) {
+		if ((atomic_read(&least->weight) > 0) &&
+		    (least->cflags & IP_VS_DEST_CF_AVAILABLE)) {
 			loh = ip_vs_dest_conn_overhead(least);
 			goto nextstage;
 		}
@@ -187,8 +187,8 @@ static inline struct ip_vs_dest *ip_vs_dest_set_min(struct ip_vs_dest_set *set)
 
 		doh = ip_vs_dest_conn_overhead(dest);
 		if (((__s64)loh * atomic_read(&dest->weight) >
-		     (__s64)doh * atomic_read(&least->weight))
-		    && (dest->flags & IP_VS_DEST_F_AVAILABLE)) {
+		     (__s64)doh * atomic_read(&least->weight)) &&
+		    (dest->cflags & IP_VS_DEST_CF_AVAILABLE)) {
 			least = dest;
 			loh = doh;
 		}
Signature · 1 line
2.53.0
author maintainer
#13 lore raw

PATCH 13/220 net: mana: Fix EQ leak in mana_remove on NULL port

In reply to Greg Kroah-Hartman

6.12-stable review patch. If anyone has any objections, please let me know.

------------------

From: Erni Sri Satya Vennela <[email protected]>

[ Upstream commit 65267c9c4f28199985505977bc2c628c82fc50ef ]

In mana_remove(), when a NULL port is encountered in the port iteration loop, 'goto out' skips the mana_destroy_eq(ac) call, leaking the event queues allocated earlier by mana_create_eq().

This can happen when mana_probe_port() fails for port 0, leaving ac->ports[0] as NULL. On driver unload or error cleanup, mana_remove() hits the NULL entry and jumps past mana_destroy_eq().

Change 'goto out' to 'break' so the for-loop exits normally and mana_destroy_eq() is always reached. Remove the now-unreferenced out:
label.

[ hmahfooz: resolve conflict in mana_remove() due to missing commit
3b194343c250 ("net: mana: Implement ndo_tx_timeout and serialize queue resets per port.") ]

---
 drivers/net/ethernet/microsoft/mana/mana_en.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)
drivers/net/ethernet/microsoft/mana/mana_en.c +2 -2
index 476f7ff0648ea..70fe4d837a0b6 100644
@@ -3011,7 +3011,7 @@ void mana_remove(struct gdma_dev *gd, bool suspending)
 		if (!ndev) {
 			if (i == 0)
 				dev_err(dev, "No net device to remove\n");
-			goto out;
+			break;
 		}
 
 		/* All cleanup actions should stay after rtnl_lock(), otherwise
@@ -3039,7 +3039,7 @@ void mana_remove(struct gdma_dev *gd, bool suspending)
 	}
 
 	mana_destroy_eq(ac);
-out:
+
 	mana_gd_deregister_device(gd);
 
 	if (suspending)
Signature · 1 line
2.53.0
#14 lore raw

Re: [PATCH 6.12 013/220] net: mana: Fix EQ leak in mana_remove on NULL port

Hi Greg/Sasha,

Quoted 25 lines · Greg Kroah-Hartman

6.12-stable review patch. If anyone has any objections, please let me know.

------------------

From: Erni Sri Satya Vennela <[email protected]>

[ Upstream commit 65267c9c4f28199985505977bc2c628c82fc50ef ]

In mana_remove(), when a NULL port is encountered in the port iteration loop, 'goto out' skips the mana_destroy_eq(ac) call, leaking the event queues allocated earlier by mana_create_eq().

This can happen when mana_probe_port() fails for port 0, leaving ac->ports[0] as NULL. On driver unload or error cleanup, mana_remove() hits the NULL entry and jumps past mana_destroy_eq().

Change 'goto out' to 'break' so the for-loop exits normally and mana_destroy_eq() is always reached. Remove the now-unreferenced out:
label.

Quoted 6 lines

[ hmahfooz: resolve conflict in mana_remove() due to missing commit
3b194343c250 ("net: mana: Implement ndo_tx_timeout and serialize queue resets per port.") ]

---

I ran an AI-assisted backport review and then checked e257399b7eab against upstream commit 65267c9c4f28 ("net: mana: Fix EQ leak in mana_remove on NULL port"). I think the conflict resolution left an ordering bug.

Upstream has:

        ndev = ac->ports[i];
        if (!ndev) {
                ...
                break;
        }
        apc = netdev_priv(ndev);

6.12.y has:

        ndev = ac->ports[i];
        apc = netdev_priv(ndev);
        if (!ndev) {
                ...
                break;
        }

netdev_priv() reads dev->priv, so a NULL port faults before the break and before mana_destroy_eq(ac). The backport therefore does not fix the EQ cleanup path it targets. Upstream commit 3b194343c250 moved this lookup below the guard; the rest of that queue-reset change is not needed here.

I think we should be moving apc = netdev_priv(ndev) below the NULL guard in mana_remove().

thanks,
Harhsit

Quoted 25 lines
  drivers/net/ethernet/microsoft/mana/mana_en.c | 4 ++--
  1 file changed, 2 insertions(+), 2 deletions(-)
drivers/net/ethernet/microsoft/mana/mana_en.c +2 -2
index 476f7ff0648ea..70fe4d837a0b6 100644
@@ -3011,7 +3011,7 @@ void mana_remove(struct gdma_dev *gd, bool suspending)
  		if (!ndev) {
  			if (i == 0)
  				dev_err(dev, "No net device to remove\n");
-			goto out;
+			break;
  		}
 
  		/* All cleanup actions should stay after rtnl_lock(), otherwise
@@ -3039,7 +3039,7 @@ void mana_remove(struct gdma_dev *gd, bool suspending)
  	}
 
  	mana_destroy_eq(ac);
-out:
+
  	mana_gd_deregister_device(gd);
 
  	if (suspending)
maintainer
#15 lore raw

Re: [PATCH 6.12 013/220] net: mana: Fix EQ leak in mana_remove on NULL port

Quoted 9 lines

Upstream has:

        ndev = ac->ports[i];
        if (!ndev) {
                ...
                break;
        }
        apc = netdev_priv(ndev);
Quoted 4 lines

6.12.y has:

        ndev = ac->ports[i];
        apc =

I agree.... A backport will be appreciated.

Signature · 2 lines
Thanks,
Sasha
author maintainer
#16 lore raw

PATCH 14/220 crypto: ccp: Add external API interface for PSP module initialization

In reply to Greg Kroah-Hartman

6.12-stable review patch. If anyone has any objections, please let me know.

------------------

From: Sean Christopherson <[email protected]>

[ Upstream commit 435b344a7042e91fb4719d589f18310e8919e39f ]

KVM is dependent on the PSP SEV driver and PSP SEV driver needs to be loaded before KVM module. In case of module loading any dependent modules are automatically loaded but in case of built-in modules there is no inherent mechanism available to specify dependencies between modules and ensure that any dependent modules are loaded implicitly.

Add a new external API interface for PSP module initialization which allows PSP SEV driver to be loaded explicitly if KVM is built-in.

Message-ID: <15279ca0cad56a07cf12834ec544310f85ff5edc.1739226950.git.ashish.kalra@amd.com>

---
 drivers/crypto/ccp/sp-dev.c | 14 ++++++++++++++
 include/linux/psp-sev.h     |  9 +++++++++
 2 files changed, 23 insertions(+)
drivers/crypto/ccp/sp-dev.c +14 -0
index ccbe009ad6e58..f204aa5df96e2 100644
@@ -19,6 +19,7 @@
 #include <linux/types.h>
 #include <linux/ccp.h>
 
+#include "sev-dev.h"
 #include "ccp-dev.h"
 #include "sp-dev.h"
 
@@ -265,8 +266,12 @@ struct sp_device *sp_get_psp_master_device(void)
 static int __init sp_mod_init(void)
 {
 #ifdef CONFIG_X86
+	static bool initialized;
 	int ret;
 
+	if (initialized)
+		return 0;
+
 	ret = sp_pci_init();
 	if (ret)
 		return ret;
@@ -275,6 +280,8 @@ static int __init sp_mod_init(void)
 	psp_pci_init();
 #endif
 
+	initialized = true;
+
 	return 0;
 #endif
 
@@ -291,6 +298,13 @@ static int __init sp_mod_init(void)
 	return -ENODEV;
 }
 
+#if IS_BUILTIN(CONFIG_KVM_AMD) && IS_ENABLED(CONFIG_KVM_AMD_SEV)
+int __init sev_module_init(void)
+{
+	return sp_mod_init();
+}
+#endif
+
 static void __exit sp_mod_exit(void)
 {
 #ifdef CONFIG_X86
include/linux/psp-sev.h +9 -0
index 6a4435cfa977e..0f5f94137f6da 100644
@@ -816,6 +816,15 @@ struct sev_data_snp_commit {
 
 #ifdef CONFIG_CRYPTO_DEV_SP_PSP
 
+/**
+ * sev_module_init - perform PSP SEV module initialization
+ *
+ * Returns:
+ * 0 if the PSP module is successfully initialized
+ * negative value if the PSP module initialization fails
+ */
+int sev_module_init(void);
+
 /**
  * sev_platform_init - perform SEV INIT command
  *
Signature · 1 line
2.53.0
author maintainer
#17 lore raw

PATCH 15/220 KVM: SVM: Ensure PSP module is initialized if KVM module is built-in

In reply to Greg Kroah-Hartman

6.12-stable review patch. If anyone has any objections, please let me know.

------------------

From: Sean Christopherson <[email protected]>

[ Upstream commit 44e70718df4fc2fadf1665eb9374df71aeda1f03 ]

The kernel's initcall infrastructure lacks the ability to express dependencies between initcalls, whereas the modules infrastructure automatically handles dependencies via symbol loading. Ensure the PSP SEV driver is initialized before proceeding in sev_hardware_setup() if KVM is built-in as the dependency isn't handled by the initcall infrastructure.

Message-ID: <f78ddb64087df27e7bcb1ae0ab53f55aa0804fab.1739226950.git.ashish.kalra@amd.com>

---
 arch/x86/kvm/svm/sev.c | 10 ++++++++++
 1 file changed, 10 insertions(+)
arch/x86/kvm/svm/sev.c +10 -0
index bd0344bacaf1e..6a98bfb417ae7 100644
@@ -2992,6 +2992,16 @@ void __init sev_hardware_setup(void)
 	    WARN_ON_ONCE(!boot_cpu_has(X86_FEATURE_FLUSHBYASID)))
 		goto out;
 
+	/*
+	 * The kernel's initcall infrastructure lacks the ability to express
+	 * dependencies between initcalls, whereas the modules infrastructure
+	 * automatically handles dependencies via symbol loading.  Ensure the
+	 * PSP SEV driver is initialized before proceeding if KVM is built-in,
+	 * as the dependency isn't handled by the initcall infrastructure.
+	 */
+	if (IS_BUILTIN(CONFIG_KVM_AMD) && sev_module_init())
+		goto out;
+
 	/* Retrieve SEV CPUID information */
 	cpuid(0x8000001f, &eax, &ebx, &ecx, &edx);
 
Signature · 1 line
2.53.0
author maintainer
#18 lore raw

PATCH 16/220 selinux: require every boolean value to be defined

In reply to Greg Kroah-Hartman

6.12-stable review patch. If anyone has any objections, please let me know.

------------------

From: Bryam Vargas <[email protected]>

commit a93d37a09b863810653f93d371fb197457d59deb upstream.

p_bools.nprim comes from the policy image independently of how many booleans follow it, and cond_index_bool() fills bool_val_to_struct[] at value - 1, so a count larger than the values present leaves NULL entries. Every user of that array then walks it by index and dereferences each
entry: cond_evaluate_expr() on the access-vector path, security_get_bools() and security_get_bool_value() behind selinuxfs, and security_set_bools(). A sparse class value is absorbed by policydb_class_isvalid() and its siblings; booleans have no such predicate, and no consumer that could use one.

Reject a boolean value that no boolean defines, once, where the array is built. Conforming policies define every boolean they declare and are unaffected.

---
 security/selinux/ss/policydb.c |   19 +++++++++++++++++++
 1 file changed, 19 insertions(+)
security/selinux/ss/policydb.c +19 -0
@@ -712,6 +712,7 @@ static inline void symtab_hash_eval(stru
 static int policydb_index(struct policydb *p)
 {
 	int i, rc;
+	u32 v;
 
 	if (p->mls_enabled)
 		pr_debug(
@@ -764,6 +765,24 @@ static int policydb_index(struct policyd
 		if (rc)
 			goto out;
 	}
+
+	/*
+	 * A sparse class value is absorbed by policydb_class_isvalid() and
+	 * its siblings, but no such predicate exists for booleans: every
+	 * user of bool_val_to_struct[] walks it by index and dereferences
+	 * each entry -- cond_evaluate_expr(), the two getters and
+	 * security_set_bools() -- so an unclaimed one has no consumer that
+	 * can tolerate it.
+	 */
+	for (v = 0; v < p->p_bools.nprim; v++) {
+		if (!p->bool_val_to_struct[v]) {
+			pr_err("SELinux:  boolean %u is declared but not defined\n",
+			       v + 1);
+			rc = -EINVAL;
+			goto out;
+		}
+	}
+
 	rc = 0;
 out:
 	return rc;
#19 lore raw

Re: [PATCH 6.12 016/220] selinux: require every boolean value to be defined

Hi Greg/Sasha,

Quoted 18 lines · Greg Kroah-Hartman

6.12-stable review patch. If anyone has any objections, please let me know.

------------------

From: Bryam Vargas <[email protected]>

commit a93d37a09b863810653f93d371fb197457d59deb upstream.

p_bools.nprim comes from the policy image independently of how many booleans follow it, and cond_index_bool() fills bool_val_to_struct[] at value - 1, so a count larger than the values present leaves NULL entries. Every user of that array then walks it by index and dereferences each
entry: cond_evaluate_expr() on the access-vector path, security_get_bools() and security_get_bool_value() behind selinuxfs, and security_set_bools(). A sparse class value is absorbed by policydb_class_isvalid() and its siblings; booleans have no such predicate, and no consumer that could use one.

Quoted 3 lines

Reject a boolean value that no boolean defines, once, where the array is built. Conforming policies define every boolean they declare and are unaffected.

I ran an AI-assisted backport review and then checked 740012aebdb8 against upstream commit a93d37a09b86 ("selinux: require every boolean value to be defined"). I think it is missing a required predecessor.

Upstream already had cf6a513f1937 ("selinux: switch two allocations to use kzalloc_objs()") and therefore has:

        p->bool_val_to_struct = kzalloc_objs(...);

6.12.y instead has:

        p->bool_val_to_struct = kmalloc_array(..., GFP_KERNEL);

cond_index_bool() writes only the slots for boolean records that are present. The new loop in 740012aebdb8 assumes every omitted slot remains NULL, but kmalloc_array() leaves those slots uninitialized. A non-NULL stale value can therefore evade the rejection and later be dereferenced by existing boolean consumers, so the fix is incomplete.

I think we should apply cf6a513f1937 maybe.

thanks,
Harshit

Quoted 49 lines
---
  security/selinux/ss/policydb.c |   19 +++++++++++++++++++
  1 file changed, 19 insertions(+)
security/selinux/ss/policydb.c +19 -0
@@ -712,6 +712,7 @@ static inline void symtab_hash_eval(stru
  static int policydb_index(struct policydb *p)
  {
  	int i, rc;
+	u32 v;
 
  	if (p->mls_enabled)
  		pr_debug(
@@ -764,6 +765,24 @@ static int policydb_index(struct policyd
  		if (rc)
  			goto out;
  	}
+
+	/*
+	 * A sparse class value is absorbed by policydb_class_isvalid() and
+	 * its siblings, but no such predicate exists for booleans: every
+	 * user of bool_val_to_struct[] walks it by index and dereferences
+	 * each entry -- cond_evaluate_expr(), the two getters and
+	 * security_set_bools() -- so an unclaimed one has no consumer that
+	 * can tolerate it.
+	 */
+	for (v = 0; v < p->p_bools.nprim; v++) {
+		if (!p->bool_val_to_struct[v]) {
+			pr_err("SELinux:  boolean %u is declared but not defined\n",
+			       v + 1);
+			rc = -EINVAL;
+			goto out;
+		}
+	}
+
  	rc = 0;
  out:
  	return rc;
maintainer Applied
#20 lore raw

Re: [PATCH 6.12 016/220] selinux: require every boolean value to be defined

Quoted 8 lines

Upstream already had cf6a513f1937 ("selinux: switch two allocations to use kzalloc_objs()") and therefore has:

        p->bool_val_to_struct = kzalloc_objs(...);

6.12.y instead has:

        p->bool_val_to_struct = kmall

Queued for 7.1, 6.18, 6.12, 6.6, 6.1, 5.15 and 5.10, thanks. Used kcalloc() on 6.6 and older, where kzalloc_objs() doesn't exist yet.

Signature · 2 lines
Thanks,
Sasha
author maintainer
#21 lore raw

PATCH 17/220 selinux: reject a class permission count below its inherited common

In reply to Greg Kroah-Hartman

6.12-stable review patch. If anyone has any objections, please let me know.

------------------

From: Bryam Vargas <[email protected]>

commit 9a82dcd98b6e6e11cfd162410967951f12152528 upstream.

security_get_permissions() maps an inherited common's permissions into an array sized by the class's own permissions.nprim, but class_read() takes that nprim verbatim from the policy image and never checks that it covers the common. A class that inherits a common of N permissions while declaring a smaller nprim is accepted, and on load the common's permissions are written past the class-sized array -- an out-of-bounds heap write.

Reject a class whose permission count is below its inherited common's. Well-formed policies, where the class count already includes the inherited permissions, are unaffected.

Fixes 55fcf09b3fe4 ("selinux: add support for querying object classes and permissions from the running policy")
---
 security/selinux/ss/policydb.c |   12 ++++++++++++
 1 file changed, 12 insertions(+)
security/selinux/ss/policydb.c +12 -0
@@ -1375,6 +1375,18 @@ static int class_read(struct policydb *p
 			       cladatum->comkey);
 			goto bad;
 		}
+
+		/*
+		 * security_get_permissions() maps the common's permissions
+		 * into an array sized by this class's nprim, so a class must
+		 * declare at least as many as the common it inherits.
+		 */
+		if (cladatum->permissions.nprim <
+		    cladatum->comdatum->permissions.nprim) {
+			pr_err("SELinux:  class %s has fewer permissions than common %s\n",
+			       key, cladatum->comkey);
+			goto bad;
+		}
 	}
 	for (i = 0; i < nel; i++) {
 		rc = perm_read(p, &cladatum->permissions, fp);
author maintainer
#22 lore raw

PATCH 18/220 selinux: do not cancel a policy conversion that never started

In reply to Greg Kroah-Hartman

6.12-stable review patch. If anyone has any objections, please let me know.

------------------

From: Bryam Vargas <[email protected]>

commit e5c0235a3c4e9eb047a16cd02323fe4ecf2f570e upstream.

sel_write_load() calls selinux_policy_cancel() when sel_make_policy_nodes() fails, and that helper dereferences the outgoing policy to cancel its sidtab conversion. On the first policy load there is no outgoing policy:
security_load_policy() returns early for that case, before it converts anything, and state->policy is still NULL. A first load that fails while building the selinuxfs tree therefore takes a NULL dereference in selinux_policy_cancel(), reached from a write(2) to /sys/fs/selinux/load.

Skip the cancel when there is no old policy, mirroring the check security_load_policy() already makes before it converts.

Fixes 02a52c5c8c3b ("selinux: move policy commit after updating selinuxfs")
---
 security/selinux/ss/services.c |    4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)
security/selinux/ss/services.c +3 -1
@@ -2175,7 +2175,9 @@ void selinux_policy_cancel(struct selinu
 	oldpolicy = rcu_dereference_protected(state->policy,
 					lockdep_is_held(&state->policy_mutex));
 
-	sidtab_cancel_convert(oldpolicy->sidtab);
+	/* a first load has no outgoing policy and converted nothing */
+	if (oldpolicy)
+		sidtab_cancel_convert(oldpolicy->sidtab);
 	selinux_policy_free(load_state->policy);
 	kfree(load_state->convert_data);
 }
author maintainer
#23 lore raw

PATCH 19/220 selinux: reject an unclaimed class value in security_get_classes()

In reply to Greg Kroah-Hartman

6.12-stable review patch. If anyone has any objections, please let me know.

------------------

From: Bryam Vargas <[email protected]>

commit 22b05fec62c0fe9864cfceb52f7d0f3a34d9b1dd upstream.

security_get_classes() sizes an array by p_classes.nprim and fills it at value - 1, so a class value the policy never defines leaves a NULL. sel_make_classes() passes every entry to sel_make_dir(), reaching the same d_alloc_name() dereference as the permission array. The class symbol table is allowed to be sparse (policydb_class_isvalid() exists to absorb that), but this getter builds its own array straight from the hash table and has no such predicate.

Fail the lookup when a value went unclaimed instead of handing out the NULL. Conforming policies define every class they declare and are unaffected.

Fixes 55fcf09b3fe4 ("selinux: add support for querying object classes and permissions from the running policy")
---
 security/selinux/ss/services.c |   24 +++++++++++++++++++-----
 1 file changed, 19 insertions(+), 5 deletions(-)
security/selinux/ss/services.c +19 -5
@@ -3367,6 +3367,7 @@ int security_get_classes(struct selinux_
 			 char ***classes, u32 *nclasses)
 {
 	struct policydb *policydb;
+	u32 i;
 	int rc;
 
 	policydb = &policy->policydb;
@@ -3379,16 +3380,29 @@ int security_get_classes(struct selinux_
 
 	rc = hashtab_map(&policydb->p_classes.table, get_classes_callback,
 			 *classes);
-	if (rc) {
-		u32 i;
+	if (rc)
+		goto err;
 
-		for (i = 0; i < *nclasses; i++)
-			kfree((*classes)[i]);
-		kfree(*classes);
+	/*
+	 * The class symtab may be sparse, which policydb_class_isvalid() exists
+	 * to absorb; the callback fills this array by value, so an unclaimed
+	 * one leaves a NULL that sel_make_classes() hands to sel_make_dir().
+	 */
+	for (i = 0; i < *nclasses; i++) {
+		if (!(*classes)[i]) {
+			rc = -EINVAL;
+			goto err;
+		}
 	}
 
 out:
 	return rc;
+
+err:
+	for (i = 0; i < *nclasses; i++)
+		kfree((*classes)[i]);
+	kfree(*classes);
+	return rc;
 }
 
 static int get_permissions_callback(void *k, void *d, void *args)
author maintainer
#24 lore raw

PATCH 20/220 selftests: mptcp: join: mark tests with data corruption as failed

In reply to Greg Kroah-Hartman

6.12-stable review patch. If anyone has any objections, please let me know.

------------------

From: Gang Yan <[email protected]>

commit ca318e7bbb7723f57bcd9e69a2873b5884435552 upstream.

check_transfer() compares the input and output files byte-by-byte using cmp -l "$in" "$out" | while read .... Because the while-loop body runs in a subshell (the script sets neither lastpipe nor pipefail), the fail_test call inside it -- which sets the global ret/last_test_failed -- and the return 1 both act on the subshell, not on check_transfer().

check_transfer() thus always falls through to return 0, and any data corruption affecting only the payload (leaving the subflow/PM counters untouched) is silently reported as PASS.

---
 tools/testing/selftests/net/mptcp/mptcp_join.sh |    4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)
tools/testing/selftests/net/mptcp/mptcp_join.sh +2 -2
@@ -552,7 +552,7 @@ check_transfer()
 		mv "$tmpfile" "$out"
 		tmpfile=""
 	fi
-	cmp -l "$in" "$out" | while read -r i a b; do
+	while read -r i a b; do
 		local sum=$((0${a} + 0${b}))
 		if [ $check_invert -eq 0 ] || [ $sum -ne $((0xff)) ]; then
 			fail_test "$what does not match (in, out):"
@@ -563,7 +563,7 @@ check_transfer()
 		else
 			print_info "$what has inverted byte at ${i}"
 		fi
-	done
+	done < <(cmp -l "$in" "$out")
 
 	return 0
 }
author maintainer
#25 lore raw

PATCH 21/220 mptcp: avoid combining some incoming suboptions

In reply to Greg Kroah-Hartman

6.12-stable review patch. If anyone has any objections, please let me know.

------------------

From: Matthieu Baerts (NGI0) <[email protected]>

commit b6ee361524641f57b2e2363f7737f20e17f67827 upstream.

Some MPTCP suboptions are mutually exclusive according to the RFC8684, but also because in different places, the code doesn't expect some combinations to be present. That's specially true for suboptions that would be present twice, but with different attributes.

The new restrictions are the same as the ones applied on the output side, with mptcp_write_options. The same rules can be reused with a small fix: an MP_FASTCLOSE can be used with a DSS when the sender picks this option [1], which is not the case on Linux. Here are the rules:

Which options can be used together?

X: mutually exclusive
O: often used together
C: can be used together in some cases
P: could be used together but we prefer not to (optimisations)

  | Opt: | MPC  | MPJ  | DSS  | ADD  |  RM  | PRIO | FAIL |  FC  |
  |------|------|------|------|------|------|------|------|------|
  | MPC  |------|------|------|------|------|------|------|------|
  | MPJ  |  X   |------|------|------|------|------|------|------|
  | DSS  |  X   |  X   |------|------|------|------|------|------|
  | ADD  |  X   |  X   |  P   |------|------|------|------|------|
  | RM   |  C   |  C   |  C   |  P   |------|------|------|------|
  | PRIO |  X   |  C   |  C   |  C   |  C   |------|------|------|
  | FAIL |  X   |  X   |  C   |  X   |  X   |  X   |------|------|
  | FC   |  X   |  X   |  P   |  X   |  X   |  X   |  X   |------|
  | RST  |  X   |  X   |  X   |  X   |  X   |  X   |  O   |  O   |
  |------|------|------|------|------|------|------|------|------|

The only difference is with the 'P': another stack could send and ADD_ADDR with other suboptions (DSS, RM_ADDR), and this should be allowed.

A few points of attention:

- In theory, an MP_CAPABLE could be used with a RM_ADDR, but there is no reason to add it with a SYN. Note that even with a 4th ACK, it doesn't seem to be useful, except when IDs are known in advance via another channel. Better not to break that.

- Now, combining both an MP_CAPABLE and an MP_JOIN will no longer result to a reject of the two options, but only the second suboption is ignored. That seems OK to do that for this unexpected error. At least now all inconsistent combinations are handled the same way. This could change later in next. This also means the explicit checks for having both MPC + MPJ in subflow.c will now be unreachable. That's fine, they will be removed in a follow-up patch.

- In case of conflicting combinations, the extra suboption(s) is/are
ignored: having such combinations either means the remote peer is buggy, or is evil. The simplest action is then taken in this case:
stop processing the current suboption.

- In mp_opt->suboptions, there is also a bit reserved to the checksum, which can be used in an MP_CAPABLE and a DSS. Each time a DSS option can be used in parallel with another option, the checksum can be set, so the verification is combined into a new OPTIONS_MPTCP_DSS macro.

- An MP_CAPABLE ACK can carry a Data-Level Length, and an optional
Checksum: they are the same as the ones found in a DSS, because a DSS cannot be used in parallel to an MP_CAPABLE. Similarly, even if there is room, a DSS cannot be used with an MP_JOIN.

---
 net/mptcp/options.c  |   59 ++++++++++++++++++++++++++++++++++++++++++++++++++-
 net/mptcp/protocol.h |    1 
 2 files changed, 59 insertions(+), 1 deletion(-)
net/mptcp/options.c +58 -1
@@ -50,6 +50,14 @@ static void mptcp_parse_option(const str
 			}
 		}
 
+		/* Only the MPC + ACK can be used with a RM_ADDR */
+		if (subopt == OPTION_MPTCP_MPC_ACK) {
+			if ((mp_opt->suboptions & ~OPTION_MPTCP_RM_ADDR) != 0)
+				break;
+		} else if (mp_opt->suboptions != 0) {
+			break;
+		}
+
 		/* Cfr RFC 8684 Section 3.3.0:
 		 * If a checksum is present but its use had
 		 * not been negotiated in the MP_CAPABLE handshake, the receiver MUST
@@ -122,6 +130,11 @@ static void mptcp_parse_option(const str
 		break;
 
 	case MPTCPOPT_MP_JOIN:
+		/* Can be used with a restricted number of other options */
+		if ((mp_opt->suboptions & ~(OPTION_MPTCP_RM_ADDR |
+					    OPTION_MPTCP_PRIO)) != 0)
+			break;
+
 		if (opsize == TCPOLEN_MPTCP_MPJ_SYN) {
 			mp_opt->suboptions |= OPTION_MPTCP_MPJ_SYN;
 			mp_opt->backup = *ptr++ & MPTCPOPT_BACKUP;
@@ -153,6 +166,14 @@ static void mptcp_parse_option(const str
 		break;
 
 	case MPTCPOPT_DSS:
+		/* Can be used with a restricted number of other options */
+		if ((mp_opt->suboptions & ~(OPTION_MPTCP_ADD_ADDR |
+					    OPTION_MPTCP_RM_ADDR |
+					    OPTION_MPTCP_PRIO |
+					    OPTION_MPTCP_FASTCLOSE |
+					    OPTION_MPTCP_FAIL)) != 0)
+			break;
+
 		pr_debug("DSS\n");
 		ptr++;
 
@@ -234,6 +255,12 @@ static void mptcp_parse_option(const str
 		break;
 
 	case MPTCPOPT_ADD_ADDR:
+		/* Can be used with a restricted number of other options */
+		if ((mp_opt->suboptions & ~(OPTIONS_MPTCP_DSS |
+					    OPTION_MPTCP_RM_ADDR |
+					    OPTION_MPTCP_PRIO)) != 0)
+			break;
+
 		mp_opt->echo = (*ptr++) & MPTCP_ADDR_ECHO;
 		if (!mp_opt->echo) {
 			if (opsize == TCPOLEN_MPTCP_ADD_ADDR ||
@@ -293,6 +320,14 @@ static void mptcp_parse_option(const str
 		break;
 
 	case MPTCPOPT_RM_ADDR:
+		/* Can be used with a restricted number of other options */
+		if ((mp_opt->suboptions & ~(OPTION_MPTCP_MPC_ACK |
+					    OPTIONS_MPTCP_MPJ |
+					    OPTIONS_MPTCP_DSS |
+					    OPTION_MPTCP_ADD_ADDR |
+					    OPTION_MPTCP_PRIO)) != 0)
+			break;
+
 		if (opsize < TCPOLEN_MPTCP_RM_ADDR_BASE + 1 ||
 		    opsize > TCPOLEN_MPTCP_RM_ADDR_BASE + MPTCP_RM_IDS_MAX)
 			break;
@@ -307,6 +342,13 @@ static void mptcp_parse_option(const str
 		break;
 
 	case MPTCPOPT_MP_PRIO:
+		/* Can be used with a restricted number of other options */
+		if ((mp_opt->suboptions & ~(OPTIONS_MPTCP_MPJ |
+					    OPTIONS_MPTCP_DSS |
+					    OPTION_MPTCP_ADD_ADDR |
+					    OPTION_MPTCP_RM_ADDR)) != 0)
+			break;
+
 		if (opsize != TCPOLEN_MPTCP_PRIO)
 			break;
 
@@ -316,6 +358,11 @@ static void mptcp_parse_option(const str
 		break;
 
 	case MPTCPOPT_MP_FASTCLOSE:
+		/* Can be used with a restricted number of other options */
+		if ((mp_opt->suboptions & ~(OPTIONS_MPTCP_DSS |
+					    OPTION_MPTCP_RST)) != 0)
+			break;
+
 		if (opsize != TCPOLEN_MPTCP_FASTCLOSE)
 			break;
 
@@ -327,6 +374,11 @@ static void mptcp_parse_option(const str
 		break;
 
 	case MPTCPOPT_RST:
+		/* Can be used with a restricted number of other options */
+		if ((mp_opt->suboptions & ~(OPTION_MPTCP_FAIL |
+					    OPTION_MPTCP_FASTCLOSE)) != 0)
+			break;
+
 		if (opsize != TCPOLEN_MPTCP_RST)
 			break;
 
@@ -342,6 +394,11 @@ static void mptcp_parse_option(const str
 		break;
 
 	case MPTCPOPT_MP_FAIL:
+		/* Can be used with a restricted number of other options */
+		if ((mp_opt->suboptions & ~(OPTIONS_MPTCP_DSS |
+					    OPTION_MPTCP_RST)) != 0)
+			break;
+
 		if (opsize != TCPOLEN_MPTCP_FAIL)
 			break;
 
@@ -1416,7 +1473,7 @@ void mptcp_write_options(struct tcphdr *
 	 *  RM   |  C   |  C   |  C   |  P   |------|------|------|------|
 	 *  PRIO |  X   |  C   |  C   |  C   |  C   |------|------|------|
 	 *  FAIL |  X   |  X   |  C   |  X   |  X   |  X   |------|------|
-	 *  FC   |  X   |  X   |  X   |  X   |  X   |  X   |  X   |------|
+	 *  FC   |  X   |  X   |  P   |  X   |  X   |  X   |  X   |------|
 	 *  RST  |  X   |  X   |  X   |  X   |  X   |  X   |  O   |  O   |
 	 * ------|------|------|------|------|------|------|------|------|
 	 *
net/mptcp/protocol.h +1 -0
@@ -37,6 +37,7 @@
 				 OPTION_MPTCP_MPC_ACK)
 #define OPTIONS_MPTCP_MPJ	(OPTION_MPTCP_MPJ_SYN | OPTION_MPTCP_MPJ_SYNACK | \
 				 OPTION_MPTCP_MPJ_ACK)
+#define OPTIONS_MPTCP_DSS	(OPTION_MPTCP_DSS | OPTION_MPTCP_CSUMREQD)
 
 /* MPTCP option subtypes */
 #define MPTCPOPT_MP_CAPABLE	0
author maintainer
#26 lore raw

PATCH 22/220 mptcp: options: reset DSS fields in case of unexpected size

In reply to Greg Kroah-Hartman

6.12-stable review patch. If anyone has any objections, please let me know.

------------------

From: Matthieu Baerts (NGI0) <[email protected]>

commit 35772b4981f38ba8059372cde8753e8e477e98ec upstream.

A remote peer could send a malformed DSS with a wrong size, followed by another DSS or MPC + Data. In this case, the first suboption will be ignored, but leaving some fields written, which could lead to inconsistency or access uninitialized data.

Explicitly reset the fields that could have been modified in case of unexpected size.

---
 net/mptcp/options.c |    8 +++++++-
 1 file changed, 7 insertions(+), 1 deletion(-)
net/mptcp/options.c +7 -1
@@ -209,8 +209,14 @@ static void mptcp_parse_option(const str
 		 * RFC 8684 Section 3.3.0 checks later in subflow_data_ready
 		 */
 		if (opsize != expected_opsize &&
-		    opsize != expected_opsize + TCPOLEN_MPTCP_DSS_CHECKSUM)
+		    opsize != expected_opsize + TCPOLEN_MPTCP_DSS_CHECKSUM) {
+			mp_opt->dsn64 = 0;
+			mp_opt->use_map = 0;
+			mp_opt->ack64 = 0;
+			mp_opt->use_ack = 0;
+			mp_opt->data_fin = 0;
 			break;
+		}
 
 		mp_opt->suboptions |= OPTION_MPTCP_DSS;
 		if (mp_opt->use_ack) {
author maintainer
#27 lore raw

PATCH 23/220 mptcp: fastopen: only mark MPTFO subflows with SYN data

In reply to Greg Kroah-Hartman

6.12-stable review patch. If anyone has any objections, please let me know.

------------------

From: Wyatt Feng <[email protected]>

commit e00b63056fb4f261455b3e5df5268a1f8ce47a87 upstream.

Passive TCP Fast Open accepts a valid-cookie SYN even when it carries no data. In that case the child socket's receive queue is intentionally left empty.

mptcp_fastopen_subflow_synack_set_params() set is_mptfo before checking for queued SYN data. That made data-less TFO SYNs hit a WARN and, if the warning was non-fatal, left stale MPTFO state behind. The stale flag could later trigger a state-confusion bug in check_fully_established().

Only mark the subflow as MPTFO after confirming that an SKB was queued. Return quietly when the receive queue is empty.

Note that mptcp_subflow_context's is_mptfo field is now not just about subflows where the TFO was present, but about MPTFO subflow that consumed SYN data. Only having a valid cookie but not carrying data is not really "doing TFO".

---
 net/mptcp/fastopen.c |    7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)
net/mptcp/fastopen.c +4 -3
@@ -24,12 +24,13 @@ void mptcp_fastopen_subflow_synack_set_p
 	sk = subflow->conn;
 	tp = tcp_sk(ssk);
 
-	subflow->is_mptfo = 1;
-
+	/* A valid TFO cookie does not guarantee SYN data. */
 	skb = skb_peek(&ssk->sk_receive_queue);
-	if (WARN_ON_ONCE(!skb))
+	if (!skb)
 		return;
 
+	subflow->is_mptfo = 1;
+
 	/* dequeue the skb from sk receive queue */
 	__skb_unlink(skb, &ssk->sk_receive_queue);
 	skb_ext_reset(skb);
author maintainer
#28 lore raw

PATCH 24/220 s390/qeth: validate user buffer length in SNMP and ARP query ioctls

In reply to Greg Kroah-Hartman

6.12-stable review patch. If anyone has any objections, please let me know.

------------------

From: Hidayath Khan <[email protected]>

commit d141f087b1af656f055d7c5793a3e87817ba0bbe upstream.

qeth_snmp_command() and qeth_l3_arp_query() allocate a buffer sized by a user-supplied length (udata_len) without checking a lower bound, then set udata_offset to a fixed non-zero value and pass both to a reply callback. The callback bounds-checks the copy with

if ((udata_len - udata_offset) < len)

Both fields are u32, so a udata_len smaller than udata_offset makes the subtraction wrap and the check pass, and the following memcpy() writes past the allocation. A udata_len of 0 also yields ZERO_SIZE_PTR from kzalloc(), which the existing NULL check does not catch.

Reject buffers smaller than udata_offset before allocating, so the callback subtraction can no longer underflow.

---
 drivers/s390/net/qeth_core_main.c |    3 +++
 drivers/s390/net/qeth_l3_main.c   |    5 +++++
 2 files changed, 8 insertions(+)
drivers/s390/net/qeth_core_main.c +3 -0
@@ -4710,6 +4710,9 @@ static int qeth_snmp_command(struct qeth
 	if (req_len > QETH_BUFSIZE)
 		return -EINVAL;
 
+	if (qinfo.udata_len < sizeof(struct qeth_snmp_ureq_hdr))
+		return -EINVAL;
+
 	iob = qeth_get_adapter_cmd(card, IPA_SETADP_SET_SNMP_CONTROL, req_len);
 	if (!iob)
 		return -ENOMEM;
drivers/s390/net/qeth_l3_main.c +5 -0
@@ -1415,6 +1415,11 @@ static int qeth_l3_arp_query(struct qeth
 		rc = -EFAULT;
 		goto out;
 	}
+
+	if (qinfo.udata_len < QETH_QARP_ENTRIES_OFFSET) {
+		rc = -EINVAL;
+		goto out;
+	}
 	qinfo.udata = kzalloc(qinfo.udata_len, GFP_KERNEL);
 	if (!qinfo.udata) {
 		rc = -ENOMEM;
author maintainer
#29 lore raw

PATCH 25/220 ASoC: SOF: sof-audio: Fix error path in sof_widget_setup_unlocked()

In reply to Greg Kroah-Hartman

6.12-stable review patch. If anyone has any objections, please let me know.

------------------

From: Peter Ujfalusi <[email protected]>

commit e780e4917d43683224812400fe3dc4816fceba75 upstream.

If either tplg_ops->dai_config or widget_kcontrol_setup fail during widget setup we would double decrement the use_count of the widget because the sof_widget_free_unlocked() would be called twice, similarly the core_put would be invoked twice as well.

Since the use_count and core_put() is handled within the widget_free function we need to return without falling through the pipe_widget_free label.

The fixes tag is picked to the last change around this part of the code which is adequately old enough for backporting purposes.

---
 sound/soc/sof/sof-audio.c |    9 ++++-----
 1 file changed, 4 insertions(+), 5 deletions(-)
sound/soc/sof/sof-audio.c +4 -5
@@ -136,7 +136,6 @@ static int sof_widget_setup_unlocked(str
 {
 	const struct sof_ipc_tplg_ops *tplg_ops = sof_ipc_get_ops(sdev, tplg);
 	struct snd_sof_pipeline *spipe = swidget->spipe;
-	bool use_count_decremented = false;
 	int ret;
 	int i;
 
@@ -215,9 +214,10 @@ static int sof_widget_setup_unlocked(str
 	return 0;
 
 widget_free:
-	/* widget use_count will be decremented by sof_widget_free() */
+	/* widget use_count and core_put handled by sof_widget_free() */
 	sof_widget_free_unlocked(sdev, swidget);
-	use_count_decremented = true;
+	return ret;
+
 pipe_widget_free:
 	if (swidget->id != snd_soc_dapm_scheduler) {
 		sof_widget_free_unlocked(sdev, swidget->spipe->pipe_widget);
@@ -232,8 +232,7 @@ pipe_widget_free:
 		}
 	}
 use_count_dec:
-	if (!use_count_decremented)
-		swidget->use_count--;
+	swidget->use_count--;
 
 	return ret;
 }
author maintainer
#30 lore raw

PATCH 26/220 ASoC: cs4265: sort the register default table

In reply to Greg Kroah-Hartman

6.12-stable review patch. If anyone has any objections, please let me know.

------------------

From: Peter Ujfalusi <[email protected]>

commit e4fe3e046524e5de3c04c6eef3743780cbdc231c upstream.

reg_defaults must be sorted by ascending register address, as regcache_lookup_reg() locates entries in it with bsearch(). See commit fd80df352ba1 ("regcache: Add support for sorting defaults arrays").

cs4265_reg_defaults[] lists CS4265_INT_MASK (0x0e), CS4265_STATUS_MODE_MSB (0x0f) and CS4265_STATUS_MODE_LSB (0x10) after CS4265_SPDIF_CTL1 (0x11) and CS4265_SPDIF_CTL2 (0x12), so the binary search does not find those three entries. regcache_reg_needs_sync() then cannot compare them against their default and reports that a sync is needed, so they are written to the device on every regcache_sync() even when they were never touched.

Sort the table by register address.

---
 sound/soc/codecs/cs4265.c |    4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)
sound/soc/codecs/cs4265.c +2 -2
@@ -46,11 +46,11 @@ static const struct reg_default cs4265_r
 	{ CS4265_DAC_CHA_VOL, 0x00 },
 	{ CS4265_DAC_CHB_VOL, 0x00 },
 	{ CS4265_DAC_CTL2, 0xC0 },
-	{ CS4265_SPDIF_CTL1, 0x00 },
-	{ CS4265_SPDIF_CTL2, 0x00 },
 	{ CS4265_INT_MASK, 0x00 },
 	{ CS4265_STATUS_MODE_MSB, 0x00 },
 	{ CS4265_STATUS_MODE_LSB, 0x00 },
+	{ CS4265_SPDIF_CTL1, 0x00 },
+	{ CS4265_SPDIF_CTL2, 0x00 },
 };
 
 static bool cs4265_readable_register(struct device *dev, unsigned int reg)
author maintainer
#31 lore raw

PATCH 27/220 ASoC: cs35l45: sort the register default table

In reply to Greg Kroah-Hartman

6.12-stable review patch. If anyone has any objections, please let me know.

------------------

From: Peter Ujfalusi <[email protected]>

commit f39a68ed08bb6eef0ae711b41d645ee5e9448c09 upstream.

reg_defaults must be sorted by ascending register address, as regcache_lookup_reg() locates entries in it with bsearch(). See commit fd80df352ba1 ("regcache: Add support for sorting defaults arrays").

cs35l45_defaults[] lists the DSP1_RX*_RATE and DSP1_TX*_RATE registers (0x02b80080 - 0x02b802b8) in the middle of the table, ahead of entries with much lower addresses, so the binary search does not find 36 of its 73 entries. regcache_reg_needs_sync() then cannot compare those against their default and reports that a sync is needed, so they are written to the device on every regcache_sync() even when they were never touched.

Sort the table by register address.

---
 sound/soc/codecs/cs35l45-tables.c |   32 ++++++++++++++++----------------
 1 file changed, 16 insertions(+), 16 deletions(-)
sound/soc/codecs/cs35l45-tables.c +16 -16
@@ -66,22 +66,6 @@ static const struct reg_default cs35l45_
 	{ CS35L45_ASPTX3_INPUT,			0x00000020 },
 	{ CS35L45_ASPTX4_INPUT,			0x00000028 },
 	{ CS35L45_ASPTX5_INPUT,			0x00000048 },
-	{ CS35L45_DSP1_RX1_RATE,		0x00000001 },
-	{ CS35L45_DSP1_RX2_RATE,		0x00000001 },
-	{ CS35L45_DSP1_RX3_RATE,		0x00000001 },
-	{ CS35L45_DSP1_RX4_RATE,		0x00000001 },
-	{ CS35L45_DSP1_RX5_RATE,		0x00000001 },
-	{ CS35L45_DSP1_RX6_RATE,		0x00000001 },
-	{ CS35L45_DSP1_RX7_RATE,		0x00000001 },
-	{ CS35L45_DSP1_RX8_RATE,		0x00000001 },
-	{ CS35L45_DSP1_TX1_RATE,		0x00000001 },
-	{ CS35L45_DSP1_TX2_RATE,		0x00000001 },
-	{ CS35L45_DSP1_TX3_RATE,		0x00000001 },
-	{ CS35L45_DSP1_TX4_RATE,		0x00000001 },
-	{ CS35L45_DSP1_TX5_RATE,		0x00000001 },
-	{ CS35L45_DSP1_TX6_RATE,		0x00000001 },
-	{ CS35L45_DSP1_TX7_RATE,		0x00000001 },
-	{ CS35L45_DSP1_TX8_RATE,		0x00000001 },
 	{ CS35L45_DSP1RX1_INPUT,		0x00000008 },
 	{ CS35L45_DSP1RX2_INPUT,		0x00000009 },
 	{ CS35L45_DSP1RX3_INPUT,		0x00000018 },
@@ -114,6 +98,22 @@ static const struct reg_default cs35l45_
 	{ CS35L45_GPIO1_CTRL1,			0x81000001 },
 	{ CS35L45_GPIO2_CTRL1,			0x81000001 },
 	{ CS35L45_GPIO3_CTRL1,			0x81000001 },
+	{ CS35L45_DSP1_RX1_RATE,		0x00000001 },
+	{ CS35L45_DSP1_RX2_RATE,		0x00000001 },
+	{ CS35L45_DSP1_RX3_RATE,		0x00000001 },
+	{ CS35L45_DSP1_RX4_RATE,		0x00000001 },
+	{ CS35L45_DSP1_RX5_RATE,		0x00000001 },
+	{ CS35L45_DSP1_RX6_RATE,		0x00000001 },
+	{ CS35L45_DSP1_RX7_RATE,		0x00000001 },
+	{ CS35L45_DSP1_RX8_RATE,		0x00000001 },
+	{ CS35L45_DSP1_TX1_RATE,		0x00000001 },
+	{ CS35L45_DSP1_TX2_RATE,		0x00000001 },
+	{ CS35L45_DSP1_TX3_RATE,		0x00000001 },
+	{ CS35L45_DSP1_TX4_RATE,		0x00000001 },
+	{ CS35L45_DSP1_TX5_RATE,		0x00000001 },
+	{ CS35L45_DSP1_TX6_RATE,		0x00000001 },
+	{ CS35L45_DSP1_TX7_RATE,		0x00000001 },
+	{ CS35L45_DSP1_TX8_RATE,		0x00000001 },
 };
 
 static bool cs35l45_readable_reg(struct device *dev, unsigned int reg)
author maintainer
#32 lore raw

PATCH 28/220 ASoC: cs35l41: sort the register default table

In reply to Greg Kroah-Hartman

6.12-stable review patch. If anyone has any objections, please let me know.

------------------

From: Peter Ujfalusi <[email protected]>

commit d74aac116cfb2058b15df53996d23232b310f7ff upstream.

reg_defaults must be sorted by ascending register address, as regcache_lookup_reg() locates entries in it with bsearch(). See commit fd80df352ba1 ("regcache: Add support for sorting defaults arrays").

cs35l41_reg[] lists CS35L41_BSTCVRT_PEAK_CUR (0x3808) after CS35L41_BSTCVRT_COEFF (0x3810) and CS35L41_BSTCVRT_SLOPE_LBST (0x3814), so the binary search does not find those two entries. regcache_reg_needs_sync() then cannot compare them against their default and reports that a sync is needed, so they are written to the device on every regcache_sync() even when they were never touched.

Sort the table by register address.

---
 sound/soc/codecs/cs35l41-lib.c |    2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
sound/soc/codecs/cs35l41-lib.c +1 -1
@@ -25,9 +25,9 @@ static const struct reg_default cs35l41_
 	{ CS35L41_GPIO_PAD_CONTROL,		0x00000000 },
 	{ CS35L41_GLOBAL_CLK_CTRL,		0x00000003 },
 	{ CS35L41_TST_FS_MON0,			0x00020016 },
+	{ CS35L41_BSTCVRT_PEAK_CUR,		0x0000004A },
 	{ CS35L41_BSTCVRT_COEFF,		0x00002424 },
 	{ CS35L41_BSTCVRT_SLOPE_LBST,		0x00007500 },
-	{ CS35L41_BSTCVRT_PEAK_CUR,		0x0000004A },
 	{ CS35L41_SP_ENABLES,			0x00000000 },
 	{ CS35L41_SP_RATE_CTRL,			0x00000028 },
 	{ CS35L41_SP_FORMAT,			0x18180200 },
author maintainer
#33 lore raw

PATCH 29/220 ASoC: codecs: lpass-wsa-macro: Fix enum kcontrol accesses

In reply to Greg Kroah-Hartman

6.12-stable review patch. If anyone has any objections, please let me know.

------------------

From: Dawid Wróbel <[email protected]>

commit 56f24311fd5607588a47e44675195a9efb200f29 upstream.

EAR SPKR PA Gain" and the four "WSA RX* Mux" controls are enumerated, but their get and put callbacks access the value through ucontrol->value.integer.value[0] (a long) instead of ucontrol->value.enumerated.item[0] (an unsigned int).

This same pattern was fixed in the sibling drivers by commit bcfe5f76cc40 ("ASoC: codecs: rx-macro: fix accessing array out of bounds for enum type") and
commit 0ea5eff7c606 ("ASoC: codecs: va-macro: fix accessing array out of bounds for enum type"), but wsa-macro was missed.

On 64-bit kernels with CONFIG_SND_CTL_DEBUG this trips the elem value sanity check and every read of these controls fails with -EINVAL.

Fixes 809bcbcecebf ("ASoC: codecs: lpass-wsa-macro: Add support to WSA Macro")
Fixes 2c4066e5d428 ("ASoC: codecs: lpass-wsa-macro: add dapm widgets and route")
Assisted-by Claude:claude-fable-5
---
 sound/soc/codecs/lpass-wsa-macro.c |    8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)
sound/soc/codecs/lpass-wsa-macro.c +4 -4
@@ -2166,7 +2166,7 @@ static int wsa_macro_ear_spkr_pa_gain_ge
 	struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol);
 	struct wsa_macro *wsa = snd_soc_component_get_drvdata(component);
 
-	ucontrol->value.integer.value[0] = wsa->ear_spkr_gain;
+	ucontrol->value.enumerated.item[0] = wsa->ear_spkr_gain;
 
 	return 0;
 }
@@ -2177,7 +2177,7 @@ static int wsa_macro_ear_spkr_pa_gain_pu
 	struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol);
 	struct wsa_macro *wsa = snd_soc_component_get_drvdata(component);
 
-	wsa->ear_spkr_gain =  ucontrol->value.integer.value[0];
+	wsa->ear_spkr_gain =  ucontrol->value.enumerated.item[0];
 
 	return 0;
 }
@@ -2191,7 +2191,7 @@ static int wsa_macro_rx_mux_get(struct s
 				snd_soc_dapm_to_component(widget->dapm);
 	struct wsa_macro *wsa = snd_soc_component_get_drvdata(component);
 
-	ucontrol->value.integer.value[0] =
+	ucontrol->value.enumerated.item[0] =
 			wsa->rx_port_value[widget->shift];
 	return 0;
 }
@@ -2205,7 +2205,7 @@ static int wsa_macro_rx_mux_put(struct s
 				snd_soc_dapm_to_component(widget->dapm);
 	struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
 	struct snd_soc_dapm_update *update = NULL;
-	u32 rx_port_value = ucontrol->value.integer.value[0];
+	u32 rx_port_value = ucontrol->value.enumerated.item[0];
 	u32 bit_input;
 	u32 aif_rst;
 	struct wsa_macro *wsa = snd_soc_component_get_drvdata(component);
author maintainer
#34 lore raw

PATCH 30/220 fbdev: core: Fix pointer desynchronization in fb_io_read()

In reply to Greg Kroah-Hartman

6.12-stable review patch. If anyone has any objections, please let me know.

------------------

From: Mingyu Wang <[email protected]>

commit 81cc73be40c6f028f1ee3f438ace46afe666dbae upstream.

In fb_io_read(), if copy_to_user() performs a partial copy (e.g., due to a faulty user buffer), the loop adjusts the chunk size 'c' and updates the remaining 'count'. However, the hardware 'src' pointer has already been eagerly advanced by the original chunk size.

If the loop is allowed to continue, the read will resume from an incorrect, over-advanced offset. Since the remaining 'count' was only decremented by the successful bytes, this desynchronization causes the next iterations to execute more hardware reads than originally bounded, eventually leading to out-of-bounds I/O reads.

Fix this by breaking out of the loop immediately upon a partial copy_to_user(). A partial copy indicates a faulty user buffer, making subsequent read attempts futile. Breaking out ensures we return the number of successfully read bytes without risking out-of-bounds hardware accesses in subsequent mismatched iterations.

Fixes 6121cd9ef911 ("fbdev: Move I/O read and write code into helper functions")
---
 drivers/video/fbdev/core/fb_io_fops.c |    8 ++++++++
 1 file changed, 8 insertions(+)
drivers/video/fbdev/core/fb_io_fops.c +8 -0
@@ -60,6 +60,14 @@ ssize_t fb_io_read(struct fb_info *info,
 		buf += c;
 		cnt += c;
 		count -= c;
+
+		/*
+		 * If there was a partial copy, the user buffer is faulty.
+		 * Break out to avoid over-advancing the src pointer and
+		 * reading out of bounds in the next iteration.
+		 */
+		if (trailing)
+			break;
 	}
 
 	kfree(buffer);
author maintainer
#35 lore raw

PATCH 31/220 drm/panthor: skip zero-sized firmware sections

In reply to Greg Kroah-Hartman

6.12-stable review patch. If anyone has any objections, please let me know.

------------------

From: Osama Abdelkader <[email protected]>

commit 2b8f13d3c7e26c46c20d9e367904cf01729c88e6 upstream.

panthor_fw_load_section_entry() skips BO creation when the firmware section VA range is empty. If such a section is added to the firmware section list, section->mem is left as NULL.

Later reload and unplug paths iterate over all firmware sections and dereference section->mem, which can lead to a NULL pointer dereference.

Zero-sized firmware sections are valid, so accept them as no-op entries but skip adding them to the section list.

---
 drivers/gpu/drm/panthor/panthor_fw.c |    3 +++
 1 file changed, 3 insertions(+)
drivers/gpu/drm/panthor/panthor_fw.c +3 -0
@@ -549,6 +549,9 @@ static int panthor_fw_load_section_entry
 		return -EINVAL;
 	}
 
+	if (!section_size)
+		return 0;
+
 	name_len = iter->size - iter->offset;
 
 	section = drmm_kzalloc(&ptdev->base, sizeof(*section), GFP_KERNEL);
author maintainer
#36 lore raw

PATCH 32/220 drm/amdgpu: reject oversized IBs with per-ring packet limits

In reply to Greg Kroah-Hartman

6.12-stable review patch. If anyone has any objections, please let me know.

------------------

From: Candice Li <[email protected]>

commit fd37f9dd5b5ab70a46fa7bc76623c0528d602b27 upstream.

On GFX rings, amdgpu_cs_p2_ib() passed user-supplied ib_bytes through to ib->length_dw without a limit, while ring_emit_ib() encodes length into packet fields. Oversized values can corrupt adjacent control bits and destabilize command submission.

Add a per-ring IB packet size limit helper and reject command submissions exceeding the corresponding dword limit before IB allocation. Use the documented 20-bit limit for GFX/compute/SDMA/VPE, and apply the MM fallback limit for other ring types.

(cherry picked from commit 7f48fa2cf62e3fa6c9c3870aa74988f773247e52)
---
 drivers/gpu/drm/amd/amdgpu/amdgpu_cs.c |   27 ++++++++++++++++++++++++++-
 1 file changed, 26 insertions(+), 1 deletion(-)
drivers/gpu/drm/amd/amdgpu/amdgpu_cs.c +26 -1
@@ -41,6 +41,26 @@
 #include "amdgpu_gem.h"
 #include "amdgpu_ras.h"
 
+/*
+ * Maximum IB length (dwords) for rings whose emit_ib packet format
+ * documents a 20-bit size field.
+ */
+#define AMDGPU_GFX_SDMA_IB_PACKET_SIZE_MAX_DW	0xFFFFF
+#define AMDGPU_MM_IB_PACKET_SIZE_MAX_DW	0x7FFFF0
+
+static u32 amdgpu_cs_ib_packet_size_max_dw(enum amdgpu_ring_type type)
+{
+	switch (type) {
+	case AMDGPU_RING_TYPE_GFX:
+	case AMDGPU_RING_TYPE_COMPUTE:
+	case AMDGPU_RING_TYPE_SDMA:
+	case AMDGPU_RING_TYPE_VPE:
+		return AMDGPU_GFX_SDMA_IB_PACKET_SIZE_MAX_DW;
+	default:
+		return AMDGPU_MM_IB_PACKET_SIZE_MAX_DW;
+	}
+}
+
 static int amdgpu_cs_parser_init(struct amdgpu_cs_parser *p,
 				 struct amdgpu_device *adev,
 				 struct drm_file *filp,
@@ -352,7 +372,6 @@ static int amdgpu_cs_p2_ib(struct amdgpu
 
 	job = p->jobs[r];
 	ring = amdgpu_job_ring(job);
-	ib = &job->ibs[job->num_ibs++];
 
 	/* MM engine doesn't support user fences */
 	if (p->uf_bo && ring->funcs->no_user_fence)
@@ -371,6 +390,12 @@ static int amdgpu_cs_p2_ib(struct amdgpu
 			return -EINVAL;
 	}
 
+	if (chunk_ib->ib_bytes / 4 >
+	    amdgpu_cs_ib_packet_size_max_dw(ring->funcs->type))
+		return -EINVAL;
+
+	ib = &job->ibs[job->num_ibs++];
+
 	if (chunk_ib->flags & AMDGPU_IB_FLAG_PREAMBLE)
 		job->preamble_status |= AMDGPU_PREAMBLE_IB_PRESENT;
 
author maintainer
#37 lore raw

PATCH 33/220 drm/amdgpu: read TRUNCATE_COORD_MODE on gfx12

In reply to Greg Kroah-Hartman

6.12-stable review patch. If anyone has any objections, please let me know.

------------------

From: Qiang Yu <[email protected]>

commit 2d69604b4d0b9c0c0ac71624b5fafb36cf249729 upstream.

TA_CNTL2.TRUNCATE_COORD_MODE selects whether texture coordinate truncation is D3D9/GL/Vulkan conformant. gfx11 reads it and reports it to userspace via AMDGPU_IDS_FLAGS_CONFORMANT_TRUNC_COORD, but gfx12 never read it, so the flag was always reported as 0 and userspace fell back to the non-conformant path.

Read it in gfx_v12_0_constants_init() like gfx11 does.

Fixes 52cb80c12e8a ("drm/amdgpu: Add gfx v12_0 ip block support (v6)")
(cherry picked from commit 4261cbc7b03f1f56e95aeaf1492b8690fa5a253e)
---
 drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c |    5 +++++
 1 file changed, 5 insertions(+)
drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c +5 -0
@@ -1692,6 +1692,11 @@ static void gfx_v12_0_constants_init(str
 	gfx_v12_0_get_tcc_info(adev);
 	adev->gfx.config.pa_sc_tile_steering_override = 0;
 
+	/* Set whether texture coordinate truncation is conformant. */
+	tmp = RREG32_SOC15(GC, 0, regTA_CNTL2);
+	adev->gfx.config.ta_cntl2_truncate_coord_mode =
+		REG_GET_FIELD(tmp, TA_CNTL2, TRUNCATE_COORD_MODE);
+
 	/* XXX SH_MEM regs */
 	/* where to put LDS, scratch, GPUVM in FSA64 space */
 	mutex_lock(&adev->srbm_mutex);
author maintainer
#38 lore raw

PATCH 34/220 drm/amdgpu: fix aperture iounmap skipped on device removal

In reply to Greg Kroah-Hartman

6.12-stable review patch. If anyone has any objections, please let me know.

------------------

From: Asad Kamal <[email protected]>

commit f9e5f51549000e2665e3b5e02ff876b9e09cfe95 upstream.

amdgpu_pci_remove() calls drm_dev_unplug() before invoking the fini routines. After drm_dev_unplug() the drm_dev_enter() guard in amdgpu_ttm_fini() always returns false, so iounmap() for aper_base_kaddr is silently skipped. On connected_to_cpu hardware ioremap_cache() maps the aperture as WB; when iounmap() is skipped the stale WB PAT entry persists. On reload IP discovery's memremap(MEMREMAP_WC) on the same aperture range hits a WB/WC conflict, producing an ioremap error and failing re-probe.

Remove the drm_dev_enter() guard and call iounmap() unconditionally. The aperture mapping is plain MMIO and does not require device-presence protection. Surprise-removal cleanup of aper_base_kaddr is already handled unconditionally by amdgpu_device_unmap_mmio().

Fixes 62d5f9f7110a ("drm/amdgpu: Unmap MMIO mappings when device is not unplugged")
(cherry picked from commit fb3f68af9f6fce9343a2bd13b4d68a1c02d283df)
---
 drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.c |   10 ++--------
 1 file changed, 2 insertions(+), 8 deletions(-)
drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.c +2 -8
@@ -2066,8 +2066,6 @@ int amdgpu_ttm_init(struct amdgpu_device
  */
 void amdgpu_ttm_fini(struct amdgpu_device *adev)
 {
-	int idx;
-
 	if (!adev->mman.initialized)
 		return;
 
@@ -2090,13 +2088,9 @@ void amdgpu_ttm_fini(struct amdgpu_devic
 	amdgpu_ttm_fw_reserve_vram_fini(adev);
 	amdgpu_ttm_drv_reserve_vram_fini(adev);
 
-	if (drm_dev_enter(adev_to_drm(adev), &idx)) {
-
-		if (adev->mman.aper_base_kaddr)
-			iounmap(adev->mman.aper_base_kaddr);
+	if (adev->mman.aper_base_kaddr) {
+		iounmap(adev->mman.aper_base_kaddr);
 		adev->mman.aper_base_kaddr = NULL;
-
-		drm_dev_exit(idx);
 	}
 
 	amdgpu_vram_mgr_fini(adev);
author maintainer
#39 lore raw

PATCH 35/220 ASoC: SOF: topology: Use acpi mach from the machine driver

In reply to Greg Kroah-Hartman

6.12-stable review patch. If anyone has any objections, please let me know.

------------------

From: Bard Liao <[email protected]>

commit ae63720dd7c3647d64f7a85e5e1870f90eb569d6 upstream.

The parameters may be changed by the sof_sdw machine driver is the SOC_SDW_PCH_DMIC quirk is set. Use the mach_params from the machine driver to ensure the sof_sdw_get_tplg_files() function select the right function topologies.

---
 sound/soc/intel/common/sof-function-topology-lib.c |    7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)
sound/soc/intel/common/sof-function-topology-lib.c +6 -1
@@ -30,7 +30,12 @@ enum tplg_device_id {
 int sof_sdw_get_tplg_files(struct snd_soc_card *card, const struct snd_soc_acpi_mach *mach,
 			   const char *prefix, const char ***tplg_files)
 {
-	struct snd_soc_acpi_mach_params mach_params = mach->mach_params;
+	struct snd_soc_acpi_mach *card_mach = dev_get_platdata(card->dev);
+	/*
+	 * Use the acpi mach from the machine driver because the machine driver
+	 * may change the dmic_num based on the machine driver quirk.
+	 */
+	struct snd_soc_acpi_mach_params mach_params = card_mach->mach_params;
 	struct snd_soc_dai_link *dai_link;
 	const struct firmware *fw;
 	char platform[SOF_INTEL_PLATFORM_NAME_MAX];
author maintainer
#40 lore raw

PATCH 36/220 Input: xpad - add support for ZENAIM LEVERLESS

In reply to Greg Kroah-Hartman

6.12-stable review patch. If anyone has any objections, please let me know.

------------------

From: Kyohei Kadota <[email protected]>

commit 7d971337ebfad0b173cb46097c709db174ac3557 upstream.

Add the VID/PID for the ZENAIM LEVERLESS controller to xpad_device and the VID to xpad_table.

---
 drivers/input/joystick/xpad.c |    2 ++
 1 file changed, 2 insertions(+)
drivers/input/joystick/xpad.c +2 -0
@@ -418,6 +418,7 @@ static const struct xpad_device {
 	{ 0x3285, 0x0646, "Nacon Pro Compact", 0, XTYPE_XBOXONE },
 	{ 0x3285, 0x0662, "Nacon Revolution5 Pro", 0, XTYPE_XBOX360 },
 	{ 0x3285, 0x0663, "Nacon Evol-X", 0, XTYPE_XBOXONE },
+	{ 0x3507, 0x000b, "ZENAIM LEVERLESS", 0, XTYPE_XBOX360 },
 	{ 0x3537, 0x1004, "GameSir T4 Kaleid", 0, XTYPE_XBOX360 },
 	{ 0x3537, 0x100f, "GameSir Nova 2 Lite", 0, XTYPE_XBOX360 },
 	{ 0x3537, 0x1010, "GameSir G7 SE", 0, XTYPE_XBOXONE },
@@ -577,6 +578,7 @@ static const struct usb_device_id xpad_t
 	XPAD_XBOX360_VENDOR(0x31e3),		/* Wooting Keyboards */
 	XPAD_XBOX360_VENDOR(0x3285),		/* Nacon GC-100 */
 	XPAD_XBOXONE_VENDOR(0x3285),		/* Nacon Evol-X */
+	XPAD_XBOX360_VENDOR(0x3507),		/* ZENAIM Controllers */
 	XPAD_XBOX360_VENDOR(0x3537),		/* GameSir Controllers */
 	XPAD_XBOXONE_VENDOR(0x3537),		/* GameSir Controllers */
 	XPAD_XBOX360_VENDOR(0x413d),		/* Black Shark Green Ghost Controller */
author maintainer
#41 lore raw

PATCH 37/220 Input: cs40l50-vibra - validate custom data from user space

In reply to Greg Kroah-Hartman

6.12-stable review patch. If anyone has any objections, please let me know.

------------------

From: HyeongJun An <[email protected]>

commit 7d5c576cb1c86047b1fcb1aa9532e17fc5e46c1d upstream.

cs40l50_add() copies the custom data of an FF_PERIODIC/FF_CUSTOM effect straight from the ff_effect the user passed to EVIOCSFF, without requiring it to hold anything:

    work_data.custom_data = memdup_array_user(periodic->custom_data,
                                              periodic->custom_len,
                                              sizeof(s16));
    work_data.custom_len = periodic->custom_len;

The driver then reads two words out of that buffer: custom_data[0] as the waveform bank in cs40l50_effect_bank_set(), and custom_data[1] as the index within the bank in cs40l50_effect_index_set(). Neither read is covered by a length check, and custom_len is fully user controlled:

- custom_len == 0 makes memdup_array_user() call memdup_user() with a length of zero, which returns ZERO_SIZE_PTR rather than an error, so custom_data[0] dereferences it.

- custom_len == 1 allocates two bytes. A bank of ROM or RAM keeps effect->type out of the OWT case, and custom_data[1] is then read one word past the allocation.

The bank value itself is also mishandled. It is masked with CS40L50_CUSTOM_DATA_MASK (0xffff) but stored in an s16, so a custom_data[0] of 0x8000 or above wraps to a negative value that passes the "bank_type >= CS40L50_WVFRM_BANK_NUM" test. cs40l50_effect_index_set() indexes vib->dsp.banks[] with it before the switch statement's default case gets a chance to reject it:

    base_index = vib->dsp.banks[effect->type].base_index;
    max_index = vib->dsp.banks[effect->type].max_index;

Require the two words the driver reads to be present, and hold the masked bank in a u32 so the existing upper-bound test covers the whole range. The da7280 haptic driver already range checks custom_len this way.

Fixes c38fe1bb5d21 ("Input: cs40l50 - Add support for the CS40L50 haptic driver")
Assisted-by Claude:claude-opus-4-8
---
 drivers/input/misc/cs40l50-vibra.c |   10 ++++++++--
 1 file changed, 8 insertions(+), 2 deletions(-)
drivers/input/misc/cs40l50-vibra.c +8 -2
@@ -139,10 +139,10 @@ static struct cs40l50_effect *cs40l50_fi
 static int cs40l50_effect_bank_set(struct cs40l50_work *work_data,
 				   struct cs40l50_effect *effect)
 {
-	s16 bank_type = work_data->custom_data[0] & CS40L50_CUSTOM_DATA_MASK;
+	u32 bank_type = work_data->custom_data[0] & CS40L50_CUSTOM_DATA_MASK;
 
 	if (bank_type >= CS40L50_WVFRM_BANK_NUM) {
-		dev_err(work_data->vib->dev, "Invalid bank (%d)\n", bank_type);
+		dev_err(work_data->vib->dev, "Invalid bank (%u)\n", bank_type);
 		return -EINVAL;
 	}
 
@@ -327,6 +327,12 @@ static int cs40l50_add(struct input_dev
 		return -EINVAL;
 	}
 
+	if (periodic->custom_len < CS40L50_OWT_CUSTOM_DATA_SIZE) {
+		dev_err(vib->dev, "Invalid custom data length (%u)\n",
+			periodic->custom_len);
+		return -EINVAL;
+	}
+
 	work_data.custom_data = memdup_array_user(effect->u.periodic.custom_data,
 						  effect->u.periodic.custom_len,
 						  sizeof(s16));
author maintainer
#42 lore raw

PATCH 38/220 powerpc/pseries: pci - logic bug

In reply to Greg Kroah-Hartman

6.12-stable review patch. If anyone has any objections, please let me know.

------------------

From: George Wilson <[email protected]>

commit 649c10bff5cb7a514bf299094833ec8c9190aac3 upstream.

The checks on num_vfs in pseries_pci_sriov_enable() are ANDed where OR was apparently intended. Change it to OR.

Fixes 9a7f6b438664 ("powerpc/pseries/pci: Associate PEs to VFs in configure SR-IOV")
---
 arch/powerpc/platforms/pseries/pci.c |    2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
arch/powerpc/platforms/pseries/pci.c +1 -1
@@ -132,7 +132,7 @@ static int pseries_pci_sriov_enable(stru
 
 	/* First integer stores max config */
 	max_config_vfs = of_read_number(&max_vfs[0], 1);
-	if (max_config_vfs < num_vfs && num_vfs > MAX_VFS_FOR_MAP_PE) {
+	if (max_config_vfs < num_vfs || num_vfs > MAX_VFS_FOR_MAP_PE) {
 		dev_err(&pdev->dev,
 			"Num VFs %x > %x Configurable VFs\n",
 			num_vfs, (num_vfs > MAX_VFS_FOR_MAP_PE) ?
author maintainer
#43 lore raw

PATCH 39/220 Input: synaptics-rmi4 - fix F55 transmitter electrode count typo

In reply to Greg Kroah-Hartman

6.12-stable review patch. If anyone has any objections, please let me know.

------------------

From: Dmitry Torokhov <[email protected]>

commit 6058f0fea10f3caf63a435677358d1b8e9325114 upstream.

During F55 sensor detection, the transmitter (TX) electrode count was incorrectly assigned the value of the receiver (RX) electrode count due to copy-paste typos.

This incorrect value was then propagated to the driver data and used by F54 to determine the diagnostics report size. On devices with more RX than TX electrodes, this inflated the perceived TX count, leading to incorrect report size calculations and potential out-of-bounds buffer accesses.

Fix the typos by correctly assigning the TX electrode counts.

Fixes 6adba43fd222 ("Input: synaptics-rmi4 - add support for F55 sensor tuning")
Fixes c762cc68b6a1 ("Input: synaptics-rmi4 - propagate correct number of rx and tx electrodes to F54")
Reported-by [email protected]
Assisted-by Antigravity:gemini-3.5-flash
---
 drivers/input/rmi4/rmi_f55.c |    4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)
drivers/input/rmi4/rmi_f55.c +2 -2
@@ -54,10 +54,10 @@ static int rmi_f55_detect(struct rmi_fun
 	f55->num_tx_electrodes = f55->qry[F55_NUM_TX_OFFSET];
 
 	f55->cfg_num_rx_electrodes = f55->num_rx_electrodes;
-	f55->cfg_num_tx_electrodes = f55->num_rx_electrodes;
+	f55->cfg_num_tx_electrodes = f55->num_tx_electrodes;
 
 	drv_data->num_rx_electrodes = f55->cfg_num_rx_electrodes;
-	drv_data->num_tx_electrodes = f55->cfg_num_rx_electrodes;
+	drv_data->num_tx_electrodes = f55->cfg_num_tx_electrodes;
 
 	if (f55->qry[F55_PHYS_CHAR_OFFSET] & F55_CAP_SENSOR_ASSIGN) {
 		int i, total;
author maintainer
#44 lore raw

PATCH 40/220 Input: focaltech - fix array out-of-bounds in focaltech_process_rel_packet

In reply to Greg Kroah-Hartman

6.12-stable review patch. If anyone has any objections, please let me know.

------------------

From: Richard Davies <[email protected]>

commit 296736076b3fd078742651c719555a488624023a upstream.

Make finger2 (and also finger1) unsigned, so that if the finger index in the packet is 0 then subtracting 1 creates an array index which overflows above the existing check for FOC_MAX_FINGERS, as the existing comment says it should, instead of writing to state->fingers[-1].

Fixes 05be1d079ec0 ("Input: psmouse - support for the FocalTech PS/2 protocol extensions")
---
 drivers/input/mouse/focaltech.c |    2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
drivers/input/mouse/focaltech.c +1 -1
@@ -197,7 +197,7 @@ static void focaltech_process_rel_packet
 {
 	struct focaltech_data *priv = psmouse->private;
 	struct focaltech_hw_state *state = &priv->state;
-	int finger1, finger2;
+	unsigned int finger1, finger2;
 
 	state->pressed = packet[0] >> 7;
 	finger1 = ((packet[0] >> 4) & 0x7) - 1;
author maintainer
#45 lore raw

PATCH 41/220 Input: psxpad-spi - set driver data before use

In reply to Greg Kroah-Hartman

6.12-stable review patch. If anyone has any objections, please let me know.

------------------

From: Linmao Li <[email protected]>

commit 732f38c36059e68ba3b4b89c56911d777fd3185c upstream.

psxpad_spi_suspend() retrieves the controller state with spi_get_drvdata(), but probe never stores it, so suspend dereferences a NULL pointer. Store it during probe.

Fixes 8be193c7b1f4 ("Input: add support for PlayStation 1/2 joypads connected via SPI")
---
 drivers/input/joystick/psxpad-spi.c |    1 +
 1 file changed, 1 insertion(+)
drivers/input/joystick/psxpad-spi.c +1 -0
@@ -369,6 +369,7 @@ static int psxpad_spi_probe(struct spi_d
 		return err;
 	}
 
+	spi_set_drvdata(spi, pad);
 	pm_runtime_enable(&spi->dev);
 
 	return 0;
author maintainer
#46 lore raw

PATCH 42/220 Input: atkbd - skip deactivate for Xiaomi Book Pro 14s internal keyboard

In reply to Greg Kroah-Hartman

6.12-stable review patch. If anyone has any objections, please let me know.

------------------

From: Zhefu Zhang <[email protected]>

commit 3a046db33bb9f28b43a951a7a090db771dc0f8b3 upstream.

The internal keyboard of the Xiaomi Book Pro 14 does not work unless atkbd skips deactivating it at the end of atkbd_probe().

Using 'i8042.dumbkbd=1' also makes the keyboard work, but then the driver never writes to the keyboard at all, so the Caps Lock LED is lost. The atkbd_deactivate_fixup quirk fixes both without a boot parameter.

DMI: XIAOMI Xiaomi Book Pro 14/TM2424, BIOS XMAPT4B0P0909 05/06/2026

---
 drivers/input/keyboard/atkbd.c |    8 ++++++++
 1 file changed, 8 insertions(+)
drivers/input/keyboard/atkbd.c +8 -0
@@ -1952,6 +1952,14 @@ static const struct dmi_system_id atkbd_
 		},
 		.callback = atkbd_deactivate_fixup,
 	},
+	{
+		/* Xiaomi Book Pro 14 (TM2424) */
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "XIAOMI"),
+			DMI_MATCH(DMI_PRODUCT_NAME, "Xiaomi Book Pro 14"),
+		},
+		.callback = atkbd_deactivate_fixup,
+	},
 	{ }
 };
 
author maintainer
#47 lore raw

PATCH 43/220 Input: iforce - validate input packet lengths

In reply to Greg Kroah-Hartman

6.12-stable review patch. If anyone has any objections, please let me know.

------------------

From: Pengpeng Hou <[email protected]>

commit 5751c781d3c97ab6ce0e2a966156ed882152c415 upstream.

iforce_process_packet() reads fixed fields from joystick, wheel and status packets without first checking their lengths. In particular, the shared hats-and-buttons helper unconditionally reads data[6]. The status tail is a sequence of 16-bit effect addresses, but an incomplete final address is also consumed. A successful zero-length USB URB additionally reads the packet ID before the common parser is called.

Reject the zero-length USB transfer, require the seven-byte joystick and wheel prefixes and the two-byte status prefix, and consume only complete status-tail addresses.

---
 drivers/input/joystick/iforce/iforce-packets.c |   11 ++++++++++-
 drivers/input/joystick/iforce/iforce-usb.c     |    3 +++
 2 files changed, 13 insertions(+), 1 deletion(-)
drivers/input/joystick/iforce/iforce-packets.c +10 -1
@@ -161,6 +161,9 @@ void iforce_process_packet(struct iforce
 	switch (packet_id) {
 
 	case 0x01:	/* joystick position data */
+		if (len < 7)
+			break;
+
 		input_report_abs(dev, ABS_X,
 				 (__s16) get_unaligned_le16(data));
 		input_report_abs(dev, ABS_Y,
@@ -176,6 +179,9 @@ void iforce_process_packet(struct iforce
 		break;
 
 	case 0x03:	/* wheel position data */
+		if (len < 7)
+			break;
+
 		input_report_abs(dev, ABS_WHEEL,
 				 (__s16) get_unaligned_le16(data));
 		input_report_abs(dev, ABS_GAS,   255 - data[2]);
@@ -187,6 +193,9 @@ void iforce_process_packet(struct iforce
 		break;
 
 	case 0x02:	/* status report */
+		if (len < 2)
+			break;
+
 		input_report_key(dev, BTN_DEAD, data[0] & 0x02);
 		input_sync(dev);
 
@@ -206,7 +215,7 @@ void iforce_process_packet(struct iforce
 			}
 		}
 
-		for (j = 3; j < len; j += 2)
+		for (j = 3; j + sizeof(u16) <= len; j += sizeof(u16))
 			mark_core_as_ready(iforce, get_unaligned_le16(data + j));
 
 		break;
drivers/input/joystick/iforce/iforce-usb.c +3 -0
@@ -159,6 +159,9 @@ static void iforce_usb_irq(struct urb *u
 		goto exit;
 	}
 
+	if (!urb->actual_length)
+		goto exit;
+
 	iforce_process_packet(iforce, iforce_usb->data_in[0],
 			      iforce_usb->data_in + 1, urb->actual_length - 1);
 
author maintainer
#48 lore raw

PATCH 44/220 powerpc/pseries: lparcfg - fix kbuf[] underflow

In reply to Greg Kroah-Hartman

6.12-stable review patch. If anyone has any objections, please let me know.

------------------

From: George Wilson <[email protected]>

commit fb442a6673ff1046bf67754957d95880fdb394b5 upstream.

In lparcfg_write(), a count of 0 results in kbuf[] being indexed at -1. Check for count == 0 in the existing check for count > sizeof(kbuf) and return -EINVAL if true.

Fixes 74422e2b1939 ("powerpc/pseries: Remove VLA from lparcfg_write()")
---
 arch/powerpc/platforms/pseries/lparcfg.c |    2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
arch/powerpc/platforms/pseries/lparcfg.c +1 -1
@@ -687,7 +687,7 @@ static ssize_t lparcfg_write(struct file
 	if (!firmware_has_feature(FW_FEATURE_SPLPAR))
 		return -EINVAL;
 
-	if (count > sizeof(kbuf))
+	if (count == 0 || count > sizeof(kbuf))
 		return -EINVAL;
 
 	if (copy_from_user(kbuf, buf, count))
author maintainer
#49 lore raw

PATCH 45/220 Input: synaptics-rmi4 - zero report size on F54 work error

In reply to Greg Kroah-Hartman

6.12-stable review patch. If anyone has any objections, please let me know.

------------------

From: Dmitry Torokhov <[email protected]>

commit dc76c3c8e8ad09362b8c1561f3928288c15cba2e upstream.

In rmi_f54_work(), if an error occurs during report request or command verification, the code jumped directly to the 'error' label, bypassing the 'abort' label where f54->report_size was normally zeroed out.

This left f54->report_size containing its previous successful payload size. If a user then altered the V4L2 format to a smaller size, and a subsequent run failed, rmi_f54_buffer_queue() would copy the stale, larger payload size into the shrunken V4L2 buffer, causing a heap buffer overflow.

Fix this by merging the 'abort' and 'error' labels into a single 'out' exit path, and ensuring that f54->report_size is always set to 0 on failure by checking for error and zeroing the local report_size first.

Fixes 3a762dbd5347 ("[media] Input: synaptics-rmi4 - add support for F54 diagnostics")
Reported-by [email protected]
Assisted-by Antigravity:gemini-3.5-flash
---
 drivers/input/rmi4/rmi_f54.c |   16 ++++++++--------
 1 file changed, 8 insertions(+), 8 deletions(-)
drivers/input/rmi4/rmi_f54.c +8 -8
@@ -547,7 +547,7 @@ static void rmi_f54_work(struct work_str
 		dev_err(&fn->dev, "Bad report size, report type=%d\n",
 				f54->report_type);
 		error = -EINVAL;
-		goto error;     /* retry won't help */
+		goto out;     /* retry won't help */
 	}
 
 	/*
@@ -558,7 +558,7 @@ static void rmi_f54_work(struct work_str
 			 &command);
 	if (error) {
 		dev_err(&fn->dev, "Failed to read back command\n");
-		goto error;
+		goto out;
 	}
 	if (command & F54_GET_REPORT) {
 		if (time_after(jiffies, f54->timeout)) {
@@ -566,7 +566,7 @@ static void rmi_f54_work(struct work_str
 			error = -ETIMEDOUT;
 		}
 		report_size = 0;
-		goto error;
+		goto out;
 	}
 
 	rmi_dbg(RMI_DEBUG_FN, &fn->dev, "Get report command completed, reading data\n");
@@ -581,7 +581,7 @@ static void rmi_f54_work(struct work_str
 					fifo, sizeof(fifo));
 		if (error) {
 			dev_err(&fn->dev, "Failed to set fifo start offset\n");
-			goto abort;
+			goto out;
 		}
 
 		error = rmi_read_block(fn->rmi_dev, fn->fd.data_base_addr +
@@ -590,16 +590,16 @@ static void rmi_f54_work(struct work_str
 		if (error) {
 			dev_err(&fn->dev, "%s: read [%d bytes] returned %d\n",
 				__func__, size, error);
-			goto abort;
+			goto out;
 		}
 	}
 
-abort:
-	f54->report_size = error ? 0 : report_size;
-error:
+out:
 	if (error)
 		report_size = 0;
 
+	f54->report_size = report_size;
+
 	if (report_size == 0 && !error) {
 		queue_delayed_work(f54->workqueue, &f54->work,
 				   msecs_to_jiffies(1));
author maintainer
#50 lore raw

PATCH 46/220 Input: synaptics-rmi4 - bound the F54 report size to the allocated buffer

In reply to Greg Kroah-Hartman

6.12-stable review patch. If anyone has any objections, please let me know.

------------------

From: Bryam Vargas <[email protected]>

commit 49c5adc2b7d6e43c5cf033e1c86fdb9c16ababb1 upstream.

rmi_f54_work() reads a diagnostics report from the device into f54->report_data, sizing the transfer with rmi_f54_get_report_size():

	report_size = rmi_f54_get_report_size(f54);
	...
	for (i = 0; i < report_size; i += F54_REPORT_DATA_SIZE) {
		int size = min(F54_REPORT_DATA_SIZE, report_size - i);
		...
		rmi_read_block(.., f54->report_data + i, size);
	}

report_data is allocated once at probe from F54's own electrode counts (array3_size(f54->num_tx_electrodes, f54->num_rx_electrodes, sizeof(u16))), but rmi_f54_get_report_size() computes the size from drv_data->num_*_electrodes when those are set, i.e. from the F55 function's electrode counts. Both counts come straight from device queries (F54 and F55 each report up to 255 electrodes) and nothing constrains the F55 counts to the F54 ones.

A malicious or malfunctioning RMI4 device that reports larger F55 electrode counts than its F54 counts makes report_size exceed the allocation, so the read loop writes past report_data (and the V4L2 dequeue memcpy() then reads past it). On conforming hardware the F55 configured electrodes are a subset of the F54 physical electrodes, so report_size never exceeds the buffer and well-behaved devices are unaffected.

Record the allocation size and reject a report that does not fit, mirroring the existing zero-size check.

Fixes c762cc68b6a1 ("Input: synaptics-rmi4 - propagate correct number of rx and tx electrodes to F54")
Assisted-by Antigravity:gemini-3.5-flash
---
 drivers/input/rmi4/rmi_f54.c |   12 ++++++++++--
 1 file changed, 10 insertions(+), 2 deletions(-)
drivers/input/rmi4/rmi_f54.c +10 -2
@@ -104,6 +104,7 @@ struct f54_data {
 
 	enum rmi_f54_report_type report_type;
 	u8 *report_data;
+	size_t max_report_size;
 	int report_size;
 
 	bool is_busy;
@@ -550,6 +551,13 @@ static void rmi_f54_work(struct work_str
 		goto out;     /* retry won't help */
 	}
 
+	if (report_size > f54->max_report_size) {
+		dev_err(&fn->dev, "Report size %d exceeds buffer size %zu\n",
+			report_size, f54->max_report_size);
+		error = -EINVAL;
+		goto out;
+	}
+
 	/*
 	 * Need to check if command has completed.
 	 * If not try again later.
@@ -680,8 +688,8 @@ static int rmi_f54_probe(struct rmi_func
 
 	rx = f54->num_rx_electrodes;
 	tx = f54->num_tx_electrodes;
-	f54->report_data = devm_kzalloc(&fn->dev,
-					array3_size(tx, rx, sizeof(u16)),
+	f54->max_report_size = array3_size(tx, rx, sizeof(u16));
+	f54->report_data = devm_kzalloc(&fn->dev, f54->max_report_size,
 					GFP_KERNEL);
 	if (f54->report_data == NULL)
 		return -ENOMEM;
author maintainer
#51 lore raw

PATCH 47/220 Input: synaptics-rmi4 - block s_input when F54 queue is busy

In reply to Greg Kroah-Hartman

6.12-stable review patch. If anyone has any objections, please let me know.

------------------

From: Dmitry Torokhov <[email protected]>

commit fbfd76746adc16d64be29ff113f673b70bc3f5c2 upstream.

Changing the input (diagnostic report type) mid-stream changes the report size. Since V4L2 buffers are allocated based on the size at stream start, changing the input while streaming could lead to a heap buffer overflow if the new size is larger than the allocated buffers.

Prevent this by blocking VIDIOC_S_INPUT with -EBUSY if the V4L2 queue is busy (streaming).

Fixes 3a762dbd5347 ("[media] Input: synaptics-rmi4 - add support for F54 diagnostics")
Assisted-by Antigravity:gemini-3.5-flash
---
 drivers/input/rmi4/rmi_f54.c |    7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)
drivers/input/rmi4/rmi_f54.c +6 -1
@@ -447,7 +447,12 @@ static int rmi_f54_set_input(struct f54_
 
 static int rmi_f54_vidioc_s_input(struct file *file, void *priv, unsigned int i)
 {
-	return rmi_f54_set_input(video_drvdata(file), i);
+	struct f54_data *f54 = video_drvdata(file);
+
+	if (vb2_is_busy(&f54->queue))
+		return -EBUSY;
+
+	return rmi_f54_set_input(f54, i);
 }
 
 static int rmi_f54_vidioc_g_input(struct file *file, void *priv,
author maintainer
#52 lore raw

PATCH 48/220 Input: synaptics-rmi4 - propagate F54 worker errors to V4L2 queue

In reply to Greg Kroah-Hartman

6.12-stable review patch. If anyone has any objections, please let me know.

------------------

From: Dmitry Torokhov <[email protected]>

commit 8786d74bf50e6797b6f655eb381ef6b25451161f upstream.

Previously, rmi_f54_buffer_queue() waited for the worker thread to finish but ignored whether it succeeded. If the worker failed (e.g., due to a timeout or register read failure), the queue thread would silently return success, delivering stale or uninitialized memory to userspace.

Add a 'report_error' field to struct f54_data to store the worker's exit status. Check this field in rmi_f54_buffer_queue() after the worker finishes, and mark the buffer as VB2_BUF_STATE_ERROR if an error occurred.

Fixes 3a762dbd5347 ("[media] Input: synaptics-rmi4 - add support for F54 diagnostics")
Reported-by [email protected]
Assisted-by Antigravity:gemini-3.5-flash
---
 drivers/input/rmi4/rmi_f54.c |    8 ++++++++
 1 file changed, 8 insertions(+)
drivers/input/rmi4/rmi_f54.c +8 -0
@@ -106,6 +106,7 @@ struct f54_data {
 	u8 *report_data;
 	size_t max_report_size;
 	int report_size;
+	int report_error;
 
 	bool is_busy;
 	struct mutex status_mutex;
@@ -340,6 +341,12 @@ static void rmi_f54_buffer_queue(struct
 		mutex_lock(&f54->data_mutex);
 	}
 
+	if (f54->report_error) {
+		dev_err(&f54->fn->dev, "Error acquiring report: %d\n", f54->report_error);
+		state = VB2_BUF_STATE_ERROR;
+		goto data_done;
+	}
+
 	ptr = vb2_plane_vaddr(vb, 0);
 	if (!ptr) {
 		dev_err(&f54->fn->dev, "Error acquiring frame ptr\n");
@@ -612,6 +619,7 @@ out:
 		report_size = 0;
 
 	f54->report_size = report_size;
+	f54->report_error = error;
 
 	if (report_size == 0 && !error) {
 		queue_delayed_work(f54->workqueue, &f54->work,
author maintainer
#53 lore raw

PATCH 49/220 Input: hynitron_cstxxx - validate touch count and finger IDs

In reply to Greg Kroah-Hartman

6.12-stable review patch. If anyone has any objections, please let me know.

------------------

From: Jianing Li <[email protected]>

commit 27f380ef0e1d3de3cde114e02d33f9320ce3a5a6 upstream.

The driver allocates max_touch_num input slots, which are indexed from zero through max_touch_num - 1. The current check allows a finger ID equal to max_touch_num to reach cst3xx_report_contact(). While the input core ignores out-of-range slot indices, reporting touch data without a valid slot change corrupts the touch state of the previously active slot.

The touch count is read from the controller's report and is used to index the fixed-size report buffer without first checking its range. Reject counts larger than the supported number of touch slots before checking the trailing byte or parsing touch data.

Reject finger IDs equal to or greater than max_touch_num, and return immediately when an invalid finger ID is encountered so that corrupt touch frames are discarded instead of reporting partial contact state.

The V821 Avaota F1 board configures the vendor driver with one touch slot, so finger ID 1 is already invalid on that device.

Fixes 66603243f528 ("Input: add driver for Hynitron cstxxx touchscreens")
Assisted-by Antigravity:gemini-3.6-flash
---
 drivers/input/touchscreen/hynitron_cstxxx.c |   13 ++++++++++---
 1 file changed, 10 insertions(+), 3 deletions(-)
drivers/input/touchscreen/hynitron_cstxxx.c +10 -3
@@ -313,6 +313,12 @@ static void cst3xx_touch_report(struct i
 		return;
 
 	touch_cnt = buf[5] & CST3XX_TOUCH_COUNT_MASK;
+	if (touch_cnt > ts_data->chip->max_touch_num) {
+		dev_err(&client->dev, "cst3xx invalid touch count (%d vs %d max)\n",
+			touch_cnt, ts_data->chip->max_touch_num);
+		return;
+	}
+
 	/*
 	 * Check the check bit of the last touch slot. The check bit is
 	 * always present after touch point 1 for valid data, and then
@@ -335,9 +341,10 @@ static void cst3xx_touch_report(struct i
 		finger_id = (buf[idx] >> 4) & 0x0f;
 
 		/* Sanity check we don't have more fingers than we expect */
-		if (ts_data->chip->max_touch_num < finger_id) {
-			dev_err(&client->dev, "cst3xx touch read failure\n");
-			break;
+		if (finger_id >= ts_data->chip->max_touch_num) {
+			dev_err(&client->dev,
+				"cst3xx invalid finger id %d\n", finger_id);
+			return;
 		}
 
 		/* sw value of 0 means no touch, 0x03 means touch */
author maintainer
#54 lore raw

PATCH 50/220 crypto: starfive - use scatterlist length before DMA mapping

In reply to Greg Kroah-Hartman

6.12-stable review patch. If anyone has any objections, please let me know.

------------------

From: Thorsten Blum <[email protected]>

commit 6b36f13891ab4709b7d60023005176cdd5c368cf upstream.

Using sg_dma_len() is only valid after mapping a scatterlist with dma_map_sg(). However, starfive_aes_aead_do_one_req() uses it before mapping the scatterlist.

Use the original scatterlist length because the DMA length has not been populated yet when CONFIG_NEED_SG_DMA_LENGTH=y.

Fixes 7467147ef9bf ("crypto: starfive - Use dma for aes requests")
---
 drivers/crypto/starfive/jh7110-aes.c |    2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
drivers/crypto/starfive/jh7110-aes.c +1 -1
@@ -681,7 +681,7 @@ static int starfive_aes_aead_do_one_req(
 
 	if (cryp->total_in)
 		sg_zero_buffer(rctx->in_sg, sg_nents(rctx->in_sg),
-			       sg_dma_len(rctx->in_sg) - cryp->total_in,
+			       rctx->in_sg->length - cryp->total_in,
 			       cryp->total_in);
 
 	ctx->rctx = rctx;
author maintainer
#55 lore raw

PATCH 51/220 crypto: qce - fix error path in devm_qce_register_algs

In reply to Greg Kroah-Hartman

6.12-stable review patch. If anyone has any objections, please let me know.

------------------

From: Thorsten Blum <[email protected]>

commit 9c75402286409f5e1a75e4a445555c84066f89db upstream.

If ops->register_algs() fails, the error path repeatedly calls the same ops->unregister_algs() from the failed registration. Use the loop index to unregister the previously registered algorithms instead.

Fixes e80cf84b6087 ("crypto: qce - unregister previously registered algos in error path")
---
 drivers/crypto/qce/core.c |    2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
drivers/crypto/qce/core.c +1 -1
@@ -58,7 +58,7 @@ static int qce_register_algs(struct qce_
 		ret = ops->register_algs(qce);
 		if (ret) {
 			for (j = i - 1; j >= 0; j--)
-				ops->unregister_algs(qce);
+				qce_ops[j]->unregister_algs(qce);
 			return ret;
 		}
 	}
author maintainer
#56 lore raw

PATCH 52/220 gpio: sloppy-logic-analyzer: fix use-after-free via debugfs trigger on unbind

In reply to Greg Kroah-Hartman

6.12-stable review patch. If anyone has any objections, please let me know.

------------------

From: Cengiz Can <[email protected]>

commit 44f3468a0aef1aabdad551898ab7cfa2a9d20e99 upstream.

The "trigger" debugfs file has a hand-rolled ->write handler (trigger_write()) that dereferences the per-device gpio_la_poll_priv. The file is created with debugfs_create_file_unsafe(), and the handler never takes a debugfs reference. Nothing keeps the object alive while the handler runs.

priv is allocated with devm_kzalloc(). devres frees it when the platform device is unbound. debugfs_create_file_unsafe() installs no full_proxy wrapper, so debugfs_remove_recursive() in gpio_la_poll_remove() does not wait for an in-flight trigger_write(). The blob_lock taken there does not help, because trigger_write() never takes it. A write that races an unbind therefore writes into freed memory:

trigger_write() gpio_la_poll_remove()

    priv = m->private
    buf = memdup_user()  [may sleep]
                                     mutex_lock(&priv->blob_lock)
                                     debugfs_remove_recursive()  [no wait]
                                     mutex_unlock(&priv->blob_lock)
                                   (remove returns; devres frees priv)
    priv->trig_data = buf   <-- use-after-free write
    priv->trig_len  = count

The race is reachable by root via /sys/bus/platform/drivers/gpio-sloppy-logic-analyzer/unbind.

Create "trigger" with debugfs_create_file() instead. Its full_proxy wrapper makes debugfs_remove_recursive() drain any in-flight ->write before it returns.

The use-after-free is confirmed under KASAN with a minimal reproducer of the same debugfs_create_file_unsafe() plus devm_kzalloc() pattern (available on request); it produces a slab-use-after-free write in the handler.

Fixes 7828b7bbbf20 ("gpio: add sloppy logic analyzer using polling")
Assisted-by Claude:claude-opus-4.8
---
 drivers/gpio/gpio-sloppy-logic-analyzer.c |    2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
drivers/gpio/gpio-sloppy-logic-analyzer.c +1 -1
@@ -301,7 +301,7 @@ static int gpio_la_poll_probe(struct pla
 	debugfs_create_ulong("delay_ns_acquisition", 0400, priv->debug_dir, &priv->acq_delay);
 	debugfs_create_file_unsafe("buf_size", 0600, priv->debug_dir, priv, &fops_buf_size);
 	debugfs_create_file_unsafe("capture", 0200, priv->debug_dir, priv, &fops_capture);
-	debugfs_create_file_unsafe("trigger", 0200, priv->debug_dir, priv, &fops_trigger);
+	debugfs_create_file("trigger", 0200, priv->debug_dir, priv, &fops_trigger);
 
 	return 0;
 }
author maintainer
#57 lore raw

PATCH 53/220 pmdomain: arm: Fix -EINVAL from scmi_pd_set_perf_state() on state 0

In reply to Greg Kroah-Hartman

6.12-stable review patch. If anyone has any objections, please let me know.

------------------

From: Praveen Talari <[email protected]>

commit 3314c90a2eda3df7da4ab6f4388e667b2758de7f upstream.

Currently, scmi_pd_set_perf_state() treats a performance state of 0 as invalid and returns -EINVAL. As a result, devices attached to SCMI performance domains can report failures when relinquishing their performance vote.

The OPP framework use performance state 0 to indicate that no performance vote is required. For example, dev_pm_opp_set_rate(dev, 0) is commonly used (by firmware or linux) when a device is runtime suspended.

A zero performance state does not require any SCMI performance request to be sent. Treat it as a no-op and return success instead of reporting an error.

Fixes 2af23ceb8624 ("pmdomain: arm: Add the SCMI performance domain")
---
 drivers/pmdomain/arm/scmi_perf_domain.c |    2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
drivers/pmdomain/arm/scmi_perf_domain.c +1 -1
@@ -33,7 +33,7 @@ scmi_pd_set_perf_state(struct generic_pm
 		return 0;
 
 	if (!state)
-		return -EINVAL;
+		return 0;
 
 	ret = pd->perf_ops->level_set(pd->ph, pd->domain_id, state, false);
 	if (ret)
author maintainer
#58 lore raw

PATCH 54/220 libceph: fix multiple unsafe decodes in decode_locker()

In reply to Greg Kroah-Hartman

6.12-stable review patch. If anyone has any objections, please let me know.

------------------

From: Pavitra Jha <[email protected]>

commit 437b6551cfcc235eea1d735a874f9d421f555e17 upstream.

decode_locker() in cls_lock_client.c contains three unsafe decode operations that allow a malicious or compromised OSD to trigger slab-out-of-bounds reads:

1. ceph_decode_copy() at the locker_id_t name field has no preceding bounds check. With p == end after ceph_start_decoding() accepts struct_len=0, this reads sizeof(ceph_entity_name) = 9 bytes past the validated buffer boundary.

2. *p += sizeof(struct ceph_timespec) after the locker_info_t header is an unchecked pointer advance. A malicious OSD can position p past end, causing all subsequent _safe checks to pass against a bogus boundary.

3. len = ceph_decode_32(p) has no preceding bounds check, and the immediately following *p += len is uncapped. A malicious OSD can send len=0xffffffff, advancing p gigabytes past end and escaping the decode window entirely.

Fix all three by replacing bare operations with their safe variants:

  ceph_decode_copy   -> ceph_decode_copy_safe
  *p += sizeof(...)  -> ceph_decode_skip_n
  ceph_decode_32(p)  -> ceph_decode_32_safe
  *p += len          -> ceph_decode_skip_n

A new label is added to return -EINVAL on any bounds violation. -EINVAL is appropriate here: the data received from the OSD is structurally malformed, which is an invalid argument to the decode contract regardless of whether the caller or the wire is at fault.

Attacker model: a malicious or compromised OSD in a multi-tenant Ceph deployment can trigger this against any kernel client that issues the lock.get_info class method (e.g. during RBD exclusive lock acquisition) without any further privileges beyond OSD session establishment.

[ idryomov: use ceph_decode_skip_string() to skip description, trim
changelog ]

Fixes d4ed4a530562 ("libceph: support for lock.lock_info")
---
 net/ceph/cls_lock_client.c |   13 +++++++++----
 1 file changed, 9 insertions(+), 4 deletions(-)
net/ceph/cls_lock_client.c +9 -4
@@ -259,7 +259,8 @@ static int decode_locker(void **p, void
 	if (ret)
 		return ret;
 
-	ceph_decode_copy(p, &locker->id.name, sizeof(locker->id.name));
+	ceph_decode_copy_safe(p, end, &locker->id.name,
+			      sizeof(locker->id.name), bad);
 	s = ceph_extract_encoded_string(p, end, NULL, GFP_NOIO);
 	if (IS_ERR(s))
 		return PTR_ERR(s);
@@ -270,19 +271,23 @@ static int decode_locker(void **p, void
 	if (ret)
 		return ret;
 
-	*p += sizeof(struct ceph_timespec); /* skip expiration */
+	/* skip expiration */
+	ceph_decode_skip_n(p, end, sizeof(struct ceph_timespec), bad);
 
 	ret = ceph_decode_entity_addr(p, end, &locker->info.addr);
 	if (ret)
 		return ret;
 
-	len = ceph_decode_32(p);
-	*p += len; /* skip description */
+	/* skip description */
+	ceph_decode_skip_string(p, end, bad);
 
 	dout("%s %s%llu cookie %s addr %s\n", __func__,
 	     ENTITY_NAME(locker->id.name), locker->id.cookie,
 	     ceph_pr_addr(&locker->info.addr));
 	return 0;
+
+bad:
+	return -EINVAL;
 }
 
 static int decode_lockers(void **p, void *end, u8 *type, char **tag,
author maintainer
#59 lore raw

PATCH 55/220 ftrace: Protect direct_functions in ftrace_find_rec_direct

In reply to Greg Kroah-Hartman

6.12-stable review patch. If anyone has any objections, please let me know.

------------------

From: Leon Hwang <[email protected]>

commit 63444b7617c09aeed36282e061c3f80818f2b600 upstream.

Fix accessing the __rcu pointer direct_functions with RCU protection.

---
 kernel/trace/ftrace.c |    3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)
kernel/trace/ftrace.c +2 -1
@@ -2593,7 +2593,8 @@ unsigned long ftrace_find_rec_direct(uns
 {
 	struct ftrace_func_entry *entry;
 
-	entry = __ftrace_lookup_ip(direct_functions, ip);
+	guard(preempt_notrace)();
+	entry = __ftrace_lookup_ip(rcu_dereference_sched(direct_functions), ip);
 	if (!entry)
 		return 0;
 
author maintainer
#60 lore raw

PATCH 56/220 ftrace: Fix off-by-one fentry site disable in ftrace_free_mem()

In reply to Greg Kroah-Hartman

6.12-stable review patch. If anyone has any objections, please let me know.

------------------

From: Josh Poimboeuf <[email protected]>

commit 8b8292d6487c81bd57c2605a9b404b1cf8f1edfb upstream.

When a module's init text is freed, do_init_module() calls ftrace_free_mem() with a half-open [start, end) range. However the ftrace_cmp_recs() comparator treats the upper bound as inclusive, as all its other users do, passing 'ip + size - 1'. So ftrace_free_mem() can delete a record sitting exactly at 'end', which is outside the freed range.

For a kernel without CFI or IBT, the first record of a function is at the function start, which for the first function in a module is also the base of its text allocation. As the module allocator packs its regions, that address is often the 'end' passed by a neighboring module's do_init_module(), causing the first function's ftrace location to get disabled, preventing an attempt to livepatch it:

livepatch: failed to find location for function 'pcspkr_probe'

Convert the exclusive end to the inclusive 'end - 1' the comparator expects, and return early for an empty range to avoid the subtraction from underflowing when the init text size is zero.

---
 kernel/trace/ftrace.c |    6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)
kernel/trace/ftrace.c +5 -1
@@ -7770,7 +7770,8 @@ static void add_to_clear_hash_list(struc
 void ftrace_free_mem(struct module *mod, void *start_ptr, void *end_ptr)
 {
 	unsigned long start = (unsigned long)(start_ptr);
-	unsigned long end = (unsigned long)(end_ptr);
+	/* end is inclusive and end_ptr is exclusive */
+	unsigned long end = (unsigned long)(end_ptr) - 1;
 	struct ftrace_page **last_pg = &ftrace_pages_start;
 	struct ftrace_page *tmp_page = NULL;
 	struct ftrace_page *pg;
@@ -7780,6 +7781,9 @@ void ftrace_free_mem(struct module *mod,
 	struct ftrace_init_func *func, *func_next;
 	LIST_HEAD(clear_hash);
 
+	if (start_ptr >= end_ptr)
+		return;
+
 	key.ip = start;
 	key.flags = end;	/* overload flags, as it is unsigned long */
 
author maintainer
#61 lore raw

PATCH 57/220 openrisc: signal: do not restore privileged SR bits on sigreturn

In reply to Greg Kroah-Hartman

6.12-stable review patch. If anyone has any objections, please let me know.

------------------

From: Ali Ahmet Memis <[email protected]>

commit 32ef1b30ad736519f7a207bcc2986f3d4129d972 upstream.

restore_sigcontext() copies the whole supervision register (SR) from the signal frame and only clears SPR_SR_SM before the value is reloaded into the hardware SR (through ESR and l.rfe) on the return to user space. All other SR bits are left under user control.

An unprivileged task can thus return from a signal handler through a crafted sigframe that clears SPR_SR_DME. With the data MMU disabled the CPU performs no translation or protection on data accesses, so the task gains read and write access to arbitrary physical memory, a local privilege escalation. SPR_SR_IME, SPR_SR_SUMRA, SPR_SR_LEE, SPR_SR_EPH and the cache-enable bits are exposed the same way. The ptrace GPR regset already refuses any change to SR for exactly this reason.

Restore only the arithmetic flag bits (F, CY, OV) from the signal frame and take every privileged control bit from the SR the kernel saved on signal entry.

Verified with qemu-system-or1k -M or1k-sim: before this change an unprivileged PoC clears SPR_SR_DME in rt_sigreturn and writes a marker to physical address 0x03000000 (beyond the kernel's mem=32M); afterwards the same PoC receives SIGSEGV and physical memory is unchanged.

Fixes ac689eb7f9d4 ("OpenRISC: Signal handling")
---
 arch/openrisc/include/asm/processor.h |    2 ++
 arch/openrisc/kernel/signal.c         |    5 +++--
 2 files changed, 5 insertions(+), 2 deletions(-)
arch/openrisc/include/asm/processor.h +2 -0
@@ -26,6 +26,8 @@
 		   | SPR_SR_DCE | SPR_SR_SM)
 #define USER_SR   (SPR_SR_DME | SPR_SR_IME | SPR_SR_ICE \
 		   | SPR_SR_DCE | SPR_SR_IEE | SPR_SR_TEE)
+/* SR bits user space may change via sigreturn, the rest stay kernel owned */
+#define SPR_SR_USER_MASK  (SPR_SR_F | SPR_SR_CY | SPR_SR_OV)
 
 /*
  * User space process size. This is hardcoded into a few places,
arch/openrisc/kernel/signal.c +3 -2
@@ -74,6 +74,7 @@ static long save_fp_state(struct sigcont
 static int restore_sigcontext(struct pt_regs *regs,
 			      struct sigcontext __user *sc)
 {
+	unsigned long old_sr = regs->sr;
 	int err = 0;
 
 	/* Always make any pending restarted system calls return -EINTR */
@@ -89,8 +90,8 @@ static int restore_sigcontext(struct pt_
 	err |= __copy_from_user(&regs->sr, &sc->regs.sr, sizeof(unsigned long));
 	err |= restore_fp_state(sc);
 
-	/* make sure the SM-bit is cleared so user-mode cannot fool us */
-	regs->sr &= ~SPR_SR_SM;
+	/* keep the privileged SR bits kernel owned, restore only user flags */
+	regs->sr = (old_sr & ~SPR_SR_USER_MASK) | (regs->sr & SPR_SR_USER_MASK);
 
 	regs->orig_gpr11 = -1;	/* Avoid syscall restart checks */
 
author maintainer
#62 lore raw

PATCH 58/220 Input: sur40 - fix input device registration ordering

In reply to Greg Kroah-Hartman

6.12-stable review patch. If anyone has any objections, please let me know.

------------------

From: Dmitry Torokhov <[email protected]>

commit 9da976eb649c9e2f588a4499410e4d8af687925f upstream.

In sur40_probe(), input_register_device() was previously called early before the V4L2 video device and vb2_queue components were fully initialized. If userspace opened the input device immediately upon registration, sur40_open() would trigger and start the sur40_poll() worker thread. This worker thread invokes sur40_process_video() and accesses the uninitialized vb2_queue structure, leading to a data race and potential system crash.

Furthermore, if V4L2 or video registration failed after input_register_device() succeeded, the error path fell through to calling input_free_device() on a successfully registered device instead of input_unregister_device(), corrupting input core state.

Move input_register_device() to the very end of sur40_probe(). This ensures the V4L2 and video queue structures are fully initialized before polling can start, and naturally resolves the error path bug since input_free_device() is now only called when input registration has not yet occurred.

To maintain strict LIFO (Last-In, First-Out) teardown ordering, also move input_unregister_device() to the very beginning of sur40_disconnect(). This guarantees that the input polling worker thread is stopped before V4L2 video components or control handlers are unregistered.

---
 drivers/input/touchscreen/sur40.c |   21 +++++++++++----------
 1 file changed, 11 insertions(+), 10 deletions(-)
drivers/input/touchscreen/sur40.c +11 -10
@@ -725,21 +725,13 @@ static int sur40_probe(struct usb_interf
 		goto err_free_input;
 	}
 
-	/* register the polled input device */
-	error = input_register_device(input);
-	if (error) {
-		dev_err(&interface->dev,
-			"Unable to register polled input device.");
-		goto err_free_buffer;
-	}
-
 	/* register the video master device */
 	snprintf(sur40->v4l2.name, sizeof(sur40->v4l2.name), "%s", DRIVER_LONG);
 	error = v4l2_device_register(sur40->dev, &sur40->v4l2);
 	if (error) {
 		dev_err(&interface->dev,
 			"Unable to register video master device.");
-		goto err_unreg_v4l2;
+		goto err_free_buffer;
 	}
 
 	/* initialize the lock and subdevice */
@@ -798,6 +790,14 @@ static int sur40_probe(struct usb_interf
 		goto err_unreg_video;
 	}
 
+	/* register the polled input device */
+	error = input_register_device(input);
+	if (error) {
+		dev_err(&interface->dev,
+			"Unable to register polled input device.");
+		goto err_unreg_video;
+	}
+
 	/* we can register the device now, as it is ready */
 	usb_set_intfdata(interface, sur40);
 	dev_dbg(&interface->dev, "%s is now attached\n", DRIVER_DESC);
@@ -823,11 +823,12 @@ static void sur40_disconnect(struct usb_
 {
 	struct sur40_state *sur40 = usb_get_intfdata(interface);
 
+	input_unregister_device(sur40->input);
+
 	v4l2_ctrl_handler_free(&sur40->hdl);
 	video_unregister_device(&sur40->vdev);
 	v4l2_device_unregister(&sur40->v4l2);
 
-	input_unregister_device(sur40->input);
 	kfree(sur40->bulk_in_buffer);
 	kfree(sur40);
 
author maintainer
#63 lore raw

PATCH 59/220 Input: sur40 - fix V4L error path cleanup

In reply to Greg Kroah-Hartman

6.12-stable review patch. If anyone has any objections, please let me know.

------------------

From: Dmitry Torokhov <[email protected]>

commit 062dc4693e2c10d77de06f61e6f3faf37c0a8383 upstream.

In sur40_probe(), if video_register_device() fails, the error path jumps to err_unreg_video. This incorrectly attempts to unregister a video device that was never successfully registered, and fails to free the V4L2 control handler (v4l2_ctrl_handler_free) that was initialized immediately prior.

Fix this by introducing an err_free_ctrl label to properly free the V4L2 control handler and bypass video_unregister_device() when video device registration fails.

---
 drivers/input/touchscreen/sur40.c |    4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)
drivers/input/touchscreen/sur40.c +3 -1
@@ -787,7 +787,7 @@ static int sur40_probe(struct usb_interf
 	if (error) {
 		dev_err(&interface->dev,
 			"Unable to register video subdevice.");
-		goto err_unreg_video;
+		goto err_free_ctrl;
 	}
 
 	/* register the polled input device */
@@ -806,6 +806,8 @@ static int sur40_probe(struct usb_interf
 
 err_unreg_video:
 	video_unregister_device(&sur40->vdev);
+err_free_ctrl:
+	v4l2_ctrl_handler_free(&sur40->hdl);
 err_unreg_v4l2:
 	v4l2_device_unregister(&sur40->v4l2);
 err_free_buffer:
author maintainer
#64 lore raw

PATCH 60/220 libceph: Avoid using invalid osd indices from primary_temp

In reply to Greg Kroah-Hartman

6.12-stable review patch. If anyone has any objections, please let me know.

------------------

From: Raphael Zimmer <[email protected]>

commit 3660b98d1204b419f6a77e9a295f148dcf38d042 upstream.

A corrupted osdmap received from a Ceph monitor or OSD may contain osd indices in its pg_temp, primary_temp, pg_upmap, and pg_upmap_items parts that don't exist, i.e., that are greater than max_osd or smaller than CEPH_HOMELESS_OSD (-1). These indices are used to create the up and acting set in ceph_pg_to_up_acting_osds(), called from calc_target(). While most of these osd indices are checked, the one from primary_temp is not. Subsequently, this may lead to calc_target() returning this (potentially invalid) index as target osd for a (linger) request. Because the osd_state, osd_weight, and osd_addr arrays only contain max_osd entries (with indices 0 to max_osd -1), this leads to out-of-bounds accesses when trying to read values from these arrays.

This patch fixes the issue by adding a check to get_temp_osds(), so that only valid osd indices from primary_temp are used, and it falls back to using the primary from pg_temp or the up set if it is invalid.

[ idryomov: changelog ]

Fixes 5e8d4d36bf23 ("libceph: add support for primary_temp mappings")
---
 net/ceph/osdmap.c |    5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)
net/ceph/osdmap.c +3 -2
@@ -2811,9 +2811,10 @@ static void get_temp_osds(struct ceph_os
 		}
 	}
 
-	/* primary_temp? */
+	/* primary_temp? (shouldn't ever be a nonexistent or down OSD) */
 	pg = lookup_pg_mapping(&osdmap->primary_temp, pgid);
-	if (pg)
+	if (pg && !WARN_ON_ONCE(ceph_osd_is_down(osdmap,
+						 pg->primary_temp.osd)))
 		temp->primary = pg->primary_temp.osd;
 }
 
author maintainer
#65 lore raw

PATCH 61/220 ceph: fix MDS random selection readiness predicate

In reply to Greg Kroah-Hartman

6.12-stable review patch. If anyone has any objections, please let me know.

------------------

From: Yiming Zhu <[email protected]>

commit 2c11c4bfdb7bd2808b3b3ac228e1f2d9bcf25457 upstream.

CEPH_MDS_IS_READY() is parsed so that the ternary expression can return true for an MDS entry with state 0 when it is not laggy. This allows the random selector to choose a down/DNE rank.

Group the ternary expression under the state check so zero-state ranks are not treated as ready.

Fixes b38c9eb4757d ("ceph: add possible_max_rank and make the code more readable")
---
 fs/ceph/mdsmap.c |    2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
fs/ceph/mdsmap.c +1 -1
@@ -15,7 +15,7 @@
 #include "super.h"
 
 #define CEPH_MDS_IS_READY(i, ignore_laggy) \
-	(m->m_info[i].state > 0 && ignore_laggy ? true : !m->m_info[i].laggy)
+	(m->m_info[i].state > 0 && (ignore_laggy ? true : !m->m_info[i].laggy))
 
 static int __mdsmap_get_random_mds(struct ceph_mdsmap *m, bool ignore_laggy)
 {
author maintainer
#66 lore raw

PATCH 62/220 libceph: tolerate addrvecs with multiple entries of the same type

In reply to Greg Kroah-Hartman

6.12-stable review patch. If anyone has any objections, please let me know.

------------------

From: Kefu Chai <[email protected]>

commit 5a87925539acecfe88229bad76ab81bd75a7e3f5 upstream.

ceph_decode_entity_addrvec() rejects any addrvec containing more than one entry that matches the requested msgr type (LEGACY or MSGR2), logging "another match of type N in addrvec" and returning -EINVAL.

Some admin tooling (e.g. pveceph mon create from Proxmox VE) generates addrvecs with multiple same-type entries when public_network lists more than one CIDR: it picks one local IP per subnet and emits both a v2 and a v1 entry for each IP. Monmaps shaped this way cause:

libceph: mon0 (1)10.10.10.15:6789 session established
libceph: another match of type 1 in addrvec
libceph: problem decoding monmap, -22

No Ceph code uses the extra entries: since Nautilus, the userspace messenger (AsyncMessenger) unconditionally picks the first address of the requested type and ignores any subsequent matches.

Match that behavior: use the first matching entry and silently skip any subsequent ones. This is a compatibility fix for existing deployments and does not enable dual-stack or multi-subnet address selection.

[ idryomov: tweak ceph_decode_entity_addrvec() comment ]

Fixes a5cbd5fc22d5 ("libceph, ceph: get and handle cluster maps with addrvecs")
---
 net/ceph/decode.c |   18 +++++++++---------
 1 file changed, 9 insertions(+), 9 deletions(-)
net/ceph/decode.c +9 -9
@@ -87,8 +87,9 @@ bad:
 EXPORT_SYMBOL(ceph_decode_entity_addr);
 
 /*
- * Return addr of desired type (MSGR2 or LEGACY) or error.
- * Make sure there is only one match.
+ * Return addr of desired type (MSGR2 or LEGACY) or error.  In case of
+ * multiple matches, use the first one for compatibility with userspace
+ * messenger.
  *
  * Assume encoding with MSG_ADDR2.
  */
@@ -121,14 +122,13 @@ int ceph_decode_entity_addrvec(void **p,
 
 		dout("%s i %d addr %s\n", __func__, i, ceph_pr_addr(&tmp_addr));
 		if (tmp_addr.type == my_type) {
-			if (found) {
-				pr_err("another match of type %d in addrvec\n",
-				       le32_to_cpu(my_type));
-				return -EINVAL;
+			if (!found) {
+				memcpy(addr, &tmp_addr, sizeof(*addr));
+				found = true;
+			} else {
+				dout("%s skipping extra match of type %d in addrvec\n",
+				     __func__, le32_to_cpu(my_type));
 			}
-
-			memcpy(addr, &tmp_addr, sizeof(*addr));
-			found = true;
 		}
 	}
 
author maintainer
#67 lore raw

PATCH 63/220 mmc: omap_hsmmc: fix busy_timeout overflow in ns conversion on 32-bit

In reply to Greg Kroah-Hartman

6.12-stable review patch. If anyone has any objections, please let me know.

------------------

From: Zhan Xusheng <[email protected]>

commit f64ea900e4bda3055ef24a2c906f8d049cf1c3bd upstream.

omap_hsmmc_prepare_data() converts the command busy timeout to nanoseconds with:

	timeout = req->cmd->busy_timeout * NSEC_PER_MSEC;

busy_timeout is an unsigned int (milliseconds) and timeout is a u64, but NSEC_PER_MSEC is 1000000L. On 32-bit builds the multiplication is performed in 32-bit arithmetic and wraps for busy_timeout values above ~4294 ms, before the result is assigned to the u64.

The driver does not set mmc->max_busy_timeout, so the core does not cap the busy timeout, and commands such as erase or SANITIZE (MMC_SANITIZE_TIMEOUT_MS is 240000 ms) can pass a busy_timeout far larger than 4294 ms. The wrapped, much smaller ns value is then programmed via set_data_timeout(), so the data timeout is set too short and the operation can time out prematurely.

Cast busy_timeout to u64 before the multiplication so the conversion is done in 64-bit arithmetic.

Fixes 8cc9a3e73de1 ("mmc: host: omap_hsmmc: use generic_cmd6_time to program timeout value for CMD6")
---
 drivers/mmc/host/omap_hsmmc.c |    2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
drivers/mmc/host/omap_hsmmc.c +1 -1
@@ -1358,7 +1358,7 @@ omap_hsmmc_prepare_data(struct omap_hsmm
 	if (req->data == NULL) {
 		OMAP_HSMMC_WRITE(host->base, BLK, 0);
 		if (req->cmd->flags & MMC_RSP_BUSY) {
-			timeout = req->cmd->busy_timeout * NSEC_PER_MSEC;
+			timeout = (u64)req->cmd->busy_timeout * NSEC_PER_MSEC;
 
 			/*
 			 * Set an arbitrary 100ms data timeout for commands with
author maintainer
#68 lore raw

PATCH 64/220 mmc: sdhci: unmap the bounce buffer before device release

In reply to Greg Kroah-Hartman

6.12-stable review patch. If anyone has any objections, please let me know.

------------------

From: Myeonghun Pak <[email protected]>

commit 9e9f561269dff35e6f84ed21776ec37fd6360b03 upstream.

sdhci_allocate_bounce_buffer() allocates its buffer with devm_kmalloc() but maps it with dma_map_single(). The buffer is therefore released by devres without the streaming DMA mapping being unmapped.

Register a managed action after dma_map_single() succeeds so the mapping is removed before devres releases the buffer. The action is registered only for buffers allocated and mapped by the SDHCI core, leaving buffers provided by host drivers under their existing ownership.

Fixes bd9b902798ab ("mmc: sdhci: Implement an SDHCI-specific bounce buffer")
---
 drivers/mmc/host/sdhci.c |   16 ++++++++++++++++
 1 file changed, 16 insertions(+)
drivers/mmc/host/sdhci.c +16 -0
@@ -4134,6 +4134,14 @@ void __sdhci_read_caps(struct sdhci_host
 }
 EXPORT_SYMBOL_GPL(__sdhci_read_caps);
 
+static void sdhci_unmap_bounce_buffer(void *data)
+{
+	struct sdhci_host *host = data;
+
+	dma_unmap_single(mmc_dev(host->mmc), host->bounce_addr,
+			 host->bounce_buffer_size, DMA_BIDIRECTIONAL);
+}
+
 static void sdhci_allocate_bounce_buffer(struct sdhci_host *host)
 {
 	struct mmc_host *mmc = host->mmc;
@@ -4188,6 +4196,14 @@ static void sdhci_allocate_bounce_buffer
 	}
 
 	host->bounce_buffer_size = bounce_size;
+	ret = devm_add_action_or_reset(mmc_dev(mmc),
+				       sdhci_unmap_bounce_buffer, host);
+	if (ret) {
+		devm_kfree(mmc_dev(mmc), host->bounce_buffer);
+		host->bounce_buffer = NULL;
+		host->bounce_buffer_size = 0;
+		return;
+	}
 
 	/* Lie about this since we're bouncing */
 	mmc->max_segs = max_blocks;
author maintainer
#69 lore raw

PATCH 65/220 mmc: sdhci: make tuning_err a signed int

In reply to Greg Kroah-Hartman

6.12-stable review patch. If anyone has any objections, please let me know.

------------------

From: Haibo Chen <[email protected]>

commit ae31bcc92bb42502bb7c9029e6dc7a824cf6cd14 upstream.

Coverity report INTEGER_OVERFLOW for host->tuning_err.
The tuning_err field in struct sdhci_host is used to store an error code for re-tuning, but it was declared as unsigned int. Several call sites store negative error codes into it and later compare against negative values:

- sdhci.c, sdhci-of-dwcmshc.c and sdhci-pci-gli.c assign it the

    return value of __sdhci_execute_tuning()/__sdhci_execute_tuning_9750(),
    both of which return a signed int (possibly a negative errno);
  - sdhci-of-esdhc.c assigns host->tuning_err = -EAGAIN and later does
    "ret = host->tuning_err; if (ret == -EAGAIN ...)";

- sdhci-of-dwcmshc.c prints it with the %d (signed) conversion.

Storing a negative errno in an unsigned int and reading it back as a signed int only happens to work because of two's-complement, same-width integer conversions. It is misleading and triggers sign-conversion warnings. All users treat the value either as a signed error code or as a boolean (zero / non-zero), so changing the type to a signed int is safe and makes the intent explicit.

Fixes 7d8bb1f46e13 ("mmc: sdhci: add tuning error codes")
Assisted-by Cline:claude-sonnet [read_file, search_files, git]
---
 drivers/mmc/host/sdhci.h |    2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
drivers/mmc/host/sdhci.h +1 -1
@@ -604,7 +604,7 @@ struct sdhci_host {
 
 	unsigned int		tuning_count;	/* Timer count for re-tuning */
 	unsigned int		tuning_mode;	/* Re-tuning mode supported by host */
-	unsigned int		tuning_err;	/* Error code for re-tuning */
+	int			tuning_err;	/* Error code for re-tuning */
 #define SDHCI_TUNING_MODE_1	0
 #define SDHCI_TUNING_MODE_2	1
 #define SDHCI_TUNING_MODE_3	2
author maintainer
#70 lore raw

PATCH 66/220 mmc: atmel-mci: Fix use-after-free in atmci_remove due to race condition

In reply to Greg Kroah-Hartman

6.12-stable review patch. If anyone has any objections, please let me know.

------------------

From: Pei Xiao <[email protected]>

commit c125ee35a49a0518521b52b27631eef061b8719a upstream.

In atmci_probe, &host->bh_work is bound with atmci_work_func, and atmci_interrupt, atmci_timeout_timer and atmci_dma_complete can all queue this work on system_bh_wq.

If we remove the module, atmci_remove makes cleanup and the memory allocated for host with devm_kzalloc() is released after the remove callback returns, while the work mentioned above may still be pending or running. The sequence of operations that may lead to a UAF bug is as follows:

CPU0 CPU1

                                          | atmci_interrupt
                                          | queue_work(system_bh_wq,
                                          |            &host->bh_work)
atmci_remove                              |
atmci_cleanup_slot(...)                   |
atmci_writel(host, ATMCI_IDR, ~0UL)       |
timer_delete_sync(&host->timer)           |
dma_release_channel(host->dma.chan)       |
free_irq(platform_get_irq(pdev, 0), host) |
                                          | atmci_work_func
                                          | // use host
// devm resources released after          |
// remove returns, host is freed          |
                                          | // use host (use-after-free)

Fix it by canceling the work after all the sources that can schedule it (IRQ handler, timeout timer and DMA completion callback) have been stopped, and before proceeding with the remaining cleanup in atmci_remove.

Fixes 7d2be0749a59 ("atmel-mci: Driver for Atmel on-chip MMC controllers")
Assisted-by Codex:deepseek-v4-flash
---
 drivers/mmc/host/atmel-mci.c |    2 ++
 1 file changed, 2 insertions(+)
drivers/mmc/host/atmel-mci.c +2 -0
@@ -2619,6 +2619,8 @@ static void atmci_remove(struct platform
 
 	free_irq(platform_get_irq(pdev, 0), host);
 
+	cancel_work_sync(&host->bh_work);
+
 	clk_disable_unprepare(host->mck);
 
 	pm_runtime_disable(dev);
author maintainer
#71 lore raw

PATCH 67/220 drm/connector/hdmi: Fix out of bounds memory read

In reply to Greg Kroah-Hartman

6.12-stable review patch. If anyone has any objections, please let me know.

------------------

From: John Harrison <[email protected]>

commit 9ecf8ba763d0ffe0673538eb4bf7806f20455d19 upstream.

A helper function was copying a given audio infoframe into the connector's copy but using the size of the destination (a generic target, sized to accept many different data blocks) not the source (a very specific type of data block). Thus, it was copying 60 bytes of data from a 28 byte allocation.

Fix that by using the source size instead, together with a build bug on the source size actually being smaller than the destination.

I hit this running KUnit tests under KASAN (while debugging something else entirely). In the real world, it seems unlikely to cause an actual problem. It is a read not a write so it can't corrupt any memory. However, it could potentially fall off the end of a page and cause an accvio bug.

Fixes f378b77227bc ("drm/connector: hdmi: Add Infoframes generation")
Cc Ville Syrjälä <[email protected]>
Cc Dmitry Baryshkov <[email protected]>
Cc Maxime Ripard <[email protected]>
Cc Maarten Lankhorst <[email protected]>
Cc Thomas Zimmermann <[email protected]>
Cc David Airlie <[email protected]>
Cc Simona Vetter <[email protected]>
Cc Dmitry Baryshkov <[email protected]>
Cc Daniel Stone <[email protected]>
Cc Nicolas Frattaroli <[email protected]>
Cc Jani Nikula <[email protected]>
Cc José Expósito <[email protected]>
Cc Laurent Pinchart <[email protected]>
Cc [email protected] # v6.11+
---
 drivers/gpu/drm/display/drm_hdmi_state_helper.c |    3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)
drivers/gpu/drm/display/drm_hdmi_state_helper.c +2 -1
@@ -710,7 +710,8 @@ drm_atomic_helper_connector_hdmi_update_
 
 	mutex_lock(&connector->hdmi.infoframes.lock);
 
-	memcpy(&infoframe->data, frame, sizeof(infoframe->data));
+	BUILD_BUG_ON(sizeof(*frame) > sizeof(infoframe->data));
+	memcpy(&infoframe->data, frame, sizeof(*frame));
 	infoframe->set = true;
 
 	ret = write_infoframe(connector, infoframe);
author maintainer
#72 lore raw

PATCH 68/220 drm/xe: Order ring writes before ring tail updates

In reply to Greg Kroah-Hartman

6.12-stable review patch. If anyone has any objections, please let me know.

------------------

From: Matthew Brost <[email protected]>

commit 9f83c94469ff0fa37274b873ba24922e02531fa7 upstream.

The ring buffer and the LRC context image are both mapped WC, so the ring tail update can become visible to the device before the ring contents it is meant to publish.

The GuC CT send does contain an xe_device_wmb(), so sending the H2G would flush the ring contents. The problem is that it comes too late:
xe_lrc_set_ring_tail() publishes the tail before the H2G is sent, and the device samples the tail from the context image independently of it, either at context switch-in or while the context is already resident. A submitter which is interrupted between updating the tail and sending its H2G therefore leaves the device free to observe the new tail while the ring contents behind it are not yet visible:

1. Thread A emits a job into the ring, sets the tail to T_A and sends the H2G, which flushes A's ring contents. The GuC starts scheduling the context in, but it is not executing yet.
2. Thread B emits a job into ring[T_A..T_B]. Those writes are not yet visible to the device.
3. Thread B updates the ring tail to T_B. That write targets a different page and becomes visible first.
4. Thread B is interrupted before it sends its H2G, so the flush which would have published ring[T_A..T_B] has not happened yet.
5. The context is switched in and samples the ring tail from the context image, picking up T_B rather than T_A.
6. The GPU executes A's job, advances HEAD to T_A, and continues on to ring[T_A..T_B], which still holds the previous wrap's contents, so the CS parses stale commands.

The result is command stream corruption, which typically manifests as a hang or a spurious pagefault rather than anything that points back at the submission path.

Kernel jobs are by far the most likely to hit this. Kernel queues such as the migration queue are shared and can be driven by many threads concurrently, producing back-to-back submissions on an LRC which is already executing. User queues are typically tied to a single submitting thread, so the same interleaving is much harder to produce.

Add an xe_device_wmb() at the end of xe_lrc_write_ring() so that it covers every ring tail publication site, and so the invariant is local: once xe_lrc_write_ring() returns, the ring contents are visible to the device.

Fixes dd08ebf6c352 ("drm/xe: Introduce a new DRM driver for Intel GPUs")
Assisted-by GitHub_Copilot:claude-opus-5
(cherry picked from commit 136360290f314890428a3fbf31aaa8e4f1d43567)
---
 drivers/gpu/drm/xe/xe_lrc.c |    7 +++++++
 1 file changed, 7 insertions(+)
drivers/gpu/drm/xe/xe_lrc.c +7 -0
@@ -1139,6 +1139,13 @@ void xe_lrc_write_ring(struct xe_lrc *lr
 
 		__xe_lrc_write_ring(lrc, ring, &noop, sizeof(noop));
 	}
+
+	/*
+	 * The ring and the LRC context image are both WC, so the ring tail
+	 * update which publishes these writes can become visible to the device
+	 * first. Ensure the ring contents are visible before returning.
+	 */
+	xe_device_wmb(xe);
 }
 
 u64 xe_lrc_descriptor(struct xe_lrc *lrc)
author maintainer
#73 lore raw

PATCH 69/220 drm/radeon: fix autosuspend cleanup during teardown

In reply to Greg Kroah-Hartman

6.12-stable review patch. If anyone has any objections, please let me know.

------------------

From: Guangshuo Li <[email protected]>

commit 587be7a17358ef8c0106775fcedae5a7bef50735 upstream.

radeon_driver_load_kms() calls pm_runtime_use_autosuspend() for PX devices, but radeon_driver_unload_kms() does not call the matching pm_runtime_dont_use_autosuspend() during teardown.

If the autosuspend delay is set to a negative value while autosuspend is enabled, the runtime PM core increments usage_count to prevent runtime suspend. Without calling pm_runtime_dont_use_autosuspend() during teardown, this reference is not dropped.

The documentation for pm_runtime_use_autosuspend() also notes that it is important to undo it with pm_runtime_dont_use_autosuspend() at driver exit time, unless runtime PM was initially enabled with devm_pm_runtime_enable().

Add the missing pm_runtime_dont_use_autosuspend() call to the driver unload path.

This issue was found by manual code inspection.

Fixes 10ebc0bc0934 ("drm/radeon: add runtime PM support (v2)")
(cherry picked from commit 0fdc1ff82ea14844c22795e9e0813c3ca03235e1)
---
 drivers/gpu/drm/radeon/radeon_kms.c |    1 +
 1 file changed, 1 insertion(+)
drivers/gpu/drm/radeon/radeon_kms.c +1 -0
@@ -71,6 +71,7 @@ void radeon_driver_unload_kms(struct drm
 	if (radeon_is_px(dev)) {
 		pm_runtime_get_sync(dev->dev);
 		pm_runtime_forbid(dev->dev);
+		pm_runtime_dont_use_autosuspend(dev->dev);
 	}
 
 	radeon_acpi_fini(rdev);
author maintainer
#74 lore raw

PATCH 70/220 s390/vfio_ccw: Free all memory if cp_init() fails

In reply to Greg Kroah-Hartman

6.12-stable review patch. If anyone has any objections, please let me know.

------------------

From: Eric Farman <[email protected]>

commit 74186c2968f8f756ac3226b545b598457c910c75 upstream.

The routine cp_free() is called to unpin/free any memory once an I/O is completed successfully, or if cp_prefetch() fails. But if cp_init() fails, and cp->initialized is not enabled, the same routine cannot be used to free all the memory.

An attempt to address this exists in ccwchain_handle_ccw(), where a single call to ccwchain_free() is made for the currently-processed CCW segment. But this will leak other segments (created as a result of a Transfer in Channel) that had been allocated as part of the same channel program.

Address this by performing the cleanup outside of the recursive ccwchain_handle_ccw()/ccwchain_loop_tic() logic.

Fixes 8b515be512a2 ("vfio-ccw: Fix memory leak and don't call cp_free in cp_init")
---
 drivers/s390/cio/vfio_ccw_cp.c |   22 ++++++++++++++++++----
 1 file changed, 18 insertions(+), 4 deletions(-)
drivers/s390/cio/vfio_ccw_cp.c +18 -4
@@ -455,9 +455,6 @@ static int ccwchain_handle_ccw(dma32_t c
 	/* Loop for tics on this new chain. */
 	ret = ccwchain_loop_tic(chain, cp);
 
-	if (ret)
-		ccwchain_free(chain);
-
 	return ret;
 }
 
@@ -486,6 +483,23 @@ static int ccwchain_loop_tic(struct ccwc
 	return 0;
 }
 
+static int ccwchain_build_ccws(dma32_t cda, struct channel_program *cp)
+{
+	struct ccwchain *chain, *temp;
+	int ret;
+
+	ret = ccwchain_handle_ccw(cda, cp);
+
+	if (ret) {
+		/* Cleanup if an error occurred */
+		list_for_each_entry_safe(chain, temp, &cp->ccwchain_list, next) {
+			ccwchain_free(chain);
+		}
+	}
+
+	return ret;
+}
+
 static int ccwchain_fetch_tic(struct ccw1 *ccw,
 			      struct channel_program *cp)
 {
@@ -735,7 +749,7 @@ int cp_init(struct channel_program *cp,
 	memcpy(&cp->orb, orb, sizeof(*orb));
 
 	/* Build a ccwchain for the first CCW segment */
-	ret = ccwchain_handle_ccw(orb->cmd.cpa, cp);
+	ret = ccwchain_build_ccws(orb->cmd.cpa, cp);
 
 	if (!ret)
 		cp->initialized = true;
author maintainer
#75 lore raw

PATCH 71/220 s390/vfio_ccw: Limit the number of channel program segments

In reply to Greg Kroah-Hartman

6.12-stable review patch. If anyone has any objections, please let me know.

------------------

From: Eric Farman <[email protected]>

commit 5405c90d6a47b3014e74ee0618a162449abbbc93 upstream.

The processing of channel programs, and the CCWs within them, is done recursively. As such, there is an arbitrary (but not architectural) limit to the number of CCWs that can exist in a single channel program.

The vfio-ccw logic breaks these channel programs into segments whenever it encounters a Transfer-In-Channel (TIC) CCW, and the combined number of segments count towards the global limit. Impose an equivalent limit to the number of segments until such logic can be made non-recursive.

Fixes 0a19e61e6d4c ("vfio: ccw: introduce channel program interfaces")
---
 drivers/s390/cio/vfio_ccw_cp.c |    6 ++++++
 drivers/s390/cio/vfio_ccw_cp.h |    8 ++++++++
 2 files changed, 14 insertions(+)
drivers/s390/cio/vfio_ccw_cp.c +6 -0
@@ -332,6 +332,7 @@ static struct ccwchain *ccwchain_alloc(s
 		goto out_err;
 
 	list_add_tail(&chain->next, &cp->ccwchain_list);
+	cp->ccwchain_count++;
 
 	return chain;
 
@@ -441,6 +442,10 @@ static int ccwchain_handle_ccw(dma32_t c
 	if (len < 0)
 		return len;
 
+	/* Limit number of chains in a single channel program */
+	if (cp->ccwchain_count >= CCWCHAIN_COUNT_MAX)
+		return -EINVAL;
+
 	/* Need alloc a new chain for this one. */
 	chain = ccwchain_alloc(cp, len);
 	if (!chain)
@@ -745,6 +750,7 @@ int cp_init(struct channel_program *cp,
 			vdev->dev,
 			"Prefetching channel program even though prefetch not specified in ORB");
 
+	cp->ccwchain_count = 0;
 	INIT_LIST_HEAD(&cp->ccwchain_list);
 	memcpy(&cp->orb, orb, sizeof(*orb));
 
drivers/s390/cio/vfio_ccw_cp.h +8 -0
@@ -23,11 +23,18 @@
  */
 #define CCWCHAIN_LEN_MAX	256
 
+/*
+ * Maximum number of chains
+ */
+#define CCWCHAIN_COUNT_MAX	16
+
 /**
  * struct channel_program - manage information for channel program
  * @ccwchain_list: list head of ccwchains
  * @orb: orb for the currently processed ssch request
  * @initialized: whether this instance is actually initialized
+ * @guest_cp: copy of guest channel program
+ * @ccwchain_count: number of channel program segments (linked by TIC)
  *
  * @ccwchain_list is the head of a ccwchain list, that contents the
  * translated result of the guest channel program that pointed out by
@@ -38,6 +45,7 @@ struct channel_program {
 	union orb orb;
 	bool initialized;
 	struct ccw1 *guest_cp;
+	unsigned int ccwchain_count;
 };
 
 int cp_init(struct channel_program *cp, union orb *orb);
author maintainer
#76 lore raw

PATCH 72/220 s390/vfio_ccw: Cancel existing workqueues

In reply to Greg Kroah-Hartman

6.12-stable review patch. If anyone has any objections, please let me know.

------------------

From: Eric Farman <[email protected]>

commit 79c60b2c61105368dcc8444eb45847e21734f7c4 upstream.

The initialization of the io_work and crw_work workqueues begs the question of whether they should be un-initialized. Add the corresponding cleanup tags in _release_dev to ensure work isn't dispatched after the private struct is free'd.

Fixes e5f84dbaea59 ("vfio: ccw: return I/O results asynchronously")
Fixes 3f02cb2fd9d2 ("vfio-ccw: Wire up the CRW irq and CRW region")
---
 drivers/s390/cio/vfio_ccw_ops.c |   15 +++++++++++++++
 1 file changed, 15 insertions(+)
drivers/s390/cio/vfio_ccw_ops.c +15 -0
@@ -132,6 +132,13 @@ static void vfio_ccw_mdev_release_dev(st
 		container_of(vdev, struct vfio_ccw_private, vdev);
 	struct vfio_ccw_crw *crw, *temp;
 
+	/*
+	 * Ensure these work items are fully drained, so none can
+	 * fire after being released.
+	 */
+	cancel_work_sync(&private->io_work);
+	cancel_work_sync(&private->crw_work);
+
 	list_for_each_entry_safe(crw, temp, &private->crw, next) {
 		list_del(&crw->next);
 		kfree(crw);
@@ -203,6 +210,14 @@ static void vfio_ccw_mdev_close_device(s
 		container_of(vdev, struct vfio_ccw_private, vdev);
 
 	vfio_ccw_fsm_event(private, VFIO_CCW_EVENT_CLOSE);
+
+	/*
+	 * Ensure these work items are drained, in the event the
+	 * device is re-opened instead of released.
+	 */
+	cancel_work_sync(&private->io_work);
+	cancel_work_sync(&private->crw_work);
+
 	vfio_ccw_unregister_dev_regions(private);
 }
 
author maintainer
#77 lore raw

PATCH 73/220 s390/vfio_ccw: Ensure index for read/write regions are within range

In reply to Greg Kroah-Hartman

6.12-stable review patch. If anyone has any objections, please let me know.

------------------

From: Eric Farman <[email protected]>

commit 9f5f9a78fedc45bc29d6a0a64e3a3472361afae5 upstream.

The introduction of the capability chain rightly clamped the region indexes to the range of the capabilities itself, but neglected to do so for the existing read/write regions which should also be enforced.

Fixes db8e5d17ac03 ("vfio-ccw: add capabilities chain")
Cc Cornelia Huck <[email protected]>
---
 drivers/s390/cio/vfio_ccw_async.c |   16 ++++++++++++++++
 drivers/s390/cio/vfio_ccw_chp.c   |   15 +++++++++++++++
 drivers/s390/cio/vfio_ccw_ops.c   |    7 +++----
 3 files changed, 34 insertions(+), 4 deletions(-)
drivers/s390/cio/vfio_ccw_async.c +16 -0
@@ -8,6 +8,7 @@
  */
 
 #include <linux/vfio.h>
+#include <linux/nospec.h>
 
 #include "vfio_ccw_private.h"
 
@@ -24,11 +25,20 @@ static ssize_t vfio_ccw_async_region_rea
 		return -EINVAL;
 
 	mutex_lock(&private->io_mutex);
+
+	if (i >= private->num_regions) {
+		ret = -EINVAL;
+		goto out_unlock;
+	}
+
+	i = array_index_nospec(i, private->num_regions);
 	region = private->region[i].data;
 	if (copy_to_user(buf, (void *)region + pos, count))
 		ret = -EFAULT;
 	else
 		ret = count;
+
+out_unlock:
 	mutex_unlock(&private->io_mutex);
 	return ret;
 }
@@ -48,6 +58,12 @@ static ssize_t vfio_ccw_async_region_wri
 	if (!mutex_trylock(&private->io_mutex))
 		return -EAGAIN;
 
+	if (i >= private->num_regions) {
+		ret = -EINVAL;
+		goto out_unlock;
+	}
+
+	i = array_index_nospec(i, private->num_regions);
 	region = private->region[i].data;
 	if (copy_from_user((void *)region + pos, buf, count)) {
 		ret = -EFAULT;
drivers/s390/cio/vfio_ccw_chp.c +15 -0
@@ -9,6 +9,7 @@
  */
 
 #include <linux/slab.h>
+#include <linux/nospec.h>
 #include <linux/vfio.h>
 #include "vfio_ccw_private.h"
 
@@ -26,6 +27,13 @@ static ssize_t vfio_ccw_schib_region_rea
 		return -EINVAL;
 
 	mutex_lock(&private->io_mutex);
+
+	if (i >= private->num_regions) {
+		ret = -EINVAL;
+		goto out;
+	}
+
+	i = array_index_nospec(i, private->num_regions);
 	region = private->region[i].data;
 
 	if (cio_update_schib(sch)) {
@@ -97,6 +105,12 @@ static ssize_t vfio_ccw_crw_region_read(
 		list_del(&crw->next);
 
 	mutex_lock(&private->io_mutex);
+	if (i >= private->num_regions) {
+		ret = -EINVAL;
+		goto out;
+	}
+
+	i = array_index_nospec(i, private->num_regions);
 	region = private->region[i].data;
 
 	if (crw)
@@ -109,6 +123,7 @@ static ssize_t vfio_ccw_crw_region_read(
 
 	region->crw = 0;
 
+out:
 	mutex_unlock(&private->io_mutex);
 
 	kfree(crw);
drivers/s390/cio/vfio_ccw_ops.c +3 -4
@@ -259,6 +259,7 @@ static ssize_t vfio_ccw_mdev_read(struct
 		return vfio_ccw_mdev_read_io_region(private, buf, count, ppos);
 	default:
 		index -= VFIO_CCW_NUM_REGIONS;
+		index = array_index_nospec(index, private->num_regions);
 		return private->region[index].ops->read(private, buf, count,
 							ppos);
 	}
@@ -311,6 +312,7 @@ static ssize_t vfio_ccw_mdev_write(struc
 		return vfio_ccw_mdev_write_io_region(private, buf, count, ppos);
 	default:
 		index -= VFIO_CCW_NUM_REGIONS;
+		index = array_index_nospec(index, private->num_regions);
 		return private->region[index].ops->write(private, buf, count,
 							 ppos);
 	}
@@ -353,11 +355,8 @@ static int vfio_ccw_mdev_get_region_info
 		    VFIO_CCW_NUM_REGIONS + private->num_regions)
 			return -EINVAL;
 
-		info->index = array_index_nospec(info->index,
-						 VFIO_CCW_NUM_REGIONS +
-						 private->num_regions);
-
 		i = info->index - VFIO_CCW_NUM_REGIONS;
+		i = array_index_nospec(i, private->num_regions);
 
 		info->offset = VFIO_CCW_INDEX_TO_OFFSET(info->index);
 		info->size = private->region[i].size;
author maintainer
#78 lore raw

PATCH 74/220 s390/vfio_ccw: Ensure first IDAW remains constant

In reply to Greg Kroah-Hartman

6.12-stable review patch. If anyone has any objections, please let me know.

------------------

From: Eric Farman <[email protected]>

commit 565bef268d75bf7df665bce6923a88cd0eb74592 upstream.

The first IDAW in a list does not need to be on a 2K/4K boundary like all others, and so is read separately to accurately calculate the size of the buffer needed to read the full IDAL.

Verify that the address found in the first IDAW is unchanged between reads, to ensure a consistent set of IDAWs being worked with.

Fixes 01aa26c672c0 ("s390/cio: Combine direct and indirect CCW paths")
---
 drivers/s390/cio/vfio_ccw_cp.c |   16 ++++++++++++++++
 drivers/s390/cio/vfio_ccw_cp.h |    2 ++
 2 files changed, 18 insertions(+)
drivers/s390/cio/vfio_ccw_cp.c +16 -0
@@ -530,6 +530,7 @@ static dma64_t *get_guest_idal(struct cc
 		&container_of(cp, struct vfio_ccw_private, cp)->vdev;
 	dma64_t *idaws;
 	dma32_t *idaws_f1;
+	u64 first_idaw;
 	int idal_len = idaw_nr * sizeof(*idaws);
 	int idaw_size = idal_is_2k(cp) ? PAGE_SIZE / 2 : PAGE_SIZE;
 	int idaw_mask = ~(idaw_size - 1);
@@ -546,6 +547,18 @@ static dma64_t *get_guest_idal(struct cc
 			kfree(idaws);
 			return ERR_PTR(ret);
 		}
+
+		idaws_f1 = (dma32_t *)idaws;
+		if (cp->orb.cmd.c64)
+			first_idaw = dma64_to_u64(idaws[0]);
+		else
+			first_idaw = dma32_to_u32(idaws_f1[0]);
+
+		/* Unexpected mismatch from earlier read */
+		if (first_idaw != cp->guest_iova) {
+			kfree(idaws);
+			return ERR_PTR(-EINVAL);
+		}
 	} else {
 		/* Fabricate an IDAL based off CCW data address */
 		if (cp->orb.cmd.c64) {
@@ -611,6 +624,9 @@ static int ccw_count_idaws(struct ccw1 *
 		iova = dma32_to_u32(ccw->cda);
 	}
 
+	/* Save the read address for later */
+	cp->guest_iova = iova;
+
 	/* Format-1 IDAWs operate on 2K each */
 	if (!cp->orb.cmd.c64)
 		return idal_2k_nr_words((void *)iova, bytes);
drivers/s390/cio/vfio_ccw_cp.h +2 -0
@@ -35,6 +35,7 @@
  * @initialized: whether this instance is actually initialized
  * @guest_cp: copy of guest channel program
  * @ccwchain_count: number of channel program segments (linked by TIC)
+ * @guest_iova: first data address of a guest channel program
  *
  * @ccwchain_list is the head of a ccwchain list, that contents the
  * translated result of the guest channel program that pointed out by
@@ -46,6 +47,7 @@ struct channel_program {
 	bool initialized;
 	struct ccw1 *guest_cp;
 	unsigned int ccwchain_count;
+	u64 guest_iova;
 };
 
 int cp_init(struct channel_program *cp, union orb *orb);
author maintainer
#79 lore raw

PATCH 75/220 s390/vfio_ccw: Fix out of bounds check on CCW array

In reply to Greg Kroah-Hartman

6.12-stable review patch. If anyone has any objections, please let me know.

------------------

From: Eric Farman <[email protected]>

commit a005b7f1a491ffda61bff0fd0f6548f8986fb977 upstream.

The routine ccwchain_calc_length() counts the number of channel command words (CCWs) that are chained together in a single channel program, and rejects anything larger than CCWCHAIN_LEN_MAX (256) CCWs.

The loop itself is "do..while (count < 257)", and while the logic in is_cpa_within_range() correctly adjusts between the 0-index array of CCWs and the count of CCWs starting at 1, this means it would look at a possible 257th CCW before ending the loop and (correctly) returning an error.

Fix this by restructuring the loop to break as soon as 256 CCWs (thus indexes 0-255) are examined, without looking at memory outside the range.

Fixes 0a19e61e6d4c ("vfio: ccw: introduce channel program interfaces")
---
 drivers/s390/cio/vfio_ccw_cp.c |   17 +++++------------
 1 file changed, 5 insertions(+), 12 deletions(-)
drivers/s390/cio/vfio_ccw_cp.c +5 -12
@@ -377,11 +377,9 @@ static void ccwchain_cda_free(struct ccw
 static int ccwchain_calc_length(u64 iova, struct channel_program *cp)
 {
 	struct ccw1 *ccw = cp->guest_cp;
-	int cnt = 0;
-
-	do {
-		cnt++;
+	int cnt;
 
+	for (cnt = 1; cnt <= CCWCHAIN_LEN_MAX; cnt++, ccw++) {
 		/*
 		 * We want to keep counting if the current CCW has the
 		 * command-chaining flag enabled, or if it is a TIC CCW
@@ -391,15 +389,10 @@ static int ccwchain_calc_length(u64 iova
 		 * after the TIC, depending on the results of its operation.
 		 */
 		if (!ccw_is_chain(ccw) && !is_tic_within_range(ccw, iova, cnt))
-			break;
-
-		ccw++;
-	} while (cnt < CCWCHAIN_LEN_MAX + 1);
-
-	if (cnt == CCWCHAIN_LEN_MAX + 1)
-		cnt = -EINVAL;
+			return cnt;
+	}
 
-	return cnt;
+	return -EINVAL;
 }
 
 static int tic_target_chain_exists(struct ccw1 *tic, struct channel_program *cp)
author maintainer
#80 lore raw

PATCH 76/220 s390/vfio_ccw: Move cp cleanup out of not operational

In reply to Greg Kroah-Hartman

6.12-stable review patch. If anyone has any objections, please let me know.

------------------

From: Eric Farman <[email protected]>

commit 0c11f61a876ed6fcca53d442ed3f33ea8362a0f9 upstream.

The fsm_notoper() routine is called when the device has been lost, and is (by definition) no longer operational. Since this can happen asynchronously from the normal behavior of the driver, the cleanup may happen when holding other locks in the calling sequence (notably, the cio subchannel lock).

Push the cleanup of the private->cp resources to a workqueue, where it can be done out from under that lock sequence and a future patch can safely manage the locking requirements.

Fixes 204b394a23ad ("vfio/ccw: Move FSM open/close to MDEV open/close")
---
 drivers/s390/cio/vfio_ccw_drv.c     |    9 +++++++++
 drivers/s390/cio/vfio_ccw_fsm.c     |    3 +--
 drivers/s390/cio/vfio_ccw_ops.c     |   13 +++++++++++++
 drivers/s390/cio/vfio_ccw_private.h |    3 +++
 4 files changed, 26 insertions(+), 2 deletions(-)
drivers/s390/cio/vfio_ccw_drv.c +9 -0
@@ -125,6 +125,15 @@ void vfio_ccw_crw_todo(struct work_struc
 		eventfd_signal(private->crw_trigger);
 }
 
+void vfio_ccw_notoper_todo(struct work_struct *work)
+{
+	struct vfio_ccw_private *private;
+
+	private = container_of(work, struct vfio_ccw_private, notoper_work);
+
+	cp_free(&private->cp);
+}
+
 /*
  * Css driver callbacks
  */
drivers/s390/cio/vfio_ccw_fsm.c +1 -2
@@ -170,8 +170,7 @@ static void fsm_notoper(struct vfio_ccw_
 	css_sched_sch_todo(sch, SCH_TODO_UNREG);
 	private->state = VFIO_CCW_STATE_NOT_OPER;
 
-	/* This is usually handled during CLOSE event */
-	cp_free(&private->cp);
+	queue_work(vfio_ccw_work_q, &private->notoper_work);
 }
 
 /*
drivers/s390/cio/vfio_ccw_ops.c +13 -0
@@ -54,6 +54,7 @@ static int vfio_ccw_mdev_init_dev(struct
 	INIT_LIST_HEAD(&private->crw);
 	INIT_WORK(&private->io_work, vfio_ccw_sch_io_todo);
 	INIT_WORK(&private->crw_work, vfio_ccw_crw_todo);
+	INIT_WORK(&private->notoper_work, vfio_ccw_notoper_todo);
 
 	private->cp.guest_cp = kcalloc(CCWCHAIN_LEN_MAX, sizeof(struct ccw1),
 				       GFP_KERNEL);
@@ -135,9 +136,16 @@ static void vfio_ccw_mdev_release_dev(st
 	/*
 	 * Ensure these work items are fully drained, so none can
 	 * fire after being released.
+	 *
+	 * notoper_work should have nothing to do here, because only
+	 * open devices could have channel_program resources in use
+	 * and those would be released during close. Nevertheless,
+	 * call flush here as well to be certain anything that was
+	 * allocated is freed.
 	 */
 	cancel_work_sync(&private->io_work);
 	cancel_work_sync(&private->crw_work);
+	flush_work(&private->notoper_work);
 
 	list_for_each_entry_safe(crw, temp, &private->crw, next) {
 		list_del(&crw->next);
@@ -214,9 +222,14 @@ static void vfio_ccw_mdev_close_device(s
 	/*
 	 * Ensure these work items are drained, in the event the
 	 * device is re-opened instead of released.
+	 *
+	 * notoper_work needs to be given a chance to run if it
+	 * is queued, so any memory associated with the channel
+	 * program can be returned.
 	 */
 	cancel_work_sync(&private->io_work);
 	cancel_work_sync(&private->crw_work);
+	flush_work(&private->notoper_work);
 
 	vfio_ccw_unregister_dev_regions(private);
 }
drivers/s390/cio/vfio_ccw_private.h +3 -0
@@ -102,6 +102,7 @@ struct vfio_ccw_parent {
  * @req_trigger: eventfd ctx for signaling userspace to return device
  * @io_work: work for deferral process of I/O handling
  * @crw_work: work for deferral process of CRW handling
+ * @notoper_work: work for deferred processing in not-operational state
  */
 struct vfio_ccw_private {
 	struct vfio_device vdev;
@@ -125,11 +126,13 @@ struct vfio_ccw_private {
 	struct eventfd_ctx	*req_trigger;
 	struct work_struct	io_work;
 	struct work_struct	crw_work;
+	struct work_struct	notoper_work;
 } __aligned(8);
 
 int vfio_ccw_sch_quiesce(struct subchannel *sch);
 void vfio_ccw_sch_io_todo(struct work_struct *work);
 void vfio_ccw_crw_todo(struct work_struct *work);
+void vfio_ccw_notoper_todo(struct work_struct *work);
 
 extern struct mdev_driver vfio_ccw_mdev_driver;
 
author maintainer
#81 lore raw

PATCH 77/220 s390/vfio_ccw: Selectively expand io_mutex

In reply to Greg Kroah-Hartman

6.12-stable review patch. If anyone has any objections, please let me know.

------------------

From: Eric Farman <[email protected]>

commit 34f4feff3e90bd09308fad0974e97113b23b812a upstream.

The io_mutex was defined to serialize the io_regions, but then has also sort of been associated with the I/O themselves because of the close relationship they share.

With the handful of races that are possible, the choices are either to:
A) expand the scope of io_mutex to close these remaining windows, or
B) reduce the scope of io_mutex to just io_region, and introduce a new lock mechanism for the remaining I/O resources

This patch implements A, since B brings with it a lot more interactions that would need to be tracked and kept in a correct hierarchy. It also takes advantage of the workqueue element for cp_free() that now gets called out of fsm_notoper(), which could be invoked out of an interrupt context and thus cannot acquire a mutex itself.

Fixes 4f76617378ee ("vfio-ccw: protect the I/O region")
---
 drivers/s390/cio/vfio_ccw_chp.c     |    2 +-
 drivers/s390/cio/vfio_ccw_cp.c      |    8 +++++++-
 drivers/s390/cio/vfio_ccw_drv.c     |    6 ++++--
 drivers/s390/cio/vfio_ccw_fsm.c     |    5 +++++
 drivers/s390/cio/vfio_ccw_private.h |    3 ++-
 5 files changed, 19 insertions(+), 5 deletions(-)
drivers/s390/cio/vfio_ccw_chp.c +1 -1
@@ -98,13 +98,13 @@ static ssize_t vfio_ccw_crw_region_read(
 	if (pos + count > sizeof(*region))
 		return -EINVAL;
 
+	mutex_lock(&private->io_mutex);
 	crw = list_first_entry_or_null(&private->crw,
 				       struct vfio_ccw_crw, next);
 
 	if (crw)
 		list_del(&crw->next);
 
-	mutex_lock(&private->io_mutex);
 	if (i >= private->num_regions) {
 		ret = -EINVAL;
 		goto out;
drivers/s390/cio/vfio_ccw_cp.c +7 -1
@@ -976,17 +976,23 @@ void cp_update_scsw(struct channel_progr
  */
 bool cp_iova_pinned(struct channel_program *cp, u64 iova, u64 length)
 {
+	struct vfio_ccw_private *private =
+		container_of(cp, struct vfio_ccw_private, cp);
 	struct ccwchain *chain;
 	int i;
 
 	if (!cp->initialized)
 		return false;
 
+	mutex_lock(&private->io_mutex);
 	list_for_each_entry(chain, &cp->ccwchain_list, next) {
 		for (i = 0; i < chain->ch_len; i++)
-			if (page_array_iova_pinned(&chain->ch_pa[i], iova, length))
+			if (page_array_iova_pinned(&chain->ch_pa[i], iova, length)) {
+				mutex_unlock(&private->io_mutex);
 				return true;
+			}
 	}
+	mutex_unlock(&private->io_mutex);
 
 	return false;
 }
drivers/s390/cio/vfio_ccw_drv.c +4 -2
@@ -91,6 +91,7 @@ void vfio_ccw_sch_io_todo(struct work_st
 
 	is_final = !(scsw_actl(&irb->scsw) &
 		     (SCSW_ACTL_DEVACT | SCSW_ACTL_SCHACT));
+	mutex_lock(&private->io_mutex);
 	if (scsw_is_solicited(&irb->scsw)) {
 		cp_update_scsw(&private->cp, &irb->scsw);
 		if (is_final && private->state == VFIO_CCW_STATE_CP_PENDING) {
@@ -98,9 +99,7 @@ void vfio_ccw_sch_io_todo(struct work_st
 			cp_is_finished = true;
 		}
 	}
-	mutex_lock(&private->io_mutex);
 	memcpy(private->io_region->irb_area, irb, sizeof(*irb));
-	mutex_unlock(&private->io_mutex);
 
 	/*
 	 * Reset to IDLE only if processing of a channel program
@@ -110,6 +109,7 @@ void vfio_ccw_sch_io_todo(struct work_st
 	 */
 	if (cp_is_finished)
 		private->state = VFIO_CCW_STATE_IDLE;
+	mutex_unlock(&private->io_mutex);
 
 	if (private->io_trigger)
 		eventfd_signal(private->io_trigger);
@@ -131,7 +131,9 @@ void vfio_ccw_notoper_todo(struct work_s
 
 	private = container_of(work, struct vfio_ccw_private, notoper_work);
 
+	mutex_lock(&private->io_mutex);
 	cp_free(&private->cp);
+	mutex_unlock(&private->io_mutex);
 }
 
 /*
drivers/s390/cio/vfio_ccw_fsm.c +5 -0
@@ -170,6 +170,7 @@ static void fsm_notoper(struct vfio_ccw_
 	css_sched_sch_todo(sch, SCH_TODO_UNREG);
 	private->state = VFIO_CCW_STATE_NOT_OPER;
 
+	/* This routine could be called from IRQ context, so defer */
 	queue_work(vfio_ccw_work_q, &private->notoper_work);
 }
 
@@ -409,7 +410,11 @@ static void fsm_close(struct vfio_ccw_pr
 
 	private->state = VFIO_CCW_STATE_STANDBY;
 	spin_unlock_irq(&sch->lock);
+
+	mutex_lock(&private->io_mutex);
 	cp_free(&private->cp);
+	mutex_unlock(&private->io_mutex);
+
 	return;
 
 err_unlock:
drivers/s390/cio/vfio_ccw_private.h +2 -1
@@ -88,7 +88,8 @@ struct vfio_ccw_parent {
  * @state: internal state of the device
  * @completion: synchronization helper of the I/O completion
  * @io_region: MMIO region to input/output I/O arguments/results
- * @io_mutex: protect against concurrent update of I/O regions
+ * @io_mutex: protect against concurrent update of I/O resources
+ *            and @cp lifecycle
  * @region: additional regions for other subchannel operations
  * @cmd_region: MMIO region for asynchronous I/O commands other than START
  * @schib_region: MMIO region for SCHIB information
author maintainer
#82 lore raw

PATCH 78/220 s390/vfio_ccw: Calculate idal length based on idaw type

In reply to Greg Kroah-Hartman

6.12-stable review patch. If anyone has any objections, please let me know.

------------------

From: Eric Farman <[email protected]>

commit 4f6fdc6e1a7fbfa36b945af33c65a417948feac0 upstream.

Sashiko pointed out that get_guest_idal() unconditionally calculates the length of the IDAL presuming everything is a Format-2 IDAW.

The output of vfio-ccw is always Format-2, but the input can be either Format-1 (31-bit addresses) or Format-2 (64-bit addresses). As a result, the size of the guest IDAL may be incorrect and should be trimmed down.

---
 drivers/s390/cio/vfio_ccw_cp.c |    5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)
drivers/s390/cio/vfio_ccw_cp.c +3 -2
@@ -233,6 +233,7 @@ static void convert_ccw0_to_ccw1(struct
 }
 
 #define idal_is_2k(_cp) (!(_cp)->orb.cmd.c64 || (_cp)->orb.cmd.i2k)
+#define get_idaw_size(_cp) ((_cp)->orb.cmd.c64 ? sizeof(u64) : sizeof(u32))
 
 /*
  * Helpers to operate ccwchain.
@@ -524,7 +525,7 @@ static dma64_t *get_guest_idal(struct cc
 	dma64_t *idaws;
 	dma32_t *idaws_f1;
 	u64 first_idaw;
-	int idal_len = idaw_nr * sizeof(*idaws);
+	int idal_len = idaw_nr * get_idaw_size(cp);
 	int idaw_size = idal_is_2k(cp) ? PAGE_SIZE / 2 : PAGE_SIZE;
 	int idaw_mask = ~(idaw_size - 1);
 	int i, ret;
@@ -593,7 +594,7 @@ static int ccw_count_idaws(struct ccw1 *
 	struct vfio_device *vdev =
 		&container_of(cp, struct vfio_ccw_private, cp)->vdev;
 	u64 iova;
-	int size = cp->orb.cmd.c64 ? sizeof(u64) : sizeof(u32);
+	int size = get_idaw_size(cp);
 	int ret;
 	int bytes = 1;
 
author maintainer
#83 lore raw

PATCH 79/220 s390/vfio_ccw: Implement a crw lock

In reply to Greg Kroah-Hartman

6.12-stable review patch. If anyone has any objections, please let me know.

------------------

From: Eric Farman <[email protected]>

commit 16b0798024c0e9117e395829ddbbe70981c79d9c upstream.

Unlike the channel_program struct, which covers synchronous I/O submissions and asynchronous interrupts, the CRW region relies exclusively on asynchronous events coming from hardware.

Implement a lock to manage the list of those payloads, to ensure they are read cohesively.

Fixes 3f02cb2fd9d2 ("vfio-ccw: Wire up the CRW irq and CRW region")
---
 drivers/s390/cio/vfio_ccw_chp.c     |   26 ++++++++++++++++----------
 drivers/s390/cio/vfio_ccw_drv.c     |    6 ++++++
 drivers/s390/cio/vfio_ccw_ops.c     |    4 ++++
 drivers/s390/cio/vfio_ccw_private.h |    4 ++++
 4 files changed, 30 insertions(+), 10 deletions(-)
drivers/s390/cio/vfio_ccw_chp.c +16 -10
@@ -93,18 +93,13 @@ static ssize_t vfio_ccw_crw_region_read(
 	loff_t pos = *ppos & VFIO_CCW_OFFSET_MASK;
 	struct ccw_crw_region *region;
 	struct vfio_ccw_crw *crw;
+	unsigned long flags;
 	int ret;
 
 	if (pos + count > sizeof(*region))
 		return -EINVAL;
 
 	mutex_lock(&private->io_mutex);
-	crw = list_first_entry_or_null(&private->crw,
-				       struct vfio_ccw_crw, next);
-
-	if (crw)
-		list_del(&crw->next);
-
 	if (i >= private->num_regions) {
 		ret = -EINVAL;
 		goto out;
@@ -113,6 +108,16 @@ static ssize_t vfio_ccw_crw_region_read(
 	i = array_index_nospec(i, private->num_regions);
 	region = private->region[i].data;
 
+	spin_lock_irqsave(&private->crw_lock, flags);
+	crw = list_first_entry_or_null(&private->crw,
+				       struct vfio_ccw_crw, next);
+
+	if (crw)
+		list_del(&crw->next);
+
+	/* Drop CRW lock while copying to userspace */
+	spin_unlock_irqrestore(&private->crw_lock, flags);
+
 	if (crw)
 		memcpy(&region->crw, &crw->crw, sizeof(region->crw));
 
@@ -122,15 +127,16 @@ static ssize_t vfio_ccw_crw_region_read(
 		ret = count;
 
 	region->crw = 0;
-
-out:
-	mutex_unlock(&private->io_mutex);
-
 	kfree(crw);
 
 	/* Notify the guest if more CRWs are on our queue */
+	spin_lock_irqsave(&private->crw_lock, flags);
 	if (!list_empty(&private->crw) && private->crw_trigger)
 		eventfd_signal(private->crw_trigger);
+	spin_unlock_irqrestore(&private->crw_lock, flags);
+
+out:
+	mutex_unlock(&private->io_mutex);
 
 	return ret;
 }
drivers/s390/cio/vfio_ccw_drv.c +6 -0
@@ -118,11 +118,14 @@ void vfio_ccw_sch_io_todo(struct work_st
 void vfio_ccw_crw_todo(struct work_struct *work)
 {
 	struct vfio_ccw_private *private;
+	unsigned long flags;
 
 	private = container_of(work, struct vfio_ccw_private, crw_work);
 
+	spin_lock_irqsave(&private->crw_lock, flags);
 	if (!list_empty(&private->crw) && private->crw_trigger)
 		eventfd_signal(private->crw_trigger);
+	spin_unlock_irqrestore(&private->crw_lock, flags);
 }
 
 void vfio_ccw_notoper_todo(struct work_struct *work)
@@ -286,6 +289,7 @@ static void vfio_ccw_queue_crw(struct vf
 			       unsigned int rsid)
 {
 	struct vfio_ccw_crw *crw;
+	unsigned long flags;
 
 	/*
 	 * If unable to allocate a CRW, just drop the event and
@@ -303,7 +307,9 @@ static void vfio_ccw_queue_crw(struct vf
 	crw->crw.erc = erc;
 	crw->crw.rsid = rsid;
 
+	spin_lock_irqsave(&private->crw_lock, flags);
 	list_add_tail(&crw->next, &private->crw);
+	spin_unlock_irqrestore(&private->crw_lock, flags);
 	queue_work(vfio_ccw_work_q, &private->crw_work);
 }
 
drivers/s390/cio/vfio_ccw_ops.c +4 -0
@@ -55,6 +55,7 @@ static int vfio_ccw_mdev_init_dev(struct
 	INIT_WORK(&private->io_work, vfio_ccw_sch_io_todo);
 	INIT_WORK(&private->crw_work, vfio_ccw_crw_todo);
 	INIT_WORK(&private->notoper_work, vfio_ccw_notoper_todo);
+	spin_lock_init(&private->crw_lock);
 
 	private->cp.guest_cp = kcalloc(CCWCHAIN_LEN_MAX, sizeof(struct ccw1),
 				       GFP_KERNEL);
@@ -132,6 +133,7 @@ static void vfio_ccw_mdev_release_dev(st
 	struct vfio_ccw_private *private =
 		container_of(vdev, struct vfio_ccw_private, vdev);
 	struct vfio_ccw_crw *crw, *temp;
+	unsigned long flags;
 
 	/*
 	 * Ensure these work items are fully drained, so none can
@@ -147,10 +149,12 @@ static void vfio_ccw_mdev_release_dev(st
 	cancel_work_sync(&private->crw_work);
 	flush_work(&private->notoper_work);
 
+	spin_lock_irqsave(&private->crw_lock, flags);
 	list_for_each_entry_safe(crw, temp, &private->crw, next) {
 		list_del(&crw->next);
 		kfree(crw);
 	}
+	spin_unlock_irqrestore(&private->crw_lock, flags);
 
 	kmem_cache_free(vfio_ccw_crw_region, private->crw_region);
 	kmem_cache_free(vfio_ccw_schib_region, private->schib_region);
drivers/s390/cio/vfio_ccw_private.h +4 -0
@@ -98,6 +98,8 @@ struct vfio_ccw_parent {
  * @cp: channel program for the current I/O operation
  * @irb: irb info received from interrupt
  * @scsw: scsw info
+ * @crw_lock: serialization of CRW list information
+ * @crw: list of Channel Report Word elements
  * @io_trigger: eventfd ctx for signaling userspace I/O results
  * @crw_trigger: eventfd ctx for signaling userspace CRW information
  * @req_trigger: eventfd ctx for signaling userspace to return device
@@ -120,6 +122,8 @@ struct vfio_ccw_private {
 	struct channel_program	cp;
 	struct irb		irb;
 	union scsw		scsw;
+
+	spinlock_t		crw_lock;
 	struct list_head	crw;
 
 	struct eventfd_ctx	*io_trigger;
author maintainer
#84 lore raw

PATCH 80/220 drm/amd/display: fix BT.2020 YCbCr limited output CSC matrix

In reply to Greg Kroah-Hartman

6.12-stable review patch. If anyone has any objections, please let me know.

------------------

From: Nathan Lucas <[email protected]>

commit 2f9a5c0f018d4a1586ee892f81f1383219676415 upstream.

COLOR_SPACE_YCBCR2020_TYPE, which is selected for COLOR_SPACE_2020_YCBCR_LIMITED color_space, has coefficients that are incorrect for limited-range output. Its luma and chroma scaling is full-range so output is too bright and colors are incorrect.

COLOR_SPACE_YCBCR2020_TYPE is closer to a full-range conversion matrix with incorrect luma offset, so correct the luma offset for full-range and rename it to COLOR_SPACE_YCBCR2020_FULL_TYPE.

Add COLOR_SPACE_YCBCR2020_LIMITED_TYPE with correct scaling and range for limited-range output.

Fix related functions so COLOR_SPACE_YCBCR2020_LIMITED_TYPE and COLOR_SPACE_YCBCR2020_FULL_TYPE are correctly selected based on dc_color_space.

Derivation of both matrices follows ITU-T H.273:

Table 4, MatrixCoefficients 9, BT.2020-NCL weights:
KR = 0.2627, KB = 0.0593, KG = 1 - KR - KB = 0.6780.

Equations 45-47 in matrix form:

            [  KR             KG             KB            0 ]
M2020_NCL = [ -KR/(2(1-KB))  -KG/(2(1-KB))   1/2           0 ]
            [  1/2           -KG/(2(1-KR))  -KB/(2(1-KR))  0 ]
            [  0              0              0             1 ]

Limited and Full transforms based on equations 30-32 and 36-38 with bit depth 10, normalized by 1023:

            [ 876/1023   0         0         64/1023  ]
MLimited  = [ 0          896/1023  0         512/1023 ]
            [ 0          0         896/1023  512/1023 ]
            [ 0          0         0         1        ]
            [ 1023/1023  0         0         0        ]
    MFull = [ 0          1023/1023 0         512/1023 ]
            [ 0          0         1023/1023 512/1023 ]
            [ 0          0         0         1        ]

M2020_NCL_Limited = MLimited x M2020_NCL
M2020_NCL_Full = MFull x M2020_NCL

The upper three rows of M2020_NCL_* are stored in CR, Y, CB order. Each M2020_NCL_* value is stored as Round(value * 8192) in its 16-bit two's-complement representation.

Fixes 973a9c810c78 ("drm/amd/display: Fix COLOR_SPACE_YCBCR2020_TYPE matrix")
Assisted-by OpenAI-Codex:GPT-5.6-Sol
(cherry picked from commit 3b906e1dc7e3c9ff9f7940f6828b367a6a9ec73c)
---
 drivers/gpu/drm/amd/display/dc/core/dc_hw_sequencer.c |   31 +++++++++++-------
 1 file changed, 20 insertions(+), 11 deletions(-)
drivers/gpu/drm/amd/display/dc/core/dc_hw_sequencer.c +20 -11
@@ -50,7 +50,8 @@ enum dc_color_space_type {
 	COLOR_SPACE_RGB_LIMITED_TYPE,
 	COLOR_SPACE_YCBCR601_TYPE,
 	COLOR_SPACE_YCBCR709_TYPE,
-	COLOR_SPACE_YCBCR2020_TYPE,
+	COLOR_SPACE_YCBCR2020_LIMITED_TYPE,
+	COLOR_SPACE_YCBCR2020_FULL_TYPE,
 	COLOR_SPACE_YCBCR601_LIMITED_TYPE,
 	COLOR_SPACE_YCBCR709_LIMITED_TYPE,
 	COLOR_SPACE_YCBCR709_BLACK_TYPE,
@@ -102,9 +103,15 @@ static const struct out_csc_color_matrix
 		{ 0xE00, 0xF349, 0xFEB7, 0x1000,
 		  0x6CE, 0x16E3, 0x24F,  0x200,
 		  0xFCCB, 0xF535, 0xE00, 0x1000} },
-	{ COLOR_SPACE_YCBCR2020_TYPE,
+	/* Corrected. Not included in the TODO above. */
+	{ COLOR_SPACE_YCBCR2020_LIMITED_TYPE,
+		{ 0x0E04, 0xF31D, 0xFEDF, 0x1004,
+		  0x0733, 0x1294, 0x01A0, 0x0201,
+		  0xFC16, 0xF5E6, 0x0E04, 0x1004} },
+	/* Corrected. Not included in the TODO above. */
+	{ COLOR_SPACE_YCBCR2020_FULL_TYPE,
 		{ 0x1000, 0xF149, 0xFEB7, 0x1004,
-		  0x0868, 0x15B2, 0x01E6, 0x201,
+		  0x0868, 0x15B2, 0x01E6, 0,
 		  0xFB88, 0xF478, 0x1000, 0x1004} },
 	{ COLOR_SPACE_YCBCR709_BLACK_TYPE,
 		{ 0x0000, 0x0000, 0x0000, 0x1000,
@@ -171,14 +178,14 @@ static bool is_ycbcr709_type(
 	return ret;
 }
 
-static bool is_ycbcr2020_type(
-	enum dc_color_space color_space)
+static bool is_ycbcr2020_limited_type(enum dc_color_space color_space)
 {
-	bool ret = false;
+	return color_space == COLOR_SPACE_2020_YCBCR_LIMITED;
+}
 
-	if (color_space == COLOR_SPACE_2020_YCBCR_LIMITED || color_space == COLOR_SPACE_2020_YCBCR_FULL)
-		ret = true;
-	return ret;
+static bool is_ycbcr2020_full_type(enum dc_color_space color_space)
+{
+	return color_space == COLOR_SPACE_2020_YCBCR_FULL;
 }
 
 static bool is_ycbcr709_limited_type(
@@ -207,8 +214,10 @@ static enum dc_color_space_type get_colo
 		type = COLOR_SPACE_YCBCR601_LIMITED_TYPE;
 	else if (is_ycbcr709_limited_type(color_space))
 		type = COLOR_SPACE_YCBCR709_LIMITED_TYPE;
-	else if (is_ycbcr2020_type(color_space))
-		type = COLOR_SPACE_YCBCR2020_TYPE;
+	else if (is_ycbcr2020_limited_type(color_space))
+		type = COLOR_SPACE_YCBCR2020_LIMITED_TYPE;
+	else if (is_ycbcr2020_full_type(color_space))
+		type = COLOR_SPACE_YCBCR2020_FULL_TYPE;
 	else if (color_space == COLOR_SPACE_YCBCR709)
 		type = COLOR_SPACE_YCBCR709_BLACK_TYPE;
 	else if (color_space == COLOR_SPACE_YCBCR709_BLACK)
author maintainer
#85 lore raw

PATCH 81/220 drm/amd/display: fix BT.2020 YCbCr output CSC matrices for DCE

In reply to Greg Kroah-Hartman

6.12-stable review patch. If anyone has any objections, please let me know.

------------------

From: Nathan Lucas <[email protected]>

commit cd22349e86faf6e15e6c622d70c0efc57d43201e upstream.

The commit cited by the Fixes tag added separate limited and full-range BT.2020 YCbCr entries to the DCE output CSC tables, but populated both entries with the same matrix copied from the common DC table. That matrix combined full-range scaling with limited-range luma offset and was incorrect for both limited and full-range output.

Replace the coefficients in both entries in the DCE paths with those from the new COLOR_SPACE_YCBCR2020_LIMITED_TYPE
and COLOR_SPACE_YCBCR2020_FULL_TYPE entries in the preceding commit ("drm/amd/display: fix BT.2020 YCbCr limited output CSC matrix").

Fixes 51e6668ab4ba ("drm/amd/display: add missing CSC entries for BT.2020 for DCE IPs")
Assisted-by OpenAI-Codex:GPT-5.6-Sol
(cherry picked from commit 14c8726b79d19934d6eb6d35c612e3f7204af2c6)
---
 drivers/gpu/drm/amd/display/dc/dce/dce_transform.c       |    7 ++++---
 drivers/gpu/drm/amd/display/dc/dce110/dce110_opp_csc_v.c |    7 ++++---
 2 files changed, 8 insertions(+), 6 deletions(-)
drivers/gpu/drm/amd/display/dc/dce/dce_transform.c +4 -3
@@ -115,10 +115,11 @@ static const struct out_csc_color_matrix
 	{ 0x2000, 0, 0, 0, 0, 0x2000, 0, 0, 0, 0, 0x2000, 0} },
 { COLOR_SPACE_2020_RGB_LIMITEDRANGE,
 	{ 0x1B67, 0, 0, 0x201, 0, 0x1B67, 0, 0x201, 0, 0, 0x1B67, 0x201} },
-{ COLOR_SPACE_2020_YCBCR_LIMITED, { 0x1000, 0xF149, 0xFEB7, 0x1004, 0x0868,
-	0x15B2, 0x01E6, 0x201, 0xFB88, 0xF478, 0x1000, 0x1004} },
+/* COLOR_SPACE_2020_YCBCR_* values corrected. Not included in the TODO above. */
+{ COLOR_SPACE_2020_YCBCR_LIMITED, { 0x0E04, 0xF31D, 0xFEDF, 0x1004, 0x0733,
+	0x1294, 0x01A0, 0x201, 0xFC16, 0xF5E6, 0x0E04, 0x1004} },
 { COLOR_SPACE_2020_YCBCR_FULL, { 0x1000, 0xF149, 0xFEB7, 0x1004, 0x0868, 0x15B2,
-	0x01E6, 0x201, 0xFB88, 0xF478, 0x1000, 0x1004} }
+	0x01E6, 0, 0xFB88, 0xF478, 0x1000, 0x1004} }
 };
 
 static bool setup_scaling_configuration(
drivers/gpu/drm/amd/display/dc/dce110/dce110_opp_csc_v.c +4 -3
@@ -93,10 +93,11 @@ static const struct out_csc_color_matrix
 	{ 0x2000, 0, 0, 0, 0, 0x2000, 0, 0, 0, 0, 0x2000, 0} },
 { COLOR_SPACE_2020_RGB_LIMITEDRANGE,
 	{ 0x1B67, 0, 0, 0x201, 0, 0x1B67, 0, 0x201, 0, 0, 0x1B67, 0x201} },
-{ COLOR_SPACE_2020_YCBCR_LIMITED, { 0x1000, 0xF149, 0xFEB7, 0x1004, 0x0868,
-	0x15B2, 0x01E6, 0x201, 0xFB88, 0xF478, 0x1000, 0x1004} },
+/* COLOR_SPACE_2020_YCBCR_* values corrected. Not included in the TODO above. */
+{ COLOR_SPACE_2020_YCBCR_LIMITED, { 0x0E04, 0xF31D, 0xFEDF, 0x1004, 0x0733,
+	0x1294, 0x01A0, 0x201, 0xFC16, 0xF5E6, 0x0E04, 0x1004} },
 { COLOR_SPACE_2020_YCBCR_FULL, { 0x1000, 0xF149, 0xFEB7, 0x1004, 0x0868, 0x15B2,
-	0x01E6, 0x201, 0xFB88, 0xF478, 0x1000, 0x1004} }
+	0x01E6, 0, 0xFB88, 0xF478, 0x1000, 0x1004} }
 };
 
 enum csc_color_mode {
author maintainer
#86 lore raw

PATCH 82/220 drm/amdgpu: Reject UVD message with invalid number of h265 refs

In reply to Greg Kroah-Hartman

6.12-stable review patch. If anyone has any objections, please let me know.

------------------

From: David Rosca <[email protected]>

commit 9fca434208f1f9ab977feac62df8ebb1cc7ce893 upstream.

Same change as for h264, avoids overflow later when calculating min dpb size.

(cherry picked from commit a4b0720e4f1601f97f59a2be9c1b4b94fa6527d5)
---
 drivers/gpu/drm/amd/amdgpu/amdgpu_uvd.c |    3 +++
 1 file changed, 3 insertions(+)
drivers/gpu/drm/amd/amdgpu/amdgpu_uvd.c +3 -0
@@ -802,6 +802,9 @@ static int amdgpu_uvd_cs_msg_decode(stru
 		image_size = ALIGN(image_size, 256);
 
 		num_dpb_buffer = (le32_to_cpu(msg[59]) & 0xff) + 2;
+		if (num_dpb_buffer > 17)
+			return -EINVAL;
+
 		min_dpb_size = image_size * num_dpb_buffer;
 		min_ctx_size = ((width + 255) / 16) * ((height + 255) / 16)
 					   * 16 * num_dpb_buffer + 52 * 1024;
author maintainer
#87 lore raw

PATCH 83/220 drm/amdgpu: fix nbif 6.3.1 l1 low power not functional

In reply to Greg Kroah-Hartman

6.12-stable review patch. If anyone has any objections, please let me know.

------------------

From: Yang Wang <[email protected]>

commit cd688a3cb342b9f56399aa076157f1c324c15c5a upstream.

The PCIe L1 low‑power settings for NBIF 6.3.1 were never applied due to unresolved register mapping, which caused the relevant code to be compiled out. As a result, the PCIe link could not enter L1/L23 power‑down states or transition to L0s.

Properly configure the link control register to enable L1 and L23 power‑down, and permit L0s link transitions. Keep LTR disabled and let the PCI core enable it only after verifying end‑to‑end root complex support across switches.

Fixes 894c6d3522d1 ("drm/amdgpu: Add nbif v6_3_1 ip block support")
(cherry picked from commit c2417f9fd7049d5a8d87eefd82fd6e36ba1ff7b6)
---
 drivers/gpu/drm/amd/amdgpu/nbif_v6_3_1.c |   42 ++++++++++++-------------------
 1 file changed, 17 insertions(+), 25 deletions(-)
drivers/gpu/drm/amd/amdgpu/nbif_v6_3_1.c +17 -25
@@ -315,7 +315,6 @@ static u32 nbif_v6_3_1_get_rom_offset(st
 static void nbif_v6_3_1_program_ltr(struct amdgpu_device *adev)
 {
 	uint32_t def, data;
-	u16 devctl2;
 
 	def = RREG32_SOC15(NBIO, 0, regRCC_EP_DEV0_0_EP_PCIE_TX_LTR_CNTL);
 	data = 0x35EB;
@@ -329,15 +328,8 @@ static void nbif_v6_3_1_program_ltr(stru
 	if (def != data)
 		WREG32_SOC15(NBIO, 0, regRCC_STRAP0_RCC_BIF_STRAP2, data);
 
-	pcie_capability_read_word(adev->pdev, PCI_EXP_DEVCTL2, &devctl2);
-
-	if (adev->pdev->ltr_path == (devctl2 & PCI_EXP_DEVCTL2_LTR_EN))
-		return;
-
-	if (adev->pdev->ltr_path)
-		pcie_capability_set_word(adev->pdev, PCI_EXP_DEVCTL2, PCI_EXP_DEVCTL2_LTR_EN);
-	else
-		pcie_capability_clear_word(adev->pdev, PCI_EXP_DEVCTL2, PCI_EXP_DEVCTL2_LTR_EN);
+	pcie_capability_set_word(adev->pdev, PCI_EXP_DEVCTL2,
+				 PCI_EXP_DEVCTL2_LTR_EN);
 }
 #endif
 
@@ -345,7 +337,7 @@ static void nbif_v6_3_1_program_aspm(str
 {
 #ifdef CONFIG_PCIEASPM
 	uint32_t def, data;
-	u16 devctl2, ltr;
+	u16 ltr;
 
 	def = data = RREG32_SOC15(PCIE, 0, regPCIE_LC_CNTL);
 	data &= ~PCIE_LC_CNTL__LC_L1_INACTIVITY_MASK;
@@ -375,11 +367,8 @@ static void nbif_v6_3_1_program_aspm(str
 	if (def != data)
 		WREG32_SOC15(NBIO, 0, regRCC_STRAP0_RCC_BIF_STRAP5, data);
 
-	pcie_capability_read_word(adev->pdev, PCI_EXP_DEVCTL2, &devctl2);
-	data = def = devctl2;
-	data &= ~PCI_EXP_DEVCTL2_LTR_EN;
-	if (def != data)
-		pcie_capability_set_word(adev->pdev, PCI_EXP_DEVCTL2, (u16)data);
+	pcie_capability_clear_word(adev->pdev, PCI_EXP_DEVCTL2,
+				   PCI_EXP_DEVCTL2_LTR_EN);
 
 	ltr = pci_find_ext_capability(adev->pdev, PCI_EXT_CAP_ID_LTR);
 
@@ -387,15 +376,13 @@ static void nbif_v6_3_1_program_aspm(str
 		pci_write_config_dword(adev->pdev, ltr + PCI_LTR_MAX_SNOOP_LAT, 0x10011001);
 	}
 
-#if 0
-	/* regPSWUSP0_PCIE_LC_CNTL2 should be replace by PCIE_LC_CNTL2 or someone else ? */
-	def = data = RREG32_SOC15(NBIO, 0, regPSWUSP0_PCIE_LC_CNTL2);
-	data |= PSWUSP0_PCIE_LC_CNTL2__LC_ALLOW_PDWN_IN_L1_MASK |
-		PSWUSP0_PCIE_LC_CNTL2__LC_ALLOW_PDWN_IN_L23_MASK;
-	data &= ~PSWUSP0_PCIE_LC_CNTL2__LC_RCV_L0_TO_RCV_L0S_DIS_MASK;
+	def = data = RREG32_SOC15(PCIE, 0, regPCIE_LC_CNTL2);
+	data |= PCIE_LC_CNTL2__LC_ALLOW_PDWN_IN_L1_MASK |
+		PCIE_LC_CNTL2__LC_ALLOW_PDWN_IN_L23_MASK;
+	data &= ~PCIE_LC_CNTL2__LC_RCV_L0_TO_RCV_L0S_DIS_MASK;
 	if (def != data)
-		WREG32_SOC15(NBIO, 0, regPSWUSP0_PCIE_LC_CNTL2, data);
-#endif
+		WREG32_SOC15(PCIE, 0, regPCIE_LC_CNTL2, data);
+
 	def = data = RREG32_SOC15(PCIE, 0, regPCIE_LC_CNTL4);
 	data |= PCIE_LC_CNTL4__LC_L1_POWERDOWN_MASK;
 	if (def != data)
@@ -406,7 +393,12 @@ static void nbif_v6_3_1_program_aspm(str
 	if (def != data)
 		WREG32_SOC15(PCIE, 0, regPCIE_LC_RXRECOVER_RXSTANDBY_CNTL, data);
 
-	nbif_v6_3_1_program_ltr(adev);
+	/*
+	 * Do not enable endpoint LTR unless the Root Complex and every
+	 * upstream switch support it.
+	 */
+	if (adev->pdev->ltr_path)
+		nbif_v6_3_1_program_ltr(adev);
 
 	def = data = RREG32_SOC15(NBIO, 0, regRCC_STRAP0_RCC_BIF_STRAP3);
 	data |= 0x5DE0 << RCC_STRAP0_RCC_BIF_STRAP3__STRAP_VLINK_ASPM_IDLE_TIMER__SHIFT;
author maintainer
#88 lore raw

PATCH 84/220 drm/amdgpu: check ASPM on the dGPU host link

In reply to Greg Kroah-Hartman

6.12-stable review patch. If anyone has any objections, please let me know.

------------------

From: Yang Wang <[email protected]>

commit 2a9c5154a5650c09ad44ff5e1dff74754e15a3c6 upstream.

dGPUs with an internal PCIe switch expose graphics functions below the switch downstream port. The automatic ASPM check uses the display endpoint and evaluates the internal link instead of the host link.

Use the switch upstream port for the check and report the selected link.

Fixes 0ab5d711ec74 ("drm/amd: Refactor `amdgpu_aspm` to be evaluated per device")
(cherry picked from commit 4e0d6f2876e704fff707b18c40dbd383aea4a1c9)
---
 drivers/gpu/drm/amd/amdgpu/amdgpu_device.c |   50 ++++++++++++++++++++++++++++-
 1 file changed, 49 insertions(+), 1 deletion(-)
drivers/gpu/drm/amd/amdgpu/amdgpu_device.c +49 -1
@@ -1806,6 +1806,31 @@ static bool amdgpu_device_aspm_support_q
 #endif
 }
 
+/*
+ * Some dGPUs expose their display endpoint below an internal PCIe switch.
+ * Use the switch upstream port to query the host-facing link.
+ */
+static struct pci_dev *amdgpu_device_get_aspm_pdev(struct amdgpu_device *adev)
+{
+	struct pci_dev *swds, *swus;
+
+	swds = pci_upstream_bridge(adev->pdev);
+	if (!swds ||
+	    (swds->vendor != PCI_VENDOR_ID_ATI &&
+	     swds->vendor != PCI_VENDOR_ID_AMD) ||
+	    pci_pcie_type(swds) != PCI_EXP_TYPE_DOWNSTREAM)
+		return adev->pdev;
+
+	swus = pci_upstream_bridge(swds);
+	if (!swus ||
+	    (swus->vendor != PCI_VENDOR_ID_ATI &&
+	     swus->vendor != PCI_VENDOR_ID_AMD) ||
+	    pci_pcie_type(swus) != PCI_EXP_TYPE_UPSTREAM)
+		return adev->pdev;
+
+	return swus;
+}
+
 /**
  * amdgpu_device_should_use_aspm - check if the device should program ASPM
  *
@@ -1818,6 +1843,9 @@ static bool amdgpu_device_aspm_support_q
  */
 bool amdgpu_device_should_use_aspm(struct amdgpu_device *adev)
 {
+	struct pci_dev *aspm_pdev, *parent;
+	bool enabled;
+
 	switch (amdgpu_aspm) {
 	case -1:
 		break;
@@ -1832,7 +1860,27 @@ bool amdgpu_device_should_use_aspm(struc
 		return false;
 	if (amdgpu_device_aspm_support_quirk(adev))
 		return false;
-	return pcie_aspm_enabled(adev->pdev);
+
+	/*
+	 * pcie_aspm_enabled() checks the link between its argument and
+	 * the immediate upstream bridge. Use SWUS for dGPUs with an
+	 * internal switch so that this is the host-facing link.
+	 */
+	aspm_pdev = amdgpu_device_get_aspm_pdev(adev);
+	parent = pci_upstream_bridge(aspm_pdev);
+	if (!parent) {
+		dev_dbg(adev->dev, "ASPM: no upstream PCIe link for %s\n",
+			pci_name(aspm_pdev));
+		return false;
+	}
+
+	enabled = pcie_aspm_enabled(aspm_pdev);
+	/* Report the exact link used for the automatic ASPM decision. */
+	dev_dbg(adev->dev, "ASPM: link %s <-> %s is %s\n",
+		pci_name(parent), pci_name(aspm_pdev),
+		enabled ? "enabled" : "disabled");
+
+	return enabled;
 }
 
 /* if we get transitioned to only one device, take VGA back */
author maintainer
#89 lore raw

PATCH 85/220 drm/amdgpu: validate GEM_CREATE domain combinations

In reply to Greg Kroah-Hartman

6.12-stable review patch. If anyone has any objections, please let me know.

------------------

From: Candice Li <[email protected]>

commit 5e9d136ad74df4edec67e502ce267597064d8f86 upstream.

AMDGPU_GEM_CREATE checked domain bits against AMDGPU_GEM_DOMAIN_MASK, but did not validate domain combinations. Userspace could combine CPU|GTT|VRAM with DOORBELL, GDS, GWS, or OA, making amdgpu_bo_placement_from_domain() exceed AMDGPU_BO_MAX_PLACEMENTS and hit BUG_ON().

Allow combinations only within CPU/GTT/VRAM, and require non-CPU/GTT/ VRAM domains to be specified one at a time. Return -EINVAL for invalid combinations in amdgpu_gem_create_ioctl().

v2: Rename helper from amdgpu_gem_domain_valid() to
amdgpu_gem_are_domains_valid() (Christian)

(cherry picked from commit db39852d0c39843cb02048dfb47e4b8c703e9080)
---
 drivers/gpu/drm/amd/amdgpu/amdgpu_gem.c |   21 +++++++++++++++++++++
 1 file changed, 21 insertions(+)
drivers/gpu/drm/amd/amdgpu/amdgpu_gem.c +21 -0
@@ -304,6 +304,25 @@ const struct drm_gem_object_funcs amdgpu
 	.vm_ops = &amdgpu_gem_vm_ops,
 };
 
+static bool amdgpu_gem_are_domains_valid(u32 domains)
+{
+	u32 normal = AMDGPU_GEM_DOMAIN_CPU |
+		     AMDGPU_GEM_DOMAIN_GTT |
+		     AMDGPU_GEM_DOMAIN_VRAM;
+	/* Treat all non CPU/GTT/VRAM domains as special domains. */
+	u32 special = AMDGPU_GEM_DOMAIN_MASK & ~normal;
+	u32 normal_mask = domains & normal;
+	u32 special_mask = domains & special;
+
+	if (!special_mask)
+		return true;
+
+	if (normal_mask)
+		return false;
+
+	return !(special_mask & (special_mask - 1));
+}
+
 /*
  * GEM ioctls.
  */
@@ -340,6 +359,8 @@ int amdgpu_gem_create_ioctl(struct drm_d
 	/* reject invalid gem domains */
 	if (args->in.domains & ~AMDGPU_GEM_DOMAIN_MASK)
 		return -EINVAL;
+	if (!amdgpu_gem_are_domains_valid(args->in.domains))
+		return -EINVAL;
 
 	if (!amdgpu_is_tmz(adev) && (flags & AMDGPU_GEM_CREATE_ENCRYPTED)) {
 		DRM_NOTE_ONCE("Cannot allocate secure buffer since TMZ is disabled\n");
author maintainer
#90 lore raw

PATCH 86/220 drm/amdgpu: Reject UVD message with dimensions above 4096

In reply to Greg Kroah-Hartman

6.12-stable review patch. If anyone has any objections, please let me know.

------------------

From: David Rosca <[email protected]>

commit 8c9aebcdd9f46f7a14b98d6ab18574b7a48fbb08 upstream.

Fixes potential overflow in DPB size calculations.

(cherry picked from commit 05e1387d151f71569fbe122d2c89f9db0c21dc10)
---
 drivers/gpu/drm/amd/amdgpu/amdgpu_uvd.c |    4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)
drivers/gpu/drm/amd/amdgpu/amdgpu_uvd.c +2 -2
@@ -654,8 +654,8 @@ static int amdgpu_uvd_cs_msg_decode(stru
 	unsigned int image_size, tmp, min_dpb_size, num_dpb_buffer;
 	unsigned int min_ctx_size = ~0;
 
-	/* Reject invalid dimensions to prevent division by zero */
-	if (width < 16 || height < 16) {
+	/* Reject invalid dimensions */
+	if (width < 16 || height < 16 || width > 4096 || height > 4096) {
 		dev_WARN_ONCE(adev->dev, 1,
 			      "Invalid UVD decoding dimensions (%dx%d)!\n",
 			      width, height);
author maintainer
#91 lore raw

PATCH 87/220 drm/amdgpu: Implement insert_end for VCE 3

In reply to Greg Kroah-Hartman

6.12-stable review patch. If anyone has any objections, please let me know.

------------------

From: David Rosca <[email protected]>

commit d5ab4c6a64efef2d143a96df5357f59703cd703d upstream.

After a recent change VCE now hangs when VCE_CMD_END is emitted after a pipeline sync without VM flush.
Implement insert_end to correctly insert only one VCE_CMD_END per job.

Fixes bc639a9eadc7 ("drm/amdgpu: always emit the job vm fence")
(cherry picked from commit 8897ea8c761b856f02061848a7908040a1fe5e68)
---
 drivers/gpu/drm/amd/amdgpu/vce_v3_0.c |   26 ++++++++++++++++++++++----
 1 file changed, 22 insertions(+), 4 deletions(-)
drivers/gpu/drm/amd/amdgpu/vce_v3_0.c +22 -4
@@ -871,6 +871,23 @@ static void vce_v3_0_ring_emit_ib(struct
 	amdgpu_ring_write(ring, ib->length_dw);
 }
 
+static void vce_v3_0_ring_emit_fence(struct amdgpu_ring *ring, u64 addr,
+			u64 seq, unsigned flags)
+{
+	WARN_ON(flags & AMDGPU_FENCE_FLAG_64BIT);
+
+	amdgpu_ring_write(ring, VCE_CMD_FENCE);
+	amdgpu_ring_write(ring, addr);
+	amdgpu_ring_write(ring, upper_32_bits(addr));
+	amdgpu_ring_write(ring, seq);
+	amdgpu_ring_write(ring, VCE_CMD_TRAP);
+}
+
+static void vce_v3_0_ring_insert_end(struct amdgpu_ring *ring)
+{
+	amdgpu_ring_write(ring, VCE_CMD_END);
+}
+
 static void vce_v3_0_emit_vm_flush(struct amdgpu_ring *ring,
 				   unsigned int vmid, uint64_t pd_addr)
 {
@@ -880,7 +897,6 @@ static void vce_v3_0_emit_vm_flush(struc
 
 	amdgpu_ring_write(ring, VCE_CMD_FLUSH_TLB);
 	amdgpu_ring_write(ring, vmid);
-	amdgpu_ring_write(ring, VCE_CMD_END);
 }
 
 static void vce_v3_0_emit_pipeline_sync(struct amdgpu_ring *ring)
@@ -952,17 +968,19 @@ static const struct amdgpu_ring_funcs vc
 	.set_wptr = vce_v3_0_ring_set_wptr,
 	.patch_cs_in_place = amdgpu_vce_ring_parse_cs_vm,
 	.emit_frame_size =
-		6 + /* vce_v3_0_emit_vm_flush */
+		5 + /* vce_v3_0_emit_vm_flush */
 		4 + /* vce_v3_0_emit_pipeline_sync */
-		6 + 6, /* amdgpu_vce_ring_emit_fence x2 vm fence */
+		5 + 5 + /* vce_v3_0_ring_emit_fence x2 vm fence */
+		1, /* vce_v3_0_ring_insert_end */
 	.emit_ib_size = 5, /* vce_v3_0_ring_emit_ib */
 	.emit_ib = vce_v3_0_ring_emit_ib,
 	.emit_vm_flush = vce_v3_0_emit_vm_flush,
 	.emit_pipeline_sync = vce_v3_0_emit_pipeline_sync,
-	.emit_fence = amdgpu_vce_ring_emit_fence,
+	.emit_fence = vce_v3_0_ring_emit_fence,
 	.test_ring = amdgpu_vce_ring_test_ring,
 	.test_ib = amdgpu_vce_ring_test_ib,
 	.insert_nop = amdgpu_ring_insert_nop,
+	.insert_end = vce_v3_0_ring_insert_end,
 	.pad_ib = amdgpu_ring_generic_pad_ib,
 	.begin_use = amdgpu_vce_ring_begin_use,
 	.end_use = amdgpu_vce_ring_end_use,
author maintainer
#92 lore raw

PATCH 88/220 drm/amdgpu: Fix UVD min buffer sizes

In reply to Greg Kroah-Hartman

6.12-stable review patch. If anyone has any objections, please let me know.

------------------

From: David Rosca <[email protected]>

commit 18727670b44753865b81c56a9338c0d7bd102c54 upstream.

Use correct size for message buffer = sizeof(struct ruvd_msg). Add ITSCALING_TABLE_BUFFER size.

(cherry picked from commit 37519d007e4261febbcf35b3045f8344f3145497)
---
 drivers/gpu/drm/amd/amdgpu/amdgpu_uvd.c |   12 +++++++-----
 1 file changed, 7 insertions(+), 5 deletions(-)
drivers/gpu/drm/amd/amdgpu/amdgpu_uvd.c +7 -5
@@ -974,15 +974,16 @@ static int amdgpu_uvd_cs_pass2(struct am
 				  ctx->buf_sizes[cmd]);
 			return -EINVAL;
 		}
+	} else if (cmd == 0x204 || cmd == 0x206) {
+		unsigned int min_size = ctx->buf_sizes[cmd == 0x204 ? 5 : 4];
 
-	} else if (cmd == 0x206) {
-		if ((end - start) < ctx->buf_sizes[4]) {
+		if ((end - start) < min_size) {
 			DRM_ERROR("buffer (%d) to small (%d / %d)!\n", cmd,
 					  (unsigned int)(end - start),
-					  ctx->buf_sizes[4]);
+					  min_size);
 			return -EINVAL;
 		}
-	} else if ((cmd != 0x100) && (cmd != 0x204)) {
+	} else if ((cmd != 0x100)) {
 		DRM_ERROR("invalid UVD command %X!\n", cmd);
 		return -EINVAL;
 	}
@@ -1112,11 +1113,12 @@ int amdgpu_uvd_ring_parse_cs(struct amdg
 {
 	struct amdgpu_uvd_cs_ctx ctx = {};
 	unsigned int buf_sizes[] = {
-		[0x00000000]	=	2048,
+		[0x00000000]	=	3556,
 		[0x00000001]	=	0xFFFFFFFF,
 		[0x00000002]	=	0xFFFFFFFF,
 		[0x00000003]	=	2048,
 		[0x00000004]	=	0xFFFFFFFF,
+		[0x00000005]	=	992,
 	};
 	int r;
 
author maintainer
#93 lore raw

PATCH 89/220 drm/amdgpu: Fix UVD dpb min size calculation for H264

In reply to Greg Kroah-Hartman

6.12-stable review patch. If anyone has any objections, please let me know.

------------------

From: David Rosca <[email protected]>

commit 21a8084cd76223a13493237e04d45f5226d7cee6 upstream.

This should use actual number of references from the decode message, instead of maximum derived from level.

(cherry picked from commit 64b525edb7e7bdfcdc77883c5e413804e2396856)
---
 drivers/gpu/drm/amd/amdgpu/amdgpu_uvd.c |   62 ++------------------------------
 1 file changed, 4 insertions(+), 58 deletions(-)
drivers/gpu/drm/amd/amdgpu/amdgpu_uvd.c +4 -58
@@ -645,11 +645,9 @@ static int amdgpu_uvd_cs_msg_decode(stru
 	unsigned int height = msg[7];
 	unsigned int dpb_size = msg[9];
 	unsigned int pitch = msg[28];
-	unsigned int level = msg[57];
 
 	unsigned int width_in_mb = width / 16;
 	unsigned int height_in_mb = ALIGN(height / 16, 2);
-	unsigned int fs_in_mb = width_in_mb * height_in_mb;
 
 	unsigned int image_size, tmp, min_dpb_size, num_dpb_buffer;
 	unsigned int min_ctx_size = ~0;
@@ -668,35 +666,9 @@ static int amdgpu_uvd_cs_msg_decode(stru
 
 	switch (stream_type) {
 	case 0: /* H264 */
-		switch (level) {
-		case 30:
-			num_dpb_buffer = 8100 / fs_in_mb;
-			break;
-		case 31:
-			num_dpb_buffer = 18000 / fs_in_mb;
-			break;
-		case 32:
-			num_dpb_buffer = 20480 / fs_in_mb;
-			break;
-		case 41:
-			num_dpb_buffer = 32768 / fs_in_mb;
-			break;
-		case 42:
-			num_dpb_buffer = 34816 / fs_in_mb;
-			break;
-		case 50:
-			num_dpb_buffer = 110400 / fs_in_mb;
-			break;
-		case 51:
-			num_dpb_buffer = 184320 / fs_in_mb;
-			break;
-		default:
-			num_dpb_buffer = 184320 / fs_in_mb;
-			break;
-		}
-		num_dpb_buffer++;
+		num_dpb_buffer = ((msg[61] >> 16) & 0xff) + 1;
 		if (num_dpb_buffer > 17)
-			num_dpb_buffer = 17;
+			return -EINVAL;
 
 		/* reference picture buffer */
 		min_dpb_size = image_size * num_dpb_buffer;
@@ -746,35 +718,9 @@ static int amdgpu_uvd_cs_msg_decode(stru
 		break;
 
 	case 7: /* H264 Perf */
-		switch (level) {
-		case 30:
-			num_dpb_buffer = 8100 / fs_in_mb;
-			break;
-		case 31:
-			num_dpb_buffer = 18000 / fs_in_mb;
-			break;
-		case 32:
-			num_dpb_buffer = 20480 / fs_in_mb;
-			break;
-		case 41:
-			num_dpb_buffer = 32768 / fs_in_mb;
-			break;
-		case 42:
-			num_dpb_buffer = 34816 / fs_in_mb;
-			break;
-		case 50:
-			num_dpb_buffer = 110400 / fs_in_mb;
-			break;
-		case 51:
-			num_dpb_buffer = 184320 / fs_in_mb;
-			break;
-		default:
-			num_dpb_buffer = 184320 / fs_in_mb;
-			break;
-		}
-		num_dpb_buffer++;
+		num_dpb_buffer = ((msg[61] >> 16) & 0xff) + 1;
 		if (num_dpb_buffer > 17)
-			num_dpb_buffer = 17;
+			return -EINVAL;
 
 		/* reference picture buffer */
 		min_dpb_size = image_size * num_dpb_buffer;
author maintainer
#94 lore raw

PATCH 90/220 drm/amdgpu: Fix UVD decode image min size calculation

In reply to Greg Kroah-Hartman

6.12-stable review patch. If anyone has any objections, please let me know.

------------------

From: David Rosca <[email protected]>

commit b8bb9ba3f101a1b0011f785a577a4a0a38371174 upstream.

This needs to use pitch instead of width. Also reject pitch over 4096 to avoid overflow.

(cherry picked from commit b41c8cb12e202b220353332ab87dc01a11f69304)
---
 drivers/gpu/drm/amd/amdgpu/amdgpu_uvd.c |    4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)
drivers/gpu/drm/amd/amdgpu/amdgpu_uvd.c +2 -2
@@ -761,7 +761,7 @@ static int amdgpu_uvd_cs_msg_decode(stru
 		return -EINVAL;
 	}
 
-	if (width > pitch) {
+	if (width > pitch || pitch > 4096) {
 		DRM_ERROR("Invalid UVD decoding target pitch!\n");
 		return -EINVAL;
 	}
@@ -773,7 +773,7 @@ static int amdgpu_uvd_cs_msg_decode(stru
 	}
 
 	buf_sizes[0x1] = dpb_size;
-	buf_sizes[0x2] = image_size;
+	buf_sizes[0x2] = (pitch * height) * 3 / 2;
 	buf_sizes[0x4] = min_ctx_size;
 	/* store image width to adjust nb memory pstate */
 	adev->uvd.decode_image_width = width;
author maintainer
#95 lore raw

PATCH 91/220 drm/amdgpu: disallow multiple FENCE chunks in one submit

In reply to Greg Kroah-Hartman

6.12-stable review patch. If anyone has any objections, please let me know.

------------------

From: Junrui Luo <[email protected]>

commit 931cd1d1baeae68e8eb2c23bc1f3d8934dca6241 upstream.

amdgpu_cs_pass1() dispatches on chunk_id once per chunk without rejecting repeated ids. p->uf_bo is a single-slot field, so a submission carrying two AMDGPU_CHUNK_ID_FENCE chunks runs amdgpu_cs_p1_user_fence() twice, and the second run overwrites p->uf_bo with a freshly referenced BO without dropping the reference taken by the first.

amdgpu_cs_parser_fini() only unrefs the final p->uf_bo, so every FENCE chunk but the last leaks a BO reference. The leaked BO outlives handle close and process exit.

Reject duplicate FENCE chunks the same way commit fec5f8e8c6bc ("drm/amdgpu: disallow multiple BO_HANDLES chunks in one submit") did for p->bo_list.

Fixes d38ceaf99ed0 ("drm/amdgpu: add core driver (v4)")
Assisted-by Claude:claude-opus-5
(cherry picked from commit 665b1fc2a1845206408f9a2c6da67101789edb82)
---
 drivers/gpu/drm/amd/amdgpu/amdgpu_cs.c |    4 ++++
 1 file changed, 4 insertions(+)
drivers/gpu/drm/amd/amdgpu/amdgpu_cs.c +4 -0
@@ -273,6 +273,10 @@ static int amdgpu_cs_pass1(struct amdgpu
 			if (size < sizeof(struct drm_amdgpu_cs_chunk_fence))
 				goto free_partial_kdata;
 
+			/* Only a single user fence is allowed to simplify handling. */
+			if (p->uf_bo)
+				goto free_partial_kdata;
+
 			ret = amdgpu_cs_p1_user_fence(p, p->chunks[i].kdata,
 						      &uf_offset);
 			if (ret)
author maintainer
#96 lore raw

PATCH 92/220 xfs: clear zapped attr fork state when bmap repair finds no attr fork

In reply to Greg Kroah-Hartman

6.12-stable review patch. If anyone has any objections, please let me know.

------------------

From: Qiang Ma <[email protected]>

commit 87b2a82e135ba81e49d82b59e3d72468cb66ea98 upstream.

xfstests xfs/377 can make xfs_scrub repeatedly check and repair the attr block map after inode repair zaps an attr fork.

When inode repair zaps an attr fork, it records XFS_SICK_INO_BMBTA_ZAPPED so that scrub/repair can revisit the attr fork block map. If the fork has been reset to an empty state and removed, BMBTA repair has no attr fork mappings to rebuild and can return success.

The post-repair scrub then runs with XREP_ALREADY_FIXED set, which means xchk_file_looks_zapped() deliberately ignores the stale zapped health bit and asks xchk_bmap() to check the current attr fork. For an absent attr fork, xchk_bmap() returns -ENOENT. Returning that error prevents xchk_bmap_attr() from marking XFS_SICK_INO_BMBTA_ZAPPED healthy, leaving the zapped health state behind even though there are no attr fork mappings left to check.

Treat -ENOENT during post-repair BMBTA revalidation as a clean result for the zapped attr fork: clear XFS_SICK_INO_BMBTA_ZAPPED and return success. Keep the existing -ENOENT behavior for ordinary scrubs of absent attr forks.

Fixes d9041681dd2f ("xfs: set inode sick state flags when we zap either ondisk fork")
---
 fs/xfs/scrub/bmap.c |    5 +++++
 1 file changed, 5 insertions(+)
fs/xfs/scrub/bmap.c +5 -0
@@ -1062,6 +1062,11 @@ xchk_bmap_attr(
 	}
 
 	error = xchk_bmap(sc, XFS_ATTR_FORK);
+	/* A repaired, empty attr fork no longer has mappings to check. */
+	if (error == -ENOENT && (sc->flags & XREP_ALREADY_FIXED)) {
+		xchk_mark_healthy_if_clean(sc, XFS_SICK_INO_BMBTA_ZAPPED);
+		return 0;
+	}
 	if (error)
 		return error;
 
author maintainer
#97 lore raw

PATCH 93/220 xfs: zero i_nlink before repair puts inode on unlinked list

In reply to Greg Kroah-Hartman

6.12-stable review patch. If anyone has any objections, please let me know.

------------------

From: Darrick J. Wong <[email protected]>

commit 49933254ba8d421ed706cbe6ed0fbc264e572cab upstream.

LOLLM observes that we don't reset i_nlink if we encounter a file with no parent that isn't on the unlinked list. This causes unnecessary assertion trips on debugging kernels and an inconsistent file, so let's fix that.

Fixes 669dfe883c8e20 ("xfs: update the unlinked list when repairing link counts")
Assisted-by LOLLM # finding obvious bugs
---
 fs/xfs/scrub/nlinks_repair.c |    7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)
fs/xfs/scrub/nlinks_repair.c +6 -1
@@ -234,9 +234,14 @@ xrep_nlinks_repair_inode(
 	 * unlinked list, put it on the unlinked list.
 	 */
 	if (total_links == 0 && !xfs_inode_on_unlinked_list(ip)) {
+		if (actual_nlink)
+			clear_nlink(VFS_I(ip));
 		error = xfs_iunlink(sc->tp, ip);
-		if (error)
+		if (error) {
+			if (actual_nlink)
+				set_nlink(VFS_I(ip), actual_nlink);
 			goto out_trans;
+		}
 		dirty = true;
 	}
 
author maintainer
#98 lore raw

PATCH 94/220 xfs: only check mergeability of bnobt records

In reply to Greg Kroah-Hartman

6.12-stable review patch. If anyone has any objections, please let me know.

------------------

From: Darrick J. Wong <[email protected]>

commit 97efed1091a50e4dbf31307015138b43a972d2e4 upstream.

In the cntbt (free space by block count) btree, records are not supposed to be in startblock order. Hence the mergeability check is pointless. Remove it, since it does nothing, as LOLLM points out.

Fixes d5784ae82778d9 ("xfs: flag free space btree records that could be merged")
Assisted-by LOLLM # finding obvious bugs
---
 fs/xfs/scrub/alloc.c |    5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)
fs/xfs/scrub/alloc.c +3 -2
@@ -136,7 +136,7 @@ xchk_allocbt_rec(
 	const union xfs_btree_rec	*rec)
 {
 	struct xfs_alloc_rec_incore	irec;
-	struct xchk_alloc	*ca = bs->private;
+	struct xchk_alloc		*ca = bs->private;
 
 	xfs_alloc_btrec_to_irec(rec, &irec);
 	if (xfs_alloc_check_irec(bs->cur->bc_ag.pag, &irec) != NULL) {
@@ -144,7 +144,8 @@ xchk_allocbt_rec(
 		return 0;
 	}
 
-	xchk_allocbt_mergeable(bs, ca, &irec);
+	if (bs->sc->sm->sm_type == XFS_SCRUB_TYPE_BNOBT)
+		xchk_allocbt_mergeable(bs, ca, &irec);
 	xchk_allocbt_xref(bs->sc, &irec);
 
 	return 0;
author maintainer
#99 lore raw

PATCH 95/220 xfs: dont double-lock when deleting a self-referential directory

In reply to Greg Kroah-Hartman

6.12-stable review patch. If anyone has any objections, please let me know.

------------------

From: Darrick J. Wong <[email protected]>

commit 5fc643fb86599e29b38e7b2c2680b4b15bf8f772 upstream.

LOLLM notices that the dirtree scrubber can detect a directory that refers to itself. In this case, it's not correct for the directory tree repair code to try to iolock/ilock both sc->ip and dp, because they're the same inode. Fix this by detecting that corner case and handling it appropriately.

Fixes 3f31406aef493b ("xfs: fix corruptions in the directory tree")
Assisted-by LOLLM # finding obvious bugs
---
 fs/xfs/scrub/dirtree_repair.c |   22 ++++++++++++++++++----
 1 file changed, 18 insertions(+), 4 deletions(-)
fs/xfs/scrub/dirtree_repair.c +18 -4
@@ -349,6 +349,8 @@ xrep_dirtree_unlink_iolock(
 
 	ASSERT(sc->ilock_flags & XFS_IOLOCK_EXCL);
 
+	if (sc->ip == dp)
+		return 0;
 	if (xfs_ilock_nowait(dp, XFS_IOLOCK_EXCL))
 		return 0;
 
@@ -400,8 +402,18 @@ xrep_dirtree_unlink(
 	 * directory code can handle a reservationless update.
 	 */
 	resblks = xfs_remove_space_res(mp, step->name_len);
-	error = xfs_trans_alloc_dir(dp, &M_RES(mp)->tr_remove, sc->ip,
-			&resblks, &sc->tp, &dontcare);
+	if (sc->ip == dp) {
+again:
+		error = xfs_trans_alloc_inode(dp, &M_RES(mp)->tr_remove,
+				resblks, 0, false, &sc->tp);
+		if ((error == -ENOSPC || error == -EDQUOT) && resblks > 0) {
+			resblks = 0;
+			goto again;
+		}
+	} else {
+		error = xfs_trans_alloc_dir(dp, &M_RES(mp)->tr_remove, sc->ip,
+				&resblks, &sc->tp, &dontcare);
+	}
 	if (error)
 		goto out_iolock;
 
@@ -489,9 +501,11 @@ out_trans_cancel:
 	xchk_trans_cancel(sc);
 out_ilock:
 	xfs_iunlock(sc->ip, XFS_ILOCK_EXCL);
-	xfs_iunlock(dp, XFS_ILOCK_EXCL);
+	if (dp != sc->ip)
+		xfs_iunlock(dp, XFS_ILOCK_EXCL);
 out_iolock:
-	xfs_iunlock(dp, XFS_IOLOCK_EXCL);
+	if (dp != sc->ip)
+		xfs_iunlock(dp, XFS_IOLOCK_EXCL);
 	return error;
 }
 
author maintainer
#100 lore raw

PATCH 96/220 xfs: set the prev pointer when reinserting an inode on the unlinked list

In reply to Greg Kroah-Hartman

6.12-stable review patch. If anyone has any objections, please let me know.

------------------

From: Darrick J. Wong <[email protected]>

commit af146cb7ff8ff5c54162f35c238f3ff1d5ad110f upstream.

If we find a rogue free inode and decide to reinsert it into the unlinked list, we need to set the prev pointer to NULLAGINO so that the incore list gets updated.

Fixes ab97f4b1c03075 ("xfs: repair AGI unlinked inode bucket lists")
---
 fs/xfs/scrub/agheader_repair.c |    4 ++++
 1 file changed, 4 insertions(+)
fs/xfs/scrub/agheader_repair.c +4 -0
@@ -1438,6 +1438,10 @@ xrep_iunlink_add_to_bucket(
 	if (error)
 		return error;
 
+	error = xrep_iunlink_store_prev(ragi, agino, NULLAGINO);
+	if (error)
+		return error;
+
 	/* Remember the head inode's previous pointer. */
 	if (current_head != NULLAGINO) {
 		error = xrep_iunlink_store_prev(ragi, current_head, agino);
author maintainer
#101 lore raw

PATCH 97/220 xfs: pass runtime errors from xrep_iunlink_mark_ondisk_rec up to callers

In reply to Greg Kroah-Hartman

6.12-stable review patch. If anyone has any objections, please let me know.

------------------

From: Darrick J. Wong <[email protected]>

commit 5644fab990fc72406dddc91cbb8304659d77f3f1 upstream.

LOLLM points out that the only error that xrep_iunlink_mark_ondisk_rec returns is ENOMEM, but we ignore that, and can end up writing a garbage AGI based on incomplete information. We shouldn't do that, though here we must be screen out EFSCORRUPTED/EFSBASDCRC because we haven't checked the inobt yet.

Fixes ab97f4b1c03075 ("xfs: repair AGI unlinked inode bucket lists")
Assisted-by LOLLM # finding obvious bugs
---
 fs/xfs/scrub/agheader_repair.c |   14 ++++++++++++--
 1 file changed, 12 insertions(+), 2 deletions(-)
fs/xfs/scrub/agheader_repair.c +12 -2
@@ -1306,7 +1306,7 @@ xrep_iunlink_mark_ondisk_rec(
  * iunlink_bmp.   We haven't checked the inobt yet, so we don't error out if
  * the btree is corrupt.
  */
-STATIC void
+STATIC int
 xrep_iunlink_mark_ondisk(
 	struct xrep_agi		*ragi)
 {
@@ -1318,6 +1318,14 @@ xrep_iunlink_mark_ondisk(
 	cur = xfs_inobt_init_cursor(sc->sa.pag, sc->tp, agi_bp);
 	error = xfs_btree_query_all(cur, xrep_iunlink_mark_ondisk_rec, ragi);
 	xfs_btree_del_cursor(cur, error);
+
+	/*
+	 * Don't proceed if we couldn't set a bit in the bitmap.  All other
+	 * errors we ignore because we haven't actually checked the inobt yet.
+	 */
+	if (error == -ENOMEM)
+		return -ENOMEM;
+	return 0;
 }
 
 /*
@@ -1509,7 +1517,9 @@ xrep_iunlink_rebuild_buckets(
 	 * If there are ondisk inodes that are unlinked and are not been loaded
 	 * into cache, record them in iunlink_bmp.
 	 */
-	xrep_iunlink_mark_ondisk(ragi);
+	error = xrep_iunlink_mark_ondisk(ragi);
+	if (error)
+		return error;
 
 	/*
 	 * Walk each iunlink bucket to (re)construct as much of the incore list
author maintainer
#102 lore raw

PATCH 98/220 xfs: nlink scrub must take IOLOCK before determining ILOCK state

In reply to Greg Kroah-Hartman

6.12-stable review patch. If anyone has any objections, please let me know.

------------------

From: Darrick J. Wong <[email protected]>

commit b1a296fc2241f724ef8f14da6a4efa800d444dac upstream.

In xchk_nlinks_ilock_dir, take the IOLOCK before accessing internal inode state to figure out if we need to take ILOCK shared or exclusive. That way we can't race with directory updates. LOLLM pointed out that the code was initially correct w.r.t. the IOLOCK, but then I broke it.

Fixes f477af0cfa0487 ("xfs: fix locking in xchk_nlinks_collect_dir")
Assisted-by LOLLM # finding obvious bugs
---
 fs/xfs/scrub/nlinks.c | 13 +++++++------
 1 file changed, 7 insertions(+), 6 deletions(-)
fs/xfs/scrub/nlinks.c +7 -6
index 355ab6de23ea..bcedb8c3e4e6 100644
@@ -382,6 +382,12 @@ xchk_nlinks_ilock_dir(
 {
 	uint			lock_mode = XFS_ILOCK_SHARED;
 
+	/*
+	 * Take the IOLOCK so that other threads cannot start a directory
+	 * update while we're scanning.
+	 */
+	xfs_ilock(ip, XFS_IOLOCK_SHARED);
+
 	/*
 	 * We're going to scan the directory entries, so we must be ready to
 	 * pull the data fork mappings into memory if they aren't already.
@@ -397,13 +403,8 @@ xchk_nlinks_ilock_dir(
 	    xfs_need_iread_extents(&ip->i_af))
 		lock_mode = XFS_ILOCK_EXCL;
 
-	/*
-	 * Take the IOLOCK so that other threads cannot start a directory
-	 * update while we're scanning.
-	 */
-	lock_mode |= XFS_IOLOCK_SHARED;
 	xfs_ilock(ip, lock_mode);
-	return lock_mode;
+	return lock_mode | XFS_IOLOCK_SHARED;
 }
 
 /* Walk a directory to bump the observed link counts of the children. */
Signature · 1 line
2.55.0
author maintainer
#103 lore raw

PATCH 99/220 xfs: load next_agino from the correct xfarray in xrep_iunlink_relink_prev

In reply to Greg Kroah-Hartman

6.12-stable review patch. If anyone has any objections, please let me know.

------------------

From: Darrick J. Wong <[email protected]>

commit 6b9cd540138a06660a843a519facc147060acbef upstream.

LOLLM notices that xrep_iunlink_relink_prev has the comment "set the forward pointer..." but then loads the value from the xfarray that stores pointers to the previous inode in the unlinked list. That's wrong, so fix the variable access.

Fixes ab97f4b1c03075 ("xfs: repair AGI unlinked inode bucket lists")
Assisted-by LOLLM # finding obvious bugs
---
 fs/xfs/scrub/agheader_repair.c |    2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
fs/xfs/scrub/agheader_repair.c +1 -1
@@ -1634,7 +1634,7 @@ xrep_iunlink_relink_prev(
 		want_rele = true;
 
 		/* Set the forward pointer since this just came off disk. */
-		error = xfarray_load(ragi->iunlink_prev, agino, &next_agino);
+		error = xfarray_load(ragi->iunlink_next, agino, &next_agino);
 		if (error)
 			goto out_rele;
 
author maintainer
#104 lore raw

PATCH 100/220 xfs: fix ilock leak on error in xfs_dq_get_next_id

In reply to Greg Kroah-Hartman

6.12-stable review patch. If anyone has any objections, please let me know.

------------------

From: Long Li <[email protected]>

commit 63320a0f70f66f311f4bccff3af0719c2119f46c upstream.

xfs_dq_get_next_id() takes the quota inode ILOCK before calling xfs_iread_extents(). If xfs_iread_extents() fails, the function returns immediately without releasing the lock, leaking the quota inode ILOCK. This can leave the quota inode locked and cause subsequent quota operations to hang.

Fix this by jumping to a common unlock path on error instead of returning directly.

Fixes bda250dbaf39f ("xfs: rewrite xfs_dq_get_next_id using xfs_iext_lookup_extent")
---
 fs/xfs/xfs_dquot.c |    3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)
fs/xfs/xfs_dquot.c +2 -1
@@ -773,7 +773,7 @@ xfs_dq_get_next_id(
 	lock_flags = xfs_ilock_data_map_shared(quotip);
 	error = xfs_iread_extents(NULL, quotip, XFS_DATA_FORK);
 	if (error)
-		return error;
+		goto out_unlock;
 
 	if (xfs_iext_lookup_extent(quotip, &quotip->i_df, start, &cur, &got)) {
 		/* contiguous chunk, bump startoff for the id calculation */
@@ -784,6 +784,7 @@ xfs_dq_get_next_id(
 		error = -ENOENT;
 	}
 
+out_unlock:
 	xfs_iunlock(quotip, lock_flags);
 
 	return error;
author maintainer
#105 lore raw

PATCH 101/220 xfs: dont zap the attr fork on repair when there are queued pptr updates

In reply to Greg Kroah-Hartman

6.12-stable review patch. If anyone has any objections, please let me know.

------------------

From: Darrick J. Wong <[email protected]>

commit 5ee37132ea81abd36213b31a72140660c2aac54b upstream.

LOLLM noticed that xrep_xattr_rebuild_tree doesn't check for queued parent pointer updates when it decides that it's going to zap the attr fork. This is obviously incorrect, so fix that. We hold the IOLOCK and the ILOCK of sc->ip at that point in time, so we can't race with any /new/ operations.

Fixes e5d7ce0364d8ee ("xfs: replay unlocked parent pointer updates that accrue during xattr repair")
Assisted-by LOLLM # finding obvious bugs
---
 fs/xfs/scrub/attr_repair.c |    3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)
fs/xfs/scrub/attr_repair.c +2 -1
@@ -1427,7 +1427,8 @@ xrep_xattr_rebuild_tree(
 	 * If we didn't find any attributes to salvage, repair the file by
 	 * zapping its attr fork.
 	 */
-	if (rx->attrs_found == 0) {
+	if (rx->attrs_found == 0 &&
+	    (!xfs_has_parent(sc->mp) || xfarray_length(rx->pptr_recs) == 0)) {
 		xfs_trans_ijoin(sc->tp, sc->ip, 0);
 		error = xrep_xattr_reset_fork(sc);
 		if (error)
author maintainer
#106 lore raw

PATCH 102/220 xfs: dont walk off the end of a null sc->sa.agi_bp in AGI repair

In reply to Greg Kroah-Hartman

6.12-stable review patch. If anyone has any objections, please let me know.

------------------

From: Darrick J. Wong <[email protected]>

commit 1e96a00e0d3a00be6e4f368b2f18e2d345f813ce upstream.

LOLLM noticed a longstanding bug where xrep_iunlink_walk_ondisk_bucket tries to walk ragi->sc->sa.agi_bp to rebuild the unlinked inode lists. Unfortunately, it's possible for agi_bp to be null if the buffer verifier fails, so we have to use ragi->agi_bp (which skips verifier checks) instead.

Fixes ab97f4b1c03075 ("xfs: repair AGI unlinked inode bucket lists")
Assisted-by LOLLM # finding obvious bugs
---
 fs/xfs/scrub/agheader_repair.c |    2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
fs/xfs/scrub/agheader_repair.c +1 -1
@@ -1083,7 +1083,7 @@ xrep_iunlink_walk_ondisk_bucket(
 	unsigned int		bucket)
 {
 	struct xfs_scrub	*sc = ragi->sc;
-	struct xfs_agi		*agi = sc->sa.agi_bp->b_addr;
+	struct xfs_agi		*agi = ragi->agi_bp->b_addr;
 	xfs_agino_t		prev_agino = NULLAGINO;
 	xfs_agino_t		next_agino;
 	int			error = 0;
author maintainer
#107 lore raw

PATCH 103/220 xfs: fix allocated inodes that show up in the unlinked list

In reply to Greg Kroah-Hartman

6.12-stable review patch. If anyone has any objections, please let me know.

------------------

From: Darrick J. Wong <[email protected]>

commit 68ab37650ce5195b4f4f8466444a36a78207840c upstream.

If an allocated inode shows up in the unlinked list, we need to get it completely off the list. Set the corrected next/prev pointers such that the inode will not look like it should be on an unlinked list at all.

Fixes ab97f4b1c03075 ("xfs: repair AGI unlinked inode bucket lists")
---
 fs/xfs/scrub/agheader_repair.c |   63 ++++++++++++++++++++++++++++++++++++-----
 fs/xfs/scrub/trace.h           |    1 
 2 files changed, 57 insertions(+), 7 deletions(-)
fs/xfs/scrub/agheader_repair.c +56 -7
@@ -980,6 +980,13 @@ err:
 }
 
 /*
+ * Magic value that means "not unlinked" because xfarrays don't support storing
+ * totally zeroed elements.  There can't be a cluster that starts in daddr 0 so
+ * there can't be an inode #1 either.
+ */
+#define LINKED_AGINO	(0x1)
+
+/*
  * Record a forwards unlinked chain pointer from agino -> next_agino in our
  * staging information.
  */
@@ -1359,6 +1366,35 @@ xrep_iunlink_resolve_bucket(
 			break;
 		}
 
+		if (VFS_I(ip)->i_nlink != 0) {
+			/*
+			 * Inode is linked somewhere!  Blow out both unlinked
+			 * list pointers, advance the list, and pretend we
+			 * didn't see this inode.  Clear it from iunlink_bmp
+			 * because it's linked.
+			 */
+			trace_xrep_iunlink_resolve_allocated(sc->sa.pag,
+					bucket, prev_agino, next_agino);
+
+			error = xrep_iunlink_store_next(ragi, next_agino,
+					NULLAGINO);
+			if (error)
+				return error;
+
+			error = xrep_iunlink_store_prev(ragi, next_agino,
+					LINKED_AGINO);
+			if (error)
+				return error;
+
+			error = xagino_bitmap_clear(&ragi->iunlink_bmp,
+					next_agino, 1);
+			if (error)
+				return error;
+
+			next_agino = ip->i_next_unlinked;
+			continue;
+		}
+
 		if (next_agino % XFS_AGI_UNLINKED_BUCKETS != bucket) {
 			/*
 			 * Inode is in the wrong bucket.  Advance the list,
@@ -1541,6 +1577,24 @@ xrep_iunlink_rebuild_buckets(
 			xrep_iunlink_add_lost_inodes, ragi);
 }
 
+static inline void
+set_inode_prev_unlinked(
+	struct xfs_inode	*ip,
+	xfs_agino_t		prev_agino)
+{
+	/*
+	 * Magic value that means "not unlinked" because xfarrays don't support
+	 * storing totally zeroed elements.
+	 */
+	if (prev_agino == LINKED_AGINO)
+		prev_agino = 0;
+
+	if (ip->i_prev_unlinked != prev_agino) {
+		trace_xrep_iunlink_relink_prev(ip, prev_agino);
+		ip->i_prev_unlinked = prev_agino;
+	}
+}
+
 /* Update i_next_iunlinked for the inode @agino. */
 STATIC int
 xrep_iunlink_relink_next(
@@ -1576,8 +1630,7 @@ xrep_iunlink_relink_next(
 		if (error)
 			goto out_rele;
 
-		trace_xrep_iunlink_relink_prev(ip, prev_agino);
-		ip->i_prev_unlinked = prev_agino;
+		set_inode_prev_unlinked(ip, prev_agino);
 	}
 
 	/* Update the forward pointer. */
@@ -1646,11 +1699,7 @@ xrep_iunlink_relink_prev(
 		ip->i_next_unlinked = next_agino;
 	}
 
-	/* Update the backward pointer. */
-	if (ip->i_prev_unlinked != prev_agino) {
-		trace_xrep_iunlink_relink_prev(ip, prev_agino);
-		ip->i_prev_unlinked = prev_agino;
-	}
+	set_inode_prev_unlinked(ip, prev_agino);
 
 out_rele:
 	/*
fs/xfs/scrub/trace.h +1 -0
@@ -3460,6 +3460,7 @@ DEFINE_REPAIR_IUNLINK_RESOLVE_EVENT(xrep
 DEFINE_REPAIR_IUNLINK_RESOLVE_EVENT(xrep_iunlink_resolve_wronglist);
 DEFINE_REPAIR_IUNLINK_RESOLVE_EVENT(xrep_iunlink_resolve_nolist);
 DEFINE_REPAIR_IUNLINK_RESOLVE_EVENT(xrep_iunlink_resolve_ok);
+DEFINE_REPAIR_IUNLINK_RESOLVE_EVENT(xrep_iunlink_resolve_allocated);
 
 TRACE_EVENT(xrep_iunlink_relink_next,
 	TP_PROTO(struct xfs_inode *ip, xfs_agino_t next_agino),
author maintainer
#108 lore raw

PATCH 104/220 xfs: fix another iunlink infinite loop bug in online fsck

In reply to Greg Kroah-Hartman

6.12-stable review patch. If anyone has any objections, please let me know.

------------------

From: Darrick J. Wong <[email protected]>

commit 6d67c6b99f1fc07c64b97fcbc974c6f1ada7f622 upstream.

xrep_iunlink_resolve_bucket is supposed to reconstruct as much of the incore prev and next unlinked list pointers based on what it finds on disk and in memory before we move on to relinking the truly lost inodes back into the unlinked list. However, it's still vulnerable to infinite loops that come in via the next_unlinked pointers.

Fix this problem by remembering which inodes we've already seen and checking new agino pointers against that. If a bit is already set, either this is a loop or the inode has nonzero link count. We'll deal with the second case in a subsequent patch.

Fixes ab97f4b1c03075 ("xfs: repair AGI unlinked inode bucket lists")
---
 fs/xfs/scrub/agheader_repair.c |   37 ++++++++++++++++++++++++++++---------
 fs/xfs/scrub/trace.h           |    1 +
 2 files changed, 29 insertions(+), 9 deletions(-)
fs/xfs/scrub/agheader_repair.c +28 -9
@@ -1345,15 +1345,32 @@ xrep_iunlink_resolve_bucket(
 	struct xrep_agi		*ragi,
 	unsigned int		bucket)
 {
+	struct xagino_bitmap	seen;
 	struct xfs_scrub	*sc = ragi->sc;
 	struct xfs_inode	*ip;
 	xfs_agino_t		prev_agino = NULLAGINO;
 	xfs_agino_t		next_agino = ragi->iunlink_heads[bucket];
 	int			error = 0;
 
+	xagino_bitmap_init(&seen);
+
 	while (next_agino != NULLAGINO) {
+		unsigned int len = 1;
+
 		if (xchk_should_terminate(ragi->sc, &error))
-			return error;
+			goto out_bitmap;
+
+		/* Inode already seen?  We're stuck in a loop */
+		if (xagino_bitmap_test(&seen, next_agino, &len)) {
+			trace_xrep_iunlink_resolve_infinite_loop(sc->sa.pag,
+					bucket, prev_agino, next_agino);
+			next_agino = NULLAGINO;
+			break;
+		}
+
+		error = xagino_bitmap_set(&seen, next_agino, 1);
+		if (error)
+			goto out_bitmap;
 
 		/* Find the next inode in the chain. */
 		ip = xfs_iunlink_lookup(sc->sa.pag, next_agino);
@@ -1379,17 +1396,17 @@ xrep_iunlink_resolve_bucket(
 			error = xrep_iunlink_store_next(ragi, next_agino,
 					NULLAGINO);
 			if (error)
-				return error;
+				goto out_bitmap;
 
 			error = xrep_iunlink_store_prev(ragi, next_agino,
 					LINKED_AGINO);
 			if (error)
-				return error;
+				goto out_bitmap;
 
 			error = xagino_bitmap_clear(&ragi->iunlink_bmp,
 					next_agino, 1);
 			if (error)
-				return error;
+				goto out_bitmap;
 
 			next_agino = ip->i_next_unlinked;
 			continue;
@@ -1430,20 +1447,20 @@ xrep_iunlink_resolve_bucket(
 		 */
 		error = xagino_bitmap_clear(&ragi->iunlink_bmp, next_agino, 1);
 		if (error)
-			return error;
+			goto out_bitmap;
 
 		/* Remember the previous inode's next pointer. */
 		if (prev_agino != NULLAGINO) {
 			error = xrep_iunlink_store_next(ragi, prev_agino,
 					next_agino);
 			if (error)
-				return error;
+				goto out_bitmap;
 		}
 
 		/* Remember this inode's previous pointer. */
 		error = xrep_iunlink_store_prev(ragi, next_agino, prev_agino);
 		if (error)
-			return error;
+			goto out_bitmap;
 
 		/* Advance the list and remember this inode. */
 		prev_agino = next_agino;
@@ -1454,10 +1471,12 @@ xrep_iunlink_resolve_bucket(
 	if (prev_agino != NULLAGINO) {
 		error = xrep_iunlink_store_next(ragi, prev_agino, next_agino);
 		if (error)
-			return error;
+			goto out_bitmap;
 	}
 
-	return 0;
+out_bitmap:
+	xagino_bitmap_destroy(&seen);
+	return error;
 }
 
 /* Reinsert this unlinked inode into the head of the staged bucket list. */
fs/xfs/scrub/trace.h +1 -0
@@ -3456,6 +3456,7 @@ DEFINE_EVENT(xrep_iunlink_resolve_class,
 	TP_PROTO(struct xfs_perag *pag, unsigned int bucket, \
 		 xfs_agino_t prev_agino, xfs_agino_t next_agino), \
 	TP_ARGS(pag, bucket, prev_agino, next_agino))
+DEFINE_REPAIR_IUNLINK_RESOLVE_EVENT(xrep_iunlink_resolve_infinite_loop);
 DEFINE_REPAIR_IUNLINK_RESOLVE_EVENT(xrep_iunlink_resolve_uncached);
 DEFINE_REPAIR_IUNLINK_RESOLVE_EVENT(xrep_iunlink_resolve_wronglist);
 DEFINE_REPAIR_IUNLINK_RESOLVE_EVENT(xrep_iunlink_resolve_nolist);
author maintainer
#109 lore raw

PATCH 105/220 xfs: dont return EFSCORRUPTED when scrubbing corrupt parent pointers

In reply to Greg Kroah-Hartman

6.12-stable review patch. If anyone has any objections, please let me know.

------------------

From: Darrick J. Wong <[email protected]>

commit 8af9cd79cdf6ee96ec610d707db22244fa21eb40 upstream.

LOLLM noticed that scrub sets the CORRUPT flag when xfs_parent_from_attr thinks it's been given a corrupt parent pointer. This eliminates the potential to repair the filesystem because that error code is bubbled up the call stack. Fix this by collapsing them all to ECANCELED in xchk_parent_pptr, which doesn't have that trait.

Fixes 0d29a20fbdba89 ("xfs: scrub parent pointers")
Assisted-by LOLLM # finding obvious bugs
---
 fs/xfs/scrub/parent.c |    2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
fs/xfs/scrub/parent.c +1 -1
@@ -472,7 +472,7 @@ xchk_parent_scan_attr(
 			valuelen, &parent_ino, NULL);
 	if (error) {
 		xchk_fblock_set_corrupt(sc, XFS_ATTR_FORK, 0);
-		return error;
+		return -ECANCELED;
 	}
 
 	/* No self-referential parent pointers. */
author maintainer
#110 lore raw

PATCH 106/220 xfs: avoid UAF on sc->tempip in xrep_tempfile_create

In reply to Greg Kroah-Hartman

6.12-stable review patch. If anyone has any objections, please let me know.

------------------

From: Darrick J. Wong <[email protected]>

commit 0c88e10d12de9ca7cbed1467bb1b52310101bff8 upstream.

LOLLM noticed a potential UAF if the tempfile creation code fails after it set sc->tempip. Fix that.

Fixes 84c14ee39dd388 ("xfs: create temporary files and directories for online repair")
Assisted-by LOLLM # finding obvious bugs
---
 fs/xfs/scrub/tempfile.c |    1 +
 1 file changed, 1 insertion(+)
fs/xfs/scrub/tempfile.c +1 -0
@@ -173,6 +173,7 @@ out_release_inode:
 		xfs_iunlock(sc->tempip, XFS_ILOCK_EXCL);
 		xfs_finish_inode_setup(sc->tempip);
 		xchk_irele(sc, sc->tempip);
+		sc->tempip = NULL;
 	}
 out_release_dquots:
 	xfs_qm_dqrele(udqp);
author maintainer
#111 lore raw

PATCH 107/220 xfs: fix exchange-range reflink flag clearing issue with INO1_WRITTEN

In reply to Greg Kroah-Hartman

6.12-stable review patch. If anyone has any objections, please let me know.

------------------

From: Lin Jiapeng <[email protected]>

commit b2d5a81dae385333f9734910277fbf94c78bd17f upstream.

When exchanging two full-file ranges, xmi_can_exchange_reflink_flags() can move the reflink inode flag from the file that currently has it to the other file, as long as exactly one side is marked. This assumes that the file contents, and therefore all shared extents, are exchanged.

That assumption is not true when XFS_EXCHMAPS_INO1_WRITTEN is set. xfs_exchmaps_can_skip_mapping() can skip hole and unwritten mappings from file1, so an exchange can complete without moving every mapping that the earlier flag-swap decision accounted for. In that case the post-operation cleanup can clear the reflink flag from an inode that still owns shared written extents. Later writes then take the non-reflink write path and may update blocks that should still have been protected by CoW, which shows up as data corruption between reflink-related files.

Fix this by disabling the reflink flag exchange whenever XFS_EXCHMAPS_INO1_WRITTEN is requested. The contents exchange can still proceed; the conservative outcome is that both inodes keep the reflink flag. The regular reflink flag cleanup path can drop the extra flag later once the inode no longer has shared extents.

Fixes 966ceafc7a43 ("xfs: create deferred log items for file mapping exchanges")
---
 fs/xfs/libxfs/xfs_exchmaps.c |   10 ++++++++++
 1 file changed, 10 insertions(+)
fs/xfs/libxfs/xfs_exchmaps.c +10 -0
@@ -957,6 +957,16 @@ xmi_can_exchange_reflink_flags(
 {
 	struct xfs_mount		*mp = req->ip1->i_mount;
 
+	/*
+	 * The INO1_WRITTEN optimization can skip exchanging hole and
+	 * unwritten mappings, which means we cannot guarantee that all
+	 * shared extents actually moved to the other file.  Clearing the
+	 * reflink flag of an inode that still holds shared extents breaks
+	 * the CoW write path, so refuse to exchange the flags in that case.
+	 */
+	if (req->flags & XFS_EXCHMAPS_INO1_WRITTEN)
+		return false;
+
 	if (hweight32(reflink_state) != 1)
 		return false;
 	if (req->startoff1 != 0 || req->startoff2 != 0)
author maintainer
#112 lore raw

PATCH 108/220 xfs: dont swallow dquot recovery verification errors

In reply to Greg Kroah-Hartman

6.12-stable review patch. If anyone has any objections, please let me know.

------------------

From: Long Li <[email protected]>

commit e2b4a856085e9bd939bde2dee0d08b1d41babde9 upstream.

xlog_recover_dquot_commit_pass2() validates the recovered dquot with xfs_dqblk_verify() and, on failure, sets error = -EFSCORRUPTED and jumps to out_release. But out_release unconditionally returns 0, so the corruption error is discarded: the caller xlog_recover_items_pass2() sees success, log recovery proceeds as if the dquot were valid, and the corrupt quota buffer can be written back to disk.

Fixes 9c235dfc3d3f ("xfs: dquot recovery does not validate the recovered dquot")
---
 fs/xfs/xfs_dquot_item_recover.c |    2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
fs/xfs/xfs_dquot_item_recover.c +1 -1
@@ -173,7 +173,7 @@ xlog_recover_dquot_commit_pass2(
 
 out_release:
 	xfs_buf_relse(bp);
-	return 0;
+	return error;
 }
 
 const struct xlog_recover_item_ops xlog_dquot_item_ops = {
author maintainer
#113 lore raw

PATCH 109/220 xfs: check xfarray iteration errors when committing unlinked inode lists

In reply to Greg Kroah-Hartman

6.12-stable review patch. If anyone has any objections, please let me know.

------------------

From: Darrick J. Wong <[email protected]>

commit 2daf3ed5d059dec79c123aec42eb8d28e0c016d4 upstream.

LOLLM noticed that we neglect to check for xfarray_iter itself returning errors when writing a new AGI. Fix that.

Fixes ab97f4b1c03075 ("xfs: repair AGI unlinked inode bucket lists")
Assisted-by LOLLM # finding obvious bugs
---
 fs/xfs/scrub/agheader_repair.c |    4 ++++
 1 file changed, 4 insertions(+)
fs/xfs/scrub/agheader_repair.c +4 -0
@@ -1748,6 +1748,8 @@ xrep_iunlink_commit(
 		if (error)
 			return error;
 	}
+	if (error < 0)
+		return error;
 
 	/* Fix all the back links */
 	idx = XFARRAY_CURSOR_INIT;
@@ -1756,6 +1758,8 @@ xrep_iunlink_commit(
 		if (error)
 			return error;
 	}
+	if (error < 0)
+		return error;
 
 	/* Copy the staged iunlink buckets to the new AGI. */
 	for (i = 0; i < XFS_AGI_UNLINKED_BUCKETS; i++) {
author maintainer
#114 lore raw

PATCH 110/220 xfs: check v5 superblock features early

In reply to Greg Kroah-Hartman

6.12-stable review patch. If anyone has any objections, please let me know.

------------------

From: Christoph Hellwig <[email protected]>

commit eb6b2cc1fc8ad566d746d128a559989ff0bba5cc upstream.

When working on a new features that reuses the existing pad in the superblock, I noticed that mounting such a file system on an old kernel logs a rather confusing warning:

XFS (vdc): Metadir superblock padding fields must be zero.

This is because we only validate the various feature fields in v5 superblocks after the common superblock validation helper is called.

Fix this by calling the feature validation first.

Fixes eca383fcd63b ("xfs: refactor superblock verifiers")
Cc <[email protected]> # v4.19
---
 fs/xfs/libxfs/xfs_sb.c |    4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)
fs/xfs/libxfs/xfs_sb.c +2 -2
@@ -891,10 +891,10 @@ xfs_sb_read_verify(
 	 * because _verify_common checks the on-disk values.
 	 */
 	__xfs_sb_from_disk(&sb, dsb, false);
-	error = xfs_validate_sb_common(mp, bp, &sb);
+	error = xfs_validate_sb_read(mp, &sb);
 	if (error)
 		goto out_error;
-	error = xfs_validate_sb_read(mp, &sb);
+	error = xfs_validate_sb_common(mp, bp, &sb);
 
 out_error:
 	if (error == -EFSCORRUPTED || error == -EFSBADCRC)
author maintainer
#115 lore raw

PATCH 111/220 ceph: Remove ceph_writepage()

In reply to Greg Kroah-Hartman

6.12-stable review patch. If anyone has any objections, please let me know.

------------------

From: "Matthew Wilcox (Oracle)" <[email protected]>

[ Upstream commit 19a288110435408fcf2cb274823f2a17d03caf9b ]

Ceph already has a writepages operation which is preferred over writepage in all situations except for page migration. By adding a migrate_folio operation, there will be no situations in which ->writepage should be called. filemap_migrate_folio() is an appropriate operation to use because the ceph data stored in folio->private does not contain any reference to the memory address of the folio.

Stable-dep-of: cbf59617cd71 ("ceph: fix writeback_count leak in write_folio_nounlock()")

---
 fs/ceph/addr.c |   28 +---------------------------
 1 file changed, 1 insertion(+), 27 deletions(-)
fs/ceph/addr.c +1 -27
@@ -824,32 +824,6 @@ static int writepage_nounlock(struct pag
 	return err;
 }
 
-static int ceph_writepage(struct page *page, struct writeback_control *wbc)
-{
-	int err;
-	struct inode *inode = page->mapping->host;
-	BUG_ON(!inode);
-	ihold(inode);
-
-	if (wbc->sync_mode == WB_SYNC_NONE &&
-	    ceph_inode_to_fs_client(inode)->write_congested) {
-		redirty_page_for_writepage(wbc, page);
-		return AOP_WRITEPAGE_ACTIVATE;
-	}
-
-	folio_wait_private_2(page_folio(page)); /* [DEPRECATED] */
-
-	err = writepage_nounlock(page, wbc);
-	if (err == -ERESTARTSYS) {
-		/* direct memory reclaimer was killed by SIGKILL. return 0
-		 * to prevent caller from setting mapping/page error */
-		err = 0;
-	}
-	unlock_page(page);
-	iput(inode);
-	return err;
-}
-
 /*
  * async writeback completion handler.
  *
@@ -1598,7 +1572,6 @@ out:
 const struct address_space_operations ceph_aops = {
 	.read_folio = netfs_read_folio,
 	.readahead = netfs_readahead,
-	.writepage = ceph_writepage,
 	.writepages = ceph_writepages_start,
 	.write_begin = ceph_write_begin,
 	.write_end = ceph_write_end,
@@ -1606,6 +1579,7 @@ const struct address_space_operations ce
 	.invalidate_folio = ceph_invalidate_folio,
 	.release_folio = netfs_release_folio,
 	.direct_IO = noop_direct_IO,
+	.migrate_folio = filemap_migrate_folio,
 };
 
 static void ceph_block_sigs(sigset_t *oldset)
author maintainer
#116 lore raw

PATCH 112/220 ceph: Use a folio in ceph_page_mkwrite()

In reply to Greg Kroah-Hartman

6.12-stable review patch. If anyone has any objections, please let me know.

------------------

From: "Matthew Wilcox (Oracle)" <[email protected]>

[ Upstream commit 88a59bda3f3786107694a3f5fd7f9df421752c21 ]

Convert the passed page to a folio and use it
throughout ceph_page_mkwrite(). Removes the last call to page_mkwrite_check_truncate(), the last call to offset_in_thp() and one of the last calls to thp_size(). Saves a few calls to compound_head().

Stable-dep-of: cbf59617cd71 ("ceph: fix writeback_count leak in write_folio_nounlock()")

---
 fs/ceph/addr.c |   26 +++++++++++++-------------
 1 file changed, 13 insertions(+), 13 deletions(-)
fs/ceph/addr.c +13 -13
@@ -1696,8 +1696,8 @@ static vm_fault_t ceph_page_mkwrite(stru
 	struct ceph_inode_info *ci = ceph_inode(inode);
 	struct ceph_file_info *fi = vma->vm_file->private_data;
 	struct ceph_cap_flush *prealloc_cf;
-	struct page *page = vmf->page;
-	loff_t off = page_offset(page);
+	struct folio *folio = page_folio(vmf->page);
+	loff_t off = folio_pos(folio);
 	loff_t size = i_size_read(inode);
 	size_t len;
 	int want, got, err;
@@ -1714,10 +1714,10 @@ static vm_fault_t ceph_page_mkwrite(stru
 	sb_start_pagefault(inode->i_sb);
 	ceph_block_sigs(&oldset);
 
-	if (off + thp_size(page) <= size)
-		len = thp_size(page);
+	if (off + folio_size(folio) <= size)
+		len = folio_size(folio);
 	else
-		len = offset_in_thp(page, size);
+		len = offset_in_folio(folio, size);
 
 	doutc(cl, "%llx.%llx %llu~%zd getting caps i_size %llu\n",
 	      ceph_vinop(inode), off, len, size);
@@ -1734,30 +1734,30 @@ static vm_fault_t ceph_page_mkwrite(stru
 	doutc(cl, "%llx.%llx %llu~%zd got cap refs on %s\n", ceph_vinop(inode),
 	      off, len, ceph_cap_string(got));
 
-	/* Update time before taking page lock */
+	/* Update time before taking folio lock */
 	file_update_time(vma->vm_file);
 	inode_inc_iversion_raw(inode);
 
 	do {
 		struct ceph_snap_context *snapc;
 
-		lock_page(page);
+		folio_lock(folio);
 
-		if (page_mkwrite_check_truncate(page, inode) < 0) {
-			unlock_page(page);
+		if (folio_mkwrite_check_truncate(folio, inode) < 0) {
+			folio_unlock(folio);
 			ret = VM_FAULT_NOPAGE;
 			break;
 		}
 
-		snapc = ceph_find_incompatible(page);
+		snapc = ceph_find_incompatible(&folio->page);
 		if (!snapc) {
-			/* success.  we'll keep the page locked. */
-			set_page_dirty(page);
+			/* success.  we'll keep the folio locked. */
+			folio_mark_dirty(folio);
 			ret = VM_FAULT_LOCKED;
 			break;
 		}
 
-		unlock_page(page);
+		folio_unlock(folio);
 
 		if (IS_ERR(snapc)) {
 			ret = VM_FAULT_SIGBUS;
author maintainer
#117 lore raw

PATCH 113/220 ceph: Convert ceph_find_incompatible() to take a folio

In reply to Greg Kroah-Hartman

6.12-stable review patch. If anyone has any objections, please let me know.

------------------

From: "Matthew Wilcox (Oracle)" <[email protected]>

[ Upstream commit f9707a8b5b9d0a631e0a64eab5c3d2bb6d43758c ]

Both callers already have the folio. Pass it in and use it throughout. Removes some hidden calls to compound_head() and a reference to page->mapping.

Stable-dep-of: cbf59617cd71 ("ceph: fix writeback_count leak in write_folio_nounlock()")

---
 fs/ceph/addr.c |   38 +++++++++++++++++++-------------------
 1 file changed, 19 insertions(+), 19 deletions(-)
fs/ceph/addr.c +19 -19
@@ -1425,56 +1425,56 @@ static int context_is_writeable_or_writt
 
 /**
  * ceph_find_incompatible - find an incompatible context and return it
- * @page: page being dirtied
+ * @folio: folio being dirtied
  *
- * We are only allowed to write into/dirty a page if the page is
+ * We are only allowed to write into/dirty a folio if the folio is
  * clean, or already dirty within the same snap context. Returns a
  * conflicting context if there is one, NULL if there isn't, or a
  * negative error code on other errors.
  *
- * Must be called with page lock held.
+ * Must be called with folio lock held.
  */
 static struct ceph_snap_context *
-ceph_find_incompatible(struct page *page)
+ceph_find_incompatible(struct folio *folio)
 {
-	struct inode *inode = page->mapping->host;
+	struct inode *inode = folio->mapping->host;
 	struct ceph_client *cl = ceph_inode_to_client(inode);
 	struct ceph_inode_info *ci = ceph_inode(inode);
 
 	if (ceph_inode_is_shutdown(inode)) {
-		doutc(cl, " %llx.%llx page %p is shutdown\n",
-		      ceph_vinop(inode), page);
+		doutc(cl, " %llx.%llx folio %p is shutdown\n",
+		      ceph_vinop(inode), folio);
 		return ERR_PTR(-ESTALE);
 	}
 
 	for (;;) {
 		struct ceph_snap_context *snapc, *oldest;
 
-		wait_on_page_writeback(page);
+		folio_wait_writeback(folio);
 
-		snapc = page_snap_context(page);
+		snapc = page_snap_context(&folio->page);
 		if (!snapc || snapc == ci->i_head_snapc)
 			break;
 
 		/*
-		 * this page is already dirty in another (older) snap
+		 * this folio is already dirty in another (older) snap
 		 * context!  is it writeable now?
 		 */
 		oldest = get_oldest_context(inode, NULL, NULL);
 		if (snapc->seq > oldest->seq) {
 			/* not writeable -- return it for the caller to deal with */
 			ceph_put_snap_context(oldest);
-			doutc(cl, " %llx.%llx page %p snapc %p not current or oldest\n",
-			      ceph_vinop(inode), page, snapc);
+			doutc(cl, " %llx.%llx folio %p snapc %p not current or oldest\n",
+			      ceph_vinop(inode), folio, snapc);
 			return ceph_get_snap_context(snapc);
 		}
 		ceph_put_snap_context(oldest);
 
-		/* yay, writeable, do it now (without dropping page lock) */
-		doutc(cl, " %llx.%llx page %p snapc %p not current, but oldest\n",
-		      ceph_vinop(inode), page, snapc);
-		if (clear_page_dirty_for_io(page)) {
-			int r = writepage_nounlock(page, NULL);
+		/* yay, writeable, do it now (without dropping folio lock) */
+		doutc(cl, " %llx.%llx folio %p snapc %p not current, but oldest\n",
+		      ceph_vinop(inode), folio, snapc);
+		if (folio_clear_dirty_for_io(folio)) {
+			int r = writepage_nounlock(&folio->page, NULL);
 			if (r < 0)
 				return ERR_PTR(r);
 		}
@@ -1489,7 +1489,7 @@ static int ceph_netfs_check_write_begin(
 	struct ceph_inode_info *ci = ceph_inode(inode);
 	struct ceph_snap_context *snapc;
 
-	snapc = ceph_find_incompatible(folio_page(*foliop, 0));
+	snapc = ceph_find_incompatible(*foliop);
 	if (snapc) {
 		int r;
 
@@ -1749,7 +1749,7 @@ static vm_fault_t ceph_page_mkwrite(stru
 			break;
 		}
 
-		snapc = ceph_find_incompatible(&folio->page);
+		snapc = ceph_find_incompatible(folio);
 		if (!snapc) {
 			/* success.  we'll keep the folio locked. */
 			folio_mark_dirty(folio);
author maintainer
#118 lore raw

PATCH 114/220 ceph: Convert writepage_nounlock() to write_folio_nounlock()

In reply to Greg Kroah-Hartman

6.12-stable review patch. If anyone has any objections, please let me know.

------------------

From: "Matthew Wilcox (Oracle)" <[email protected]>

[ Upstream commit 62171c16da6000811e172a76a1f73d132c4697e8 ]

Remove references to page->index, page->mapping, thp_size(), page_offset() and other page APIs in favour of their more efficient folio replacements.

Stable-dep-of: cbf59617cd71 ("ceph: fix writeback_count leak in write_folio_nounlock()")

---
 fs/ceph/addr.c |   67 ++++++++++++++++++++++++++++-----------------------------
 1 file changed, 34 insertions(+), 33 deletions(-)
fs/ceph/addr.c +34 -33
@@ -670,22 +670,23 @@ static u64 get_writepages_data_length(st
 }
 
 /*
- * Write a single page, but leave the page locked.
+ * Write a folio, but leave it locked.
  *
  * If we get a write error, mark the mapping for error, but still adjust the
- * dirty page accounting (i.e., page is no longer dirty).
+ * dirty page accounting (i.e., folio is no longer dirty).
  */
-static int writepage_nounlock(struct page *page, struct writeback_control *wbc)
+static int write_folio_nounlock(struct folio *folio,
+		struct writeback_control *wbc)
 {
-	struct folio *folio = page_folio(page);
-	struct inode *inode = page->mapping->host;
+	struct page *page = &folio->page;
+	struct inode *inode = folio->mapping->host;
 	struct ceph_inode_info *ci = ceph_inode(inode);
 	struct ceph_fs_client *fsc = ceph_inode_to_fs_client(inode);
 	struct ceph_client *cl = fsc->client;
 	struct ceph_snap_context *snapc, *oldest;
-	loff_t page_off = page_offset(page);
+	loff_t page_off = folio_pos(folio);
 	int err;
-	loff_t len = thp_size(page);
+	loff_t len = folio_size(folio);
 	loff_t wlen;
 	struct ceph_writeback_ctl ceph_wbc;
 	struct ceph_osd_client *osdc = &fsc->client->osdc;
@@ -693,27 +694,27 @@ static int writepage_nounlock(struct pag
 	bool caching = ceph_is_cache_enabled(inode);
 	struct page *bounce_page = NULL;
 
-	doutc(cl, "%llx.%llx page %p idx %lu\n", ceph_vinop(inode), page,
-	      page->index);
+	doutc(cl, "%llx.%llx folio %p idx %lu\n", ceph_vinop(inode), folio,
+	      folio->index);
 
 	if (ceph_inode_is_shutdown(inode))
 		return -EIO;
 
 	/* verify this is a writeable snap context */
-	snapc = page_snap_context(page);
+	snapc = page_snap_context(&folio->page);
 	if (!snapc) {
-		doutc(cl, "%llx.%llx page %p not dirty?\n", ceph_vinop(inode),
-		      page);
+		doutc(cl, "%llx.%llx folio %p not dirty?\n", ceph_vinop(inode),
+		      folio);
 		return 0;
 	}
 	oldest = get_oldest_context(inode, &ceph_wbc, snapc);
 	if (snapc->seq > oldest->seq) {
-		doutc(cl, "%llx.%llx page %p snapc %p not writeable - noop\n",
-		      ceph_vinop(inode), page, snapc);
+		doutc(cl, "%llx.%llx folio %p snapc %p not writeable - noop\n",
+		      ceph_vinop(inode), folio, snapc);
 		/* we should only noop if called by kswapd */
 		WARN_ON(!(current->flags & PF_MEMALLOC));
 		ceph_put_snap_context(oldest);
-		redirty_page_for_writepage(wbc, page);
+		folio_redirty_for_writepage(wbc, folio);
 		return 0;
 	}
 	ceph_put_snap_context(oldest);
@@ -730,8 +731,8 @@ static int writepage_nounlock(struct pag
 		len = ceph_wbc.i_size - page_off;
 
 	wlen = IS_ENCRYPTED(inode) ? round_up(len, CEPH_FSCRYPT_BLOCK_SIZE) : len;
-	doutc(cl, "%llx.%llx page %p index %lu on %llu~%llu snapc %p seq %lld\n",
-	      ceph_vinop(inode), page, page->index, page_off, wlen, snapc,
+	doutc(cl, "%llx.%llx folio %p index %lu on %llu~%llu snapc %p seq %lld\n",
+	      ceph_vinop(inode), folio, folio->index, page_off, wlen, snapc,
 	      snapc->seq);
 
 	if (atomic_long_inc_return(&fsc->writeback_count) >
@@ -744,32 +745,32 @@ static int writepage_nounlock(struct pag
 				    ceph_wbc.truncate_seq,
 				    ceph_wbc.truncate_size, true);
 	if (IS_ERR(req)) {
-		redirty_page_for_writepage(wbc, page);
+		folio_redirty_for_writepage(wbc, folio);
 		return PTR_ERR(req);
 	}
 
 	if (wlen < len)
 		len = wlen;
 
-	set_page_writeback(page);
+	folio_start_writeback(folio);
 	if (caching)
-		ceph_set_page_fscache(page);
+		ceph_set_page_fscache(&folio->page);
 	ceph_fscache_write_to_cache(inode, page_off, len, caching);
 
 	if (IS_ENCRYPTED(inode)) {
-		bounce_page = fscrypt_encrypt_pagecache_blocks(page,
+		bounce_page = fscrypt_encrypt_pagecache_blocks(&folio->page,
 						    CEPH_FSCRYPT_BLOCK_SIZE, 0,
 						    GFP_NOFS);
 		if (IS_ERR(bounce_page)) {
-			redirty_page_for_writepage(wbc, page);
-			end_page_writeback(page);
+			folio_redirty_for_writepage(wbc, folio);
+			folio_end_writeback(folio);
 			ceph_osdc_put_request(req);
 			return PTR_ERR(bounce_page);
 		}
 	}
 
 	/* it may be a short write due to an object boundary */
-	WARN_ON_ONCE(len > thp_size(page));
+	WARN_ON_ONCE(len > folio_size(folio));
 	osd_req_op_extent_osd_data_pages(req, 0,
 			bounce_page ? &bounce_page : &page, wlen, 0,
 			false, false);
@@ -795,25 +796,25 @@ static int writepage_nounlock(struct pag
 		if (err == -ERESTARTSYS) {
 			/* killed by SIGKILL */
 			doutc(cl, "%llx.%llx interrupted page %p\n",
-			      ceph_vinop(inode), page);
-			redirty_page_for_writepage(wbc, page);
-			end_page_writeback(page);
+			      ceph_vinop(inode), folio);
+			folio_redirty_for_writepage(wbc, folio);
+			folio_end_writeback(folio);
 			return err;
 		}
 		if (err == -EBLOCKLISTED)
 			fsc->blocklisted = true;
-		doutc(cl, "%llx.%llx setting page/mapping error %d %p\n",
-		      ceph_vinop(inode), err, page);
+		doutc(cl, "%llx.%llx setting mapping error %d %p\n",
+		      ceph_vinop(inode), err, folio);
 		mapping_set_error(&inode->i_data, err);
 		wbc->pages_skipped++;
 	} else {
 		doutc(cl, "%llx.%llx cleaned page %p\n",
-		      ceph_vinop(inode), page);
+		      ceph_vinop(inode), folio);
 		err = 0;  /* vfs expects us to return 0 */
 	}
-	oldest = detach_page_private(page);
+	oldest = folio_detach_private(folio);
 	WARN_ON_ONCE(oldest != snapc);
-	end_page_writeback(page);
+	folio_end_writeback(folio);
 	ceph_put_wrbuffer_cap_refs(ci, 1, snapc);
 	ceph_put_snap_context(snapc);  /* page's reference */
 
@@ -1474,7 +1475,7 @@ ceph_find_incompatible(struct folio *fol
 		doutc(cl, " %llx.%llx folio %p snapc %p not current, but oldest\n",
 		      ceph_vinop(inode), folio, snapc);
 		if (folio_clear_dirty_for_io(folio)) {
-			int r = writepage_nounlock(&folio->page, NULL);
+			int r = write_folio_nounlock(folio, NULL);
 			if (r < 0)
 				return ERR_PTR(r);
 		}
author maintainer
#119 lore raw

PATCH 115/220 ceph: fix writeback_count leak in write_folio_nounlock()

In reply to Greg Kroah-Hartman

6.12-stable review patch. If anyone has any objections, please let me know.

------------------

From: Wentao Liang <[email protected]>

[ Upstream commit cbf59617cd715219e84c50d106a3d0e1e8ba054e ]

write_folio_nounlock() increments fsc->writeback_count to track in-flight writeback operations. On several error paths where the function returns early (folio lookup failure, snapshot context allocation failure, and writepages submission failure), the function returns without calling atomic_long_dec_return() to decrement the counter.

Each leaked increment keeps the counter above zero, which can prevent the filesystem from cleanly unmounting or suspending writes.

Add atomic_long_dec_return() calls on all error paths that currently return without decrementing the counter.

Fixes d55207717ded ("ceph: add encryption support to writepage and writepages")
---
 fs/ceph/addr.c |    9 +++++++++
 1 file changed, 9 insertions(+)
fs/ceph/addr.c +9 -0
@@ -746,6 +746,9 @@ static int write_folio_nounlock(struct f
 				    ceph_wbc.truncate_size, true);
 	if (IS_ERR(req)) {
 		folio_redirty_for_writepage(wbc, folio);
+		if (atomic_long_dec_return(&fsc->writeback_count) <
+				CONGESTION_OFF_THRESH(fsc->mount_options->congestion_kb))
+			fsc->write_congested = false;
 		return PTR_ERR(req);
 	}
 
@@ -765,6 +768,9 @@ static int write_folio_nounlock(struct f
 			folio_redirty_for_writepage(wbc, folio);
 			folio_end_writeback(folio);
 			ceph_osdc_put_request(req);
+			if (atomic_long_dec_return(&fsc->writeback_count) <
+					CONGESTION_OFF_THRESH(fsc->mount_options->congestion_kb))
+				fsc->write_congested = false;
 			return PTR_ERR(bounce_page);
 		}
 	}
@@ -799,6 +805,9 @@ static int write_folio_nounlock(struct f
 			      ceph_vinop(inode), folio);
 			folio_redirty_for_writepage(wbc, folio);
 			folio_end_writeback(folio);
+			if (atomic_long_dec_return(&fsc->writeback_count) <
+					CONGESTION_OFF_THRESH(fsc->mount_options->congestion_kb))
+				fsc->write_congested = false;
 			return err;
 		}
 		if (err == -EBLOCKLISTED)
author maintainer
#120 lore raw

PATCH 116/220 ceph: avoid fs reclaim while using current->journal_info

In reply to Greg Kroah-Hartman

6.12-stable review patch. If anyone has any objections, please let me know.

------------------

From: Max Kellermann <[email protected]>

[ Upstream commit 5b602344a49e039e792ce5a8923bcc61412ee134 ]

handle_reply() stores a ceph_mds_request pointer in current->journal_info while filling the inode and dentry cache from an MDS reply.

An allocation in this section can enter direct reclaim and prune dentries from another filesystem. If this dirties an ext4 inode, ext4 starts a JBD2 transaction. JBD2 interprets the Ceph request in current->journal_info as a journal handle and dereferences the request's r_tid as h_transaction, causing a kernel crash, e.g.:

 Unable to handle kernel paging request at virtual address 00000000077b4818
 [...]
 Internal error: Oops: 0000000096000004 [#1]  SMP
 Modules linked in:
 CPU: 6 UID: 0 PID: 2699135 Comm: kworker/6:3 Tainted: G        W           6.18.38-i3 #1113 NONE
 [...]
 Workqueue: ceph-msgr ceph_con_workfn
 pstate: 80400009 (Nzcv daif +PAN -UAO -TCO -DIT -SSBS BTYPE=--)
 pc : jbd2__journal_start+0x2c/0x208
 lr : __ext4_journal_start_sb+0x100/0x178
 [...]
 Call trace:
  jbd2__journal_start+0x2c/0x208 (P)
  __ext4_journal_start_sb+0x100/0x178
  ext4_dirty_inode+0x3c/0x90
  __mark_inode_dirty+0x58/0x400
  iput.part.0+0x2b0/0x370
  iput+0x18/0x30
  dentry_unlink_inode+0xc0/0x158
  __dentry_kill+0x80/0x250
  shrink_dentry_list+0x90/0x130
  prune_dcache_sb+0x60/0x98
  super_cache_scan+0xe8/0x190
  do_shrink_slab+0x174/0x388
  shrink_slab+0xd8/0x4c0
  shrink_node+0x31c/0x908
  do_try_to_free_pages+0xd0/0x508
  try_to_free_pages+0x11c/0x238
  __alloc_frozen_pages_noprof+0x4d0/0xdd0
  __folio_alloc_noprof+0x18/0x70
  __filemap_get_folio+0x248/0x440
  ceph_readdir_prepopulate+0x570/0x9e8
  mds_dispatch+0x1424/0x1ba0
  ceph_con_process_message+0x74/0xa0
  ceph_con_v1_try_read+0x3a0/0x1510
  ceph_con_workfn+0x260/0x460

Enter a scoped NOFS allocation context and leave it after clearing journal_info. This prevents filesystem reclaim from recursing into another filesystem while the field contains Ceph-private data.

---
 fs/ceph/mds_client.c |   11 +++++++++++
 1 file changed, 11 insertions(+)
fs/ceph/mds_client.c +11 -0
@@ -6,6 +6,7 @@
 #include <linux/slab.h>
 #include <linux/gfp.h>
 #include <linux/sched.h>
+#include <linux/sched/mm.h>
 #include <linux/debugfs.h>
 #include <linux/seq_file.h>
 #include <linux/ratelimit.h>
@@ -3842,6 +3843,7 @@ static void handle_reply(struct ceph_mds
 	struct ceph_mds_reply_head *head = msg->front.iov_base;
 	struct ceph_mds_reply_info_parsed *rinfo;  /* parsed reply info */
 	struct ceph_snap_realm *realm;
+	unsigned int nofs_flags;
 	u64 tid;
 	int err, result;
 	int mds = session->s_mds;
@@ -3984,6 +3986,14 @@ static void handle_reply(struct ceph_mds
 
 	/* insert trace into our cache */
 	mutex_lock(&req->r_fill_mutex);
+
+	/* disable fs reclaim while we are using current->journal_info
+	 * for our own purposes, or else shrinkers of other
+	 * filesystems might dereference this pointer as a different
+	 * type
+	 */
+	nofs_flags = memalloc_nofs_save();
+
 	current->journal_info = req;
 	err = ceph_fill_trace(mdsc->fsc->sb, req);
 	if (err == 0) {
@@ -3992,6 +4002,7 @@ static void handle_reply(struct ceph_mds
 			err = ceph_readdir_prepopulate(req, req->r_session);
 	}
 	current->journal_info = NULL;
+	memalloc_nofs_restore(nofs_flags);
 	mutex_unlock(&req->r_fill_mutex);
 
 	up_read(&mdsc->snap_rwsem);
author maintainer
#121 lore raw

PATCH 117/220 ceph: fix hanging __ceph_get_caps() with stale mds_wanted

In reply to Greg Kroah-Hartman

6.12-stable review patch. If anyone has any objections, please let me know.

------------------

From: Max Kellermann <[email protected]>

[ Upstream commit 50958bb928bad3bdba9e5d1b7ff4bbadcf6951e6 ]

A reader can hang forever in __ceph_get_caps() when the client no longer holds FILE_RD, but local cap state still says that the capability is already wanted (via mds_wanted).

One way to trigger this is through MDS cap revocation. If another client performs a conflicting operation, the MDS can revoke FILE_RD from the reader; the next read then has to reacquire FILE_RD. If the cap update that should request FILE_RD never reaches the MDS after cap->mds_wanted was raised, the reader is left holding only non-file caps while local mds_wanted still includes the file read caps.

In that state, try_get_cap_refs() sees need <= mds_wanted and returns 0, so __ceph_get_caps() just waits on i_cap_wq. If the cap update that was supposed to request FILE_RD never reaches the MDS after cap->mds_wanted was` raised, no further request is sent and the waiter can sleep indefinitely until unrelated cap traffic happens to wake it up.

The ordering issue is that cap->mds_wanted is updated in __prep_cap() before the CEPH_MSG_CLIENT_CAPS message is actually queued for send. That makes one field serve two different meanings at
once: what this client wants, and what the client believes the MDS already knows it wants.

A proper fix would be to split those states and track whether a cap update is actually in flight or has been observed by the MDS. However, simply moving the cap->mds_wanted assignment later would not be sufficient: queueing the message in the messenger does not guarantee that the MDS processed that specific wanted set, and reconnect or message loss can still invalidate that assumption. Fixing that properly would require a larger rework of the cap state machine.

To allow simpler backports to stable kernels, this patch implements a simpler workaround:

- stop waiting forever in __ceph_get_caps(); after a bounded wait, fall back to the renew path

- make ceph_renew_caps() issue a synchronous OPEN request whenever the inode still does not actually hold the wanted caps, instead of only calling ceph_check_caps()

The extra issued-vs-wanted check in ceph_renew_caps() is necessary because the previous test only checked whether the inode still had any real caps at all. That is not enough after revocation: the client can still hold something like pLs and yet be missing FILE_RD completely. In that case, falling back to ceph_check_caps() is not sufficient, because it still trusts cap->mds_wanted and may resend nothing. By requiring (issued & wanted) == wanted before taking the asynchronous path, the code only uses ceph_check_caps() when the wanted caps are already actually issued. Otherwise, it sends the synchronous OPEN renew.

This preserves the existing asynchronous fast path when the wanted caps are already issued, avoids changing cap-state semantics, and fixes the hang by guaranteeing that a stalled waiter eventually retries through a path that does not rely on the stale mds_wanted state.

[ idryomov: move CEPH_GET_CAPS_WAIT_TIMEOUT from libceph.h to
mds_client.h, formatting ]

Fixes 0a454bdd501a ("ceph: reorganize __send_cap for less spinlock abuse")
---
 fs/ceph/caps.c       |   17 +++++++++++++++--
 fs/ceph/file.c       |    9 +++++----
 fs/ceph/mds_client.h |    1 +
 3 files changed, 21 insertions(+), 6 deletions(-)
fs/ceph/caps.c +15 -2
@@ -3104,7 +3104,19 @@ int __ceph_get_caps(struct inode *inode,
 					ret = -ERESTARTSYS;
 					break;
 				}
-				wait_woken(&wait, TASK_INTERRUPTIBLE, MAX_SCHEDULE_TIMEOUT);
+
+				/*
+				 * If a cap update is lost after
+				 * mds_wanted was raised, waiting
+				 * forever will never make progress.
+				 * Retry the renew path periodically
+				 * so we can resend synchronously.
+				 */
+				if (!wait_woken(&wait, TASK_INTERRUPTIBLE,
+						CEPH_GET_CAPS_WAIT_TIMEOUT)) {
+					ret = -EUCLEAN;
+					break;
+				}
 			}
 
 			remove_wait_queue(&ci->i_cap_wq, &wait);
@@ -3138,7 +3150,8 @@ int __ceph_get_caps(struct inode *inode,
 				continue;
 			}
 			if (ret == -EUCLEAN) {
-				/* session was killed, try renew caps */
+				/* session was killed or a waited cap
+				 * request needs a retry */
 				ret = ceph_renew_caps(inode, flags);
 				if (ret == 0)
 					continue;
fs/ceph/file.c +5 -4
@@ -295,7 +295,7 @@ static int ceph_init_file(struct inode *
 }
 
 /*
- * try renew caps after session gets killed.
+ * Retry cap acquisition after a stale session or a lost cap update.
  */
 int ceph_renew_caps(struct inode *inode, int fmode)
 {
@@ -303,14 +303,15 @@ int ceph_renew_caps(struct inode *inode,
 	struct ceph_client *cl = mdsc->fsc->client;
 	struct ceph_inode_info *ci = ceph_inode(inode);
 	struct ceph_mds_request *req;
-	int err, flags, wanted;
+	int err, flags, wanted, issued;
 
 	spin_lock(&ci->i_ceph_lock);
 	__ceph_touch_fmode(ci, mdsc, fmode);
 	wanted = __ceph_caps_file_wanted(ci);
+	issued = __ceph_caps_issued(ci, NULL);
 	if (__ceph_is_any_real_caps(ci) &&
-	    (!(wanted & CEPH_CAP_ANY_WR) || ci->i_auth_cap)) {
-		int issued = __ceph_caps_issued(ci, NULL);
+	    (!(wanted & CEPH_CAP_ANY_WR) || ci->i_auth_cap) &&
+	    (issued & wanted) == wanted) {
 		spin_unlock(&ci->i_ceph_lock);
 		doutc(cl, "%p %llx.%llx want %s issued %s updating mds_wanted\n",
 		      inode, ceph_vinop(inode), ceph_cap_string(wanted),
fs/ceph/mds_client.h +1 -0
@@ -74,6 +74,7 @@ struct ceph_fs_client;
 struct ceph_cap;
 
 #define MDS_AUTH_UID_ANY -1
+#define CEPH_GET_CAPS_WAIT_TIMEOUT (5 * HZ)
 
 struct ceph_mds_cap_match {
 	s64 uid;  /* default to MDS_AUTH_UID_ANY */
author maintainer
#122 lore raw

PATCH 118/220 libceph: Amend checking to fix `make W=1` build breakage

In reply to Greg Kroah-Hartman

6.12-stable review patch. If anyone has any objections, please let me know.

------------------

From: Andy Shevchenko <[email protected]>

[ Upstream commit 04d8712b079327409b09dee628378f9583e2e035 ]

In a few cases the code compares 32-bit value to a SIZE_MAX derived constant which is much higher than that value on 64-bit platforms, Clang, in particular, is not happy about this

net/ceph/osdmap.c:1441:10: error: result of comparison of constant 4611686018427387891 with expression of type 'u32' (aka 'unsigned int') is always false [-Werror,-Wtautological-constant-out-of-range-compare]
1441 | if (len > (SIZE_MAX - sizeof(*pg)) / sizeof(u32))

      |             ~~~ ^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
net/ceph/osdmap.c:1624:10: error: result of comparison of constant 2305843009213693945 with expression of type 'u32' (aka 'unsigned int') is always false [-Werror,-Wtautological-constant-out-of-range-compare]
 1624 |         if (len > (SIZE_MAX - sizeof(*pg)) / (2 * sizeof(u32)))
      |             ~~~ ^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Fix this by casting to size_t. Note, that possible replacement of SIZE_MAX by U32_MAX may lead to the behaviour changes on the corner cases.

Stable-dep-of: 9f00f9cf2be2 ("libceph: bound pg_{temp,upmap,upmap_items} length to CEPH_PG_MAX_SIZE")

---
 net/ceph/osdmap.c |    4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)
net/ceph/osdmap.c +2 -2
@@ -1440,7 +1440,7 @@ static struct ceph_pg_mapping *__decode_
 	ceph_decode_32_safe(p, end, len, e_inval);
 	if (len == 0 && incremental)
 		return NULL;	/* new_pg_temp: [] to remove */
-	if (len > (SIZE_MAX - sizeof(*pg)) / sizeof(u32))
+	if ((size_t)len > (SIZE_MAX - sizeof(*pg)) / sizeof(u32))
 		return ERR_PTR(-EINVAL);
 
 	ceph_decode_need(p, end, len * sizeof(u32), e_inval);
@@ -1621,7 +1621,7 @@ static struct ceph_pg_mapping *__decode_
 	u32 len, i;
 
 	ceph_decode_32_safe(p, end, len, e_inval);
-	if (len > (SIZE_MAX - sizeof(*pg)) / (2 * sizeof(u32)))
+	if ((size_t)len > (SIZE_MAX - sizeof(*pg)) / (2 * sizeof(u32)))
 		return ERR_PTR(-EINVAL);
 
 	ceph_decode_need(p, end, 2 * len * sizeof(u32), e_inval);
author maintainer
#123 lore raw

PATCH 119/220 libceph: bound pg_{temp,upmap,upmap_items} length to CEPH_PG_MAX_SIZE

In reply to Greg Kroah-Hartman

6.12-stable review patch. If anyone has any objections, please let me know.

------------------

From: Xiang Mei <[email protected]>

[ Upstream commit 9f00f9cf2be293efe899db67dc5272e3a9c62717 ]

__decode_pg_temp() decodes an user-controlled length but only rejects values large enough to overflow the allocation; it does not bound it to CEPH_PG_MAX_SIZE. The helper backs both pg_temp and pg_upmap decoding, and apply_upmap()/get_temp_osds() later copy the decoded list into the fixed-size on-stack array struct ceph_osds.osds[CEPH_PG_MAX_SIZE]. A monitor that sends an OSDMap with a pg_temp/pg_upmap entry longer than 32 thus causes a stack out-of-bounds write.

An OSD set for a single PG can never exceed CEPH_PG_MAX_SIZE, so reject longer entries at decode time. The bound is well below the old overflow threshold, so it also covers the allocation-size overflow the previous check guarded against.

  BUG: KASAN: stack-out-of-bounds in ceph_pg_to_up_acting_osds
  Write of size 4 ... by task exploit
   kasan_report (mm/kasan/report.c:595)
   ceph_pg_to_up_acting_osds (net/ceph/osdmap.c:2617 net/ceph/osdmap.c:2833)
   calc_target (net/ceph/osd_client.c:1638)
   __submit_request (net/ceph/osd_client.c:2394)
   ceph_osdc_start_request (net/ceph/osd_client.c:2490)
   ceph_osdc_call (net/ceph/osd_client.c:5164)
   rbd_dev_image_probe (drivers/block/rbd.c:6899)
   do_rbd_add (drivers/block/rbd.c:7138)
   ...
  kernel BUG at net/ceph/osdmap.c:2670!

[ idryomov: do the same in __decode_pg_upmap_items() ]

Fixes a303bb0e5834 ("libceph: introduce and switch to decode_pg_mapping()")
Assisted-by Claude:claude-opus-4-8
---
 net/ceph/osdmap.c |    4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)
net/ceph/osdmap.c +2 -2
@@ -1440,7 +1440,7 @@ static struct ceph_pg_mapping *__decode_
 	ceph_decode_32_safe(p, end, len, e_inval);
 	if (len == 0 && incremental)
 		return NULL;	/* new_pg_temp: [] to remove */
-	if ((size_t)len > (SIZE_MAX - sizeof(*pg)) / sizeof(u32))
+	if (len > CEPH_PG_MAX_SIZE)
 		return ERR_PTR(-EINVAL);
 
 	ceph_decode_need(p, end, len * sizeof(u32), e_inval);
@@ -1621,7 +1621,7 @@ static struct ceph_pg_mapping *__decode_
 	u32 len, i;
 
 	ceph_decode_32_safe(p, end, len, e_inval);
-	if ((size_t)len > (SIZE_MAX - sizeof(*pg)) / (2 * sizeof(u32)))
+	if (len > CEPH_PG_MAX_SIZE)
 		return ERR_PTR(-EINVAL);
 
 	ceph_decode_need(p, end, 2 * len * sizeof(u32), e_inval);
author maintainer
#124 lore raw

PATCH 120/220 mm/khugepaged: guard is_zero_pfn() calls with pte_present()

In reply to Greg Kroah-Hartman

6.12-stable review patch. If anyone has any objections, please let me know.

------------------

From: Lance Yang <[email protected]>

[ Upstream commit 074f027d15c10cb376b3ad88405b8e512fa5b3a8 ]

A non-present entry, like a swap PTE, contains completely different data (swap type and offset). pte_pfn() doesn't know this, so if we feed it a non-present entry, it will spit out a junk PFN.

What if that junk PFN happens to match the zeropage's PFN by sheer chance? While really unlikely, this would be really bad if it did.

So, let's fix this potential bug by ensuring all calls to is_zero_pfn() in khugepaged.c are properly guarded by a pte_present() check.

Stable-dep-of: 3c58f641e813 ("userfaultfd: prevent registration of special VMAs")

---
 mm/khugepaged.c |   35 +++++++++++++++++++++--------------
 1 file changed, 21 insertions(+), 14 deletions(-)
mm/khugepaged.c +21 -14
@@ -346,6 +346,13 @@ struct attribute_group khugepaged_attr_g
 };
 #endif /* CONFIG_SYSFS */
 
+static bool pte_none_or_zero(pte_t pte)
+{
+	if (pte_none(pte))
+		return true;
+	return pte_present(pte) && is_zero_pfn(pte_pfn(pte));
+}
+
 int hugepage_madvise(struct vm_area_struct *vma,
 		     unsigned long *vm_flags, int advice)
 {
@@ -529,6 +536,7 @@ static void release_pte_pages(pte_t *pte
 
 		if (pte_none(pteval))
 			continue;
+		VM_WARN_ON_ONCE(!pte_present(pteval));
 		pfn = pte_pfn(pteval);
 		if (is_zero_pfn(pfn))
 			continue;
@@ -572,8 +580,7 @@ static int __collapse_huge_page_isolate(
 	for (_pte = pte; _pte < pte + HPAGE_PMD_NR;
 	     _pte++, address += PAGE_SIZE) {
 		pte_t pteval = ptep_get(_pte);
-		if (pte_none(pteval) || (pte_present(pteval) &&
-				is_zero_pfn(pte_pfn(pteval)))) {
+		if (pte_none_or_zero(pteval)) {
 			++none_or_zero;
 			if (!userfaultfd_armed(vma) &&
 			    (!cc->is_khugepaged ||
@@ -716,17 +723,17 @@ static void __collapse_huge_page_copy_su
 	for (_pte = pte; _pte < pte + HPAGE_PMD_NR;
 	     _pte++, address += PAGE_SIZE) {
 		pteval = ptep_get(_pte);
-		if (pte_none(pteval) || is_zero_pfn(pte_pfn(pteval))) {
+		if (pte_none_or_zero(pteval)) {
 			add_mm_counter(vma->vm_mm, MM_ANONPAGES, 1);
-			if (is_zero_pfn(pte_pfn(pteval))) {
-				/*
-				 * ptl mostly unnecessary.
-				 */
-				spin_lock(ptl);
-				ptep_clear(vma->vm_mm, address, _pte);
-				spin_unlock(ptl);
-				ksm_might_unmap_zero_page(vma->vm_mm, pteval);
-			}
+			if (pte_none(pteval))
+				continue;
+			/*
+			 * ptl mostly unnecessary.
+			 */
+			spin_lock(ptl);
+			ptep_clear(vma->vm_mm, address, _pte);
+			spin_unlock(ptl);
+			ksm_might_unmap_zero_page(vma->vm_mm, pteval);
 		} else {
 			struct page *src_page = pte_page(pteval);
 
@@ -812,7 +819,7 @@ static int __collapse_huge_page_copy(pte
 		unsigned long src_addr = address + i * PAGE_SIZE;
 		struct page *src_page;
 
-		if (pte_none(pteval) || is_zero_pfn(pte_pfn(pteval))) {
+		if (pte_none_or_zero(pteval)) {
 			clear_user_highpage(page, src_addr);
 			continue;
 		}
@@ -1305,7 +1312,7 @@ static int hpage_collapse_scan_pmd(struc
 				goto out_unmap;
 			}
 		}
-		if (pte_none(pteval) || is_zero_pfn(pte_pfn(pteval))) {
+		if (pte_none_or_zero(pteval)) {
 			++none_or_zero;
 			if (!userfaultfd_armed(vma) &&
 			    (!cc->is_khugepaged ||
author maintainer
#125 lore raw

PATCH 121/220 userfaultfd: prevent registration of special VMAs

In reply to Greg Kroah-Hartman

6.12-stable review patch. If anyone has any objections, please let me know.

------------------

From: "Mike Rapoport (Microsoft)" <[email protected]>

[ Upstream commit 3c58f641e813c3c71039f8fd4d4e2a3aab713288 ]

Vova Tokarev says:

userfaultfd allows registration on shadow stack VMAs. With userfaultfd access, you can register on the shadow stack, discard a page ... and inject a page with chosen return addresses via UFFDIO_COPY.

Update vma_can_userfault() to reject VM_SHADOW_STACK.

While on it, also reject VM_SPECIAL so that if a driver would implement vm_uffd_ops, it wouldn't be possible to register special VMAs with userfaultfd.

Since VM_SPECIAL includes VM_DONTEXPAND which is set but hugetlb, exclude hugetlb VMAs from the check for VM_SPECIAL.

---
 include/linux/userfaultfd_k.h |    6 ++++++
 1 file changed, 6 insertions(+)
include/linux/userfaultfd_k.h +6 -0
@@ -216,6 +216,12 @@ static inline bool vma_can_userfault(str
 				     unsigned long vm_flags,
 				     bool wp_async)
 {
+	if (vma->vm_flags & VM_SHADOW_STACK)
+		return false;
+
+	if (!is_vm_hugetlb_page(vma) && (vma->vm_flags & VM_SPECIAL))
+		return false;
+
 	vm_flags &= __VM_UFFD_FLAGS;
 
 	if (vm_flags & VM_DROPPABLE)
author maintainer
#126 lore raw

PATCH 122/220 libceph: fix two unsafe bare decodes in decode_lockers()

In reply to Greg Kroah-Hartman

6.12-stable review patch. If anyone has any objections, please let me know.

------------------

From: Pavitra Jha <[email protected]>

[ Upstream commit a109a556115271ca7896dcda7b4b7e45e156c227 ]

decode_lockers() in cls_lock_client.c contains two bare decode operations that allow a malicious or compromised OSD to trigger slab-out-of-bounds reads:

1. ceph_decode_32(p) at the num_lockers field has no preceding bounds check. ceph_start_decoding() accepts struct_len=0 as valid -- the internal ceph_decode_need(p, end, 0, bad) always passes -- so when an OSD sends struct_len=0, ceph_start_decoding() returns success with p == end. The immediately following bare ceph_decode_32(p) then reads 4 bytes past the validated buffer boundary. The garbage value is passed directly to kzalloc_objs() as the locker count.

The sibling function decode_watchers() in osd_client.c already uses ceph_decode_32_safe() after its own ceph_start_decoding() call. decode_lockers() was the only site using the bare variant.

2. ceph_decode_8(p) after the decode_locker() loop has no preceding bounds check. If an OSD crafts num_lockers such that the loop advances p exactly to end, the subsequent bare ceph_decode_8(p) reads one byte past the validated buffer boundary. The result is passed directly into *type, which is used as a lock type discriminator by callers, giving an OSD-controlled one-byte OOB read with direct influence over the lock type field.

Fix both by replacing bare operations with their safe variants:
ceph_decode_32(p) -> ceph_decode_32_safe(p, end, *num_lockers,
err_inval)
ceph_decode_8(p) -> ceph_decode_8_safe(p, end, *type,
err_free_lockers)

The goto targets differ intentionally:
err_inval: is a new label returning -EINVAL directly. It is used for the pre-allocation failure path where *lockers is not yet allocated and must not be passed to ceph_free_lockers().

err_free_lockers: is the existing label. It is used for the post-allocation failure path where *lockers is allocated and must be freed.

ret is set to -EINVAL before ceph_decode_8_safe() so that err_free_lockers returns the correct error code on bounds violation. Without this, err_free_lockers would return a stale ret value (0 from the successful decode_locker() loop), silently swallowing the error.

-EINVAL is correct for both failure paths. The data received from the OSD is structurally malformed. -ENOMEM would misrepresent the failure class to callers and to stable@ backporters triaging error paths.

Attacker model: a malicious or compromised OSD in a multi-tenant Ceph deployment can trigger this against any kernel client that issues the lock.get_info class method (e.g. during RBD exclusive lock acquisition).

[ idryomov: trim changelog, formatting ]

Fixes d4ed4a530562 ("libceph: support for lock.lock_info")
---
 net/ceph/cls_lock_client.c |    8 ++++++--
 1 file changed, 6 insertions(+), 2 deletions(-)
net/ceph/cls_lock_client.c +6 -2
@@ -304,7 +304,7 @@ static int decode_lockers(void **p, void
 	if (ret)
 		return ret;
 
-	*num_lockers = ceph_decode_32(p);
+	ceph_decode_32_safe(p, end, *num_lockers, err_inval);
 	*lockers = kcalloc(*num_lockers, sizeof(**lockers), GFP_NOIO);
 	if (!*lockers)
 		return -ENOMEM;
@@ -315,7 +315,8 @@ static int decode_lockers(void **p, void
 			goto err_free_lockers;
 	}
 
-	*type = ceph_decode_8(p);
+	ret = -EINVAL;
+	ceph_decode_8_safe(p, end, *type, err_free_lockers);
 	s = ceph_extract_encoded_string(p, end, NULL, GFP_NOIO);
 	if (IS_ERR(s)) {
 		ret = PTR_ERR(s);
@@ -325,6 +326,9 @@ static int decode_lockers(void **p, void
 	*tag = s;
 	return 0;
 
+err_inval:
+	return -EINVAL;
+
 err_free_lockers:
 	ceph_free_lockers(*lockers, *num_lockers);
 	return ret;
author maintainer
#127 lore raw

PATCH 123/220 net/sched: serialize qdisc_rtab_list against concurrent get/put

In reply to Greg Kroah-Hartman

6.12-stable review patch. If anyone has any objections, please let me know.

------------------

From: Aldo Ariel Panzardo <[email protected]>

[ Upstream commit f43ee0c0730d6191629b5ee1ceae27b1ebfdc047 ]

qdisc_get_rtab() and qdisc_put_rtab() mutate the process-global singly linked list qdisc_rtab_list and a plain non-atomic 'int refcnt' with no lock. This was only safe because every caller historically held the RTNL mutex, which serialized all rate-table lookups, inserts and frees.

That invariant no longer holds. cls_flower sets TCF_PROTO_OPS_DOIT_UNLOCKED, so tc_new_tfilter() keeps rtnl_held == false for it and sets TCA_ACT_FLAGS_NO_RTNL. That flag propagates through tcf_exts_validate_ex() -> tcf_action_init() -> tcf_action_init_1() -> tcf_police_init(), which calls qdisc_get_rtab()/qdisc_put_rtab() with the RTNL mutex NOT held. Two RTM_NEWTFILTER requests on different CPUs, each adding a flower filter with a police action carrying the same rate, then race on qdisc_rtab_list and on the non-atomic refcnt, leading to a use-after-free / double-free of the kmalloc-2k struct qdisc_rate_table. qdisc_rtab_list is a single global (not per-netns), so the corrupted object is shared system-wide.

  BUG: KASAN: slab-use-after-free in qdisc_put_rtab+0x12f/0x160
   qdisc_put_rtab+0x12f/0x160
   tcf_police_init+0xda9/0x1590
   tcf_action_init_1+0x460/0x6b0
   tcf_action_init+0x439/0xa40
   tcf_exts_validate_ex+0x42d/0x550
   fl_change+0xddd/0x7da0
   tc_new_tfilter+0xaa7/0x2420
   rtnetlink_rcv_msg+0x95e/0xe90
  which belongs to the cache kmalloc-2k of size 2048

Protect qdisc_rtab_list and the refcount with a dedicated spinlock. The (sleeping, GFP_KERNEL) allocation in qdisc_get_rtab() is performed before taking the lock; if a concurrent inserter added an identical table in the meantime the freshly allocated one is freed under the lock, so no duplicate is leaked. qdisc_put_rtab() now decrements the refcount and unlinks under the same lock.

---
 net/sched/sch_api.c |   24 +++++++++++++++++++-----
 1 file changed, 19 insertions(+), 5 deletions(-)
net/sched/sch_api.c +19 -5
@@ -413,12 +413,13 @@ static __u8 __detect_linklayer(struct tc
 }
 
 static struct qdisc_rate_table *qdisc_rtab_list;
+static DEFINE_SPINLOCK(qdisc_rtab_lock);
 
 struct qdisc_rate_table *qdisc_get_rtab(struct tc_ratespec *r,
 					struct nlattr *tab,
 					struct netlink_ext_ack *extack)
 {
-	struct qdisc_rate_table *rtab;
+	struct qdisc_rate_table *rtab, *new_rtab;
 
 	if (tab == NULL || r->rate == 0 ||
 	    r->cell_log == 0 || r->cell_log >= 32 ||
@@ -427,15 +428,20 @@ struct qdisc_rate_table *qdisc_get_rtab(
 		return NULL;
 	}
 
+	new_rtab = kmalloc_obj(*new_rtab);
+
+	spin_lock(&qdisc_rtab_lock);
 	for (rtab = qdisc_rtab_list; rtab; rtab = rtab->next) {
 		if (!memcmp(&rtab->rate, r, sizeof(struct tc_ratespec)) &&
 		    !memcmp(&rtab->data, nla_data(tab), 1024)) {
 			rtab->refcnt++;
+			spin_unlock(&qdisc_rtab_lock);
+			kfree(new_rtab);
 			return rtab;
 		}
 	}
 
-	rtab = kmalloc(sizeof(*rtab), GFP_KERNEL);
+	rtab = new_rtab;
 	if (rtab) {
 		rtab->rate = *r;
 		rtab->refcnt = 1;
@@ -447,6 +453,7 @@ struct qdisc_rate_table *qdisc_get_rtab(
 	} else {
 		NL_SET_ERR_MSG(extack, "Failed to allocate new qdisc rate table");
 	}
+	spin_unlock(&qdisc_rtab_lock);
 	return rtab;
 }
 EXPORT_SYMBOL(qdisc_get_rtab);
@@ -455,18 +462,25 @@ void qdisc_put_rtab(struct qdisc_rate_ta
 {
 	struct qdisc_rate_table *rtab, **rtabp;
 
-	if (!tab || --tab->refcnt)
+	if (!tab)
+		return;
+
+	spin_lock(&qdisc_rtab_lock);
+	if (--tab->refcnt) {
+		spin_unlock(&qdisc_rtab_lock);
 		return;
+	}
 
 	for (rtabp = &qdisc_rtab_list;
 	     (rtab = *rtabp) != NULL;
 	     rtabp = &rtab->next) {
 		if (rtab == tab) {
 			*rtabp = rtab->next;
-			kfree(rtab);
-			return;
+			break;
 		}
 	}
+	spin_unlock(&qdisc_rtab_lock);
+	kfree(tab);
 }
 EXPORT_SYMBOL(qdisc_put_rtab);
 
author maintainer
#128 lore raw

PATCH 124/220 super: remove pointless s_root checks

In reply to Greg Kroah-Hartman

6.12-stable review patch. If anyone has any objections, please let me know.

------------------

From: Christian Brauner <[email protected]>

[ Upstream commit af7551cf13cf7fb1d4f939db4f1f24c00550ed57 ]

The locking guarantees that the superblock is alive and sb->s_root is still set. Remove the pointless check.

Stable-dep-of: 749d7aa0377a ("super: fix emergency thaw deadlock on frozen block devices")

---
 fs/super.c |   19 ++++++-------------
 1 file changed, 6 insertions(+), 13 deletions(-)
fs/super.c +6 -13
@@ -930,8 +930,7 @@ void iterate_supers(void (*f)(struct sup
 
 		locked = super_lock_shared(sb);
 		if (locked) {
-			if (sb->s_root)
-				f(sb, arg);
+			f(sb, arg);
 			super_unlock_shared(sb);
 		}
 
@@ -967,11 +966,8 @@ void iterate_supers_type(struct file_sys
 		spin_unlock(&sb_lock);
 
 		locked = super_lock_shared(sb);
-		if (locked) {
-			if (sb->s_root)
-				f(sb, arg);
-			super_unlock_shared(sb);
-		}
+		if (locked)
+			f(sb, arg);
 
 		spin_lock(&sb_lock);
 		if (p)
@@ -991,18 +987,15 @@ struct super_block *user_get_super(dev_t
 
 	spin_lock(&sb_lock);
 	list_for_each_entry(sb, &super_blocks, s_list) {
-		if (sb->s_dev ==  dev) {
+		if (sb->s_dev == dev) {
 			bool locked;
 
 			sb->s_count++;
 			spin_unlock(&sb_lock);
 			/* still alive? */
 			locked = super_lock(sb, excl);
-			if (locked) {
-				if (sb->s_root)
-					return sb;
-				super_unlock(sb, excl);
-			}
+			if (locked)
+				return sb; /* caller will drop */
 			/* nope, got unmounted */
 			spin_lock(&sb_lock);
 			__put_super(sb);
author maintainer
#129 lore raw

PATCH 125/220 super: skip dying superblocks early

In reply to Greg Kroah-Hartman

6.12-stable review patch. If anyone has any objections, please let me know.

------------------

From: Christian Brauner <[email protected]>

[ Upstream commit 6920e3388ba4c66b0468d43bb7a373f5fff15d35 ]

Make all iterators uniform by performing an early check whether the superblock is dying.

Stable-dep-of: 749d7aa0377a ("super: fix emergency thaw deadlock on frozen block devices")

---
 fs/super.c |    6 ++++++
 1 file changed, 6 insertions(+)
fs/super.c +6 -0
@@ -925,6 +925,9 @@ void iterate_supers(void (*f)(struct sup
 	list_for_each_entry(sb, &super_blocks, s_list) {
 		bool locked;
 
+		if (super_flags(sb, SB_DYING))
+			continue;
+
 		sb->s_count++;
 		spin_unlock(&sb_lock);
 
@@ -962,6 +965,9 @@ void iterate_supers_type(struct file_sys
 	hlist_for_each_entry(sb, &type->fs_supers, s_instances) {
 		bool locked;
 
+		if (super_flags(sb, SB_DYING))
+			continue;
+
 		sb->s_count++;
 		spin_unlock(&sb_lock);
 
author maintainer
#130 lore raw

PATCH 126/220 super: use a common iterator (Part 1)

In reply to Greg Kroah-Hartman

6.12-stable review patch. If anyone has any objections, please let me know.

------------------

From: Christian Brauner <[email protected]>

[ Upstream commit 2992476528aeecbaee17ba0a6396a817481205a3 ]

Use a common iterator for all callbacks.

Stable-dep-of: 749d7aa0377a ("super: fix emergency thaw deadlock on frozen block devices")

---
 fs/super.c         |   67 ++++++++++-------------------------------------------
 include/linux/fs.h |    6 +++-
 2 files changed, 18 insertions(+), 55 deletions(-)
fs/super.c +13 -54
@@ -887,37 +887,7 @@ void drop_super_exclusive(struct super_b
 }
 EXPORT_SYMBOL(drop_super_exclusive);
 
-static void __iterate_supers(void (*f)(struct super_block *))
-{
-	struct super_block *sb, *p = NULL;
-
-	spin_lock(&sb_lock);
-	list_for_each_entry(sb, &super_blocks, s_list) {
-		if (super_flags(sb, SB_DYING))
-			continue;
-		sb->s_count++;
-		spin_unlock(&sb_lock);
-
-		f(sb);
-
-		spin_lock(&sb_lock);
-		if (p)
-			__put_super(p);
-		p = sb;
-	}
-	if (p)
-		__put_super(p);
-	spin_unlock(&sb_lock);
-}
-/**
- *	iterate_supers - call function for all active superblocks
- *	@f: function to call
- *	@arg: argument to pass to it
- *
- *	Scans the superblock list and calls given function, passing it
- *	locked superblock and given argument.
- */
-void iterate_supers(void (*f)(struct super_block *, void *), void *arg)
+void __iterate_supers(void (*f)(struct super_block *, void *), void *arg, bool excl)
 {
 	struct super_block *sb, *p = NULL;
 
@@ -927,14 +897,13 @@ void iterate_supers(void (*f)(struct sup
 
 		if (super_flags(sb, SB_DYING))
 			continue;
-
 		sb->s_count++;
 		spin_unlock(&sb_lock);
 
-		locked = super_lock_shared(sb);
+		locked = super_lock(sb, excl);
 		if (locked) {
 			f(sb, arg);
-			super_unlock_shared(sb);
+			super_unlock(sb, excl);
 		}
 
 		spin_lock(&sb_lock);
@@ -1110,11 +1079,9 @@ cancel_readonly:
 	return retval;
 }
 
-static void do_emergency_remount_callback(struct super_block *sb)
+static void do_emergency_remount_callback(struct super_block *sb, void *unused)
 {
-	bool locked = super_lock_excl(sb);
-
-	if (locked && sb->s_root && sb->s_bdev && !sb_rdonly(sb)) {
+	if (sb->s_bdev && !sb_rdonly(sb)) {
 		struct fs_context *fc;
 
 		fc = fs_context_for_reconfigure(sb->s_root,
@@ -1125,13 +1092,11 @@ static void do_emergency_remount_callbac
 			put_fs_context(fc);
 		}
 	}
-	if (locked)
-		super_unlock_excl(sb);
 }
 
 static void do_emergency_remount(struct work_struct *work)
 {
-	__iterate_supers(do_emergency_remount_callback);
+	__iterate_supers(do_emergency_remount_callback, NULL, true);
 	kfree(work);
 	printk("Emergency Remount complete\n");
 }
@@ -1147,24 +1112,18 @@ void emergency_remount(void)
 	}
 }
 
-static void do_thaw_all_callback(struct super_block *sb)
+static void do_thaw_all_callback(struct super_block *sb, void *unused)
 {
-	bool locked = super_lock_excl(sb);
-
-	if (locked && sb->s_root) {
-		if (IS_ENABLED(CONFIG_BLOCK))
-			while (sb->s_bdev && !bdev_thaw(sb->s_bdev))
-				pr_warn("Emergency Thaw on %pg\n", sb->s_bdev);
-		thaw_super_locked(sb, FREEZE_HOLDER_USERSPACE);
-		return;
-	}
-	if (locked)
-		super_unlock_excl(sb);
+	if (IS_ENABLED(CONFIG_BLOCK))
+		while (sb->s_bdev && !bdev_thaw(sb->s_bdev))
+			pr_warn("Emergency Thaw on %pg\n", sb->s_bdev);
+	thaw_super_locked(sb, FREEZE_HOLDER_USERSPACE);
+	return;
 }
 
 static void do_thaw_all(struct work_struct *work)
 {
-	__iterate_supers(do_thaw_all_callback);
+	__iterate_supers(do_thaw_all_callback, NULL, true);
 	kfree(work);
 	printk(KERN_WARNING "Emergency Thaw complete\n");
 }
include/linux/fs.h +5 -1
@@ -3398,7 +3398,11 @@ extern void put_filesystem(struct file_s
 extern struct file_system_type *get_fs_type(const char *name);
 extern void drop_super(struct super_block *sb);
 extern void drop_super_exclusive(struct super_block *sb);
-extern void iterate_supers(void (*)(struct super_block *, void *), void *);
+void __iterate_supers(void (*f)(struct super_block *, void *), void *arg, bool excl);
+static inline void iterate_supers(void (*f)(struct super_block *, void *), void *arg)
+{
+	__iterate_supers(f, arg, false);
+}
 extern void iterate_supers_type(struct file_system_type *,
 			        void (*)(struct super_block *, void *), void *);
 
author maintainer
#131 lore raw

PATCH 127/220 super: use common iterator (Part 2)

In reply to Greg Kroah-Hartman

6.12-stable review patch. If anyone has any objections, please let me know.

------------------

From: Christian Brauner <[email protected]>

[ Upstream commit b47e42d10e8c20525febccbd6e0dc8528861aea4 ]

Use a common iterator for all callbacks. We could go for something even more elaborate (advance step-by-step similar to iov_iter) but I really don't think this is warranted.

Stable-dep-of: 749d7aa0377a ("super: fix emergency thaw deadlock on frozen block devices")

---
 fs/super.c         |   48 +++++++++++++++++++++++++++++++++++++++---------
 include/linux/fs.h |    6 +-----
 2 files changed, 40 insertions(+), 14 deletions(-)
fs/super.c +39 -9
@@ -887,21 +887,46 @@ void drop_super_exclusive(struct super_b
 }
 EXPORT_SYMBOL(drop_super_exclusive);
 
-void __iterate_supers(void (*f)(struct super_block *, void *), void *arg, bool excl)
+enum super_iter_flags_t {
+	SUPER_ITER_EXCL		= (1U << 0),
+	SUPER_ITER_UNLOCKED	= (1U << 1),
+	SUPER_ITER_REVERSE	= (1U << 2),
+};
+
+static inline struct super_block *first_super(enum super_iter_flags_t flags)
+{
+	if (flags & SUPER_ITER_REVERSE)
+		return list_last_entry(&super_blocks, struct super_block, s_list);
+	return list_first_entry(&super_blocks, struct super_block, s_list);
+}
+
+static inline struct super_block *next_super(struct super_block *sb,
+					     enum super_iter_flags_t flags)
+{
+	if (flags & SUPER_ITER_REVERSE)
+		return list_prev_entry(sb, s_list);
+	return list_next_entry(sb, s_list);
+}
+
+static void __iterate_supers(void (*f)(struct super_block *, void *), void *arg,
+			     enum super_iter_flags_t flags)
 {
 	struct super_block *sb, *p = NULL;
+	bool excl = flags & SUPER_ITER_EXCL;
 
-	spin_lock(&sb_lock);
-	list_for_each_entry(sb, &super_blocks, s_list) {
-		bool locked;
+	guard(spinlock)(&sb_lock);
 
+	for (sb = first_super(flags);
+	     !list_entry_is_head(sb, &super_blocks, s_list);
+	     sb = next_super(sb, flags)) {
 		if (super_flags(sb, SB_DYING))
 			continue;
 		sb->s_count++;
 		spin_unlock(&sb_lock);
 
-		locked = super_lock(sb, excl);
-		if (locked) {
+		if (flags & SUPER_ITER_UNLOCKED) {
+			f(sb, arg);
+		} else if (super_lock(sb, excl)) {
 			f(sb, arg);
 			super_unlock(sb, excl);
 		}
@@ -913,7 +938,11 @@ void __iterate_supers(void (*f)(struct s
 	}
 	if (p)
 		__put_super(p);
-	spin_unlock(&sb_lock);
+}
+
+void iterate_supers(void (*f)(struct super_block *, void *), void *arg)
+{
+	__iterate_supers(f, arg, 0);
 }
 
 /**
@@ -1096,7 +1125,8 @@ static void do_emergency_remount_callbac
 
 static void do_emergency_remount(struct work_struct *work)
 {
-	__iterate_supers(do_emergency_remount_callback, NULL, true);
+	__iterate_supers(do_emergency_remount_callback, NULL,
+			 SUPER_ITER_EXCL | SUPER_ITER_REVERSE);
 	kfree(work);
 	printk("Emergency Remount complete\n");
 }
@@ -1123,7 +1153,7 @@ static void do_thaw_all_callback(struct
 
 static void do_thaw_all(struct work_struct *work)
 {
-	__iterate_supers(do_thaw_all_callback, NULL, true);
+	__iterate_supers(do_thaw_all_callback, NULL, SUPER_ITER_EXCL);
 	kfree(work);
 	printk(KERN_WARNING "Emergency Thaw complete\n");
 }
include/linux/fs.h +1 -5
@@ -3398,11 +3398,7 @@ extern void put_filesystem(struct file_s
 extern struct file_system_type *get_fs_type(const char *name);
 extern void drop_super(struct super_block *sb);
 extern void drop_super_exclusive(struct super_block *sb);
-void __iterate_supers(void (*f)(struct super_block *, void *), void *arg, bool excl);
-static inline void iterate_supers(void (*f)(struct super_block *, void *), void *arg)
-{
-	__iterate_supers(f, arg, false);
-}
+extern void iterate_supers(void (*f)(struct super_block *, void *), void *arg);
 extern void iterate_supers_type(struct file_system_type *,
 			        void (*)(struct super_block *, void *), void *);
 
author maintainer
#132 lore raw

PATCH 128/220 fs/super: fix emergency thaw double-unlock of s_umount

In reply to Greg Kroah-Hartman

6.12-stable review patch. If anyone has any objections, please let me know.

------------------

From: Chen Changcheng <[email protected]>

[ Upstream commit 503d67fbaec6fdeaba391cb497675071db9d16ea ]

do_thaw_all() iterates over all superblocks via __iterate_supers() with SUPER_ITER_EXCL, which acquires s_umount exclusively before calling the callback and releases it afterwards. However, the callback do_thaw_all_callback() calls thaw_super_locked() which unconditionally releases s_umount on every code path. This results in a second unlock attempt in __iterate_supers() that corrupts the rwsem state, triggering a DEBUG_RWSEMS warning:

[  182.601148] sysrq: Emergency Thaw of all frozen filesystems
[  182.601865] ------------[ cut here ]------------
[  182.602375] DEBUG_RWSEMS_WARN_ON((rwsem_owner(sem) != current) && !rwsem_test_oflags(sem, RWSEM_NONSPINNABLE)): count = 0x0, magic = 0xffff99b1011e5870, owner = 0x0, curr 0xffff99b101b06c80, list not empty
[  182.603817] WARNING: kernel/locking/rwsem.c:1412 at up_write+0xa3/0x170, CPU#2: kworker/2:1/53
[  182.604578] Modules linked in:
[  182.604864] CPU: 2 UID: 0 PID: 53 Comm: kworker/2:1 Not tainted 7.2.0-rc4-00001-gbd3bd93ea98a-dirty #4 PREEMPT(lazy)
[  182.605711] Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.13.0-1kylin1 04/01/2014
[  182.606417] Workqueue: events do_thaw_all
[  182.606750] RIP: 0010:up_write+0xaf/0x170
[  182.607076] Code: 19 3a 92 48 0f 44 c2 48 8b 55 08 48 8b 55 00 4c 8b 45 08 48 8b 55 00 48 8d 3d ad 91 e0 01 48 8b 4d 20 50 48 c7 c6 f0 8c 26 92 <67> 48 0f b9 3a e8 d7 93 4e 00 58 eb 81 48 83 7f 18 00 48 c7 c2 8d
[  182.608563] RSP: 0018:ffffb670001d7e08 EFLAGS: 00010246
[  182.609007] RAX: ffffffff92349e8d RBX: 0000000000000000 RCX: ffff99b1011e5870
[  182.609595] RDX: 0000000000000000 RSI: ffffffff92268cf0 RDI: ffffffff92914d10
[  182.610283] RBP: ffff99b1011e5870 R08: 0000000000000000 R09: ffff99b101b06c80
[  182.610847] R10: ffff99b10139a808 R11: fefefefefefefeff R12: 0000000000000000
[  182.611414] R13: ffffffff90cf74d0 R14: 0000000000000000 R15: ffff99b1011e5800
[  182.612009] FS:  0000000000000000(0000) GS:ffff99b1eaaee000(0000) knlGS:0000000000000000
[  182.612670] CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
[  182.613146] CR2: 00000000005c631c CR3: 00000000013ee000 CR4: 00000000000006f0
[  182.613722] Call Trace:
[  182.613946]  <TASK>
[  182.614130]  __iterate_supers+0x128/0x150
[  182.614463]  do_thaw_all+0x1b/0x30
[  182.614759]  process_scheduled_works+0xbb/0x3f0
[  182.615150]  ? __pfx_worker_thread+0x10/0x10
[  182.615499]  worker_thread+0x129/0x270
[  182.615816]  ? __pfx_worker_thread+0x10/0x10
[  182.616201]  kthread+0xe2/0x120
[  182.616469]  ? __pfx_kthread+0x10/0x10
[  182.616792]  ret_from_fork+0x15b/0x240
[  182.617115]  ? __pfx_kthread+0x10/0x10
[  182.617426]  ret_from_fork_asm+0x1a/0x30
[  182.617761]  </TASK>
[  182.617968] ---[ end trace 0000000000000000 ]---
[  182.618412] Emergency Thaw complete

Fix this by switching to SUPER_ITER_UNLOCKED and acquiring s_umount in the callback via super_lock_excl() before calling thaw_super_locked(). This matches the locking pattern expected by thaw_super_locked() and eliminates the double unlock.

While at it, remove the dead 'return;' at the end of do_thaw_all_callback().

Stable-dep-of: 749d7aa0377a ("super: fix emergency thaw deadlock on frozen block devices")

---
 fs/super.c |    7 +++++--
 1 file changed, 5 insertions(+), 2 deletions(-)
fs/super.c +5 -2
@@ -1144,16 +1144,19 @@ void emergency_remount(void)
 
 static void do_thaw_all_callback(struct super_block *sb, void *unused)
 {
+	if (!super_lock_excl(sb))
+		return;
+
 	if (IS_ENABLED(CONFIG_BLOCK))
 		while (sb->s_bdev && !bdev_thaw(sb->s_bdev))
 			pr_warn("Emergency Thaw on %pg\n", sb->s_bdev);
+
 	thaw_super_locked(sb, FREEZE_HOLDER_USERSPACE);
-	return;
 }
 
 static void do_thaw_all(struct work_struct *work)
 {
-	__iterate_supers(do_thaw_all_callback, NULL, SUPER_ITER_EXCL);
+	__iterate_supers(do_thaw_all_callback, NULL, SUPER_ITER_UNLOCKED);
 	kfree(work);
 	printk(KERN_WARNING "Emergency Thaw complete\n");
 }
author maintainer
#133 lore raw

PATCH 129/220 super: fix emergency thaw deadlock on frozen block devices

In reply to Greg Kroah-Hartman

6.12-stable review patch. If anyone has any objections, please let me know.

------------------

From: Christian Brauner <[email protected]>

[ Upstream commit 749d7aa0377aae32af8c0a4ad43371e7bf830ab5 ]

do_thaw_all_callback() calls bdev_thaw() while holding sb->s_umount exclusively. If the block device was frozen via bdev_freeze() dropping the last block layer freeze reference calls fs_bdev_thaw() which reacquires s_umount:

do_thaw_all_callback(sb)

    super_lock_excl(sb)                     # holds sb->s_umount
    bdev_thaw(sb->s_bdev)
      mutex_lock(&bdev->bd_fsfreeze_mutex)
      # bd_fsfreeze_count drops 1 -> 0
      bd_holder_ops->thaw == fs_bdev_thaw
        get_bdev_super(bdev)
          bdev_super_lock(bdev, true)
            super_lock(sb, true)
              down_write(&sb->s_umount)     # same task: deadlock

The emergency thaw worker deadlocks against itself holding both s_umount and bd_fsfreeze_mutex. That fscks any subsequent unmount, freeze, or thaw of that filesystem and block device.

  [   81.878470] sysrq: Show Blocked State
  [   81.880140] task:kworker/0:1     state:D stack:0     pid:11    tgid:11    ppid:2      task_flags:0x4208060 flags:0x00080000
  [   81.884876] Workqueue: events do_thaw_all
  [   81.886656] Call Trace:
  [   81.887759]  <TASK>
  [   81.888763]  __schedule+0x579/0x1420
  [   81.890372]  schedule+0x3a/0x100
  [   81.891794]  schedule_preempt_disabled+0x15/0x30
  [   81.893848]  rwsem_down_write_slowpath+0x1ea/0x900
  [   81.895191]  ? __pfx_do_thaw_all_callback+0x10/0x10
  [   81.896528]  down_write+0xbd/0xc0
  [   81.897505]  super_lock+0x91/0x180
  [   81.898457]  ? __mutex_lock+0xa99/0x1140
  [   81.900748]  ? __mutex_unlock_slowpath+0x1f/0x400
  [   81.902069]  bdev_super_lock+0x5b/0x150
  [   81.903132]  get_bdev_super+0x10/0x60
  [   81.904042]  fs_bdev_thaw+0x23/0xf0
  [   81.904755]  bdev_thaw+0x82/0x100
  [   81.905484]  do_thaw_all_callback+0x2c/0x50
  [   81.906298]  __iterate_supers+0x5d/0x130
  [   81.907067]  do_thaw_all+0x20/0x40
  [   81.907739]  process_one_work+0x206/0x5e0
  [   81.908545]  worker_thread+0x1e2/0x3c0
  [   81.909339]  ? __pfx_worker_thread+0x10/0x10
  [   81.910171]  kthread+0xf4/0x130
  [   81.910799]  ? __pfx_kthread+0x10/0x10
  [   81.911528]  ret_from_fork+0x2e2/0x3b0
  [   81.912259]  ? __pfx_kthread+0x10/0x10
  [   81.913010]  ret_from_fork_asm+0x1a/0x30
  [   81.913806]  </TASK>

bdev_super_lock() even documents the violated requirement with lockdep_assert_not_held(&sb->s_umount).

Acquiring bd_fsfreeze_mutex under s_umount also inverts the bd_fsfreeze_mutex vs. s_umount ordering established by bdev_{freeze,thaw}() and can thus ABBA against a concurrent block-layer freeze even when the recursive path isn't hit.

Fix this by not holding s_umount around the bdev_thaw() loop at all. Pin the superblock with an active reference instead as filesystems_freeze_callback() does. The active reference keeps the superblock from being shut down and so ->s_bdev stays valid without holding s_umount. The block-layer-held freeze is dropped by fs_bdev_thaw() with FREEZE_MAY_NEST | FREEZE_HOLDER_USERSPACE exactly as a regular unfreeze would and thaw_super_locked() handles filesystem-level freezes as before.

The emergency thaw path has deadlocked like this in one form or another for a long long time but the current exclusively-held shape dates back to commit [1] where thaw_bdev() already ended in thaw_super() with s_umount held by do_thaw_all_callback().

---
 fs/super.c |   18 ++++++++++++++++--
 1 file changed, 16 insertions(+), 2 deletions(-)
fs/super.c +16 -2
@@ -1142,16 +1142,30 @@ void emergency_remount(void)
 	}
 }
 
+static inline bool get_active_super(struct super_block *sb)
+{
+	bool active = false;
+
+	if (super_lock_excl(sb)) {
+		active = atomic_inc_not_zero(&sb->s_active);
+		super_unlock_excl(sb);
+	}
+	return active;
+}
+
 static void do_thaw_all_callback(struct super_block *sb, void *unused)
 {
-	if (!super_lock_excl(sb))
+	if (!get_active_super(sb))
 		return;
 
+	/* fs_bdev_thaw() acquires s_umount so it must not be held here */
 	if (IS_ENABLED(CONFIG_BLOCK))
 		while (sb->s_bdev && !bdev_thaw(sb->s_bdev))
 			pr_warn("Emergency Thaw on %pg\n", sb->s_bdev);
 
-	thaw_super_locked(sb, FREEZE_HOLDER_USERSPACE);
+	if (super_lock_excl(sb))
+		thaw_super_locked(sb, FREEZE_HOLDER_USERSPACE);
+	deactivate_super(sb);
 }
 
 static void do_thaw_all(struct work_struct *work)
author maintainer
#134 lore raw

PATCH 130/220 smb: move smb_version_values to common/smbglob.h

In reply to Greg Kroah-Hartman

6.12-stable review patch. If anyone has any objections, please let me know.

------------------

From: ZhangGuoDong <[email protected]>

[ Upstream commit 34cf191bb6a349dc88ec2c4f6355fe006ac669e0 ]

Merge the struct members of the server and the client:

- req_capabilities: from client
- header_preamble_size: from client
- cap_unicode: from client
- capabilities: from server, rename to req_capabilities
- max_read_size: from server
- max_write_size: from server
- max_trans_size: from server
- max_credits: from server
- create_durable_size: from server
- create_durable_v2_size: from server
- create_mxac_size: from server
- create_disk_id_size: from server
- create_posix_size: from server

Then move duplicate definitions to common header file.

Stable-dep-of: cfc0b8e5080a ("ksmbd: validate minimum PDU size for transform requests")

---
 fs/smb/client/cifsglob.h   |   21 ---------------------
 fs/smb/common/cifsglob.h   |   31 +++++++++++++++++++++++++++++++
 fs/smb/server/smb2misc.c   |    2 +-
 fs/smb/server/smb2ops.c    |   32 ++++++++++++++++----------------
 fs/smb/server/smb2pdu.c    |   10 +++++-----
 fs/smb/server/smb_common.h |   29 -----------------------------
 6 files changed, 53 insertions(+), 72 deletions(-)
fs/smb/client/cifsglob.h +0 -21
@@ -606,27 +606,6 @@ struct smb_version_operations {
 				      const char *symname);
 };
 
-struct smb_version_values {
-	char		*version_string;
-	__u16		protocol_id;
-	__u32		req_capabilities;
-	__u32		large_lock_type;
-	__u32		exclusive_lock_type;
-	__u32		shared_lock_type;
-	__u32		unlock_lock_type;
-	size_t		header_preamble_size;
-	size_t		header_size;
-	size_t		max_header_size;
-	size_t		read_rsp_size;
-	__le16		lock_cmd;
-	unsigned int	cap_unix;
-	unsigned int	cap_nt_find;
-	unsigned int	cap_large_files;
-	__u16		signing_enabled;
-	__u16		signing_required;
-	size_t		create_lease_size;
-};
-
 #define HEADER_SIZE(server) (server->vals->header_size)
 #define MAX_HEADER_SIZE(server) (server->vals->max_header_size)
 #define HEADER_PREAMBLE_SIZE(server) (server->vals->header_preamble_size)
fs/smb/common/cifsglob.h +31 -0
@@ -9,6 +9,37 @@
 #ifndef _COMMON_CIFS_GLOB_H
 #define _COMMON_CIFS_GLOB_H
 
+struct smb_version_values {
+	char		*version_string;
+	__u16		protocol_id;
+	__le16		lock_cmd;
+	__u32		req_capabilities;
+	__u32		max_read_size;
+	__u32		max_write_size;
+	__u32		max_trans_size;
+	__u32		max_credits;
+	__u32		large_lock_type;
+	__u32		exclusive_lock_type;
+	__u32		shared_lock_type;
+	__u32		unlock_lock_type;
+	size_t		header_preamble_size;
+	size_t		header_size;
+	size_t		max_header_size;
+	size_t		read_rsp_size;
+	unsigned int	cap_unix;
+	unsigned int	cap_nt_find;
+	unsigned int	cap_large_files;
+	unsigned int	cap_unicode;
+	__u16		signing_enabled;
+	__u16		signing_required;
+	size_t		create_lease_size;
+	size_t		create_durable_size;
+	size_t		create_durable_v2_size;
+	size_t		create_mxac_size;
+	size_t		create_disk_id_size;
+	size_t		create_posix_size;
+};
+
 static inline void inc_rfc1001_len(void *buf, int count)
 {
 	be32_add_cpu((__be32 *)buf, count);
fs/smb/server/smb2misc.c +1 -1
@@ -465,7 +465,7 @@ int ksmbd_smb2_check_message(struct ksmb
 	}
 
 validate_credit:
-	if ((work->conn->vals->capabilities & SMB2_GLOBAL_CAP_LARGE_MTU) &&
+	if ((work->conn->vals->req_capabilities & SMB2_GLOBAL_CAP_LARGE_MTU) &&
 	    smb2_validate_credit_charge(work->conn, hdr))
 		return 1;
 
fs/smb/server/smb2ops.c +16 -16
@@ -15,7 +15,7 @@
 static struct smb_version_values smb21_server_values = {
 	.version_string = SMB21_VERSION_STRING,
 	.protocol_id = SMB21_PROT_ID,
-	.capabilities = SMB2_GLOBAL_CAP_LARGE_MTU,
+	.req_capabilities = SMB2_GLOBAL_CAP_LARGE_MTU,
 	.max_read_size = SMB21_DEFAULT_IOSIZE,
 	.max_write_size = SMB21_DEFAULT_IOSIZE,
 	.max_trans_size = SMB21_DEFAULT_IOSIZE,
@@ -41,7 +41,7 @@ static struct smb_version_values smb21_s
 static struct smb_version_values smb30_server_values = {
 	.version_string = SMB30_VERSION_STRING,
 	.protocol_id = SMB30_PROT_ID,
-	.capabilities = SMB2_GLOBAL_CAP_LARGE_MTU,
+	.req_capabilities = SMB2_GLOBAL_CAP_LARGE_MTU,
 	.max_read_size = SMB3_DEFAULT_IOSIZE,
 	.max_write_size = SMB3_DEFAULT_IOSIZE,
 	.max_trans_size = SMB3_DEFAULT_TRANS_SIZE,
@@ -68,7 +68,7 @@ static struct smb_version_values smb30_s
 static struct smb_version_values smb302_server_values = {
 	.version_string = SMB302_VERSION_STRING,
 	.protocol_id = SMB302_PROT_ID,
-	.capabilities = SMB2_GLOBAL_CAP_LARGE_MTU,
+	.req_capabilities = SMB2_GLOBAL_CAP_LARGE_MTU,
 	.max_read_size = SMB3_DEFAULT_IOSIZE,
 	.max_write_size = SMB3_DEFAULT_IOSIZE,
 	.max_trans_size = SMB3_DEFAULT_TRANS_SIZE,
@@ -95,7 +95,7 @@ static struct smb_version_values smb302_
 static struct smb_version_values smb311_server_values = {
 	.version_string = SMB311_VERSION_STRING,
 	.protocol_id = SMB311_PROT_ID,
-	.capabilities = SMB2_GLOBAL_CAP_LARGE_MTU,
+	.req_capabilities = SMB2_GLOBAL_CAP_LARGE_MTU,
 	.max_read_size = SMB3_DEFAULT_IOSIZE,
 	.max_write_size = SMB3_DEFAULT_IOSIZE,
 	.max_trans_size = SMB3_DEFAULT_TRANS_SIZE,
@@ -204,7 +204,7 @@ void init_smb2_1_server(struct ksmbd_con
 	conn->signing_algorithm = SIGNING_ALG_HMAC_SHA256_LE;
 
 	if (server_conf.flags & KSMBD_GLOBAL_FLAG_SMB2_LEASES)
-		conn->vals->capabilities |= SMB2_GLOBAL_CAP_LEASING;
+		conn->vals->req_capabilities |= SMB2_GLOBAL_CAP_LEASING;
 }
 
 /**
@@ -221,20 +221,20 @@ void init_smb3_0_server(struct ksmbd_con
 	conn->signing_algorithm = SIGNING_ALG_AES_CMAC_LE;
 
 	if (server_conf.flags & KSMBD_GLOBAL_FLAG_SMB2_LEASES)
-		conn->vals->capabilities |= SMB2_GLOBAL_CAP_LEASING |
+		conn->vals->req_capabilities |= SMB2_GLOBAL_CAP_LEASING |
 			SMB2_GLOBAL_CAP_DIRECTORY_LEASING;
 
 	if (server_conf.flags & KSMBD_GLOBAL_FLAG_SMB2_ENCRYPTION &&
 	    conn->cli_cap & SMB2_GLOBAL_CAP_ENCRYPTION)
-		conn->vals->capabilities |= SMB2_GLOBAL_CAP_ENCRYPTION;
+		conn->vals->req_capabilities |= SMB2_GLOBAL_CAP_ENCRYPTION;
 
 	if (server_conf.flags & KSMBD_GLOBAL_FLAG_SMB2_ENCRYPTION ||
 	    (!(server_conf.flags & KSMBD_GLOBAL_FLAG_SMB2_ENCRYPTION_OFF) &&
 	     conn->cli_cap & SMB2_GLOBAL_CAP_ENCRYPTION))
-		conn->vals->capabilities |= SMB2_GLOBAL_CAP_ENCRYPTION;
+		conn->vals->req_capabilities |= SMB2_GLOBAL_CAP_ENCRYPTION;
 
 	if (server_conf.flags & KSMBD_GLOBAL_FLAG_SMB3_MULTICHANNEL)
-		conn->vals->capabilities |= SMB2_GLOBAL_CAP_MULTI_CHANNEL;
+		conn->vals->req_capabilities |= SMB2_GLOBAL_CAP_MULTI_CHANNEL;
 }
 
 /**
@@ -251,19 +251,19 @@ void init_smb3_02_server(struct ksmbd_co
 	conn->signing_algorithm = SIGNING_ALG_AES_CMAC_LE;
 
 	if (server_conf.flags & KSMBD_GLOBAL_FLAG_SMB2_LEASES)
-		conn->vals->capabilities |= SMB2_GLOBAL_CAP_LEASING |
+		conn->vals->req_capabilities |= SMB2_GLOBAL_CAP_LEASING |
 			SMB2_GLOBAL_CAP_DIRECTORY_LEASING;
 
 	if (server_conf.flags & KSMBD_GLOBAL_FLAG_SMB2_ENCRYPTION ||
 	    (!(server_conf.flags & KSMBD_GLOBAL_FLAG_SMB2_ENCRYPTION_OFF) &&
 	     conn->cli_cap & SMB2_GLOBAL_CAP_ENCRYPTION))
-		conn->vals->capabilities |= SMB2_GLOBAL_CAP_ENCRYPTION;
+		conn->vals->req_capabilities |= SMB2_GLOBAL_CAP_ENCRYPTION;
 
 	if (server_conf.flags & KSMBD_GLOBAL_FLAG_SMB3_MULTICHANNEL)
-		conn->vals->capabilities |= SMB2_GLOBAL_CAP_MULTI_CHANNEL;
+		conn->vals->req_capabilities |= SMB2_GLOBAL_CAP_MULTI_CHANNEL;
 
 	if (server_conf.flags & KSMBD_GLOBAL_FLAG_DURABLE_HANDLE)
-		conn->vals->capabilities |= SMB2_GLOBAL_CAP_PERSISTENT_HANDLES;
+		conn->vals->req_capabilities |= SMB2_GLOBAL_CAP_PERSISTENT_HANDLES;
 }
 
 /**
@@ -280,14 +280,14 @@ int init_smb3_11_server(struct ksmbd_con
 	conn->signing_algorithm = SIGNING_ALG_AES_CMAC_LE;
 
 	if (server_conf.flags & KSMBD_GLOBAL_FLAG_SMB2_LEASES)
-		conn->vals->capabilities |= SMB2_GLOBAL_CAP_LEASING |
+		conn->vals->req_capabilities |= SMB2_GLOBAL_CAP_LEASING |
 			SMB2_GLOBAL_CAP_DIRECTORY_LEASING;
 
 	if (server_conf.flags & KSMBD_GLOBAL_FLAG_SMB3_MULTICHANNEL)
-		conn->vals->capabilities |= SMB2_GLOBAL_CAP_MULTI_CHANNEL;
+		conn->vals->req_capabilities |= SMB2_GLOBAL_CAP_MULTI_CHANNEL;
 
 	if (server_conf.flags & KSMBD_GLOBAL_FLAG_DURABLE_HANDLE)
-		conn->vals->capabilities |= SMB2_GLOBAL_CAP_PERSISTENT_HANDLES;
+		conn->vals->req_capabilities |= SMB2_GLOBAL_CAP_PERSISTENT_HANDLES;
 
 	INIT_LIST_HEAD(&conn->preauth_sess_table);
 	return 0;
fs/smb/server/smb2pdu.c +5 -5
@@ -290,7 +290,7 @@ int init_smb2_neg_rsp(struct ksmbd_work
 	/* Not setting conn guid rsp->ServerGUID, as it
 	 * not used by client for identifying connection
 	 */
-	rsp->Capabilities = cpu_to_le32(conn->vals->capabilities);
+	rsp->Capabilities = cpu_to_le32(conn->vals->req_capabilities);
 	/* Default Max Message Size till SMB2.0, 64K*/
 	rsp->MaxTransactSize = cpu_to_le32(conn->vals->max_trans_size);
 	rsp->MaxReadSize = cpu_to_le32(conn->vals->max_read_size);
@@ -969,7 +969,7 @@ bool smb3_encryption_negotiated(struct k
 	 * SMB 3.0 and 3.0.2 dialects use the SMB2_GLOBAL_CAP_ENCRYPTION flag.
 	 * SMB 3.1.1 uses the cipher_type field.
 	 */
-	return (conn->vals->capabilities & SMB2_GLOBAL_CAP_ENCRYPTION) ||
+	return (conn->vals->req_capabilities & SMB2_GLOBAL_CAP_ENCRYPTION) ||
 	    conn->cipher_type;
 }
 
@@ -1222,7 +1222,7 @@ int smb2_handle_negotiate(struct ksmbd_w
 		rc = -EINVAL;
 		goto err_out;
 	}
-	rsp->Capabilities = cpu_to_le32(conn->vals->capabilities);
+	rsp->Capabilities = cpu_to_le32(conn->vals->req_capabilities);
 
 	/* For stats */
 	conn->connection_type = conn->dialect;
@@ -3501,7 +3501,7 @@ int smb2_open(struct ksmbd_work *work)
 	share_ret = ksmbd_smb_check_shared_mode(fp->filp, fp);
 	if (!test_share_config_flag(work->tcon->share_conf, KSMBD_SHARE_FLAG_OPLOCKS) ||
 	    (req_op_level == SMB2_OPLOCK_LEVEL_LEASE &&
-	     !(conn->vals->capabilities & SMB2_GLOBAL_CAP_LEASING))) {
+	     !(conn->vals->req_capabilities & SMB2_GLOBAL_CAP_LEASING))) {
 		if (share_ret < 0 && !S_ISDIR(file_inode(fp->filp)->i_mode)) {
 			rc = share_ret;
 			goto err_out1;
@@ -8102,7 +8102,7 @@ static int fsctl_validate_negotiate_info
 		goto err_out;
 	}
 
-	neg_rsp->Capabilities = cpu_to_le32(conn->vals->capabilities);
+	neg_rsp->Capabilities = cpu_to_le32(conn->vals->req_capabilities);
 	memset(neg_rsp->Guid, 0, SMB2_CLIENT_GUID_SIZE);
 	neg_rsp->SecurityMode = cpu_to_le16(conn->srv_sec_mode);
 	neg_rsp->Dialect = cpu_to_le16(conn->dialect);
fs/smb/server/smb_common.h +0 -29
@@ -336,35 +336,6 @@ struct file_id_full_dir_info {
 	char FileName[];
 } __packed; /* level 0x105 FF rsp data */
 
-struct smb_version_values {
-	char		*version_string;
-	__u16		protocol_id;
-	__le16		lock_cmd;
-	__u32		capabilities;
-	__u32		max_read_size;
-	__u32		max_write_size;
-	__u32		max_trans_size;
-	__u32		max_credits;
-	__u32		large_lock_type;
-	__u32		exclusive_lock_type;
-	__u32		shared_lock_type;
-	__u32		unlock_lock_type;
-	size_t		header_size;
-	size_t		max_header_size;
-	size_t		read_rsp_size;
-	unsigned int	cap_unix;
-	unsigned int	cap_nt_find;
-	unsigned int	cap_large_files;
-	__u16		signing_enabled;
-	__u16		signing_required;
-	size_t		create_lease_size;
-	size_t		create_durable_size;
-	size_t		create_durable_v2_size;
-	size_t		create_mxac_size;
-	size_t		create_disk_id_size;
-	size_t		create_posix_size;
-};
-
 struct filesystem_posix_info {
 	/* For undefined recommended transfer size return -1 in that field */
 	__le32 OptimalTransferSize;  /* bsize on some os, iosize on other os */
author maintainer
#135 lore raw

PATCH 131/220 smb: move get_rfc1002_len() to common/smbglob.h

In reply to Greg Kroah-Hartman

6.12-stable review patch. If anyone has any objections, please let me know.

------------------

From: ZhangGuoDong <[email protected]>

[ Upstream commit 36c31540cf5279262bfd148d8537cd04866499f2 ]

Rename get_rfc1002_length() to get_rfc1002_len(), then move duplicate definitions to common header file.

Stable-dep-of: cfc0b8e5080a ("ksmbd: validate minimum PDU size for transform requests")

---
 fs/smb/client/cifsglob.h   |    6 ------
 fs/smb/client/cifssmb.c    |    6 +++---
 fs/smb/client/connect.c    |    2 +-
 fs/smb/client/transport.c  |    8 ++++----
 fs/smb/common/cifsglob.h   |    5 +++++
 fs/smb/server/smb_common.h |    5 -----
 6 files changed, 13 insertions(+), 19 deletions(-)
fs/smb/client/cifsglob.h +0 -6
@@ -643,12 +643,6 @@ struct cifs_mnt_data {
 	int flags;
 };
 
-static inline unsigned int
-get_rfc1002_length(void *buf)
-{
-	return be32_to_cpu(*((__be32 *)buf)) & 0xffffff;
-}
-
 struct TCP_Server_Info {
 	struct list_head tcp_ses_list;
 	struct list_head smb_ses_list;
fs/smb/client/cifssmb.c +3 -3
@@ -597,7 +597,7 @@ CIFSSMBEcho(struct TCP_Server_Info *serv
 
 	iov[0].iov_len = 4;
 	iov[0].iov_base = smb;
-	iov[1].iov_len = get_rfc1002_length(smb);
+	iov[1].iov_len = get_rfc1002_len(smb);
 	iov[1].iov_base = (char *)smb + 4;
 
 	rc = cifs_call_async(server, &rqst, NULL, cifs_echo_callback, NULL,
@@ -1387,7 +1387,7 @@ cifs_async_readv(struct cifs_io_subreque
 	rdata->iov[0].iov_base = smb;
 	rdata->iov[0].iov_len = 4;
 	rdata->iov[1].iov_base = (char *)smb + 4;
-	rdata->iov[1].iov_len = get_rfc1002_length(smb);
+	rdata->iov[1].iov_len = get_rfc1002_len(smb);
 
 	rc = cifs_call_async(tcon->ses->server, &rqst, cifs_readv_receive,
 			     cifs_readv_callback, NULL, rdata, 0, NULL);
@@ -1743,7 +1743,7 @@ cifs_async_writev(struct cifs_io_subrequ
 	/* 4 for RFC1001 length + 1 for BCC */
 	iov[0].iov_len = 4;
 	iov[0].iov_base = smb;
-	iov[1].iov_len = get_rfc1002_length(smb) + 1;
+	iov[1].iov_len = get_rfc1002_len(smb) + 1;
 	iov[1].iov_base = (char *)smb + 4;
 
 	rqst.rq_iov = iov;
fs/smb/client/connect.c +1 -1
@@ -1213,7 +1213,7 @@ cifs_demultiplex_thread(void *p)
 		 * The right amount was read from socket - 4 bytes,
 		 * so we can now interpret the length field.
 		 */
-		pdu_length = get_rfc1002_length(buf);
+		pdu_length = get_rfc1002_len(buf);
 
 		cifs_dbg(FYI, "RFC1002 header 0x%x\n", pdu_length);
 		if (!is_smb_response(server, buf[0]))
fs/smb/client/transport.c +4 -4
@@ -886,7 +886,7 @@ SendReceiveNoRsp(const unsigned int xid,
 	int resp_buf_type;
 
 	iov[0].iov_base = in_buf;
-	iov[0].iov_len = get_rfc1002_length(in_buf) + 4;
+	iov[0].iov_len = get_rfc1002_len(in_buf) + 4;
 	flags |= CIFS_NO_RSP_BUF;
 	rc = SendReceive2(xid, ses, iov, 1, &resp_buf_type, flags, &rsp_iov);
 	cifs_dbg(NOISY, "SendRcvNoRsp flags %d rc %d\n", flags, rc);
@@ -946,7 +946,7 @@ int
 cifs_check_receive(struct mid_q_entry *mid, struct TCP_Server_Info *server,
 		   bool log_error)
 {
-	unsigned int len = get_rfc1002_length(mid->resp_buf) + 4;
+	unsigned int len = get_rfc1002_len(mid->resp_buf) + 4;
 
 	dump_smb(mid->resp_buf, min_t(u32, 92, len));
 
@@ -1466,7 +1466,7 @@ SendReceive(const unsigned int xid, stru
 		goto out;
 	}
 
-	*pbytes_returned = get_rfc1002_length(midQ->resp_buf);
+	*pbytes_returned = get_rfc1002_len(midQ->resp_buf);
 	memcpy(out_buf, midQ->resp_buf, *pbytes_returned + 4);
 	rc = cifs_check_receive(midQ, server, 0);
 out:
@@ -1651,7 +1651,7 @@ SendReceiveBlockingLock(const unsigned i
 		goto out;
 	}
 
-	*pbytes_returned = get_rfc1002_length(midQ->resp_buf);
+	*pbytes_returned = get_rfc1002_len(midQ->resp_buf);
 	memcpy(out_buf, midQ->resp_buf, *pbytes_returned + 4);
 	rc = cifs_check_receive(midQ, server, 0);
 out:
fs/smb/common/cifsglob.h +5 -0
@@ -40,6 +40,11 @@ struct smb_version_values {
 	size_t		create_posix_size;
 };
 
+static inline unsigned int get_rfc1002_len(void *buf)
+{
+	return be32_to_cpu(*((__be32 *)buf)) & 0xffffff;
+}
+
 static inline void inc_rfc1001_len(void *buf, int count)
 {
 	be32_add_cpu((__be32 *)buf, count);
fs/smb/server/smb_common.h +0 -5
@@ -421,9 +421,4 @@ unsigned int ksmbd_server_side_copy_max_
 unsigned int ksmbd_server_side_copy_max_total_size(void);
 bool is_asterisk(char *p);
 __le32 smb_map_generic_desired_access(__le32 daccess);
-
-static inline unsigned int get_rfc1002_len(void *buf)
-{
-	return be32_to_cpu(*((__be32 *)buf)) & 0xffffff;
-}
 #endif /* __SMB_COMMON_H__ */
author maintainer
#136 lore raw

PATCH 132/220 smb/server: rename include guard in smb_common.h

In reply to Greg Kroah-Hartman

6.12-stable review patch. If anyone has any objections, please let me know.

------------------

From: ChenXiaoSong <[email protected]>

[ Upstream commit 01ab0d1640e379f0a0d6602250b33ff2b45e9560 ]

Make the include guard more descriptive to avoid conflicts with include guards that may be used in the future.

Stable-dep-of: cfc0b8e5080a ("ksmbd: validate minimum PDU size for transform requests")

---
 fs/smb/server/smb_common.h |    6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)
fs/smb/server/smb_common.h +3 -3
@@ -3,8 +3,8 @@
  *   Copyright (C) 2018 Samsung Electronics Co., Ltd.
  */
 
-#ifndef __SMB_COMMON_H__
-#define __SMB_COMMON_H__
+#ifndef __SMB_SERVER_COMMON_H__
+#define __SMB_SERVER_COMMON_H__
 
 #include <linux/kernel.h>
 
@@ -421,4 +421,4 @@ unsigned int ksmbd_server_side_copy_max_
 unsigned int ksmbd_server_side_copy_max_total_size(void);
 bool is_asterisk(char *p);
 __le32 smb_map_generic_desired_access(__le32 daccess);
-#endif /* __SMB_COMMON_H__ */
+#endif /* __SMB_SERVER_COMMON_H__ */
author maintainer
#137 lore raw

PATCH 133/220 ksmbd: rename smb2_get_msg to smb_get_msg

In reply to Greg Kroah-Hartman

6.12-stable review patch. If anyone has any objections, please let me know.

------------------

From: Namjae Jeon <[email protected]>

[ Upstream commit 0b444cfd8b74ebce421ccd96eac9c495e536c92e ]

With the removal of the RFC1002 length field from the SMB header, smb2_get_msg is now used to get the smb1 request from the request buffer. Since this function is no longer exclusive to smb2 and now supports smb1 as well, This patch rename it to smb_get_msg to better reflect its usage.

Stable-dep-of: cfc0b8e5080a ("ksmbd: validate minimum PDU size for transform requests")

---
 fs/smb/server/auth.c       |    4 +-
 fs/smb/server/connection.c |    2 -
 fs/smb/server/oplock.c     |    8 ++---
 fs/smb/server/smb2pdu.c    |   70 ++++++++++++++++++++++-----------------------
 fs/smb/server/smb2pdu.h    |    9 -----
 fs/smb/server/smb_common.c |    6 +--
 fs/smb/server/smb_common.h |    9 +++++
 7 files changed, 54 insertions(+), 54 deletions(-)
fs/smb/server/auth.c +2 -2
@@ -920,7 +920,7 @@ int ksmbd_gen_preauth_integrity_hash(str
 				     __u8 *pi_hash)
 {
 	int rc;
-	struct smb2_hdr *rcv_hdr = smb2_get_msg(buf);
+	struct smb2_hdr *rcv_hdr = smb_get_msg(buf);
 	char *all_bytes_msg = (char *)&rcv_hdr->ProtocolId;
 	int msg_size = get_rfc1002_len(buf);
 	struct ksmbd_crypto_ctx *ctx = NULL;
@@ -1108,7 +1108,7 @@ int ksmbd_crypt_message(struct ksmbd_wor
 			unsigned int nvec, int enc)
 {
 	struct ksmbd_conn *conn = work->conn;
-	struct smb2_transform_hdr *tr_hdr = smb2_get_msg(iov[0].iov_base);
+	struct smb2_transform_hdr *tr_hdr = smb_get_msg(iov[0].iov_base);
 	unsigned int assoc_data_len = sizeof(struct smb2_transform_hdr) - 20;
 	int rc;
 	DECLARE_CRYPTO_WAIT(wait);
fs/smb/server/connection.c +1 -1
@@ -475,7 +475,7 @@ recheck:
 		if (!ksmbd_smb_request(conn))
 			break;
 
-		if (((struct smb2_hdr *)smb2_get_msg(conn->request_buf))->ProtocolId ==
+		if (((struct smb2_hdr *)smb_get_msg(conn->request_buf))->ProtocolId ==
 		    SMB2_PROTO_NUMBER) {
 			if (pdu_size < SMB2_MIN_SUPPORTED_HEADER_SIZE)
 				break;
fs/smb/server/oplock.c +4 -4
@@ -663,7 +663,7 @@ static void __smb2_oplock_break_noti(str
 		goto out;
 	}
 
-	rsp_hdr = smb2_get_msg(work->response_buf);
+	rsp_hdr = smb_get_msg(work->response_buf);
 	memset(rsp_hdr, 0, sizeof(struct smb2_hdr) + 2);
 	rsp_hdr->ProtocolId = SMB2_PROTO_NUMBER;
 	rsp_hdr->StructureSize = SMB2_HEADER_STRUCTURE_SIZE;
@@ -677,7 +677,7 @@ static void __smb2_oplock_break_noti(str
 	rsp_hdr->SessionId = 0;
 	memset(rsp_hdr->Signature, 0, 16);
 
-	rsp = smb2_get_msg(work->response_buf);
+	rsp = smb_get_msg(work->response_buf);
 
 	rsp->StructureSize = cpu_to_le16(24);
 	if (!br_info->open_trunc &&
@@ -776,7 +776,7 @@ static void __smb2_lease_break_noti(stru
 		goto out;
 	}
 
-	rsp_hdr = smb2_get_msg(work->response_buf);
+	rsp_hdr = smb_get_msg(work->response_buf);
 	memset(rsp_hdr, 0, sizeof(struct smb2_hdr) + 2);
 	rsp_hdr->ProtocolId = SMB2_PROTO_NUMBER;
 	rsp_hdr->StructureSize = SMB2_HEADER_STRUCTURE_SIZE;
@@ -790,7 +790,7 @@ static void __smb2_lease_break_noti(stru
 	rsp_hdr->SessionId = 0;
 	memset(rsp_hdr->Signature, 0, 16);
 
-	rsp = smb2_get_msg(work->response_buf);
+	rsp = smb_get_msg(work->response_buf);
 	rsp->StructureSize = cpu_to_le16(44);
 	rsp->Epoch = br_info->epoch;
 	rsp->Flags = 0;
fs/smb/server/smb2pdu.c +35 -35

240 lines not on this page · raw message

fs/smb/server/smb2pdu.h +0 -9
@@ -496,15 +496,6 @@ int smb2_ioctl(struct ksmbd_work *work);
 int smb2_oplock_break(struct ksmbd_work *work);
 int smb2_notify(struct ksmbd_work *ksmbd_work);
 
-/*
- * Get the body of the smb2 message excluding the 4 byte rfc1002 headers
- * from request/response buffer.
- */
-static inline void *smb2_get_msg(void *buf)
-{
-	return buf + 4;
-}
-
 #define POSIX_TYPE_FILE		0
 #define POSIX_TYPE_DIR		1
 #define POSIX_TYPE_SYMLINK	2
fs/smb/server/smb_common.c +3 -3
@@ -163,7 +163,7 @@ bool ksmbd_smb_request(struct ksmbd_conn
 	if (conn->request_buf[0] != 0)
 		return false;
 
-	proto = (__le32 *)smb2_get_msg(conn->request_buf);
+	proto = (__le32 *)smb_get_msg(conn->request_buf);
 	if (*proto == SMB2_COMPRESSION_TRANSFORM_ID) {
 		pr_err_ratelimited("smb2 compression not support yet");
 		return false;
@@ -259,14 +259,14 @@ int ksmbd_lookup_dialect_by_id(__le16 *c
 static int ksmbd_negotiate_smb_dialect(void *buf)
 {
 	int smb_buf_length = get_rfc1002_len(buf);
-	__le32 proto = ((struct smb2_hdr *)smb2_get_msg(buf))->ProtocolId;
+	__le32 proto = ((struct smb2_hdr *)smb_get_msg(buf))->ProtocolId;
 
 	if (proto == SMB2_PROTO_NUMBER) {
 		struct smb2_negotiate_req *req;
 		int smb2_neg_size =
 			offsetof(struct smb2_negotiate_req, Dialects);
 
-		req = (struct smb2_negotiate_req *)smb2_get_msg(buf);
+		req = (struct smb2_negotiate_req *)smb_get_msg(buf);
 		if (smb2_neg_size > smb_buf_length)
 			goto err_out;
 
fs/smb/server/smb_common.h +9 -0
@@ -421,4 +421,13 @@ unsigned int ksmbd_server_side_copy_max_
 unsigned int ksmbd_server_side_copy_max_total_size(void);
 bool is_asterisk(char *p);
 __le32 smb_map_generic_desired_access(__le32 daccess);
+
+/*
+ * Get the body of the smb message excluding the 4 byte rfc1002 headers
+ * from request/response buffer.
+ */
+static inline void *smb_get_msg(void *buf)
+{
+	return buf + 4;
+}
 #endif /* __SMB_SERVER_COMMON_H__ */
author maintainer
#138 lore raw

PATCH 134/220 smb/server: fix minimum SMB1 PDU size

In reply to Greg Kroah-Hartman

6.12-stable review patch. If anyone has any objections, please let me know.

------------------

From: ChenXiaoSong <[email protected]>

[ Upstream commit 3b9c30eb8f5aaad4a54cdfa470b74c0467cc71e8 ]

Since the RFC1002 header has been removed from struct smb_hdr, the minimum SMB1 PDU size should be updated as well.

Fixes 83bfbd0bb902 ("cifs: Remove the RFC1002 header from smb_hdr")

Stable-dep-of: cfc0b8e5080a ("ksmbd: validate minimum PDU size for transform requests")

---
 fs/smb/server/connection.c |    5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)
fs/smb/server/connection.c +3 -2
@@ -376,7 +376,8 @@ bool ksmbd_conn_alive(struct ksmbd_conn
 	return true;
 }
 
-#define SMB1_MIN_SUPPORTED_HEADER_SIZE (sizeof(struct smb_hdr))
+/* "+2" for BCC field (ByteCount, 2 bytes) */
+#define SMB1_MIN_SUPPORTED_PDU_SIZE (sizeof(struct smb_hdr) + 2)
 #define SMB2_MIN_SUPPORTED_HEADER_SIZE (sizeof(struct smb2_hdr) + 4)
 
 /**
@@ -444,7 +445,7 @@ recheck:
 		if (pdu_size > MAX_STREAM_PROT_LEN)
 			break;
 
-		if (pdu_size < SMB1_MIN_SUPPORTED_HEADER_SIZE)
+		if (pdu_size < SMB1_MIN_SUPPORTED_PDU_SIZE)
 			break;
 
 		/* 4 for rfc1002 length field */
#139 lore raw

Re: [PATCH 6.12 134/220] smb/server: fix minimum SMB1 PDU size

Hi Greg/Sasha,

Quoted 18 lines · Greg Kroah-Hartman

6.12-stable review patch. If anyone has any objections, please let me know.

------------------

From: ChenXiaoSong <[email protected]>

[ Upstream commit 3b9c30eb8f5aaad4a54cdfa470b74c0467cc71e8 ]

Since the RFC1002 header has been removed from struct smb_hdr, the minimum SMB1 PDU size should be updated as well.

Fixes 83bfbd0bb902 ("cifs: Remove the RFC1002 header from smb_hdr")
Quoted 3 lines

Stable-dep-of: cfc0b8e5080a ("ksmbd: validate minimum PDU size for transform requests")

I ran an AI-assisted backport review and then checked 465e7e8a68af
("smb/server: fix minimum SMB1 PDU size") against the upstream and 6.12.y receive paths. I do not think this patch belongs in 6.12.y.

Upstream has an RFC1002-free struct smb_hdr which is 33 bytes, so:

#define SMB1_MIN_SUPPORTED_PDU_SIZE \
(sizeof(struct smb_hdr) + 2)

correctly includes the two-byte ByteCount and produces a 35-byte minimum.

In 6.12.y, struct smb_hdr still begins with the four-byte smb_buf_length, while the receive path does this:

        pdu_size = get_rfc1002_len(hdr_buf);
        size = t->ops->read(t, conn->request_buf + 4, pdu_size, 2);

Here sizeof(struct smb_hdr) is 37, but pdu_size excludes those four transport bytes.

The previous 6.12.y check was already large enough to avoid upstream's
too-short 33-byte bound. Copying upstream's +2 instead raises the branch's bound from 37 to 39 and rejects records that previously reached SMB1 validation. The later transform-request fix uses separate SMB2 and transform bounds, so it does not require this SMB1 arithmetic change.

Since the upstream bug is absent with the 6.12.y header layout, I would
suggest dropping 465e7e8a68af from 6.12.y. Now that we have merged it, we need to think about other ways of dealing with this. It is probably a functional issue, still looks like something to be fixed.

thanks,
Harshit

Quoted 28 lines
---
  fs/smb/server/connection.c |    5 +++--
  1 file changed, 3 insertions(+), 2 deletions(-)
fs/smb/server/connection.c +3 -2
@@ -376,7 +376,8 @@ bool ksmbd_conn_alive(struct ksmbd_conn
  	return true;
  }
 
-#define SMB1_MIN_SUPPORTED_HEADER_SIZE (sizeof(struct smb_hdr))
+/* "+2" for BCC field (ByteCount, 2 bytes) */
+#define SMB1_MIN_SUPPORTED_PDU_SIZE (sizeof(struct smb_hdr) + 2)
  #define SMB2_MIN_SUPPORTED_HEADER_SIZE (sizeof(struct smb2_hdr) + 4)
 
  /**
@@ -444,7 +445,7 @@ recheck:
  		if (pdu_size > MAX_STREAM_PROT_LEN)
  			break;
 
-		if (pdu_size < SMB1_MIN_SUPPORTED_HEADER_SIZE)
+		if (pdu_size < SMB1_MIN_SUPPORTED_PDU_SIZE)
  			break;
 
  		/* 4 for rfc1002 length field */
maintainer
#140 lore raw

Re: [PATCH 6.12 134/220] smb/server: fix minimum SMB1 PDU size

Quoted 6 lines

Upstream has an RFC1002-free struct smb_hdr which is 33 bytes, so:

#define SMB1_MIN_SUPPORTED_PDU_SIZE \
(sizeof(struct smb_hdr) + 2)

correctly includes the two-byte ByteCount and produces a 35-byte minimu

You're right that smb_hdr still carries the 4-byte RFC1002 length on 6.18 and older, so the minimum there is 35, not 39.

It's over-rejection rather than under-rejection though, and no real client sends a NEGOTIATE that small - a single dialect string already puts it past 47 bytes. Leaving the five trees as they are.

Signature · 2 lines
Thanks,
Sasha
author maintainer
#141 lore raw

PATCH 135/220 smb/server: fix minimum SMB2 PDU size

In reply to Greg Kroah-Hartman

6.12-stable review patch. If anyone has any objections, please let me know.

------------------

From: ChenXiaoSong <[email protected]>

[ Upstream commit 4c7d8eb9a79ae5400eac19c4f6f0815bff674452 ]

The minimum SMB2 PDU size should be updated to the size of struct smb2_pdu (that is, the size of struct smb2_hdr + 2).

Stable-dep-of: cfc0b8e5080a ("ksmbd: validate minimum PDU size for transform requests")

---
 fs/smb/server/connection.c |    4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)
fs/smb/server/connection.c +2 -2
@@ -378,7 +378,7 @@ bool ksmbd_conn_alive(struct ksmbd_conn
 
 /* "+2" for BCC field (ByteCount, 2 bytes) */
 #define SMB1_MIN_SUPPORTED_PDU_SIZE (sizeof(struct smb_hdr) + 2)
-#define SMB2_MIN_SUPPORTED_HEADER_SIZE (sizeof(struct smb2_hdr) + 4)
+#define SMB2_MIN_SUPPORTED_PDU_SIZE (sizeof(struct smb2_pdu))
 
 /**
  * ksmbd_conn_handler_loop() - session thread to listen on new smb requests
@@ -478,7 +478,7 @@ recheck:
 
 		if (((struct smb2_hdr *)smb_get_msg(conn->request_buf))->ProtocolId ==
 		    SMB2_PROTO_NUMBER) {
-			if (pdu_size < SMB2_MIN_SUPPORTED_HEADER_SIZE)
+			if (pdu_size < SMB2_MIN_SUPPORTED_PDU_SIZE)
 				break;
 		}
 
author maintainer
#142 lore raw

PATCH 136/220 ksmbd: validate minimum PDU size for transform requests

In reply to Greg Kroah-Hartman

6.12-stable review patch. If anyone has any objections, please let me know.

------------------

From: Namjae Jeon <[email protected]>

[ Upstream commit cfc0b8e5080aec87700774e8568765eaa4b7b92b ]

The receive path applies the minimum SMB2 PDU size check only when ProtocolId is SMB2_PROTO_NUMBER. A packet carrying SMB2_TRANSFORM_PROTO_NUM bypasses the check even when the negotiated dialect does not provide transform handling.

On an SMB 2.1 connection, a short transform packet therefore reaches init_smb2_rsp_hdr(), which interprets the request as a full SMB2 header and reads beyond the request allocation. The copied fields can then be returned to the unauthenticated client.

Compression transforms are converted to ordinary SMB2 messages before protocol validation. After that conversion, validate ordinary SMB2 requests against SMB2_MIN_SUPPORTED_PDU_SIZE and require encryption transform requests to contain both a transform header and an SMB2 header. This rejects truncated requests before work allocation.

Fixes 368ba06881c3 ("ksmbd: check the validation of pdu_size in ksmbd_conn_handler_loop")
---
 fs/smb/server/connection.c |   16 +++++++++++-----
 1 file changed, 11 insertions(+), 5 deletions(-)
fs/smb/server/connection.c +11 -5
@@ -379,6 +379,8 @@ bool ksmbd_conn_alive(struct ksmbd_conn
 /* "+2" for BCC field (ByteCount, 2 bytes) */
 #define SMB1_MIN_SUPPORTED_PDU_SIZE (sizeof(struct smb_hdr) + 2)
 #define SMB2_MIN_SUPPORTED_PDU_SIZE (sizeof(struct smb2_pdu))
+#define SMB2_TRANSFORM_MIN_SUPPORTED_PDU_SIZE	\
+	(sizeof(struct smb2_transform_hdr) + sizeof(struct smb2_hdr))
 
 /**
  * ksmbd_conn_handler_loop() - session thread to listen on new smb requests
@@ -393,6 +395,7 @@ int ksmbd_conn_handler_loop(void *p)
 	struct ksmbd_conn *conn = (struct ksmbd_conn *)p;
 	struct ksmbd_transport *t = conn->transport;
 	unsigned int pdu_size, max_allowed_pdu_size, max_req;
+	__le32 proto;
 	char hdr_buf[4] = {0,};
 	int size;
 
@@ -476,11 +479,14 @@ recheck:
 		if (!ksmbd_smb_request(conn))
 			break;
 
-		if (((struct smb2_hdr *)smb_get_msg(conn->request_buf))->ProtocolId ==
-		    SMB2_PROTO_NUMBER) {
-			if (pdu_size < SMB2_MIN_SUPPORTED_PDU_SIZE)
-				break;
-		}
+		proto = *(__le32 *)smb_get_msg(conn->request_buf);
+		if (proto == SMB2_PROTO_NUMBER &&
+		    pdu_size < SMB2_MIN_SUPPORTED_PDU_SIZE)
+			break;
+
+		if (proto == SMB2_TRANSFORM_PROTO_NUM &&
+		    pdu_size < SMB2_TRANSFORM_MIN_SUPPORTED_PDU_SIZE)
+			break;
 
 		if (!default_conn_ops.process_fn) {
 			pr_err("No connection request callback\n");