From 93847823b6761e1791a7db10f55bbd4f79fa0dbc Mon Sep 17 00:00:00 2001 From: Babanpreet Singh Date: Fri, 21 Aug 2026 05:03:23 +0000 Subject: [PATCH 001/367] tee: qcomtee: fix kernel-doc warnings Fix kernel-doc issues present since the driver was added in commit d6e290837e50 ("tee: add Qualcomm TEE driver"): - async_release() documents its second parameter as @msg, but the parameter is named async_msg (msg is a local variable); - struct qcomtee_msg_object_invoke documents member @ctx, but the member is named cxt; - enum qcomtee_arg_type leaves QCOMTEE_ARG_TYPE_NR undescribed. The cxt spelling is part of the QTEE message format and is used throughout the driver; struct qcomtee_msg_callback in the same header already documents it as @cxt, so the comment is corrected rather than the member. No functional changes. Fixes: d6e290837e50 ("tee: add Qualcomm TEE driver") Reviewed-by: Amirreza Zarrabi Assisted-by: Claude:claude-opus-5 [kernel-doc] Signed-off-by: Babanpreet Singh Signed-off-by: Jens Wiklander --- drivers/tee/qcomtee/async.c | 4 ++-- drivers/tee/qcomtee/qcomtee_msg.h | 2 +- drivers/tee/qcomtee/qcomtee_object.h | 1 + 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/drivers/tee/qcomtee/async.c b/drivers/tee/qcomtee/async.c index 31bff4309e67..5849e5161203 100644 --- a/drivers/tee/qcomtee/async.c +++ b/drivers/tee/qcomtee/async.c @@ -97,10 +97,10 @@ static void qcomtee_get_async_buffer(struct qcomtee_object_invoke_ctx *oic, /** * async_release() - Process QTEE async release requests. * @oic: context used for the current invocation. - * @msg: async message for object release. + * @async_msg: async message for object release. * @size: size of the async buffer available. * - * Return: Size of the outbound buffer used when processing @msg. + * Return: Size of the outbound buffer used when processing @async_msg. */ static size_t async_release(struct qcomtee_object_invoke_ctx *oic, struct qcomtee_async_msg_hdr *async_msg, diff --git a/drivers/tee/qcomtee/qcomtee_msg.h b/drivers/tee/qcomtee/qcomtee_msg.h index 878f70178a5b..5d7b21fdd368 100644 --- a/drivers/tee/qcomtee/qcomtee_msg.h +++ b/drivers/tee/qcomtee/qcomtee_msg.h @@ -112,7 +112,7 @@ union qcomtee_msg_arg { /** * struct qcomtee_msg_object_invoke - Direct object invocation message. - * @ctx: object ID hosted in QTEE. + * @cxt: object ID hosted in QTEE. * @op: operation for the object. * @counts: number of different types of arguments in @args. * @args: array of arguments. diff --git a/drivers/tee/qcomtee/qcomtee_object.h b/drivers/tee/qcomtee/qcomtee_object.h index 8b4401ecad48..d5de02dcef3b 100644 --- a/drivers/tee/qcomtee/qcomtee_object.h +++ b/drivers/tee/qcomtee/qcomtee_object.h @@ -74,6 +74,7 @@ enum qcomtee_object_type { * @QCOMTEE_ARG_TYPE_OO: output object (OO). * @QCOMTEE_ARG_TYPE_IB: input buffer (IB). * @QCOMTEE_ARG_TYPE_IO: input object (IO). + * @QCOMTEE_ARG_TYPE_NR: number of argument types. * * Use the invalid type to specify the end of the argument array. */ From 35ce3c571297aee13c6da604d022929fd1aa9efb Mon Sep 17 00:00:00 2001 From: Dale Whinham Date: Sun, 30 Aug 2026 18:40:07 +0100 Subject: [PATCH 002/367] arm64: dts: qcom: x1-denali: Fix microphone distortion The microphones (2x) on Surface Pro 11 are currently ususable due to a misconfigured sample rate, which results in a very distorted recording with the voice barely audible. Under Windows, the DMIC0_CTL register is set to 0x00000005 while recording: bit 0 = 1 --> DMIC0 enabled bits 3:1 = 2 --> DMIC clock divider = /4 The current dmic-sample-rate of 4800000 corresponds to the default DMIC clock divider of /2. Dropping dmic-sample-rate to 2400000 sets the divider to /4, matching the working Windows configuration and resulting in perfect audio. While we're at it, remove the reference to dmic23_default, as we only have two microphones and don't need to configure the pins for a 3rd/4th microphone. Signed-off-by: Dale Whinham Reviewed-by: Abel Vesa Reviewed-by: Konrad Dybcio Link: https://lore.kernel.org/r/20260830-surface-pro-11-mic-fix-v1-1-36a53140373b@gmail.com Signed-off-by: Bjorn Andersson --- arch/arm64/boot/dts/qcom/x1-microsoft-denali.dtsi | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/arch/arm64/boot/dts/qcom/x1-microsoft-denali.dtsi b/arch/arm64/boot/dts/qcom/x1-microsoft-denali.dtsi index ce7d958ca687..e3a53b8930d9 100644 --- a/arch/arm64/boot/dts/qcom/x1-microsoft-denali.dtsi +++ b/arch/arm64/boot/dts/qcom/x1-microsoft-denali.dtsi @@ -863,11 +863,11 @@ spkr_01_sd_n_active: spkr-01-sd-n-active-state { }; &lpass_vamacro { - pinctrl-0 = <&dmic01_default>, <&dmic23_default>; + pinctrl-0 = <&dmic01_default>; pinctrl-names = "default"; vdd-micb-supply = <&vreg_l1b_1p8>; - qcom,dmic-sample-rate = <4800000>; + qcom,dmic-sample-rate = <2400000>; }; &mdss { From 5ffd02d16f4e2f0566b8f31d5610538987e1944c Mon Sep 17 00:00:00 2001 From: Luca Weiss Date: Fri, 7 Aug 2026 09:46:04 +0200 Subject: [PATCH 003/367] clk: qcom: gcc-eliza: Fix always-enabling PCIE_RSCC clocks The critical_cbrcrs array will get qcom_branch_set_clk_en() called on each entry which will set BIT(0) to enable the clock. This is however wrong for these two PCIE_RSCC clocks which need BIT(20) and BIT(21) set respectively to enable them. Fixes: 3d356ab4a1ec ("clk: qcom: Add support for Global clock controller on Eliza") Signed-off-by: Luca Weiss Tested-by: Abel Vesa Reviewed-by: Abel Vesa Link: https://lore.kernel.org/r/20260807-various-pcie-clk-fix-v1-1-5caf78a599d7@fairphone.com Signed-off-by: Bjorn Andersson --- drivers/clk/qcom/gcc-eliza.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/clk/qcom/gcc-eliza.c b/drivers/clk/qcom/gcc-eliza.c index 3e26c7a1e5b5..fe18942a59ac 100644 --- a/drivers/clk/qcom/gcc-eliza.c +++ b/drivers/clk/qcom/gcc-eliza.c @@ -3010,8 +3010,6 @@ static const u32 gcc_eliza_critical_cbcrs[] = { 0x26034, /* GCC_CAMERA_XO_CLK */ 0x27004, /* GCC_DISP_AHB_CLK */ 0x71004, /* GCC_GPU_CFG_AHB_CLK */ - 0x52010, /* GCC_PCIE_RSCC_CFG_AHB_CLK */ - 0x52010, /* GCC_PCIE_RSCC_XO_CLK */ 0x32004, /* GCC_VIDEO_AHB_CLK */ 0x32038, /* GCC_VIDEO_XO_CLK */ }; @@ -3045,6 +3043,10 @@ static const struct regmap_config gcc_eliza_regmap_config = { static void clk_eliza_regs_configure(struct device *dev, struct regmap *regmap) { + /* Keep clocks always enabled */ + regmap_update_bits(regmap, 0x52010, BIT(20), BIT(20)); /* GCC_PCIE_RSCC_CFG_AHB_CLK */ + regmap_update_bits(regmap, 0x52010, BIT(21), BIT(21)); /* GCC_PCIE_RSCC_XO_CLK */ + /* FORCE_MEM_CORE_ON for ufs phy ice core and gcc ufs phy axi clocks */ qcom_branch_set_force_mem_core(regmap, gcc_ufs_phy_ice_core_clk, true); qcom_branch_set_force_mem_core(regmap, gcc_ufs_phy_axi_clk, true); From 62bbf7ed9ad97c17377377aae50b7ea7feb9464e Mon Sep 17 00:00:00 2001 From: Luca Weiss Date: Fri, 7 Aug 2026 09:46:05 +0200 Subject: [PATCH 004/367] clk: qcom: gcc-hawi: Fix always-enabling PCIE_RSCC clocks The critical_cbrcrs array will get qcom_branch_set_clk_en() called on each entry which will set BIT(0) to enable the clock. This is however wrong for these two PCIE_RSCC clocks which need BIT(20) and BIT(21) set respectively to enable them. Fixes: 67121dad6cba ("clk: qcom: Add support for global clock controller on Hawi") Signed-off-by: Luca Weiss Link: https://lore.kernel.org/r/20260807-various-pcie-clk-fix-v1-2-5caf78a599d7@fairphone.com Signed-off-by: Bjorn Andersson --- drivers/clk/qcom/gcc-hawi.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/clk/qcom/gcc-hawi.c b/drivers/clk/qcom/gcc-hawi.c index 6fb5a3db9586..f1b95753c3a3 100644 --- a/drivers/clk/qcom/gcc-hawi.c +++ b/drivers/clk/qcom/gcc-hawi.c @@ -3692,8 +3692,6 @@ static const u32 gcc_hawi_critical_cbcrs[] = { 0x67084, /* GCC_PCIE_1_RSC_CORE_CLK */ 0x43014, /* GCC_PCIE_LINK_XO_CLK */ 0x6b088, /* GCC_PCIE_RSC_CORE_CLK */ - 0x52010, /* GCC_PCIE_RSCC_CFG_AHB_CLK */ - 0x52010, /* GCC_PCIE_RSCC_XO_CLK */ 0x32004, /* GCC_VIDEO_AHB_CLK */ 0x32028, /* GCC_VIDEO_XO_CLK */ }; @@ -3763,6 +3761,10 @@ static const struct regmap_config gcc_hawi_regmap_config = { static void clk_hawi_regs_configure(struct device *dev, struct regmap *regmap) { + /* Keep clocks always enabled */ + regmap_update_bits(regmap, 0x52010, BIT(20), BIT(20)); /* GCC_PCIE_RSCC_CFG_AHB_CLK */ + regmap_update_bits(regmap, 0x52010, BIT(21), BIT(21)); /* GCC_PCIE_RSCC_XO_CLK */ + /* FORCE_MEM_CORE_ON for ufs phy ice core clocks */ qcom_branch_set_force_mem_core(regmap, gcc_ufs_phy_ice_core_clk, true); } From 0bfb542fc3368371cefb4cf45ef21c3ba2d37c3f Mon Sep 17 00:00:00 2001 From: Luca Weiss Date: Fri, 7 Aug 2026 09:46:06 +0200 Subject: [PATCH 005/367] clk: qcom: gcc-kaanapali: Fix always-enabling PCIE_RSCC clocks The critical_cbrcrs array will get qcom_branch_set_clk_en() called on each entry which will set BIT(0) to enable the clock. This is however wrong for these two PCIE_RSCC clocks which need BIT(20) and BIT(21) set respectively to enable them. Fixes: d1919c375f21 ("clk: qcom: Add support for Global clock controller on Kaanapali") Signed-off-by: Luca Weiss Link: https://lore.kernel.org/r/20260807-various-pcie-clk-fix-v1-3-5caf78a599d7@fairphone.com Signed-off-by: Bjorn Andersson --- drivers/clk/qcom/gcc-kaanapali.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/clk/qcom/gcc-kaanapali.c b/drivers/clk/qcom/gcc-kaanapali.c index 842c1a70c691..5bcbcd178657 100644 --- a/drivers/clk/qcom/gcc-kaanapali.c +++ b/drivers/clk/qcom/gcc-kaanapali.c @@ -3464,8 +3464,6 @@ static const u32 gcc_kaanapali_critical_cbcrs[] = { 0x9f004, /* GCC_EVA_AHB_CLK */ 0x9f024, /* GCC_EVA_XO_CLK */ 0x71004, /* GCC_GPU_CFG_AHB_CLK */ - 0x52010, /* GCC_PCIE_RSCC_CFG_AHB_CLK */ - 0x52010, /* GCC_PCIE_RSCC_XO_CLK */ 0x32004, /* GCC_VIDEO_AHB_CLK */ 0x32040, /* GCC_VIDEO_XO_CLK */ }; @@ -3480,6 +3478,10 @@ static const struct regmap_config gcc_kaanapali_regmap_config = { static void clk_kaanapali_regs_configure(struct device *dev, struct regmap *regmap) { + /* Keep clocks always enabled */ + regmap_update_bits(regmap, 0x52010, BIT(20), BIT(20)); /* GCC_PCIE_RSCC_CFG_AHB_CLK */ + regmap_update_bits(regmap, 0x52010, BIT(21), BIT(21)); /* GCC_PCIE_RSCC_XO_CLK */ + /* FORCE_MEM_CORE_ON for ufs phy ice core clocks */ qcom_branch_set_force_mem_core(regmap, gcc_ufs_phy_ice_core_clk, true); } From 8f3f88309fa4e975ac27368ee945c2c202251a7c Mon Sep 17 00:00:00 2001 From: Taniya Das Date: Sat, 1 Aug 2026 23:01:17 +0530 Subject: [PATCH 006/367] clk: qcom: gpucc-glymur: Mark the GPU CX GDSC as votable The GPU CX GDSC on Glymur is a votable power domain and hence should not be polled for power-off status during GDSC disable. Also, its power-on status should be read from the GDS hardware control register. Accommodate this by removing POLL_CFG_GDSCR and adding the VOTABLE flag for the GPU CX GDSC, similar to other platforms (e.g. SM8750 and Milos). Fixes: 67e645285dd0 ("clk: qcom: Add support for GPUCC and GXCLK for Glymur") Signed-off-by: Taniya Das Reviewed-by: Jagadeesh Kona Link: https://lore.kernel.org/r/20260801-kaanapali-gpu-voteable-v1-1-fe804d24d2e9@oss.qualcomm.com Signed-off-by: Bjorn Andersson --- drivers/clk/qcom/gpucc-glymur.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/clk/qcom/gpucc-glymur.c b/drivers/clk/qcom/gpucc-glymur.c index 001b2454786a..cac51de20b29 100644 --- a/drivers/clk/qcom/gpucc-glymur.c +++ b/drivers/clk/qcom/gpucc-glymur.c @@ -510,7 +510,7 @@ static struct gdsc gpu_cc_cx_gdsc = { .name = "gpu_cc_cx_gdsc", }, .pwrsts = PWRSTS_OFF_ON, - .flags = POLL_CFG_GDSCR | RETAIN_FF_ENABLE, + .flags = RETAIN_FF_ENABLE | VOTABLE, }; static struct clk_regmap *gpu_cc_glymur_clocks[] = { From 55981579b27b6541aeeab81c768f248dba0491ac Mon Sep 17 00:00:00 2001 From: Taniya Das Date: Sat, 1 Aug 2026 23:01:18 +0530 Subject: [PATCH 007/367] clk: qcom: gpucc-kaanapali: Mark the GPU CX GDSC as votable The GPU CX GDSC on Kaanapali is a votable power domain and hence should not be polled for power-off status during GDSC disable. Also, its power-on status should be read from the GDS hardware control register. Accommodate this by removing POLL_CFG_GDSCR and adding the VOTABLE flag for the GPU CX GDSC, similar to other platforms (e.g. SM8750 and Milos). Fixes: 685ec348339b ("clk: qcom: Add support for GPUCC and GXCLK for Kaanapali") Signed-off-by: Taniya Das Reviewed-by: Jagadeesh Kona Link: https://lore.kernel.org/r/20260801-kaanapali-gpu-voteable-v1-2-fe804d24d2e9@oss.qualcomm.com Signed-off-by: Bjorn Andersson --- drivers/clk/qcom/gpucc-kaanapali.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/clk/qcom/gpucc-kaanapali.c b/drivers/clk/qcom/gpucc-kaanapali.c index ae5563e516f6..f75f2caa2205 100644 --- a/drivers/clk/qcom/gpucc-kaanapali.c +++ b/drivers/clk/qcom/gpucc-kaanapali.c @@ -380,7 +380,7 @@ static struct gdsc gpu_cc_cx_gdsc = { .name = "gpu_cc_cx_gdsc", }, .pwrsts = PWRSTS_OFF_ON, - .flags = POLL_CFG_GDSCR | RETAIN_FF_ENABLE, + .flags = RETAIN_FF_ENABLE | VOTABLE, }; static struct clk_regmap *gpu_cc_kaanapali_clocks[] = { From 8188a59dd6782786556b18f515090b79cb701c45 Mon Sep 17 00:00:00 2001 From: Vincent Jardin Date: Mon, 24 Aug 2026 23:25:29 +0200 Subject: [PATCH 008/367] arm64: dts: lx2160a: fix incorrect pinmux For gpio1 value set for pinctrl is outside the mask and so will do nothing. Add the missing offset for value field to be the same as the mask as expected. Fixes: 284ad7064aaa ("arm64: dts: lx2160a: complete pinmux for rcwsr12 configuration word") Suggested-by: Thibaut Collet Reviewed-by: Josua Mayer Signed-off-by: Vincent Jardin Signed-off-by: Frank Li --- arch/arm64/boot/dts/freescale/fsl-lx216x.dtsi | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/arch/arm64/boot/dts/freescale/fsl-lx216x.dtsi b/arch/arm64/boot/dts/freescale/fsl-lx216x.dtsi index ae6e37870c5e..d4522a19405a 100644 --- a/arch/arm64/boot/dts/freescale/fsl-lx216x.dtsi +++ b/arch/arm64/boot/dts/freescale/fsl-lx216x.dtsi @@ -1622,7 +1622,7 @@ fspi_data74_pins: xspi1-data74-pins { }; gpio1_31_28_pins: xspi1-data74-gpio-pins { - pinctrl-single,bits = <0x0 0x1 (0x7 << 15)>; + pinctrl-single,bits = <0x0 (0x1 << 15) (0x7 << 15)>; }; fspi_data30_pins: xspi1-data30-pins { @@ -1630,7 +1630,7 @@ fspi_data30_pins: xspi1-data30-pins { }; gpio1_27_24_pins: xspi1-data30-gpio-pins { - pinctrl-single,bits = <0x0 0x1 (0x7 << 18)>; + pinctrl-single,bits = <0x0 (0x1 << 18) (0x7 << 18)>; }; fspi_dqs_sck_cs10_pins: xspi1-base-pins { @@ -1638,7 +1638,7 @@ fspi_dqs_sck_cs10_pins: xspi1-base-pins { }; gpio1_23_20_pins: xspi1-base-gpio-pins { - pinctrl-single,bits = <0x0 0x1 (0x7 << 21)>; + pinctrl-single,bits = <0x0 (0x1 << 21) (0x7 << 21)>; }; esdhc0_cmd_data30_clk_vsel_pins: sdhc1-base-sdhc-vsel-pins { From 6affdda171ca8f2e082e8c4fba56233a2f8c9910 Mon Sep 17 00:00:00 2001 From: Vincent Jardin Date: Mon, 24 Aug 2026 23:25:30 +0200 Subject: [PATCH 009/367] arm64: dts: lx2160a: fix IIC1 pinmux submask rejected by pinctrl-single IIC1_PMUX is a single bit field at RCWSR14 bit 10, so the mask for the two iic1 pinmux nodes was narrowed from 0x7 to 0x1 to avoid writing the two reserved bits next to it. With pinctrl-single,bit-per-mux the driver does not take the supplied mask as the bits to modify. It derives the field from the node's pinctrl-single,function-mask, which is 0x7 here, and then requires the supplied mask to match it exactly: bit_pos = __ffs(mask); /* 10 */ mask_pos = ((pcs->fmask) << bit_pos); /* 0x7 << 10 = 0x1c00 */ submask = mask & mask_pos; /* 0x400 */ ... if (submask != mask_pos) { dev_warn(pcs->dev, "Invalid submask 0x%x for %pOFn at 0x%x\n", submask, np, offset); continue; } A one bit mask gives submask 0x400 against mask_pos 0x1c00, so both nodes are rejected and the pins are never muxed with this message: pinctrl-single 70010012c.pinmux: Invalid submask 0x400 for iic1-i2c-pins pinctrl-single 70010012c.pinmux: Invalid submask 0x400 for iic1-gpio-pins The values are not changed: - 0x0 selects IIC1 - 0x1 << 10 selects GPIO_1[3:2]. Fixes: 7a3cc49ad1fc ("arm64: dts: lx2160a: change i2c0 (iic1) pinmux mask to one bit") Signed-off-by: Vincent Jardin Reviewed-by: Josua Mayer Signed-off-by: Frank Li --- arch/arm64/boot/dts/freescale/fsl-lx216x.dtsi | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/arch/arm64/boot/dts/freescale/fsl-lx216x.dtsi b/arch/arm64/boot/dts/freescale/fsl-lx216x.dtsi index d4522a19405a..d9d4500a4051 100644 --- a/arch/arm64/boot/dts/freescale/fsl-lx216x.dtsi +++ b/arch/arm64/boot/dts/freescale/fsl-lx216x.dtsi @@ -1687,12 +1687,19 @@ i2c6_i2c7_pins: iic8-iic7-i2c-pins { }; /* RCWSR14 */ + /* + * IIC1_PMUX is a single bit at RCWSR14[10], but bit-per-mux + * uses the mask for the field to clear before applying the + * value. + * So bits 11 and 12, which are reserved, are written as 0 + * on every mux switch, it is harmless. + */ i2c0_pins: iic1-i2c-pins { - pinctrl-single,bits = <0x8 0x0 (0x1 << 10)>; + pinctrl-single,bits = <0x8 0x0 (0x7 << 10)>; }; gpio0_3_2_pins: iic1-gpio-pins { - pinctrl-single,bits = <0x8 (0x1 << 10) (0x1 << 10)>; + pinctrl-single,bits = <0x8 (0x1 << 10) (0x7 << 10)>; }; }; From f14f4dc8ea3810f8ed4f0f3ea13f8bba3e2b0f04 Mon Sep 17 00:00:00 2001 From: Vincent Jardin Date: Mon, 24 Aug 2026 23:25:31 +0200 Subject: [PATCH 010/367] arm64: dts: lx2160a: fix the iic5 spi3 pinmux offset and value The iic5-spi3-pins node has both of its first two cells wrong. The offset selects the register within the node's reg range, where: 0x0 is RCWSR12, 0x4 is RCWSR13, 0x8 is RCWSR14. An offset of 0x3 is not one of them and is not even aligned for the declared 32 bit register width. It is the only node in this pinmux that does not use 0x0, 0x4 neither 0x8. The value is wrong too: IIC5_PMUX is RCWSR12[11:9] and it encodes 0b000 for IIC5, 0b001 for GPIO_1[25:24], 0b010 for SDHC1_CLK_SYNC_OUT/IN, 0b011 for {IIC5_SCL,IIC5_SDA} = {SPI3_SOUT,SPI3_SIN}. This node selects the last of those, so it needs 0x3, not the 0x2, which is the value of the neighbouring sdhc1 clock sync node it was copied from. Fixes: 284ad7064aaa ("arm64: dts: lx2160a: complete pinmux for rcwsr12 configuration word") Reviewed-by: Josua Mayer Signed-off-by: Vincent Jardin Signed-off-by: Frank Li --- arch/arm64/boot/dts/freescale/fsl-lx216x.dtsi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/arm64/boot/dts/freescale/fsl-lx216x.dtsi b/arch/arm64/boot/dts/freescale/fsl-lx216x.dtsi index d9d4500a4051..bccf2d673023 100644 --- a/arch/arm64/boot/dts/freescale/fsl-lx216x.dtsi +++ b/arch/arm64/boot/dts/freescale/fsl-lx216x.dtsi @@ -1602,7 +1602,7 @@ esdhc0_clksync_pins: iic5-sdhc-clk-pins { }; dspi2_miso_mosi_pins: iic5-spi3-pins { - pinctrl-single,bits = <0x3 (0x2 << 9) (0x7 << 9)>; + pinctrl-single,bits = <0x0 (0x3 << 9) (0x7 << 9)>; }; i2c5_pins: iic6-i2c-pins { From 7ba5290db6bf365e46fb70bf13dc2a47048165ea Mon Sep 17 00:00:00 2001 From: Stefano Radaelli Date: Wed, 2 Sep 2026 18:43:17 +0200 Subject: [PATCH 011/367] arm64: dts: imx8mp-var-dart-sonata: Fix Sonata SD I/O supply LDO5 powers the USDHC2 I/O rail on DART-MX8M-PLUS. Without a regulator consumer, the kernel disables LDO5 during late init, which removes the SD card and causes filesystem I/O errors. Reference LDO5 as the Sonata USDHC2 VQMMC supply, so the regulator remains enabled while the SD interface is active. Fixes: 41cd3d91ca859 ("arm64: dts: imx8mp-var-dart: Add support for Variscite Sonata board") Co-developed-by: Luan Rafael Carneiro Signed-off-by: Luan Rafael Carneiro Signed-off-by: Stefano Radaelli Signed-off-by: Frank Li --- arch/arm64/boot/dts/freescale/imx8mp-var-dart-sonata.dts | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/arm64/boot/dts/freescale/imx8mp-var-dart-sonata.dts b/arch/arm64/boot/dts/freescale/imx8mp-var-dart-sonata.dts index 983d67a0c1de..7f78026e70ff 100644 --- a/arch/arm64/boot/dts/freescale/imx8mp-var-dart-sonata.dts +++ b/arch/arm64/boot/dts/freescale/imx8mp-var-dart-sonata.dts @@ -511,6 +511,7 @@ &usdhc2 { pinctrl-2 = <&pinctrl_usdhc2_200mhz>, <&pinctrl_usdhc2_gpio>; cd-gpios = <&gpio2 12 GPIO_ACTIVE_LOW>; vmmc-supply = <®_usdhc2_vmmc>; + vqmmc-supply = <&ldo5>; bus-width = <4>; status = "okay"; }; From 764b683a6180fd72c931c13fea98d383506a3475 Mon Sep 17 00:00:00 2001 From: Ben Cressey Date: Tue, 15 Sep 2026 00:37:35 +0000 Subject: [PATCH 012/367] dm-integrity: fix buffer overflow in inline mode with large tag size In inline mode, dm_integrity_check and dm_integrity_inline_recheck compute the checksum of each block into an on-stack buffer of HASH_MAX_DIGESTSIZE bytes. integrity_sector_checksum pads its result with zeroes up to the tag size, so if the tag size is larger than HASH_MAX_DIGESTSIZE, the padding runs past the end of the buffer. With CONFIG_STACKPROTECTOR the kernel panics in dm_integrity_check on the first read. Enlarge both buffers to hold MAX_TAG_SIZE bytes, as commit b93b6643e9b5 ("dm integrity: fix a crash with unusually large tag size") did for integrity_metadata. Note that this is not a security bug because the buffer overflow can only be triggered by root. Fixes: fb0987682c62 ("dm-integrity: introduce the Inline mode") Cc: stable@vger.kernel.org Signed-off-by: Ben Cressey Assisted-by: LLM Signed-off-by: Mikulas Patocka --- drivers/md/dm-integrity.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/md/dm-integrity.c b/drivers/md/dm-integrity.c index 92970e12267a..0862f31b8498 100644 --- a/drivers/md/dm-integrity.c +++ b/drivers/md/dm-integrity.c @@ -2721,7 +2721,7 @@ static void dm_integrity_inline_recheck(struct work_struct *w) outgoing_data = dio->integrity_payload + PAGE_SIZE; while (dio->bio_details.bi_iter.bi_size) { - char digest[HASH_MAX_DIGESTSIZE]; + char digest[MAX_T(size_t, HASH_MAX_DIGESTSIZE, MAX_TAG_SIZE)]; int r; struct bio_integrity_payload *bip; struct bio_vec bv; @@ -2788,7 +2788,7 @@ static inline bool dm_integrity_check(struct dm_integrity_c *ic, struct dm_integ unsigned pos = 0; while (dio->bio_details.bi_iter.bi_size) { - char digest[HASH_MAX_DIGESTSIZE]; + char digest[MAX_T(size_t, HASH_MAX_DIGESTSIZE, MAX_TAG_SIZE)]; struct bio_vec bv = bio_iter_iovec(bio, dio->bio_details.bi_iter); char *mem = integrity_kmap(ic, bv.bv_page); integrity_sector_checksum(ic, &dio->ahash_req, dio->bio_details.bi_iter.bi_sector, mem, bv.bv_offset, digest); From 3195e0daf590683992edb4eed32269e569e04924 Mon Sep 17 00:00:00 2001 From: Ben Cressey Date: Tue, 15 Sep 2026 00:39:10 +0000 Subject: [PATCH 013/367] dm-crypt: reject the lmk IV mode with AEAD ciphers A "capi:" table line that pairs an AEAD cipher with the lmk IV mode, for example "capi:gcm(aes)-lmk" with the integrity:28:aead option, causes an oops when it is loaded. lmk sets tfms_count to 64, but crypt_alloc_tfms_aead only allocates one transform, so crypt_setkey reads past the end of the tfms_aead array and calls crypto_aead_setkey on the values it finds there. lmk exists for compatibility with Loop-AES, which uses CBC, so refuse it with an AEAD cipher rather than allocate 64 AEAD transforms. The check cannot go in crypt_iv_lmk_ctr with the other lmk checks, because the IV constructor runs after crypt_set_key. Note that this is not a security bug because the memory corruption can only be triggered by root. Fixes: 33d2f09fcb35 ("dm crypt: introduce new format of cipher with "capi:" prefix") Cc: stable@vger.kernel.org Signed-off-by: Ben Cressey Assisted-by: LLM Signed-off-by: Mikulas Patocka --- drivers/md/dm-crypt.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/md/dm-crypt.c b/drivers/md/dm-crypt.c index 9e170de50ad3..0bd22bda3e2d 100644 --- a/drivers/md/dm-crypt.c +++ b/drivers/md/dm-crypt.c @@ -2894,8 +2894,13 @@ static int crypt_ctr_cipher_new(struct dm_target *ti, char *cipher_in, char *key } } - if (*ivmode && !strcmp(*ivmode, "lmk")) + if (*ivmode && !strcmp(*ivmode, "lmk")) { + if (crypt_integrity_aead(cc)) { + ti->error = "AEAD transforms not supported for LMK"; + return -EINVAL; + } cc->tfms_count = 64; + } if (*ivmode && !strcmp(*ivmode, "essiv")) { if (!*ivopts) { From 9b3fcf519d370df94a56f3b272d9eddffffa5be8 Mon Sep 17 00:00:00 2001 From: Palla Raghunath Date: Sat, 19 Sep 2026 00:43:39 +0100 Subject: [PATCH 014/367] dm ioctl: don't let data_start run past the buffer struct dm_ioctl has seven bytes of trailing padding, so the smallest buffer userspace is allowed to ask for, offsetof(struct dm_ioctl, data), is seven bytes shorter than sizeof(struct dm_ioctl). get_result_buffer() sets data_start to sizeof(struct dm_ioctl) without looking at how big the buffer actually is. Ask for the minimum and data_start ends up seven bytes past the end. Callers add it to data_size, and ctl_ioctl() copies that much back to userspace: BUG: KASAN: slab-out-of-bounds in _copy_to_user+0xad/0xd0 Read of size 312 at addr ffff8880169e4c00 by task repro-dm/140 ctl_ioctl+0x5e3/0xcb0 dm_ctl_ioctl+0x25/0x40 allocated 305-byte region [ffff8880169e4c00, ffff8880169e4d31) A DM_LIST_DEVICES with data_size set to 305 is enough to hit it, and the seven bytes that leak are whatever sat next to the allocation. Clamp data_start to the buffer size. Note that this is not a security bug because the out-of-bounds read can only be triggered by root. Reported-by: syzbot+48d935cf48a4a76be346@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=48d935cf48a4a76be346 Fixes: 76c072b48e39 ("dm ioctl: move compat code") Cc: stable@vger.kernel.org Signed-off-by: Palla Raghunath Signed-off-by: Mikulas Patocka --- drivers/md/dm-ioctl.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/drivers/md/dm-ioctl.c b/drivers/md/dm-ioctl.c index a6b8e97755cd..a005f124d706 100644 --- a/drivers/md/dm-ioctl.c +++ b/drivers/md/dm-ioctl.c @@ -656,10 +656,16 @@ static void *get_result_buffer(struct dm_ioctl *param, size_t param_size, { param->data_start = align_ptr(param + 1) - (void *) param; - if (param->data_start < param_size) + if (param->data_start < param_size) { *len = param_size - param->data_start; - else + } else { + /* + * The buffer can be as small as offsetof(*param, data), which + * is less than sizeof(*param), so don't run off the end of it. + */ *len = 0; + param->data_start = param_size; + } return ((void *) param) + param->data_start; } From 58a00330248154461c52a6f3bd5c01e5b67f9560 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Fri, 18 Sep 2026 11:38:38 +0200 Subject: [PATCH 015/367] power: sequencing: pcie-m2: Add Lenovo ThinkPad T14s gen6 WCN7850 subsystem PCI ids Commit a39ac4651e3b ("power: sequencing: pcie-m2: Match WCN6855 and WCN7851 UART BT variants by subdevice ID") has caused the serdev for the WCN7850 on the Lenovo ThinkPad T14s gen6 WCN7850 no longer getting created. Add a new match using the PCI subdev ids from the T14s gen6 to fix this. Fixes: a39ac4651e3b ("power: sequencing: pcie-m2: Match WCN6855 and WCN7851 UART BT variants by subdevice ID") Signed-off-by: Hans de Goede Link: https://patch.msgid.link/20260918093838.6645-1-johannes.goede@oss.qualcomm.com Signed-off-by: Bartosz Golaszewski --- drivers/power/sequencing/pwrseq-pcie-m2.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/power/sequencing/pwrseq-pcie-m2.c b/drivers/power/sequencing/pwrseq-pcie-m2.c index 471ffe914a4e..0b72e3a6992b 100644 --- a/drivers/power/sequencing/pwrseq-pcie-m2.c +++ b/drivers/power/sequencing/pwrseq-pcie-m2.c @@ -197,6 +197,8 @@ static const struct pci_device_id pwrseq_m2_pci_ids[] = { .driver_data = (kernel_ulong_t)"qcom,wcn6855-bt" }, { PCI_DEVICE_SUB(PCI_VENDOR_ID_QCOM, 0x1107, PCI_VENDOR_ID_QCOM, 0x337c), .driver_data = (kernel_ulong_t)"qcom,wcn7850-bt" }, + { PCI_DEVICE_SUB(PCI_VENDOR_ID_QCOM, 0x1107, PCI_VENDOR_ID_LENOVO, 0xe0e9), + .driver_data = (kernel_ulong_t)"qcom,wcn7850-bt" }, { } /* Sentinel */ }; From 057e5e07420c753f248f9ce148040ab6dcf8359f Mon Sep 17 00:00:00 2001 From: Peng Fan Date: Wed, 16 Sep 2026 23:23:33 +0800 Subject: [PATCH 016/367] iommu/dma: skip swiotlb bounce for DMA_ATTR_MMIO in iommu_dma_map_phys MMIO resources mapped via dma_map_resource() carry DMA_ATTR_MMIO. These never need swiotlb bounce buffering, but the current code enters the swiotlb path when the physical address is not page-aligned, then rejects the mapping because MMIO cannot be bounced. Move the DMA_ATTR_MMIO check before the swiotlb block so the entire bounce path is skipped for MMIO, falling through directly to __iommu_dma_map() which creates the IOVA mapping. Fixes: f9374de14c0e8 ("iommu/dma: implement DMA_ATTR_MMIO for iommu_dma_(un)map_phys()") Assisted-by: Claude:claude-opus-4-6 Signed-off-by: Peng Fan Acked-by: Robin Murphy Link: https://lore.kernel.org/r/20260916-iommu-dma-fix-v1-1-a59a0c45ec5b@nxp.com Signed-off-by: Marek Szyprowski --- drivers/iommu/dma-iommu.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/iommu/dma-iommu.c b/drivers/iommu/dma-iommu.c index 58c624513cd4..68ee57800fef 100644 --- a/drivers/iommu/dma-iommu.c +++ b/drivers/iommu/dma-iommu.c @@ -1228,9 +1228,9 @@ dma_addr_t iommu_dma_map_phys(struct device *dev, phys_addr_t phys, size_t size, * If both the physical buffer start address and size are page aligned, * we don't need to use a bounce page. */ - if (dev_use_swiotlb(dev, size, dir) && + if (!(attrs & DMA_ATTR_MMIO) && dev_use_swiotlb(dev, size, dir) && iova_unaligned(iovad, phys, size)) { - if (attrs & (DMA_ATTR_MMIO | DMA_ATTR_REQUIRE_COHERENT)) + if (attrs & DMA_ATTR_REQUIRE_COHERENT) return DMA_MAPPING_ERROR; phys = iommu_dma_map_swiotlb(dev, phys, size, dir, attrs); From 51c2db3ef0cb89a49ab7f47a40efd5d5b074ce8d Mon Sep 17 00:00:00 2001 From: Ming Qian Date: Mon, 21 Sep 2026 19:03:34 +0900 Subject: [PATCH 017/367] pmdomain: imx8m-blk-ctrl: Serialize power on/off across sibling domains On i.MX8MP the VPU blk-ctrl exposes G1, G2 and VC8000E as three separate genpds, each serialized only by its own genpd lock, so their power_on and power_off callbacks can run concurrently on SMP. The sequences are not independent: they share the VPUMIX bus domain, the VPU_NOC and the ADB400 handshake. On power up the GPC cannot ack-verify the ADB400 handshake - the ack only completes once blk-ctrl sets the bus clk-en bit - so it just waits a fixed delay instead of polling hskack. A sibling transition landing inside another domain's reset window disturbs that shared clock and handshake timing, the victim's reset does not take effect, and its block registers read back all zeros: the decoder times out or the encoder fails its format check. Serialize the blk-ctrl reset sequence with a per-blk-ctrl mutex; the driver deliberately avoids a genpd hierarchy, so the genpd core gives no cross-sibling serialization. Fixes: a1a5f15f7f6c ("soc: imx: imx8m-blk-ctrl: add i.MX8MP VPU blk ctrl") Signed-off-by: Ming Qian Reviewed-by: Frank Li Cc: stable@vger.kernel.org Signed-off-by: Ulf Hansson --- drivers/pmdomain/imx/imx8m-blk-ctrl.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/drivers/pmdomain/imx/imx8m-blk-ctrl.c b/drivers/pmdomain/imx/imx8m-blk-ctrl.c index 479789009c7f..f8105e87ea3c 100644 --- a/drivers/pmdomain/imx/imx8m-blk-ctrl.c +++ b/drivers/pmdomain/imx/imx8m-blk-ctrl.c @@ -15,6 +15,7 @@ #include #include #include +#include #include #include @@ -34,6 +35,12 @@ struct imx8m_blk_ctrl { struct regmap *regmap; struct imx8m_blk_ctrl_domain *domains; struct genpd_onecell_data onecell_data; + /* + * Serializes the blk-ctrl reset/clock sequence across sibling domains; + * their transitions interact through the shared VPUMIX bus domain, + * VPU_NOC and the not-ack-verified ADB400 handshake (ERR050531). + */ + struct mutex power_lock; }; struct imx8m_blk_ctrl_domain_data { @@ -98,6 +105,8 @@ static int imx8m_blk_ctrl_power_on(struct generic_pm_domain *genpd) struct imx8m_blk_ctrl *bc = domain->bc; int ret; + guard(mutex)(&bc->power_lock); + /* make sure bus domain is awake */ ret = pm_runtime_get_sync(bc->bus_power_dev); if (ret < 0) { @@ -164,6 +173,8 @@ static int imx8m_blk_ctrl_power_off(struct generic_pm_domain *genpd) const struct imx8m_blk_ctrl_domain_data *data = domain->data; struct imx8m_blk_ctrl *bc = domain->bc; + guard(mutex)(&bc->power_lock); + /* put devices into reset and disable clocks */ if (data->mipi_phy_rst_mask) regmap_clear_bits(bc->regmap, BLK_MIPI_RESET_DIV, data->mipi_phy_rst_mask); @@ -202,6 +213,10 @@ static int imx8m_blk_ctrl_probe(struct platform_device *pdev) bc->dev = dev; + ret = devm_mutex_init(dev, &bc->power_lock); + if (ret) + return ret; + bc_data = of_device_get_match_data(dev); base = devm_platform_ioremap_resource(pdev, 0); From 2579cbe68005f46fc7f8f95364f6b101b07b9d1c Mon Sep 17 00:00:00 2001 From: Rohinthan P Date: Wed, 23 Sep 2026 12:50:50 +0530 Subject: [PATCH 018/367] media: em28xx: use video_unregister_device for radio_dev Commit 7c8bf63b7937 ("media: em28xx: use vb2_video_unregister_device") converted device unregistrations in em28xx to use vb2_video_unregister_device() instead of video_unregister_device(). However, radio devices do not have an associated videobuf2 queue (v4l2->radio_dev.queue is NULL). Calling vb2_video_unregister_device() on a radio device triggers a WARNING in vb2_video_unregister_device(): !vdev->queue WARNING: drivers/media/common/videobuf2/videobuf2-v4l2.c:1264 at vb2_video_unregister_device+0x1f8/0x200 Call Trace: em28xx_v4l2_init+0x1c39/0x3150 drivers/media/usb/em28xx/em28xx-video.c:3085 As documented in vb2_video_unregister_device(), video_unregister_device() should be used instead when vdev->queue is NULL. Revert to calling video_unregister_device() for v4l2->radio_dev in both em28xx_v4l2_fini() and the em28xx_v4l2_init() error unwind path. Fixes: 7c8bf63b7937 ("media: em28xx: use vb2_video_unregister_device") Reported-by: syzbot+f26bc23e42eeb548dee1@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=f26bc23e42eeb548dee1 Signed-off-by: Rohinthan P Signed-off-by: Hans Verkuil --- drivers/media/usb/em28xx/em28xx-video.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/media/usb/em28xx/em28xx-video.c b/drivers/media/usb/em28xx/em28xx-video.c index c418add65bb5..79af154767c1 100644 --- a/drivers/media/usb/em28xx/em28xx-video.c +++ b/drivers/media/usb/em28xx/em28xx-video.c @@ -2416,7 +2416,7 @@ static int em28xx_v4l2_fini(struct em28xx *dev) if (video_is_registered(&v4l2->radio_dev)) { dev_info(&dev->intf->dev, "V4L2 device %s deregistered\n", video_device_node_name(&v4l2->radio_dev)); - vb2_video_unregister_device(&v4l2->radio_dev); + video_unregister_device(&v4l2->radio_dev); } if (video_is_registered(&v4l2->vbi_dev)) { dev_info(&dev->intf->dev, "V4L2 device %s deregistered\n", @@ -3082,7 +3082,7 @@ static int em28xx_v4l2_init(struct em28xx *dev) dev_info(&dev->intf->dev, "V4L2 device %s deregistered\n", video_device_node_name(&v4l2->radio_dev)); - vb2_video_unregister_device(&v4l2->radio_dev); + video_unregister_device(&v4l2->radio_dev); } if (video_is_registered(&v4l2->vbi_dev)) { dev_info(&dev->intf->dev, From 94cf5971b1d04c6382437387c4dc3836cc7a1437 Mon Sep 17 00:00:00 2001 From: Fan Wu Date: Tue, 15 Sep 2026 09:51:03 +0000 Subject: [PATCH 019/367] drm/vc4: drain the hangcheck timer and works on V3D unbind MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Nothing stops the hangcheck timer that submitted jobs arm, or the job_done_work that the render-done interrupt queues, at teardown: vc4_irq_disable() cancels only overflow_mem_work, and vc4_gem_destroy() runs from the drm-managed release, after vc4_v3d_unbind() has already uninstalled the V3D interrupt and cleared vc4->v3d. A timer still armed by then reads V3D registers through the NULL vc4->v3d pointer, and the late callbacks run on the vc4_dev embedding them after it has been freed. Drain them in vc4_v3d_unbind(): shut the timer down and cancel reset_work before the interrupt is taken down, because its vc4_irq_reset() re-enables it, then flush job_done_work so that completions queued at teardown still release their jobs. This issue was found by an in-house static analysis tool. Fixes: d5b1a78a772f ("drm/vc4: Add support for drawing 3D frames.") Cc: stable@vger.kernel.org # 6.2+ Assisted-by: Codex:gpt-5.6 Co-developed-by: Song Li Signed-off-by: Song Li Signed-off-by: Fan Wu Link: https://patch.msgid.link/20260915095103.156007-1-fanwu01@zju.edu.cn Reviewed-by: Maíra Canal Signed-off-by: Maíra Canal --- drivers/gpu/drm/vc4/vc4_v3d.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/gpu/drm/vc4/vc4_v3d.c b/drivers/gpu/drm/vc4/vc4_v3d.c index f32410420d3e..0f0c090afa6e 100644 --- a/drivers/gpu/drm/vc4/vc4_v3d.c +++ b/drivers/gpu/drm/vc4/vc4_v3d.c @@ -9,6 +9,7 @@ #include #include #include +#include #include @@ -497,8 +498,15 @@ static void vc4_v3d_unbind(struct device *dev, struct device *master, struct drm_device *drm = data; struct vc4_dev *vc4 = to_vc4_dev(drm); + /* A straggler vc4_reset() re-enables the interrupt. */ + timer_shutdown_sync(&vc4->hangcheck.timer); + cancel_work_sync(&vc4->hangcheck.reset_work); + vc4_irq_uninstall(drm); + /* Flush rather than cancel, so queued completions release their jobs. */ + flush_work(&vc4->job_done_work); + /* Disable the binner's overflow memory address, so the next * driver probe (if any) doesn't try to reuse our old * allocation. From 1ab8904ae61b076d1242e1f48537698c77074b5c Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Wed, 23 Sep 2026 14:27:53 +0200 Subject: [PATCH 020/367] mount: keep a copied mount unbindable It's groundhog day. MNT_UNBINDABLE used to be set in in mnt->mnt.mnt_flags and was not part of MNT_INTERNAL_FLAGS. clone_mnt() copied it with all the other flags to the new mount. This guaranteed that the copy of an unbindable mount became unbindable as well. Unbindable copies that ended up as shared lost the unbindable property. But then commit 406fea799925 ("mount: separate the flags accessed only under namespace_sem") moved MNT_UNBINDABLE from mnt->mnt.mnt_flags into mnt->mnt_t_flags as T_UNBINDABLE. clone_mnt() doesn't copy from mnt_t_flags and specifically doesn't copy T_UNBINDABLE. The only place a mount gets marked unbindable is in change_mnt_propagation() for MS_UNBINDABLE. This reintroduced an earlier bug we had already fixed. It resurfaces in copy_mnt_ns() which clones unbindable mounts via CL_COPY_UNBINDABLE. So since v6.17 a mount namespace created via clone(CLONE_NEWNS) or unshare(CLONE_NEWNS) contain private and bindable copies of every unbindable mount. The following snippet: mount --make-unbindable /mnt unshare -m --propagation unchanged mount --bind /mnt /tmp/x succeeds. This used to fail with EINVAL. This also applies to recursively binding a tree that contains an unbindable the mount. Such subtrees used to be pruned when they were moved onto a shared mount. And Documentation/filesystems/sharedsubtree.rst still says that the copy of an unbindable mount is unbindable. So once again let's fix this. Copy T_UNBINDABLE in clone_mnt(). Let set_mnt_shared() mask it off whenever a mount is made shared. This restores what the old MNT_INTERNAL_FLAGS mask did. Link: https://patch.msgid.link/20260923-work-mount-fixes-v1-1-f424cf8d3242@kernel.org Fixes: 406fea799925 ("mount: separate the flags accessed only under namespace_sem") Cc: stable@vger.kernel.org # v6.17+ Signed-off-by: Christian Brauner (Amutable) --- fs/namespace.c | 1 + 1 file changed, 1 insertion(+) diff --git a/fs/namespace.c b/fs/namespace.c index 580877e46b1a..a052f847c5df 100644 --- a/fs/namespace.c +++ b/fs/namespace.c @@ -1254,6 +1254,7 @@ static struct mount *clone_mnt(struct mount *old, struct dentry *root, mnt->mnt.mnt_flags = READ_ONCE(old->mnt.mnt_flags) & ~MNT_INTERNAL_FLAGS; + mnt->mnt_t_flags = old->mnt_t_flags & T_UNBINDABLE; if (flag & (CL_SLAVE | CL_PRIVATE)) mnt->mnt_group_id = 0; /* not a peer of original */ From 0bab6b6d77607458a25710a42c42ae2343468d2c Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Wed, 23 Sep 2026 14:27:54 +0200 Subject: [PATCH 021/367] selftests/filesystems: check that a copied mount namespace keeps unbindable Make a tmpfs unbindable, copy the mount namespace and check from the copy that: - a bind of the mount fails with EINVAL - a recursive bind fails as well - open_tree(OPEN_TREE_CLONE) of it fails - mountinfo still shows it as unbindable - a copy of the copy refuses the bind too The first case runs in the original namespace so the fixture stays honest about what it set up. Link: https://patch.msgid.link/20260923-work-mount-fixes-v1-2-f424cf8d3242@kernel.org Signed-off-by: Christian Brauner (Amutable) --- tools/testing/selftests/Makefile | 1 + .../filesystems/mntns_unbindable/Makefile | 6 + .../mntns_unbindable/mntns_unbindable_test.c | 227 ++++++++++++++++++ 3 files changed, 234 insertions(+) create mode 100644 tools/testing/selftests/filesystems/mntns_unbindable/Makefile create mode 100644 tools/testing/selftests/filesystems/mntns_unbindable/mntns_unbindable_test.c diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile index 273853937c25..a3df9a15ebb7 100644 --- a/tools/testing/selftests/Makefile +++ b/tools/testing/selftests/Makefile @@ -50,6 +50,7 @@ TARGETS += filesystems/empty_mntns TARGETS += filesystems/fsmount_ns TARGETS += filesystems/fscontext_ns TARGETS += filesystems/xattr +TARGETS += filesystems/mntns_unbindable TARGETS += firmware TARGETS += fpu TARGETS += ftrace diff --git a/tools/testing/selftests/filesystems/mntns_unbindable/Makefile b/tools/testing/selftests/filesystems/mntns_unbindable/Makefile new file mode 100644 index 000000000000..33a311c5bd72 --- /dev/null +++ b/tools/testing/selftests/filesystems/mntns_unbindable/Makefile @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: GPL-2.0 +TEST_GEN_PROGS := mntns_unbindable_test + +CFLAGS += -Wall -O2 -g $(KHDR_INCLUDES) + +include ../../lib.mk diff --git a/tools/testing/selftests/filesystems/mntns_unbindable/mntns_unbindable_test.c b/tools/testing/selftests/filesystems/mntns_unbindable/mntns_unbindable_test.c new file mode 100644 index 000000000000..9aebc37cf74d --- /dev/null +++ b/tools/testing/selftests/filesystems/mntns_unbindable/mntns_unbindable_test.c @@ -0,0 +1,227 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * An unbindable mount stays unbindable in a cloned mount namespace. + */ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../../kselftest_harness.h" + +#ifndef OPEN_TREE_CLONE +#define OPEN_TREE_CLONE 1 +#endif +#ifndef OPEN_TREE_CLOEXEC +#define OPEN_TREE_CLOEXEC O_CLOEXEC +#endif + +static int sys_open_tree(int dfd, const char *filename, unsigned int flags) +{ + return syscall(__NR_open_tree, dfd, filename, flags); +} + +/* Child exit codes. */ +enum { + CHILD_OK, /* the operation failed with EINVAL as it must */ + CHILD_ALLOWED, /* the operation succeeded: the flag was lost */ + CHILD_UNSHARE, /* unshare(CLONE_NEWNS) failed */ + CHILD_ERRNO, /* the operation failed with some other errno */ + CHILD_MOUNTINFO, /* the mount was not found in mountinfo */ +}; + +FIXTURE(mntns_unbindable) +{ + char base[64]; + char src[80]; + char dst[80]; + bool mounted; +}; + +FIXTURE_SETUP(mntns_unbindable) +{ + self->mounted = false; + + if (geteuid() != 0) + SKIP(return, "test requires CAP_SYS_ADMIN"); + + ASSERT_EQ(unshare(CLONE_NEWNS), 0); + ASSERT_EQ(mount("", "/", NULL, MS_REC | MS_PRIVATE, NULL), 0); + + snprintf(self->base, sizeof(self->base), "/tmp/mntns_unbindable.XXXXXX"); + ASSERT_NE(mkdtemp(self->base), NULL); + ASSERT_EQ(mount("tmpfs", self->base, "tmpfs", 0, NULL), 0); + self->mounted = true; + + snprintf(self->src, sizeof(self->src), "%s/src", self->base); + snprintf(self->dst, sizeof(self->dst), "%s/dst", self->base); + ASSERT_EQ(mkdir(self->src, 0755), 0); + ASSERT_EQ(mkdir(self->dst, 0755), 0); + + ASSERT_EQ(mount("tmpfs", self->src, "tmpfs", 0, NULL), 0); + ASSERT_EQ(mount(NULL, self->src, NULL, MS_UNBINDABLE, NULL), 0); +} + +FIXTURE_TEARDOWN(mntns_unbindable) +{ + if (self->mounted) + umount2(self->base, MNT_DETACH); + rmdir(self->base); +} + +static int classify(int ret, int err) +{ + if (ret >= 0) + return CHILD_ALLOWED; + return err == EINVAL ? CHILD_OK : CHILD_ERRNO; +} + +/* Is the mount on @mountpoint marked unbindable in /proc/self/mountinfo? */ +static int mountinfo_unbindable(const char *mountpoint) +{ + char line[4096]; + FILE *f; + int ret = CHILD_MOUNTINFO; + + f = fopen("/proc/self/mountinfo", "re"); + if (!f) + return CHILD_ERRNO; + + while (fgets(line, sizeof(line), f)) { + char *fields[6], *p = line, *opt; + int i; + + for (i = 0; i < 6; i++) { + fields[i] = strsep(&p, " "); + if (!fields[i]) + break; + } + if (i < 6 || strcmp(fields[4], mountpoint)) + continue; + + /* the optional fields, up to the "-" separator */ + ret = CHILD_ALLOWED; + while ((opt = strsep(&p, " ")) && strcmp(opt, "-")) { + if (!strcmp(opt, "unbindable")) + ret = CHILD_OK; + } + break; + } + fclose(f); + return ret; +} + +static int run_in_child(int (*fn)(const char *src, const char *dst), + const char *src, const char *dst) +{ + int status; + pid_t pid; + + pid = fork(); + if (pid < 0) + return -1; + if (pid == 0) + _exit(fn(src, dst)); + if (waitpid(pid, &status, 0) != pid || !WIFEXITED(status)) + return -1; + return WEXITSTATUS(status); +} + +static int bind_after_clone(const char *src, const char *dst) +{ + int ret; + + if (unshare(CLONE_NEWNS)) + return CHILD_UNSHARE; + ret = mount(src, dst, NULL, MS_BIND, NULL); + return classify(ret, errno); +} + +static int rbind_after_clone(const char *src, const char *dst) +{ + int ret; + + if (unshare(CLONE_NEWNS)) + return CHILD_UNSHARE; + ret = mount(src, dst, NULL, MS_BIND | MS_REC, NULL); + return classify(ret, errno); +} + +static int open_tree_after_clone(const char *src, const char *dst) +{ + int ret; + + if (unshare(CLONE_NEWNS)) + return CHILD_UNSHARE; + ret = sys_open_tree(AT_FDCWD, src, OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC); + return classify(ret, errno); +} + +static int mountinfo_after_clone(const char *src, const char *dst) +{ + if (unshare(CLONE_NEWNS)) + return CHILD_UNSHARE; + return mountinfo_unbindable(src); +} + +static int bind_after_two_clones(const char *src, const char *dst) +{ + int ret; + + if (unshare(CLONE_NEWNS)) + return CHILD_UNSHARE; + if (unshare(CLONE_NEWNS)) + return CHILD_UNSHARE; + ret = mount(src, dst, NULL, MS_BIND, NULL); + return classify(ret, errno); +} + +/* The namespace the mount was made unbindable in. */ +TEST_F(mntns_unbindable, refuses_bind) +{ + int ret = mount(self->src, self->dst, NULL, MS_BIND, NULL); + + ASSERT_EQ(classify(ret, errno), CHILD_OK); + ASSERT_EQ(mountinfo_unbindable(self->src), CHILD_OK); +} + +/* A copy of the namespace must not turn the mount bindable. */ +TEST_F(mntns_unbindable, refuses_bind_after_clone) +{ + ASSERT_EQ(run_in_child(bind_after_clone, self->src, self->dst), CHILD_OK) + TH_LOG("bind of an unbindable mount allowed in a copied mount namespace"); +} + +TEST_F(mntns_unbindable, refuses_rbind_after_clone) +{ + ASSERT_EQ(run_in_child(rbind_after_clone, self->src, self->dst), CHILD_OK) + TH_LOG("rbind of an unbindable mount allowed in a copied mount namespace"); +} + +TEST_F(mntns_unbindable, refuses_open_tree_after_clone) +{ + ASSERT_EQ(run_in_child(open_tree_after_clone, self->src, self->dst), CHILD_OK) + TH_LOG("OPEN_TREE_CLONE of an unbindable mount allowed in a copied mount namespace"); +} + +TEST_F(mntns_unbindable, mountinfo_after_clone) +{ + ASSERT_EQ(run_in_child(mountinfo_after_clone, self->src, self->dst), CHILD_OK) + TH_LOG("mountinfo does not show the mount as unbindable in a copied mount namespace"); +} + +TEST_F(mntns_unbindable, refuses_bind_after_two_clones) +{ + ASSERT_EQ(run_in_child(bind_after_two_clones, self->src, self->dst), CHILD_OK) + TH_LOG("bind of an unbindable mount allowed two mount namespace copies down"); +} + +TEST_HARNESS_MAIN From a2b42babc912e4fb428cbb42adb4f6e1e067113d Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Wed, 23 Sep 2026 14:27:55 +0200 Subject: [PATCH 022/367] mount: refuse MOVE_MOUNT_SET_GROUP on an unbindable mount do_set_group() only accepts mounts as targets that are neither shared nor a slave. That encompasses undindable mounts. If the source mount is a slave mount the target mount will end up with source's master as its master. It keeps T_UNBINDABLE. That means we get a mount that is both unbindable and a slave: mount --bind /tmp/src /tmp/src mount --make-shared /tmp/src mount --bind /tmp/src /tmp/b mount --make-slave /tmp/b mount --bind /tmp/src /tmp/c mount --make-unbindable /tmp/c move_mount(b, "", c, "", MOVE_MOUNT_SET_GROUP) grep /tmp/c /proc/self/mountinfo ... /tmp/c ... master:646 unbindable ... This property cannot be produced any other way. change_mnt_propagation() drops the master when it makes a mount unbindable (iow, it becomes private) and doesn't touch an unbindable mount when it is supposed to turned into a slave mount. End-result is that the unbindable-slave mount receives everything propagated from its master's peer group while it can't be bind mounted itself. Not sure what that's supposed to do. On the other side: if the source mount is shared, the target mount drops T_UNBINDABLE because because set_mnt_shared() clears T_SHARED_MASK. So teach do_set_group() to refuse an unbindable target mount the same way a shared or slave target mount is refused. The main user of MOVE_MOUNT_SET_GROUP is CRIU which restores sharing first and applies MS_UNBINDABLE afterwards. It never hits this. Link: https://patch.msgid.link/20260923-work-mount-fixes-v1-3-f424cf8d3242@kernel.org Fixes: 9ffb14ef61ba ("move_mount: allow to add a mount into an existing group") Signed-off-by: Christian Brauner (Amutable) --- fs/namespace.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/namespace.c b/fs/namespace.c index a052f847c5df..d842fc1f1f1a 100644 --- a/fs/namespace.c +++ b/fs/namespace.c @@ -3468,7 +3468,7 @@ static int do_set_group(const struct path *from_path, const struct path *to_path return -EINVAL; /* Setting sharing groups is only allowed on private mounts */ - if (IS_MNT_SHARED(to) || IS_MNT_SLAVE(to)) + if (IS_MNT_SHARED(to) || IS_MNT_SLAVE(to) || IS_MNT_UNBINDABLE(to)) return -EINVAL; /* From should not be private */ From 7541865c260f9bef9fecf212ec39538a21daaf77 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Wed, 23 Sep 2026 14:27:56 +0200 Subject: [PATCH 023/367] selftests/move_mount_set_group: check that an unbindable target is refused MOVE_MOUNT_SET_GROUP must not accept an unbindable mount as the target. Cover both sources: - a slave, which used to leave the target unbindable and a slave at once - a shared mount, which used to silently drop the unbindable flag Check that the target is untouched after the refused call. Link: https://patch.msgid.link/20260923-work-mount-fixes-v1-4-f424cf8d3242@kernel.org Signed-off-by: Christian Brauner (Amutable) --- .../move_mount_set_group_test.c | 74 +++++++++++++++++-- 1 file changed, 68 insertions(+), 6 deletions(-) diff --git a/tools/testing/selftests/move_mount_set_group/move_mount_set_group_test.c b/tools/testing/selftests/move_mount_set_group/move_mount_set_group_test.c index 12434415ec36..9c8fc8c7f62c 100644 --- a/tools/testing/selftests/move_mount_set_group/move_mount_set_group_test.c +++ b/tools/testing/selftests/move_mount_set_group/move_mount_set_group_test.c @@ -146,17 +146,19 @@ static void null_endofword(char *word) *word = '\0'; } -static bool is_shared_mount(const char *path) +/* Does the mount on @path carry the optional field @field in mountinfo? */ +static bool mount_has_field(const char *path, const char *field) { size_t len = 0; char *line = NULL; FILE *f = NULL; + bool found = false; f = fopen("/proc/self/mountinfo", "re"); if (!f) return false; - while (getline(&line, &len, f) != -1) { + while (!found && getline(&line, &len, f) != -1) { char *opts, *target; target = get_field(line, 4); @@ -172,15 +174,29 @@ static bool is_shared_mount(const char *path) if (strcmp(target, path) != 0) continue; - null_endofword(opts); - if (strstr(opts, "shared:")) - return true; + /* the optional fields end at the "-" separator */ + while (opts && *opts != '-') { + char *next = strchr(opts, ' '); + + if (next) + *next++ = '\0'; + if (!strncmp(opts, field, strlen(field))) { + found = true; + break; + } + opts = next; + } } free(line); fclose(f); - return false; + return found; +} + +static bool is_shared_mount(const char *path) +{ + return mount_has_field(path, "shared:"); } /* Attempt to de-conflict with the selftests tree. */ @@ -372,4 +388,50 @@ TEST_F(move_mount_set_group, complex_sharing_copying) ASSERT_EQ(is_shared_mount(SET_GROUP_A), 1); } +#define SET_GROUP_B "/tmp/B" +#define SET_GROUP_C "/tmp/C" + +/* + * An unbindable mount is neither shared nor a slave, so it must not be + * accepted as the target: with a slave source it would end up unbindable + * and a slave at the same time. + */ +TEST_F(move_mount_set_group, unbindable_target) +{ + bool ret; + + ret = move_mount_set_group_supported(); + ASSERT_GE(ret, 0); + if (!ret) + SKIP(return, "move_mount(MOVE_MOUNT_SET_GROUP) is not supported"); + + ASSERT_EQ(mount(NULL, SET_GROUP_A, NULL, MS_SHARED, 0), 0); + + /* B: a slave of A's peer group */ + ASSERT_EQ(mkdir(SET_GROUP_B, 0777), 0); + ASSERT_EQ(mount(SET_GROUP_A, SET_GROUP_B, NULL, MS_BIND, NULL), 0); + ASSERT_EQ(mount(NULL, SET_GROUP_B, NULL, MS_SLAVE, 0), 0); + ASSERT_TRUE(mount_has_field(SET_GROUP_B, "master:")); + + /* C: unbindable */ + ASSERT_EQ(mkdir(SET_GROUP_C, 0777), 0); + ASSERT_EQ(mount(SET_GROUP_A, SET_GROUP_C, NULL, MS_BIND, NULL), 0); + ASSERT_EQ(mount(NULL, SET_GROUP_C, NULL, MS_UNBINDABLE, 0), 0); + ASSERT_TRUE(mount_has_field(SET_GROUP_C, "unbindable")); + + /* from a slave */ + ASSERT_EQ(syscall(__NR_move_mount, AT_FDCWD, SET_GROUP_B, + AT_FDCWD, SET_GROUP_C, MOVE_MOUNT_SET_GROUP), -1); + ASSERT_EQ(errno, EINVAL); + ASSERT_FALSE(mount_has_field(SET_GROUP_C, "master:")); + ASSERT_TRUE(mount_has_field(SET_GROUP_C, "unbindable")); + + /* from a shared mount */ + ASSERT_EQ(syscall(__NR_move_mount, AT_FDCWD, SET_GROUP_A, + AT_FDCWD, SET_GROUP_C, MOVE_MOUNT_SET_GROUP), -1); + ASSERT_EQ(errno, EINVAL); + ASSERT_FALSE(mount_has_field(SET_GROUP_C, "shared:")); + ASSERT_TRUE(mount_has_field(SET_GROUP_C, "unbindable")); +} + TEST_HARNESS_MAIN From 6857e209015f95be7a8f19a9cd5dac345b9b9859 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Wed, 23 Sep 2026 14:27:57 +0200 Subject: [PATCH 024/367] fs: don't silently unmount busy mounts The propagate_mount_busy() helper exists to decide whether a synchronous can suceed. It does that by looking at the copies of the victims in the mounts its parent propagates to. One of those functions from propagation hell. It skips a copy that has child mounts except for the case of a single child mount covering the copy's root. That algorithm used to be exact. It isn't anymore. Originally only childless copies were unmounted during propagation. And 1064f874abc0 ("mnt: Tuck mounts under others instead of creating shadow/side mounts.") ended up adding covered mounts to both sides in one go. Along came 99b19d16471e ("mnt: In propgate_umount handle visiting mounts in any order") and that started to unmount more than before: [1]: A copy of a mount is unmounted when each of its children is either its overmount or a copy of the victim itself. In the former case the overmount gets reparented. In the latter case the copy of the victim will end up being unmounted. So propagate_umount() ended up being rewritten without adjusting propagate_mount_busy(). And currently propagate_mount_busy() doesn't look at copies of the victim that have two or more children nor a single child that is not the overmount. So any such copy in [1] is not checked for references. The result is that a synchronous unmount succeeds, pulling out a mount that is still in use. A container can run into this with its own mounts. The host shares a tree with the container and the host has a mount in that tree: mount -t tmpfs none /mnt mount --make-shared /mnt mount -t tmpfs none /mnt/a The container's /mnt is a slave copy of the host mount. The container now takes a detached copy of the tree and mounts that copy beneath its propagated copy of /mnt/a and keeps the file descriptor to the tree open: unshare -m --propagation unchanged mount --make-rslave / fd = open_tree(AT_FDCWD, "/mnt", OPEN_TREE_CLONE | AT_RECURSIVE) move_mount(fd, "", AT_FDCWD, "/mnt/a", MOVE_MOUNT_BENEATH) grep /mnt /proc/self/mountinfo 775 355 ... /mnt ... master:646 777 775 ... /mnt/a ... master:646 776 777 ... /mnt/a ... master:647 778 777 ... /mnt/a/a ... master:647 The now attached tree (777) is now mounted beneath the container's copy of /mnt/a. That's also where the host mount sits (as seen from the host mount namespace). So the propagated copy (776) of the host's mount is on top of its root and the copy of that (778) is inside it. Now the host runs: umount /mnt/a and it succeeds. The moved tree tree and the copy inside of it are gone from the container. Now only the propagated copy is left and it got reparented to where it was before: grep /mnt /proc/self/mountinfo 775 355 ... /mnt ... master:646 776 775 ... /mnt/a ... But the file descriptor still refers to the moved tree. So a synchronous umount detached a mount that is still in use. Teach propagate_mount_busy() the same checks that propagate_umount() does. It now checks for a single victim mount without children for which it can assume that MNT_LOCKED is cleared on every copy like propagate_umount() does. The copies form chains. IOW, when a copy itself receives propagation from the victim's parent then the mount at the same mountpoint inside that copy is a copy as well. Whether a mount receives propagation from the victim's can simply be checked by walking its masters. Walk each chain once from its top so we are sure that nested copies stay linear. Check every reference once of every copy that gets unmounted. Link: https://patch.msgid.link/20260923-work-mount-fixes-v1-5-f424cf8d3242@kernel.org Fixes: 99b19d16471e ("mnt: In propgate_umount handle visiting mounts in any order") Cc: stable@vger.kernel.org Signed-off-by: Christian Brauner (Amutable) --- fs/pnode.c | 108 ++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 90 insertions(+), 18 deletions(-) diff --git a/fs/pnode.c b/fs/pnode.c index 5d91c3e58d2a..2cd667958efe 100644 --- a/fs/pnode.c +++ b/fs/pnode.c @@ -410,19 +410,99 @@ bool propagation_would_overmount(const struct mount *from, return false; } +/* Does @m receive propagation from @parent? */ +static bool receives_from(struct mount *m, struct mount *parent) +{ + if (m == parent) + return false; + for (; m; m = m->mnt_master) + if (m == parent || peers(m, parent)) + return true; + return false; +} + +/* + * Does @m receive propagation from the victim's parent as well? If so, then + * the mount at the victim's mountpoint inside of @m is a umount candidate as + * well. So it's the next candidate in the chain. Otherwise the chain ends at + * @m. + */ +static struct mount *next_candidate(struct mount *m, struct mount *victim) +{ + if (!receives_from(m, victim->mnt_parent)) + return NULL; + return __lookup_mnt(&m->mnt, victim->mnt_mountpoint); +} + +/* + * Would propagate_umount() pull out a mount of the chain of candidates that + * starts at @c, and does that mount have references beyond its own? + * + * This mirrors how trim_one(), trim_ancestors() and handle_locked() handle a + * synchronous umount: + * + * - single victim + * - without children + * - with MNT_LOCKED already cleared on every candidate by propagate_mount_unlock() + * + * A copy of the victim gets unmounted when each of its children is + * the next candidate in the chain or its overmount, unless the next + * unmount candidate is not its overmount and some unmount candidate further + * down has a child outside the chain. Keep this in sync with + * Documentation/filesystems/propagate_umount.txt. + */ +static bool chain_busy(struct mount *c, struct mount *victim) +{ + struct mount *m, *n, *next, *deepest = NULL; + bool above; + + /* the deepest candidate with a child outside the chain */ + for (m = c; m; m = next) { + next = next_candidate(m, victim); + list_for_each_entry(n, &m->mnt_mounts, mnt_child) { + if (n != next && n != victim) { + deepest = m; + break; + } + } + } + + above = deepest != NULL; /* @deepest is at or below @m */ + for (m = c; m; m = next) { + bool goes = true; + + next = next_candidate(m, victim); + list_for_each_entry(n, &m->mnt_mounts, mnt_child) { + if (n != next && n != m->overmount && n != victim) { + goes = false; + break; + } + } + if (goes && next && next != m->overmount && above && m != deepest) + goes = false; + if (m == deepest) + above = false; + if (goes && do_refcount_check(m, 1)) + return true; + } + return false; +} + /* * check if the mount 'mnt' can be unmounted successfully. * @mnt: the mount to be checked for unmount * NOTE: unmounting 'mnt' would naturally propagate to all * other mounts its parent propagates to. - * Check if any of these mounts that **do not have submounts** - * have more references than 'refcnt'. If so return busy. + * Check if any of the mounts that propagate_umount() would pull out + * along with it have more references than their own. If so return busy. * * vfsmount lock must be held for write */ int propagate_mount_busy(struct mount *mnt, int refcnt) { struct mount *parent = mnt->mnt_parent; + struct dentry *mp = mnt->mnt_mountpoint; + struct mount *m; /* * quickly check if the current mount can be unmounted. @@ -435,24 +515,16 @@ int propagate_mount_busy(struct mount *mnt, int refcnt) if (mnt == parent) return 0; - for (struct mount *m = propagation_next(parent, parent); m; - m = propagation_next(m, parent)) { - struct list_head *head; - struct mount *child = __lookup_mnt(&m->mnt, mnt->mnt_mountpoint); + /* the candidates are the mounts at @mp below the receivers */ + for (m = propagation_next(parent, parent); m; + m = propagation_next(m, parent)) { + struct mount *c = __lookup_mnt(&m->mnt, mp); - if (!child) + /* each chain once, from its top: skip receivers that are candidates */ + if (!c || (mnt_has_parent(m) && m->mnt_mountpoint == mp && + receives_from(m->mnt_parent, parent))) continue; - - head = &child->mnt_mounts; - if (!list_empty(head)) { - /* - * a mount that covers child completely wouldn't prevent - * it being pulled out; any other would. - */ - if (!list_is_singular(head) || !child->overmount) - continue; - } - if (do_refcount_check(child, 1)) + if (chain_busy(c, mnt)) return 1; } return 0; From bba9960c073dbea8eaa31e9a6b9f4224ebdb6ec8 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Wed, 23 Sep 2026 14:27:58 +0200 Subject: [PATCH 025/367] selftests/filesystems: check that a busy propagated copy blocks a synchronous umount A slave namespace moves an open_tree() copy of the shared tree beneath the propagated copy of the victim and keeps the descriptor. Check that: - umount(2) of the victim fails with EBUSY while the descriptor is open - the moved tree is still attached in the slave namespace afterwards - the umount succeeds once the descriptor is closed The test needs CAP_SYS_ADMIN and skips otherwise. Link: https://patch.msgid.link/20260923-work-mount-fixes-v1-6-f424cf8d3242@kernel.org Signed-off-by: Christian Brauner (Amutable) --- tools/testing/selftests/Makefile | 1 + .../filesystems/umount_propagation/Makefile | 6 + .../umount_propagation_test.c | 226 ++++++++++++++++++ 3 files changed, 233 insertions(+) create mode 100644 tools/testing/selftests/filesystems/umount_propagation/Makefile create mode 100644 tools/testing/selftests/filesystems/umount_propagation/umount_propagation_test.c diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile index a3df9a15ebb7..43d4a33afe71 100644 --- a/tools/testing/selftests/Makefile +++ b/tools/testing/selftests/Makefile @@ -51,6 +51,7 @@ TARGETS += filesystems/fsmount_ns TARGETS += filesystems/fscontext_ns TARGETS += filesystems/xattr TARGETS += filesystems/mntns_unbindable +TARGETS += filesystems/umount_propagation TARGETS += firmware TARGETS += fpu TARGETS += ftrace diff --git a/tools/testing/selftests/filesystems/umount_propagation/Makefile b/tools/testing/selftests/filesystems/umount_propagation/Makefile new file mode 100644 index 000000000000..fc0a0783018b --- /dev/null +++ b/tools/testing/selftests/filesystems/umount_propagation/Makefile @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: GPL-2.0 +TEST_GEN_PROGS := umount_propagation_test + +CFLAGS += -Wall -O2 -g $(KHDR_INCLUDES) + +include ../../lib.mk diff --git a/tools/testing/selftests/filesystems/umount_propagation/umount_propagation_test.c b/tools/testing/selftests/filesystems/umount_propagation/umount_propagation_test.c new file mode 100644 index 000000000000..9e18d54dfb32 --- /dev/null +++ b/tools/testing/selftests/filesystems/umount_propagation/umount_propagation_test.c @@ -0,0 +1,226 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * A synchronous umount fails with EBUSY when a mount it would pull out by + * propagation is still in use. + */ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../../kselftest_harness.h" + +#ifndef OPEN_TREE_CLONE +#define OPEN_TREE_CLONE 1 +#endif +#ifndef OPEN_TREE_CLOEXEC +#define OPEN_TREE_CLOEXEC O_CLOEXEC +#endif +#ifndef AT_RECURSIVE +#define AT_RECURSIVE 0x8000 +#endif +#ifndef MOVE_MOUNT_F_EMPTY_PATH +#define MOVE_MOUNT_F_EMPTY_PATH 0x00000004 +#endif +#ifndef MOVE_MOUNT_BENEATH +#define MOVE_MOUNT_BENEATH 0x00000200 +#endif +#ifndef STATX_MNT_ID +#define STATX_MNT_ID 0x00001000U +#endif + +static int sys_open_tree(int dfd, const char *filename, unsigned int flags) +{ + return syscall(__NR_open_tree, dfd, filename, flags); +} + +static int sys_move_mount(int from_dfd, const char *from_pathname, + int to_dfd, const char *to_pathname, + unsigned int flags) +{ + return syscall(__NR_move_mount, from_dfd, from_pathname, to_dfd, + to_pathname, flags); +} + +/* Child exit codes. */ +enum { + CHILD_OK, + CHILD_UNSHARE, /* could not set up the slave namespace */ + CHILD_OPEN_TREE, /* open_tree() failed */ + CHILD_MOVE_MOUNT, /* move_mount() failed */ + CHILD_STATX, /* statx() failed */ + CHILD_PIPE, /* the parent went away */ +}; + +/* Messages between parent and child. */ +enum { + MSG_READY = 'r', /* child: the copy is mounted and referenced */ + MSG_CHECK = 'c', /* parent: check that the copy is still attached */ + MSG_ATTACHED = 'a', /* child: it is */ + MSG_DETACHED = 'd', /* child: it is not */ + MSG_CLOSE = 'x', /* parent: drop the reference */ + MSG_CLOSED = 'y', /* child: dropped */ + MSG_EXIT = 'e', /* parent: done */ +}; + +FIXTURE(umount_propagation) +{ + char base[64]; + char victim[80]; + bool mounted; +}; + +FIXTURE_SETUP(umount_propagation) +{ + self->mounted = false; + + if (geteuid() != 0) + SKIP(return, "test requires CAP_SYS_ADMIN"); + + ASSERT_EQ(unshare(CLONE_NEWNS), 0); + ASSERT_EQ(mount("", "/", NULL, MS_REC | MS_PRIVATE, NULL), 0); + + snprintf(self->base, sizeof(self->base), "/tmp/umount_propagation.XXXXXX"); + ASSERT_NE(mkdtemp(self->base), NULL); + ASSERT_EQ(mount("tmpfs", self->base, "tmpfs", 0, NULL), 0); + self->mounted = true; + ASSERT_EQ(mount(NULL, self->base, NULL, MS_SHARED, NULL), 0); + + snprintf(self->victim, sizeof(self->victim), "%s/victim", self->base); + ASSERT_EQ(mkdir(self->victim, 0755), 0); + ASSERT_EQ(mount("tmpfs", self->victim, "tmpfs", 0, NULL), 0); +} + +FIXTURE_TEARDOWN(umount_propagation) +{ + if (self->mounted) + umount2(self->base, MNT_DETACH); + rmdir(self->base); +} + +static int send_msg(int fd, char msg) +{ + return write(fd, &msg, 1) == 1 ? 0 : -1; +} + +static char recv_msg(int fd) +{ + char msg; + + if (read(fd, &msg, 1) != 1) + return 0; + return msg; +} + +/* Is the mount with id @mnt_id attached in this mount namespace? */ +static bool mount_attached(__u64 mnt_id) +{ + char line[4096]; + bool found = false; + FILE *f; + + f = fopen("/proc/self/mountinfo", "re"); + if (!f) + return false; + + while (fgets(line, sizeof(line), f)) { + if (strtoull(line, NULL, 10) == mnt_id) { + found = true; + break; + } + } + fclose(f); + return found; +} + +/* + * The slave namespace: take a detached copy of the shared tree and move it + * beneath the propagated copy of the victim, keeping the open_tree() + * descriptor as a reference on it. + */ +static int slave_child(const char *base, const char *victim, int to_parent, + int from_parent) +{ + struct statx stx; + int fd; + + if (unshare(CLONE_NEWNS)) + return CHILD_UNSHARE; + if (mount("", "/", NULL, MS_REC | MS_SLAVE, NULL)) + return CHILD_UNSHARE; + + fd = sys_open_tree(AT_FDCWD, base, + OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC | AT_RECURSIVE); + if (fd < 0) + return CHILD_OPEN_TREE; + if (sys_move_mount(fd, "", AT_FDCWD, victim, + MOVE_MOUNT_F_EMPTY_PATH | MOVE_MOUNT_BENEATH)) + return CHILD_MOVE_MOUNT; + if (statx(fd, "", AT_EMPTY_PATH, STATX_MNT_ID, &stx)) + return CHILD_STATX; + + if (send_msg(to_parent, MSG_READY) || recv_msg(from_parent) != MSG_CHECK) + return CHILD_PIPE; + if (send_msg(to_parent, mount_attached(stx.stx_mnt_id) ? + MSG_ATTACHED : MSG_DETACHED)) + return CHILD_PIPE; + + if (recv_msg(from_parent) != MSG_CLOSE) + return CHILD_PIPE; + close(fd); + if (send_msg(to_parent, MSG_CLOSED) || recv_msg(from_parent) != MSG_EXIT) + return CHILD_PIPE; + return CHILD_OK; +} + +TEST_F(umount_propagation, busy_copy_pulled_out) +{ + int to_child[2], to_parent[2]; + int status; + pid_t pid; + + ASSERT_EQ(pipe(to_child), 0); + ASSERT_EQ(pipe(to_parent), 0); + + pid = fork(); + ASSERT_GE(pid, 0); + if (pid == 0) { + close(to_child[1]); + close(to_parent[0]); + _exit(slave_child(self->base, self->victim, to_parent[1], + to_child[0])); + } + close(to_child[0]); + close(to_parent[1]); + + ASSERT_EQ(recv_msg(to_parent[0]), MSG_READY); + + /* the copy in the slave namespace is in use */ + ASSERT_EQ(umount2(self->victim, 0), -1); + ASSERT_EQ(errno, EBUSY); + + ASSERT_EQ(send_msg(to_child[1], MSG_CHECK), 0); + ASSERT_EQ(recv_msg(to_parent[0]), MSG_ATTACHED); + + /* and once it is not, the umount goes through */ + ASSERT_EQ(send_msg(to_child[1], MSG_CLOSE), 0); + ASSERT_EQ(recv_msg(to_parent[0]), MSG_CLOSED); + ASSERT_EQ(umount2(self->victim, 0), 0); + + ASSERT_EQ(send_msg(to_child[1], MSG_EXIT), 0); + ASSERT_EQ(waitpid(pid, &status, 0), pid); + ASSERT_TRUE(WIFEXITED(status)); + ASSERT_EQ(WEXITSTATUS(status), CHILD_OK); +} + +TEST_HARNESS_MAIN From 7eb84d54fac5002bd975d1de8fd61d57e89172ab Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Wed, 23 Sep 2026 14:27:59 +0200 Subject: [PATCH 026/367] fs: don't let a migrating task hide its reference from do_umount() propagate_mount_busy() uses mnt_get_count() via do_refcount_check() to figure out whether a synchronous umount may proceed or not by summing the per-CPU mnt_count counter under mount_lock. The fastpath for mntget() and mntput() doesn't require that lock. A task that already holds a reference and takes another one on a cpu the loop has summed can get migrated and drops that reference again on another cpu. The loop might not have reached that cpu yet. That ends up undercounting. The original reference of the task is missed and umount() ends up succeeding with the file still open. The mount is destroyed later when the task mntput()s so it degrades to a lazy umount. That's basically the umount(2) variant of commit 9ea0a46ca2c3 ("fix mntput/mntput race") closed for the final mntput(). The same cpu-migration caused a UAF. Luckily this isn't a UAF. It's just a hidden reference pinning the mount but it's wrong nonetheless. Like the other race it's a narrow window but I can reproduce it reliably with some patching. The debug patch below prints the sum the loop computed next to an immediate re-sum of the same counters. It reports who drops the last reference to that mount later: --- a/fs/namespace.c +++ b/fs/namespace.c @@ -34,6 +34,8 @@ #include #include #include +#include +#include #include "pnode.h" #include "internal.h" @@ -263,17 +265,48 @@ #endif } +static u32 mnt_get_count_delay_ms; +static int mnt_get_count_debug_id; + +static int __init mnt_get_count_debug_init(void) +{ + debugfs_create_u32("mnt_get_count_delay_ms", 0600, NULL, + &mnt_get_count_delay_ms); + return 0; +} +late_initcall(mnt_get_count_debug_init); + /* * vfsmount lock must be held for write */ int mnt_get_count(struct mount *mnt) { #ifdef CONFIG_SMP - int count = 0; + static int seen[NR_CPUS]; + bool slow = mnt_get_count_delay_ms && + !(mnt->mnt.mnt_flags & MNT_UMOUNT); + int count = 0, again = 0; int cpu; for_each_possible_cpu(cpu) { - count += per_cpu_ptr(mnt->mnt_pcp, cpu)->mnt_count; + seen[cpu] = per_cpu_ptr(mnt->mnt_pcp, cpu)->mnt_count; + count += seen[cpu]; + if (slow) + mdelay(mnt_get_count_delay_ms); + } + if (slow) { + for_each_possible_cpu(cpu) { + int now; + + now = per_cpu_ptr(mnt->mnt_pcp, cpu)->mnt_count; + again += now; + pr_err("mount %d: cpu%d read %d, now %d\n", + mnt->mnt_id, cpu, seen[cpu], now); + } + mnt_get_count_debug_id = mnt->mnt_id; + pr_err("mount %d: sum %d, re-sum %d, %ps\n", + mnt->mnt_id, count, again, + __builtin_return_address(0)); } return count; @@ -1350,6 +1383,9 @@ smp_mb(); mnt_add_count(mnt, -1); count = mnt_get_count(mnt); + if (count == 0 && mnt->mnt_id == mnt_get_count_debug_id) + pr_err("mount %d: last put by %s (%d)\n", mnt->mnt_id, + current->comm, task_pid_nr(current)); if (count != 0) { WARN_ON(count < 0); rcu_read_unlock(); A task holds open a directory fd on a tmpfs. A synchronous rumount() of the tmpfs sums up. Concurrently a task calls fchdir() on that fd on CPU 0 and fchdir()s back to / on CPU 3. This will cause a path_get() and a path_put() with a migration in between without mount_lock held. Adding a delay per CPU makes the count loop miss the get in a cpu it already visited but count the put in one it hasn't. The reproducer, run as root with debugfs mounted: /* * gcc -static -o umount-torn umount-torn.c */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #define KNOB "/sys/kernel/debug/mnt_get_count_delay_ms" #define MNT "/mnt/x" static int kmsg; static void pin(int cpu) { cpu_set_t s; CPU_ZERO(&s); CPU_SET(cpu, &s); sched_setaffinity(0, sizeof(s), &s); } static void knob(const char *v) { int fd = open(KNOB, O_WRONLY); write(fd, v, strlen(v)); close(fd); } int main(void) { int last = sysconf(_SC_NPROCESSORS_ONLN) - 1; int p[2], fd, ret; pid_t pid; char c; kmsg = open("/dev/kmsg", O_WRONLY); mkdir(MNT, 0755); mount("none", MNT, "tmpfs", 0, NULL); fd = open(MNT "/f", O_WRONLY | O_CREAT, 0644); write(fd, "still here\n", 11); close(fd); pipe(p); pid = fork(); if (pid == 0) { int dirfd = open(MNT, O_RDONLY | O_DIRECTORY); int rootfd = open("/", O_RDONLY | O_DIRECTORY); struct stat st; char buf[16]; /* one get/put pair while the umount is summing */ usleep(150000); pin(0); fchdir(dirfd); /* mntget() */ dprintf(kmsg, "holder %d: get on cpu %d\n", getpid(), sched_getcpu()); pin(last); fchdir(rootfd); /* mntput() */ dprintf(kmsg, "holder %d: put on cpu %d\n", getpid(), sched_getcpu()); /* the umount has returned, is the fd still good? */ read(p[0], &c, 1); fd = openat(dirfd, "f", O_RDONLY); ret = read(fd, buf, sizeof(buf)); dprintf(kmsg, "holder %d: fstat %d openat %d read %d\n", getpid(), fstat(dirfd, &st), fd, ret); _exit(0); } pin(1); knob("100"); ret = umount2(MNT, 0); knob("0"); if (ret) dprintf(kmsg, "umount: %s\n", strerror(errno)); else dprintf(kmsg, "umount: 0\n"); write(p[1], "x", 1); waitpid(pid, NULL, 0); return 0; } The log shows the busy check summing 2 and when read right after it reads 3. The loop read CPU 0 before the get landed there and CPU 3 after the put did. The umount succeeds while there's still someone with an fd. The mount is then dropped when the fd goes: [ 2.511944] holder 90: get on cpu 0 [ 2.512023] holder 90: put on cpu 3 [ 2.671388] mount 28: cpu0 read 1, now 2 [ 2.671398] mount 28: cpu1 read 2, now 2 [ 2.671401] mount 28: cpu2 read 0, now 0 [ 2.671404] mount 28: cpu3 read -1, now -1 [ 2.671407] mount 28: sum 2, re-sum 3, propagate_mount_busy [ 2.671773] umount: 0 [ 2.672963] holder 90: fstat 0 openat 8 read 11 [ 2.673663] mount 28: last put by umount-torn (90) Easy fix would be to order mntget() against the loop that sums up. But that's a full barrier and a flag test in mntget() afaict. No bueno... That would cost every path_get(). So learn from SRCU. Keep gets and puts in separate per-cpu counters and make mnt_get_count() sum all puts first and all gets second with a full barrier in between. A put is always after its own get (hopefully...) independent of the cpu the task is on. So a put the first pass counted must have its get visible to the second pass. Ergo, we can't undercount. This is the two pass sum srcu_readers_active_idx_check() does for srcu_locks and srcu_unlocks. mntget() keeps a single this_cpu_inc(). mntput() gains an smp_wmb() before its increment, a compiler barrier on x86 and a store fence on the weakly ordered architectures, since the two counters are two words and a put stored after a get could otherwise become visible before it. The counters are unsigned so the difference stays right after they wrap. The count can still come out high when a get is counted whose put the first pass did not see. We don't care. A task can only take a new reference if: (1) it holds one already (2) through __legitimize_mnt() which the seqcount orders against the write side So a get in this window means someone has a reference and -EBUSY is correct. The final mntput() is unchanged. Once ->mnt_ns is NULL every put takes the slow path under mount_lock and so no put is in flight while it counts. Here's an attempted proof for that crap: (i) A put never becomes visible before its get. Within one task the smp_wmb() in mntput_no_expire() orders every earlier store before the increment of the puts. Any task that migrated in between had its stores flushed by the scheduler. When that reference is handed to another task that's trivially true. (ii) The double count in mnt_get_count() is ordered via smp_mb(). That keeps every read of the puts counter before every read of the gets counter. (iii) We never undercount by (ii). (iv) There are no spurious counts/differences. A get is counted only if it happened and a put is left out only if it hasn't happened when the counter was read. With (iii) this means it's equivalent to the old count whenever nothing is taken or dropped during the loop. (v) do_umount() holds a reference and mount_lock hence. New references come from: (v.a) __legitimize_mnt() managed by the seqcount. Either it's smp_mb() made the get visible to the loop or it takes the lock and waits for the loop. By (iii) a mount with a additional references is refused. By (iv) refusing the umount just means that any additional reference existed, same as the old counter. (vi) mntput_no_expire_slowpath() runs with ->mnt_ns already NULL. Hence, every other put must take the slowpath as well and hangs on mount_lock. The grace period in namespace_unlock() will make every fast path do a put that still saw ->mnt_ns. No put is in flight during the loop. A get that is in flight comes from a task holding another reference. That must be included by the count or from __legitimize_mnt() as in (v). Ergo, zero means the last reference and non-zero means a reference is live. (vii) may_umount() uses propagate_mount_busy() as well and may_umount_tree() sums mnt_get_count() itself. With this patch the race is dead: [ 3.066786] mount 28: sum 3, re-sum 3, propagate_mount_busy [ 3.105867] umount: Device or resource busy [ 3.106047] holder 90: fstat 0 openat 8 read 11 Link: https://patch.msgid.link/20260923-work-mount-fixes-v1-7-f424cf8d3242@kernel.org Fixes: b3e19d924b6e ("fs: scale mntget/mntput") Signed-off-by: Christian Brauner (Amutable) --- fs/mount.h | 3 ++- fs/namespace.c | 48 ++++++++++++++++++++++++++++++------------------ 2 files changed, 32 insertions(+), 19 deletions(-) diff --git a/fs/mount.h b/fs/mount.h index 94fcc306d21e..85f136786bbc 100644 --- a/fs/mount.h +++ b/fs/mount.h @@ -33,7 +33,8 @@ struct mnt_namespace { } __randomize_layout; struct mnt_pcp { - int mnt_count; + unsigned int mnt_gets; + unsigned int mnt_puts; int mnt_writers; }; diff --git a/fs/namespace.c b/fs/namespace.c index d842fc1f1f1a..73215f84e04d 100644 --- a/fs/namespace.c +++ b/fs/namespace.c @@ -249,16 +249,24 @@ void mnt_release_group_id(struct mount *mnt) mnt->mnt_group_id = 0; } -/* - * vfsmount lock must be held for read - */ -static inline void mnt_add_count(struct mount *mnt, int n) +static inline void mnt_inc_count(struct mount *mnt) { #ifdef CONFIG_SMP - this_cpu_add(mnt->mnt_pcp->mnt_count, n); + this_cpu_inc(mnt->mnt_pcp->mnt_gets); #else preempt_disable(); - mnt->mnt_count += n; + mnt->mnt_count++; + preempt_enable(); +#endif +} + +static inline void mnt_dec_count(struct mount *mnt) +{ +#ifdef CONFIG_SMP + this_cpu_inc(mnt->mnt_pcp->mnt_puts); +#else + preempt_disable(); + mnt->mnt_count--; preempt_enable(); #endif } @@ -269,14 +277,17 @@ static inline void mnt_add_count(struct mount *mnt, int n) int mnt_get_count(struct mount *mnt) { #ifdef CONFIG_SMP - int count = 0; + unsigned int gets = 0, puts = 0; int cpu; - for_each_possible_cpu(cpu) { - count += per_cpu_ptr(mnt->mnt_pcp, cpu)->mnt_count; - } + /* puts first, so a put counted here has its get counted below */ + for_each_possible_cpu(cpu) + puts += per_cpu_ptr(mnt->mnt_pcp, cpu)->mnt_puts; + smp_mb(); /* pairs with the smp_wmb() in mntput_no_expire() */ + for_each_possible_cpu(cpu) + gets += per_cpu_ptr(mnt->mnt_pcp, cpu)->mnt_gets; - return count; + return gets - puts; #else return mnt->mnt_count; #endif @@ -305,7 +316,7 @@ static struct mount *alloc_vfsmnt(const char *name) if (!mnt->mnt_pcp) goto out_free_devname; - this_cpu_add(mnt->mnt_pcp->mnt_count, 1); + this_cpu_inc(mnt->mnt_pcp->mnt_gets); #else mnt->mnt_count = 1; mnt->mnt_writers = 0; @@ -746,13 +757,13 @@ int __legitimize_mnt(struct vfsmount *bastard, unsigned seq) if (bastard == NULL) return 0; mnt = real_mount(bastard); - mnt_add_count(mnt, 1); - smp_mb(); // see mntput_no_expire() and do_umount() + mnt_inc_count(mnt); + smp_mb(); /* see mntput_no_expire_slowpath() and do_umount() */ if (likely(!read_seqretry(&mount_lock, seq))) return 0; lock_mount_hash(); if (unlikely(bastard->mnt_flags & (MNT_SYNC_UMOUNT | MNT_DOOMED))) { - mnt_add_count(mnt, -1); + mnt_dec_count(mnt); unlock_mount_hash(); return 1; } @@ -1348,7 +1359,7 @@ static void noinline mntput_no_expire_slowpath(struct mount *mnt) * mount_lock, we'll see their refcount increment here. */ smp_mb(); - mnt_add_count(mnt, -1); + mnt_dec_count(mnt); count = mnt_get_count(mnt); if (count != 0) { WARN_ON(count < 0); @@ -1405,7 +1416,8 @@ static void mntput_no_expire(struct mount *mnt) * non-NULL under rcu_read_lock(), the reference * we are dropping is not the final one. */ - mnt_add_count(mnt, -1); + smp_wmb(); /* pairs with the smp_mb() in mnt_get_count() */ + mnt_dec_count(mnt); rcu_read_unlock(); return; } @@ -1427,7 +1439,7 @@ EXPORT_SYMBOL(mntput); struct vfsmount *mntget(struct vfsmount *mnt) { if (mnt) - mnt_add_count(real_mount(mnt), 1); + mnt_inc_count(real_mount(mnt)); return mnt; } EXPORT_SYMBOL(mntget); From 3f5dc09141a7bfe286620a6869d1d70ffe32d6b8 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Wed, 23 Sep 2026 14:28:00 +0200 Subject: [PATCH 027/367] docs: update the unmount propagation rule Section 5f still describes the rule propagate_umount() used before commit f0d0ba19985d ("Rewrite of propagate_umount()"). It states that a mount that receives the unmount by propagation is left alone as soon as it has any submount. That's not true anymore. A propagated unmount unmounts a mount with sub-mounts as long as every sub-mount gets unmounted together with it. This is the case when the sub-mounts are unmounted by the same propagation. Only a sub-mount that cannot be unmounted keeps its parent mounted. The section also only describes a single mount without sub-mounts. But lazy unmounts take a tree and every mount of the tree propagates its unmount from its parent mount. Link: https://patch.msgid.link/20260923-work-mount-fixes-v1-8-f424cf8d3242@kernel.org Signed-off-by: Christian Brauner (Amutable) --- Documentation/filesystems/sharedsubtree.rst | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/Documentation/filesystems/sharedsubtree.rst b/Documentation/filesystems/sharedsubtree.rst index 8b7dc9159083..8bae6d9a7e04 100644 --- a/Documentation/filesystems/sharedsubtree.rst +++ b/Documentation/filesystems/sharedsubtree.rst @@ -564,8 +564,8 @@ f) Unmount semantics where 'A' is a mount mounted on mount 'B' at dentry 'b'. If mount 'B' is shared, then all most-recently-mounted mounts at dentry - 'b' on mounts that receive propagation from mount 'B' and does not have - sub-mounts within them are unmounted. + 'b' on mounts that receive propagation from mount 'B' are unmounted as + well, if every mount below them is also unmounted. Example: Let's say 'B1', 'B2', 'B3' are shared mounts that propagate to each other. @@ -584,10 +584,18 @@ f) Unmount semantics So all 'C1', 'C2' and 'C3' should be unmounted. - If any of 'C2' or 'C3' has some child mounts, then that mount is not - unmounted, but all other mounts are unmounted. However if 'C1' is told - to be unmounted and 'C1' has some sub-mounts, the umount operation is - failed entirely. + If any of 'C2' or 'C3' has a child mount that cannot be unmounted + then that mount is not unmounted. But all other mounts are unmounted. + A child mount that is itself unmounted by the same unmount + propagation does not keep its parent mounted. However if 'C1' is + supposed to be unmounted and 'C1' has some sub-mounts, the unmount + fails. + + A lazy umount (MNT_DETACH) takes a whole tree. Every mount of the + tree then propagates its unmount from its own parent as described + above, so the mounts that receive propagation lose the corresponding + trees as well. Documentation/filesystems/propagate_umount.txt has the + precise rules, including the ones for locked mounts. g) Clone Namespace From 1ca924044bf0de879a1648be93a56939652267d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20G=C3=B6ttsche?= Date: Wed, 23 Sep 2026 12:13:23 +0200 Subject: [PATCH 028/367] selinux: fix data race on AVC latest_notif MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit avc_policy_seqno() reads latest_notif without holding notif_lock, while avc_latest_notif_update() can update it under that lock. KCSAN reports these concurrent accesses during policy boolean updates and permission checks. Use READ_ONCE() for the lockless read and WRITE_ONCE() for the update. The spinlock continues to serialize the comparison and update against other writers. Reads made while holding notif_lock cannot race with a writer and can remain plain accesses. This marks both sides of the intentional concurrent access and constrains compiler transformations, without suppressing checking with data_race(). Cc: stable@vger.kernel.org Fixes: 788e7dd4c22e ("SELinux: Improve read/write performance") Reported-by: syzbot+a7f7cb0737bd91aa698c@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=a7f7cb0737bd91aa698c Signed-off-by: Christian Göttsche Acked-by: Stephen Smalley Signed-off-by: Paul Moore --- security/selinux/avc.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/security/selinux/avc.c b/security/selinux/avc.c index 560a82c6d682..b91f914d292e 100644 --- a/security/selinux/avc.c +++ b/security/selinux/avc.c @@ -578,7 +578,7 @@ static int avc_latest_notif_update(u32 seqno, int is_insert) } } else { if (seqno > selinux_avc.avc_cache.latest_notif) - selinux_avc.avc_cache.latest_notif = seqno; + WRITE_ONCE(selinux_avc.avc_cache.latest_notif, seqno); } spin_unlock_irqrestore(¬if_lock, flag); @@ -1206,5 +1206,6 @@ int avc_has_perm(u32 ssid, u32 tsid, u16 tclass, u32 avc_policy_seqno(void) { - return selinux_avc.avc_cache.latest_notif; + /* Writers hold notif_lock; readers only sample the sequence number. */ + return READ_ONCE(selinux_avc.avc_cache.latest_notif); } From 02a3178319639f650428164d4d003de4dc03e326 Mon Sep 17 00:00:00 2001 From: Naomi Chu Date: Wed, 9 Sep 2026 17:10:45 +0800 Subject: [PATCH 029/367] scsi: ufs: core: Hold a clock reference across the probe Clock gating becomes possible as soon as ufshcd_init_clk_gating() has run, and from that point on the probe keeps accessing host registers without ever taking a clock reference. This has been safe only because of the state check in __ufshcd_release(): gate_work is not queued unless hba->ufshcd_state is UFSHCD_STATE_OPERATIONAL, and the promotion to that state used to happen after the last register access of the probe, at the end of ufshcd_probe_hba(). That is fragile: it only works while the promotion happens after the register accesses. Commit a390e6677f41 ("scsi: ufs: core: Expand the ufshcd_device_init(hba, true) call") changed that ordering by moving the promotion into ufshcd_init(), which schedules ufshcd_async_scan() afterwards. ufshcd_probe_hba() therefore now runs with the state already promoted, and it accesses host registers without holding a clock reference: - On hosts with UFSHCD_QUIRK_REINIT_AFTER_MAX_GEAR_SWITCH it calls ufshcd_hba_stop() and ufshcd_hba_enable() before ufshcd_device_init() sets the state back to UFSHCD_STATE_RESET, and both read REG_CONTROLLER_ENABLE, so gated clocks stall there instead of just losing a write. - It ends with an ufshcd_configure_auto_hibern8() write, which its other callers do take a clock reference for. Take a clock reference as soon as clock gating has been initialised and keep it until the probe is over. It then does not matter who drops a clock reference while the probe is running, and the register accesses of the probe no longer depend on hba->ufshcd_state. The reference is dropped by ufshcd_async_scan() once the scan has finished, or by the new out_release label if the probe fails after it was taken. Fixes: a390e6677f41 ("scsi: ufs: core: Expand the ufshcd_device_init(hba, true) call") Signed-off-by: Naomi Chu Reviewed-by: Peter Wang Link: https://patch.msgid.link/20260909091045.1134956-1-naomi.chu@mediatek.com Signed-off-by: Martin K. Petersen (Oracle) --- drivers/ufs/core/ufshcd.c | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/drivers/ufs/core/ufshcd.c b/drivers/ufs/core/ufshcd.c index 54f4e7d7de02..79b6a2d9b522 100644 --- a/drivers/ufs/core/ufshcd.c +++ b/drivers/ufs/core/ufshcd.c @@ -9556,6 +9556,7 @@ static void ufshcd_async_scan(void *data, async_cookie_t cookie) ret = ufshcd_add_lus(hba); out: + ufshcd_release(hba); pm_runtime_put_sync(hba->dev); if (ret) @@ -11278,6 +11279,9 @@ int ufshcd_init(struct ufs_hba *hba, void __iomem *mmio_base, unsigned int irq) ufshcd_init_clk_gating(hba); + /* Released by ufshcd_async_scan(), or by out_release on failure. */ + ufshcd_hold(hba); + ufshcd_init_clk_scaling(hba); /* @@ -11298,7 +11302,7 @@ int ufshcd_init(struct ufs_hba *hba, void __iomem *mmio_base, unsigned int irq) err = devm_request_irq(dev, irq, ufshcd_intr, IRQF_SHARED, UFSHCD, hba); if (err) { dev_err(hba->dev, "request irq failed\n"); - goto out_disable; + goto out_release; } else { hba->is_irq_enabled = true; } @@ -11314,7 +11318,7 @@ int ufshcd_init(struct ufs_hba *hba, void __iomem *mmio_base, unsigned int irq) dev_err(hba->dev, "Host controller enable failed\n"); ufshcd_print_evt_hist(hba); ufshcd_print_host_state(hba); - goto out_disable; + goto out_release; } INIT_DELAYED_WORK(&hba->rpm_dev_flush_recheck_work, ufshcd_rpm_dev_flush_recheck_work); @@ -11328,7 +11332,7 @@ int ufshcd_init(struct ufs_hba *hba, void __iomem *mmio_base, unsigned int irq) err = ufshcd_add_scsi_host(hba); if (err) - goto out_disable; + goto out_release; /* Hold auto suspend until async scan completes */ pm_runtime_get_sync(dev); @@ -11348,7 +11352,7 @@ int ufshcd_init(struct ufs_hba *hba, void __iomem *mmio_base, unsigned int irq) err = ufshcd_link_startup(hba); if (err) - goto out_disable; + goto out_release; if (hba->mcq_enabled) ufshcd_config_mcq(hba); @@ -11365,23 +11369,23 @@ int ufshcd_init(struct ufs_hba *hba, void __iomem *mmio_base, unsigned int irq) /* Verify device initialization by sending NOP OUT UPIU */ err = ufshcd_verify_dev_init(hba); if (err) - goto out_disable; + goto out_release; /* Initiate UFS initialization, and waiting until completion */ err = ufshcd_complete_dev_init(hba); if (err) - goto out_disable; + goto out_release; err = ufshcd_device_params_init(hba); if (err) - goto out_disable; + goto out_release; err = ufshcd_post_device_init(hba); initialized: ufshcd_process_probe_result(hba, probe_start, err); if (err) - goto out_disable; + goto out_release; ufs_sysfs_add_nodes(hba->dev); hba->dme_qos_sysfs_handle = sysfs_get_dirent(hba->dev->kobj.sd, @@ -11392,6 +11396,8 @@ int ufshcd_init(struct ufs_hba *hba, void __iomem *mmio_base, unsigned int irq) ufshcd_pm_qos_init(hba); return 0; +out_release: + ufshcd_release(hba); out_disable: hba->is_irq_enabled = false; ufshcd_hba_exit(hba); From 85de8b3a5eaa23d153aff4c7c83dddbc949064c9 Mon Sep 17 00:00:00 2001 From: Runyu Xiao Date: Thu, 10 Sep 2026 17:24:05 +0800 Subject: [PATCH 030/367] scsi: qedi: Initialize callback state before registration qedi_get_protocol_tlv_data() can be called asynchronously by QED after qedi_ops->register_ops(). It takes stats_lock and reads ll2_mtu, but __qedi_probe() currently registers the callback before initializing the mutex and assigning the default MTU on the normal probe path. Initialize the callback-visible state before registering qedi_cb_ops. Keep the recovery path from reinitializing state because it reuses the existing qedi context. Fixes: 3cc5746e5ad7 ("scsi: qedi: Initialize the stats mutex lock") Cc: stable@vger.kernel.org Assisted-by: LLM Codex Signed-off-by: Runyu Xiao Link: https://patch.msgid.link/20260910092405.1300129-1-runyu.xiao@seu.edu.cn Signed-off-by: Martin K. Petersen (Oracle) --- drivers/scsi/qedi/qedi_main.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/scsi/qedi/qedi_main.c b/drivers/scsi/qedi/qedi_main.c index 227ff7bd1bdc..f394fcbb97e7 100644 --- a/drivers/scsi/qedi/qedi_main.c +++ b/drivers/scsi/qedi/qedi_main.c @@ -2652,8 +2652,6 @@ static int __qedi_probe(struct pci_dev *pdev, int mode) snprintf(host_buf, sizeof(host_buf), "host_%d", qedi->shost->host_no); qedi_ops->common->set_name(qedi->cdev, host_buf); - qedi_ops->register_ops(qedi->cdev, &qedi_cb_ops, qedi); - memset(¶ms, 0, sizeof(params)); params.mtu = DEF_PATH_MTU + IPV6_HDR_LEN + TCP_HDR_LEN; qedi->ll2_mtu = DEF_PATH_MTU; @@ -2670,6 +2668,7 @@ static int __qedi_probe(struct pci_dev *pdev, int mode) spin_lock_init(&qedi->task_idx_lock); mutex_init(&qedi->stats_lock); } + qedi_ops->register_ops(qedi->cdev, &qedi_cb_ops, qedi); qedi_ops->ll2->register_cb_ops(qedi->cdev, &qedi_ll2_cb_ops, qedi); qedi_ops->ll2->start(qedi->cdev, ¶ms); From 476ef286a7a5db8bda51325dc0c45887b4d99d9a Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Sat, 26 Sep 2026 15:44:24 +0200 Subject: [PATCH 031/367] namespace: reset the old parent's ->overmount in mnt_change_mountpoint() __umount_mnt() resets ->overmount of the parent when it unmounts the mount that overmounts it. mnt_change_mountpoint() doesn't. It moves the mount to its new parent and leaves the old one pointing at it. For a mount that is moved beneath in attach_recursive_mnt() that doesn't matter. The new mount has already reset ->overmount of the old parent by the time the old top mount is moved onto it. It matters for reparent() though. propagate_umount() moves a surviving overmount onto the first ancestor that isn't going to be unmounted. The mount it was taken from is unmounted immediately afterwards. If a file descriptor keeps that unmounted mount alive ->overmount points at the moved mount. That's a dangling pointer and UAF as the moved mount is unmounted and freed. do_lock_mount() follows ->overmount from the target path under mount_lock. With a file descriptor on such a mount as the target of a mount beneath topmost_overmount() walks into the freed mount and do_lock_mount() takes references on whatever it finds there. So reset ->overmount of the old parent in mnt_change_mountpoint() the same way __umount_mnt() does. A mount's ->overmount is then always a mount that is attached to it. Fixes: ffdc52fbbd58 ("prevent mount hash conflicts") Cc: stable@vger.kernel.org # v6.17+ Link: https://patch.msgid.link/20260926-work-mount-fixes-2-v1-1-f357abf3d17b@kernel.org Signed-off-by: Christian Brauner (Amutable) --- fs/namespace.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/fs/namespace.c b/fs/namespace.c index 73215f84e04d..230f95675a93 100644 --- a/fs/namespace.c +++ b/fs/namespace.c @@ -1069,7 +1069,10 @@ static void attach_mnt(struct mount *mnt, struct mount *parent, void mnt_change_mountpoint(struct mount *parent, struct mountpoint *mp, struct mount *mnt) { struct mountpoint *old_mp = mnt->mnt_mp; + struct mount *old_parent = mnt->mnt_parent; + if (old_parent->overmount == mnt) + old_parent->overmount = NULL; list_del_init(&mnt->mnt_child); hlist_del_init(&mnt->mnt_mp_list); hlist_del_init_rcu(&mnt->mnt_hash); From bb46094057520304073130920c223451c2a694a7 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Sat, 26 Sep 2026 15:44:25 +0200 Subject: [PATCH 032/367] statmount: read the parent of an unmounted mount under mount_lock statmount_mnt_basic() reads the ids of the parent through m->mnt_parent with namespace_sem held for reading. That's fine for a mount that is in a namespace. Everything above it is pinned by that namespace. STATMOUNT_BY_FD also accepts a file descriptor on a mount that has been unmounted on purpose and that only the file descriptor keeps alive. If that mount stayed attached to its parent when it was unmounted the parent is unmounted as well and nothing pins it for the child. The final mntput() unmounts the children under mount_lock alone and frees the parent afterwards. Concurrently, statmount_mnt_basic() reads m->mnt_parent twice while that can happen and can easily dereference a mount that's already gone. Read the parent of an unmounted mount under mount_lock. The parent can't be put while it is held and both ids come from the same mount. A mounted mount's parent is pinned by the namespace and a mount can't leave its namespace while namespace_sem is held, so the common case takes no global lock. Fixes: 0e5032237ee5 ("statmount: accept fd as a parameter") Cc: stable@vger.kernel.org # v7.0+ Link: https://patch.msgid.link/20260926-work-mount-fixes-2-v1-2-f357abf3d17b@kernel.org Signed-off-by: Christian Brauner (Amutable) --- fs/namespace.c | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/fs/namespace.c b/fs/namespace.c index 230f95675a93..d1e8dd2e1e8e 100644 --- a/fs/namespace.c +++ b/fs/namespace.c @@ -5335,15 +5335,25 @@ static void statmount_sb_basic(struct kstatmount *s) s->sm.sb_flags = sb->s_flags & (SB_RDONLY|SB_SYNCHRONOUS|SB_DIRSYNC|SB_LAZYTIME); } +static void statmount_mnt_parent(struct kstatmount *s, const struct mount *m) +{ + s->sm.mnt_parent_id = m->mnt_parent->mnt_id_unique; + s->sm.mnt_parent_id_old = m->mnt_parent->mnt_id; +} + static void statmount_mnt_basic(struct kstatmount *s) { struct mount *m = real_mount(s->mnt); s->sm.mask |= STATMOUNT_MNT_BASIC; s->sm.mnt_id = m->mnt_id_unique; - s->sm.mnt_parent_id = m->mnt_parent->mnt_id_unique; s->sm.mnt_id_old = m->mnt_id; - s->sm.mnt_parent_id_old = m->mnt_parent->mnt_id; + /* An unmounted mount is cut loose from its parent under mount_lock alone. */ + if (likely(is_mounted(s->mnt))) + statmount_mnt_parent(s, m); + else + scoped_guard(mount_locked_reader) + statmount_mnt_parent(s, m); s->sm.mnt_attr = mnt_to_attr_flags(&m->mnt); s->sm.mnt_propagation = mnt_to_propagation_flags(m); s->sm.mnt_peer_group = m->mnt_group_id; From e2c6d326216642011da318b9edfd5d57ebd5a56f Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Sat, 26 Sep 2026 15:44:26 +0200 Subject: [PATCH 033/367] namespace: don't inherit MNT_UMOUNT in clone_mnt() MNT_UMOUNT gets raised on a mount that umount_tree() or umount_one() has taken out of its namespace. It's never cleared. __detach_mounts() unhooks a mount that carries it instead of unmounting it. disconnect_mount() lets an unmounted child stay attached only if its parent carries it. propagate_umount() takes it as the sign that a mount is going to be unmounted when it decides what else can be unmounted, where a chain of locked mounts ends and where a for reparenting an overmount. clone_mnt() copies the flags of the source and only masks MNT_INTERNAL_FLAGS which doesn't include MNT_UMOUNT. A mount that was lazily unmounted but is kept alive by a file descriptor can still be copied when its root is an nsfs or pidfs file because may_copy_tree() accepts those before checking whether the source is mounted at all: mount --bind /proc/self/ns/net /tmp/x exec 3< /tmp/x umount -l /tmp/x mount --bind /proc/self/fd/3 /tmp/y The mount on /tmp/y is attached to the caller's namespace with MNT_UMOUNT already set and every copy of it inherits the flag again. That causes a number of bugs: - Unlink its mountpoint from a mount namespace where it isn't one. __detach_mounts() takes it for a mount that was unmounted earlier, unhooks it from its parent and drops the reference the namespace holds on it. It stays in the namespace with one reference too few and as its own parent, so it can't be unmounted through the file descriptor anymore and the namespace never finds it again. When the namespace goes away it's leaked and points to a freed struct mnt_namespace that may_change_propagation() and anon_ns_root() read on the next propagation change or mount_setattr() through the file descriptor. - Make it shared and unmount a peer. trace_transfers() follows the flag from the peer into it and strips it from its peer group. - Let a propagated umount reach it. gather_candidates() marks it with T_UMOUNT_CANDIDATE but never puts it on the candidate list, so the mark is never cleared and every later propagated umount skips it. If it's the parent of a locked mount handle_locked() takes it as the cutoff and commits the locked child that should have stayed and disconnect_mount() leaves that child attached to it, unmounted under a mounted parent, which is what disconnect_mount() exists to prevent. When the namespace goes away umount_tree() visits the child a second time. And once __detach_mounts() has made it its own parent reparent() never finds the end of the chain of unmounted parents. A clone is a new mount that has never been unmounted. Add MNT_UMOUNT to MNT_INTERNAL_FLAGS so clone_mnt() never copies it. And warn when umount_tree() reaches a mount that already has it set. Fixes: 590ce4bcbfb4 ("mnt: Add MNT_UMOUNT flag") Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260926-work-mount-fixes-2-v1-3-f357abf3d17b@kernel.org Signed-off-by: Christian Brauner (Amutable) --- fs/namespace.c | 2 ++ include/linux/mount.h | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/fs/namespace.c b/fs/namespace.c index d1e8dd2e1e8e..6d4ecde682df 100644 --- a/fs/namespace.c +++ b/fs/namespace.c @@ -1799,6 +1799,8 @@ static void umount_tree(struct mount *mnt, enum umount_tree_flags how) /* Gather the mounts to umount */ for (p = mnt; p; p = next_mnt(p, mnt)) { + /* A mount is unmounted once. */ + VFS_WARN_ON_ONCE(p->mnt.mnt_flags & MNT_UMOUNT); p->mnt.mnt_flags |= MNT_UMOUNT; if (mnt_ns_attached(p)) move_from_ns(p); diff --git a/include/linux/mount.h b/include/linux/mount.h index acfe7ef86a1b..b16ba5d45605 100644 --- a/include/linux/mount.h +++ b/include/linux/mount.h @@ -52,7 +52,7 @@ enum mount_flags { MNT_ATIME_MASK = MNT_NOATIME | MNT_NODIRATIME | MNT_RELATIME, MNT_INTERNAL_FLAGS = MNT_INTERNAL | MNT_DOOMED | - MNT_SYNC_UMOUNT | MNT_LOCKED + MNT_SYNC_UMOUNT | MNT_LOCKED | MNT_UMOUNT }; struct vfsmount { From 8bacbf6be959973b28b691713a872c12eae69eec Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Sat, 26 Sep 2026 15:44:27 +0200 Subject: [PATCH 034/367] namespace: don't copy an unmounted mount tree may_copy_tree() accepts a mount whose root is an nsfs or pidfs file before it checks whether that mount is mounted. This exception exists for nsfs and pidfs mounts. They are part of no mount namespace so neither check_mnt() nor is_mounted() would let them through and bind-mounting a nsfsfd or a pidfd wouldn't work. Problem is that exception also admits a bind mount of such a file when it was lazily unmounted and is only kept alive by a file descriptor: mount --bind /proc/self/ns/net /tmp/x exec 3< /tmp/x umount -l /tmp/x mount --rbind /proc/self/fd/3 /tmp/y With MS_REC or AT_RECURSIVE copy_tree() doesn't stop at the unmounted mount. It copies every mount that stayed attached to it as well. So if you have a bunch of overmounts over the nsfsfd or the pidfd the whole unmounted stack is copied. That's a bug. So refuse to walk an unmounted tree. Copying its root alone stays possible. So a bind mount of a namespace that is gone can still be recreated from a file descriptor on the old one. The file descriptor pins that mount and its root and __has_locked_children() only reads the direct children which don't leave the parent's list without namespace_sem. The internal mounts carry MNT_NS_INTERNAL instead of NULL and are still walked. Copying an unmounted tree recursively fails with EINVAL. Fixes: e149ed2b805f ("take the targets of /proc/*/ns/* symlinks to separate fs") Fixes: ef4144ac2dec ("pidfs: allow bind-mounts") Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260926-work-mount-fixes-2-v1-4-f357abf3d17b@kernel.org Signed-off-by: Christian Brauner (Amutable) --- fs/namespace.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/fs/namespace.c b/fs/namespace.c index 6d4ecde682df..c7e252eb249e 100644 --- a/fs/namespace.c +++ b/fs/namespace.c @@ -3007,6 +3007,9 @@ static struct mount *__do_loopback(const struct path *old_path, if (!may_copy_tree(old_path)) return ERR_PTR(-EINVAL); + if (recurse && !old->mnt_ns) + return ERR_PTR(-EINVAL); + if (!recurse && __has_locked_children(old, old_path->dentry)) return ERR_PTR(-EINVAL); From 08f4288a37ef4b3d19b4a9599ec3048219cac828 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Sat, 26 Sep 2026 15:44:28 +0200 Subject: [PATCH 035/367] namespace: check the target before walking it in do_mount_setattr() When mount_setattr() changes the propagation type to MS_SHARED do_mount_setattr() takes namespace_sem and calls invent_group_ids() which walks the whole target tree with next_mnt() and allocates a peer group id for every mount that doesn't have one. Whether the caller may touch the target at all is only checked afterwards under mount_lock. If it may not, cleanup_group_ids() walks the tree a second time on the way out to free the ids again. A detached tree that a file descriptor keeps alive, a mount in another mount namespace or a mount below the root of an anonymous mount namespace does a bunch of allocation work that is completely useless because it will fail and revert right after. Do the right thing and check the target right after taking namespace_sem instead. A mount leaves its namespace only under namespace_sem so the mount stays stable. The check under mount_lock stays for the case where no propagation change is requested and namespace_sem isn't taken. Callers get the same EINVAL as before. Fixes: 2a1867219c7b ("fs: add mount_setattr()") Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260926-work-mount-fixes-2-v1-5-f357abf3d17b@kernel.org Signed-off-by: Christian Brauner (Amutable) --- fs/namespace.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/fs/namespace.c b/fs/namespace.c index c7e252eb249e..de28fe9058ab 100644 --- a/fs/namespace.c +++ b/fs/namespace.c @@ -4976,6 +4976,11 @@ static int do_mount_setattr(const struct path *path, struct mount_kattr *kattr) * propagation. */ namespace_lock(); + /* invent_group_ids() walks the tree, only walk a mounted one */ + if (!anon_ns_root(mnt) && !check_mnt(mnt)) { + namespace_unlock(); + return -EINVAL; + } if (kattr->propagation == MS_SHARED) { err = invent_group_ids(mnt, kattr->kflags & MOUNT_KATTR_RECURSE); if (err) { @@ -4988,6 +4993,7 @@ static int do_mount_setattr(const struct path *path, struct mount_kattr *kattr) err = -EINVAL; lock_mount_hash(); + /* Checked under namespace_sem already if the propagation changes. */ if (!anon_ns_root(mnt) && !check_mnt(mnt)) goto out; From 02587a4af82adccaef8e3b3624b4e7f1c401632d Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Sat, 26 Sep 2026 15:44:29 +0200 Subject: [PATCH 036/367] fs: refuse fspick() on internal superblocks There's a few instances where a internal superblock is reachable by fspick(). Block that. graft_tree() and fsmount() already refuse them. fanotify refuses mount and superblock marks on them for the same reason. Fixes: cf3cba4a429b ("vfs: syscall: Add fspick() to select a superblock for reconfiguration") Cc: stable@vger.kernel.org # v5.2+ Link: https://patch.msgid.link/20260926-work-mount-fixes-2-v1-6-f357abf3d17b@kernel.org Signed-off-by: Christian Brauner (Amutable) --- fs/fsopen.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/fs/fsopen.c b/fs/fsopen.c index ae19e5136598..9d5a7a22b529 100644 --- a/fs/fsopen.c +++ b/fs/fsopen.c @@ -190,6 +190,9 @@ SYSCALL_DEFINE3(fspick, int, dfd, const char __user *, path, unsigned int, flags ret = -EINVAL; if (target.mnt->mnt_root != target.dentry) goto err_path; + /* kernel-internal superblocks are nobody's to reconfigure */ + if (target.dentry->d_sb->s_flags & SB_NOUSER) + goto err_path; fc = fs_context_for_reconfigure(target.dentry, 0, 0); if (IS_ERR(fc)) { From 57088a37b2e4d194a2ae5027134b780a36acd900 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Sat, 26 Sep 2026 15:44:30 +0200 Subject: [PATCH 037/367] fs: put the old fs_struct before the old namespaces in unshare() unshare(CLONE_NEWNS) always copies the fs_struct and copy_mnt_ns() points root and pwd of that copy into the new mount namespace. The old fs_struct pins the old root and pwd until ksys_unshare() frees it at the end and switch_task_namespaces() will already put the old mount namespace before. This causes pointless work in user namespaces because the copied mounts are locked and stay connected. Switch and free the fs_struct before switching the namespaces. The old root and pwd are wasted while their mount namespace is still alive which also keeps both puts on the mntput() fast path. setns() already does this right as it updates root and pwd before the namespaces are switched. Link: https://patch.msgid.link/20260926-work-mount-fixes-2-v1-7-f357abf3d17b@kernel.org Signed-off-by: Christian Brauner (Amutable) --- kernel/fork.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/kernel/fork.c b/kernel/fork.c index 416758c8a3d4..da48168c504f 100644 --- a/kernel/fork.c +++ b/kernel/fork.c @@ -3312,14 +3312,17 @@ int ksys_unshare(unsigned long unshare_flags) shm_init_task(current); } + if (new_fs) { + new_fs = switch_fs_struct(new_fs); + if (new_fs) + free_fs_struct(no_free_ptr(new_fs)); + } + if (new_nsproxy) { switch_task_namespaces(current, new_nsproxy); new_nsproxy = NULL; } - if (new_fs) - new_fs = switch_fs_struct(new_fs); - if (new_fd) { guard(task_lock)(current); swap(current->files, new_fd); From 7253a6ff640df85fdccabe7e3dd38cce38e0a7b4 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Sat, 26 Sep 2026 15:44:31 +0200 Subject: [PATCH 038/367] namespace: drop the file's reference first in dissolve_on_fput() A detached mount tree is held alive by a file. dissolve_on_fput() unmounts the detached tree attached to that file. Currently dissolve_on_fput() leaves the file's reference to the root of the tree to the caller. So either __fput(), open_detached_copy(), or fsmount() drop the mount. That's backwards. The reference keeps the tree around and is dropped when the tree is already gone. Instead, let dissolve_on_fput() the mount and drop it after umount_tree() with namespace_sem still held. After umount_tree() the mntput() is never the last reference and so the root gets unmounted before its children. Link: https://patch.msgid.link/20260926-work-mount-fixes-2-v1-8-f357abf3d17b@kernel.org Signed-off-by: Christian Brauner (Amutable) --- fs/file_table.c | 3 ++- fs/namespace.c | 9 ++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/fs/file_table.c b/fs/file_table.c index c68b8c0a4097..e2665eaa6f8c 100644 --- a/fs/file_table.c +++ b/fs/file_table.c @@ -520,7 +520,8 @@ static void __fput(struct file *file) dput(dentry); if (unlikely(mode & FMODE_NEED_UNMOUNT)) dissolve_on_fput(mnt); - mntput(mnt); + else + mntput(mnt); out: file_free(file); } diff --git a/fs/namespace.c b/fs/namespace.c index de28fe9058ab..5b44eee28f39 100644 --- a/fs/namespace.c +++ b/fs/namespace.c @@ -2312,9 +2312,11 @@ void drop_collected_paths(const struct path *paths, const struct path *prealloc) static struct mnt_namespace *alloc_mnt_ns(struct user_namespace *, bool); +/* Consumes the caller's reference to @mnt. */ void dissolve_on_fput(struct vfsmount *mnt) { - struct mount *m = real_mount(mnt); + struct vfsmount *p __free(mntput) = mnt; + struct mount *m = real_mount(p); /* * m used to be the root of anon namespace; if it still is one, @@ -2339,6 +2341,7 @@ void dissolve_on_fput(struct vfsmount *mnt) lock_mount_hash(); umount_tree(m, UMOUNT_CONNECTED); unlock_mount_hash(); + mntput(no_free_ptr(p)); } } @@ -3109,7 +3112,7 @@ static struct file *open_detached_copy(struct path *path, unsigned int flags) path->mnt = mntget(&ns->root->mnt); file = dentry_open(path, O_PATH, current_cred()); if (IS_ERR(file)) - dissolve_on_fput(path->mnt); + dissolve_on_fput(no_free_ptr(path->mnt)); else file->f_mode |= FMODE_NEED_UNMOUNT; return file; @@ -4566,7 +4569,7 @@ SYSCALL_DEFINE3(fsmount, int, fs_fd, unsigned int, flags, FD_PREPARE(fdf, (flags & FSMOUNT_CLOEXEC) ? O_CLOEXEC : 0, dentry_open(&new_path, O_PATH, fc->cred)); if (fdf.err) { - dissolve_on_fput(new_path.mnt); + dissolve_on_fput(no_free_ptr(new_path.mnt)); return fdf.err; } From 07617913d09a5c3df9b294bd7d84f0fd40d80bdb Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Sat, 26 Sep 2026 15:44:32 +0200 Subject: [PATCH 039/367] selftests/filesystems: check that an unmounted mount tree isn't walked Add unmounted_tree_test: - a detached nsfs or pidfs bind mount that a file descriptor keeps alive can't be copied recursively, a copy of the mount itself and a copy of a mounted one still work - a detached tree with a child that was vacated behind it can't be copied recursively either - a copy of a detached bind mount is an ordinary mount: unlinking its mountpoint from another mount namespace unmounts it - mount_setattr() refuses to change the propagation of a detached tree and still allows it for the root of an anonymous mount namespace Every case runs in a user and mount namespace of its own. Link: https://patch.msgid.link/20260926-work-mount-fixes-2-v1-9-f357abf3d17b@kernel.org Signed-off-by: Christian Brauner (Amutable) --- tools/testing/selftests/Makefile | 1 + .../filesystems/mount_cycle/.gitignore | 2 + .../filesystems/mount_cycle/Makefile | 6 + .../mount_cycle/unmounted_tree_test.c | 484 ++++++++++++++++++ 4 files changed, 493 insertions(+) create mode 100644 tools/testing/selftests/filesystems/mount_cycle/.gitignore create mode 100644 tools/testing/selftests/filesystems/mount_cycle/Makefile create mode 100644 tools/testing/selftests/filesystems/mount_cycle/unmounted_tree_test.c diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile index 43d4a33afe71..bc4fdb6f5337 100644 --- a/tools/testing/selftests/Makefile +++ b/tools/testing/selftests/Makefile @@ -43,6 +43,7 @@ TARGETS += filesystems/open_tree_ns TARGETS += filesystems/overlayfs TARGETS += filesystems/statmount TARGETS += filesystems/mount-notify +TARGETS += filesystems/mount_cycle TARGETS += filesystems/nsfs TARGETS += filesystems/fuse TARGETS += filesystems/move_mount diff --git a/tools/testing/selftests/filesystems/mount_cycle/.gitignore b/tools/testing/selftests/filesystems/mount_cycle/.gitignore new file mode 100644 index 000000000000..7bdfe4268fb4 --- /dev/null +++ b/tools/testing/selftests/filesystems/mount_cycle/.gitignore @@ -0,0 +1,2 @@ +# SPDX-License-Identifier: GPL-2.0-only +unmounted_tree_test diff --git a/tools/testing/selftests/filesystems/mount_cycle/Makefile b/tools/testing/selftests/filesystems/mount_cycle/Makefile new file mode 100644 index 000000000000..46a68ca234ba --- /dev/null +++ b/tools/testing/selftests/filesystems/mount_cycle/Makefile @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: GPL-2.0 +TEST_GEN_PROGS := unmounted_tree_test + +CFLAGS += -Wall -O2 -g $(KHDR_INCLUDES) + +include ../../lib.mk diff --git a/tools/testing/selftests/filesystems/mount_cycle/unmounted_tree_test.c b/tools/testing/selftests/filesystems/mount_cycle/unmounted_tree_test.c new file mode 100644 index 000000000000..233f8ea92efc --- /dev/null +++ b/tools/testing/selftests/filesystems/mount_cycle/unmounted_tree_test.c @@ -0,0 +1,484 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * An unmounted mount tree that a file descriptor keeps alive is put and + * vacated behind the root's back, so nothing may walk it under + * namespace_sem: it can't be copied recursively and its propagation can't + * be changed. The mount at its root alone can still be copied and that copy + * is an ordinary mount. + */ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../wrappers.h" +#include "../../kselftest_harness.h" + +#ifndef __NR_mount_setattr +#define __NR_mount_setattr 442 +#endif + +#ifndef __NR_pidfd_open +#define __NR_pidfd_open 434 +#endif + +#define PATH_LEN 64 + +static inline int sys_mount_setattr(int dfd, const char *path, unsigned int flags, + struct mount_attr *attr, size_t size) +{ + return syscall(__NR_mount_setattr, dfd, path, flags, attr, size); +} + +static inline int sys_pidfd_open(pid_t pid, unsigned int flags) +{ + return syscall(__NR_pidfd_open, pid, flags); +} + +static int write_file(const char *path, const char *s) +{ + ssize_t n = -1; + int fd; + + fd = open(path, O_WRONLY | O_CLOEXEC); + if (fd >= 0) { + n = write(fd, s, strlen(s)); + close(fd); + } + return n == (ssize_t)strlen(s) ? 0 : -1; +} + +static int touch(const char *path) +{ + int fd; + + fd = open(path, O_WRONLY | O_CREAT | O_EXCL | O_CLOEXEC, 0644); + if (fd < 0) + return -1; + close(fd); + return 0; +} + +/* Become root in a new user namespace with a private mount namespace. */ +static int enter_userns(void) +{ + uid_t uid = getuid(); + gid_t gid = getgid(); + char map[32]; + + if (unshare(CLONE_NEWUSER | CLONE_NEWNS)) + return -1; + if (write_file("/proc/self/setgroups", "deny") && errno != ENOENT) + return -1; + snprintf(map, sizeof(map), "0 %d 1", uid); + if (write_file("/proc/self/uid_map", map)) + return -1; + snprintf(map, sizeof(map), "0 %d 1", gid); + if (write_file("/proc/self/gid_map", map)) + return -1; + if (setgid(0) || setuid(0)) + return -1; + return mount("", "/", NULL, MS_REC | MS_PRIVATE, NULL); +} + +/* A private mount namespace of our own, all mounts private. */ +static int own_mntns(void) +{ + if (unshare(CLONE_NEWNS)) + return -1; + return mount("", "/", NULL, MS_REC | MS_PRIVATE, NULL); +} + +FIXTURE(unmounted_tree) { + char dir[PATH_LEN]; +}; + +FIXTURE_SETUP(unmounted_tree) +{ + snprintf(self->dir, sizeof(self->dir), "/tmp/unmounted_tree.XXXXXX"); + ASSERT_NE(mkdtemp(self->dir), NULL); + if (enter_userns()) { + rmdir(self->dir); + SKIP(return, "test requires user namespaces"); + } + ASSERT_EQ(mount("tmpfs", self->dir, "tmpfs", 0, NULL), 0); +} + +FIXTURE_TEARDOWN(unmounted_tree) +{ + umount2(self->dir, MNT_DETACH); + rmdir(self->dir); +} + +/* A recursive copy of the mount @fd refers to must fail with EINVAL. */ +static void assert_not_walked(struct __test_metadata *_metadata, int fd, + const char *target) +{ + char link[PATH_LEN]; + int tfd; + + snprintf(link, sizeof(link), "/proc/self/fd/%d", fd); + EXPECT_EQ(mount(link, target, NULL, MS_BIND | MS_REC, NULL), -1); + EXPECT_EQ(errno, EINVAL); + + tfd = sys_open_tree(fd, "", AT_EMPTY_PATH | AT_RECURSIVE | OPEN_TREE_CLONE | + OPEN_TREE_CLOEXEC); + EXPECT_LT(tfd, 0); + EXPECT_EQ(errno, EINVAL); + if (tfd >= 0) + close(tfd); +} + +/* The mount @fd refers to can be copied on its own and mounted on @target. */ +static void assert_root_copied(struct __test_metadata *_metadata, int fd, + const char *target) +{ + int tfd; + + tfd = sys_open_tree(fd, "", AT_EMPTY_PATH | OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC); + ASSERT_GE(tfd, 0); + ASSERT_EQ(sys_move_mount(tfd, "", AT_FDCWD, target, MOVE_MOUNT_F_EMPTY_PATH), 0); + close(tfd); + ASSERT_EQ(umount2(target, MNT_DETACH), 0); +} + +/* + * A lazily unmounted bind mount of an nsfs file, kept alive by an fd, can't + * be copied recursively anymore. Copying the mount itself still works and + * so does copying a mounted bind mount of the same file. + */ +TEST_F(unmounted_tree, detached_nsfs_bind_not_walked) +{ + char x[PATH_LEN], y[PATH_LEN], z[PATH_LEN], link[PATH_LEN]; + int fd, tfd; + + snprintf(x, sizeof(x), "%s/x", self->dir); + snprintf(y, sizeof(y), "%s/y", self->dir); + snprintf(z, sizeof(z), "%s/z", self->dir); + ASSERT_EQ(touch(x), 0); + ASSERT_EQ(touch(y), 0); + ASSERT_EQ(touch(z), 0); + + ASSERT_EQ(mount("/proc/self/ns/net", x, NULL, MS_BIND, NULL), 0); + fd = open(x, O_PATH | O_CLOEXEC); + ASSERT_GE(fd, 0); + ASSERT_EQ(umount2(x, MNT_DETACH), 0); + + assert_not_walked(_metadata, fd, y); + + snprintf(link, sizeof(link), "/proc/self/fd/%d", fd); + ASSERT_EQ(mount(link, y, NULL, MS_BIND, NULL), 0); + ASSERT_EQ(umount2(y, MNT_DETACH), 0); + assert_root_copied(_metadata, fd, y); + /* opening it is fine too */ + tfd = sys_open_tree(fd, "", AT_EMPTY_PATH | OPEN_TREE_CLOEXEC); + ASSERT_GE(tfd, 0); + close(tfd); + close(fd); + + ASSERT_EQ(mount("/proc/self/ns/net", y, NULL, MS_BIND, NULL), 0); + ASSERT_EQ(mount(y, z, NULL, MS_BIND | MS_REC, NULL), 0); + tfd = sys_open_tree(AT_FDCWD, y, AT_RECURSIVE | OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC); + ASSERT_GE(tfd, 0); + close(tfd); +} + +/* Bind-mount the pidfd @pidfd on @target the way pidfd_bind_mount does. */ +static int bind_pidfd(int pidfd, const char *target) +{ + int tfd, ret; + + tfd = sys_open_tree(pidfd, "", AT_EMPTY_PATH | OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC); + if (tfd < 0) + return -1; + ret = sys_move_mount(tfd, "", AT_FDCWD, target, MOVE_MOUNT_F_EMPTY_PATH); + close(tfd); + return ret; +} + +/* The same for a bind mount of a pidfd. */ +TEST_F(unmounted_tree, detached_pidfs_bind_not_walked) +{ + char x[PATH_LEN], y[PATH_LEN]; + int pidfd, fd, tfd; + + snprintf(x, sizeof(x), "%s/x", self->dir); + snprintf(y, sizeof(y), "%s/y", self->dir); + ASSERT_EQ(touch(x), 0); + ASSERT_EQ(touch(y), 0); + + pidfd = sys_pidfd_open(getpid(), 0); + ASSERT_GE(pidfd, 0); + ASSERT_EQ(bind_pidfd(pidfd, x), 0); + fd = open(x, O_PATH | O_CLOEXEC); + ASSERT_GE(fd, 0); + ASSERT_EQ(umount2(x, MNT_DETACH), 0); + + assert_not_walked(_metadata, fd, y); + assert_root_copied(_metadata, fd, y); + close(fd); + + ASSERT_EQ(bind_pidfd(pidfd, y), 0); + tfd = sys_open_tree(AT_FDCWD, y, AT_RECURSIVE | OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC); + ASSERT_GE(tfd, 0); + close(tfd); + close(pidfd); +} + +/* What a child in its own mount namespace reports back. */ +enum { + CHILD_OK, + CHILD_NS, /* could not set up the mount namespace */ + CHILD_MOUNT, /* could not set up the mounts */ + CHILD_PIPE, /* the parent went away */ + CHILD_WALKED, /* the detached tree was copied recursively */ + CHILD_ROOT_REFUSED, /* the mount at its root wasn't copied */ + CHILD_STILL_MOUNTED, /* the copy survived the unlink of its mountpoint */ + CHILD_ERRNO, /* refused, but not with EINVAL */ +}; + +static const char *child_reason(int code) +{ + static const char *const reasons[] = { + [CHILD_OK] = "ok", + [CHILD_NS] = "could not set up the mount namespace", + [CHILD_MOUNT] = "could not set up the mounts", + [CHILD_PIPE] = "the parent went away", + [CHILD_WALKED] = "the detached tree was copied recursively", + [CHILD_ROOT_REFUSED] = "the mount at its root wasn't copied", + [CHILD_STILL_MOUNTED] = "the copy survived the unlink of its mountpoint", + [CHILD_ERRNO] = "refused, but not with EINVAL", + }; + + if (code < 0 || code >= (int)(sizeof(reasons) / sizeof(reasons[0]))) + return "child died"; + return reasons[code]; +} + +struct child_args { + const char *x; /* the nsfs bind mount goes here */ + const char *s; /* a file to bind on top of it */ + const char *y; /* a target to copy to */ +}; + +/* + * Run @fn in a child in a mount namespace of its own. Once the child + * reports that its mounts are in place, unlink @victim here, where it is a + * plain file, and let the child carry on. Returns what the child reported. + */ +static int run_child(struct __test_metadata *_metadata, + int (*fn)(const struct child_args *, int, int), + const struct child_args *a, const char *victim) +{ + int to_parent[2], to_child[2], status; + pid_t pid; + char c; + + if (pipe(to_parent) || pipe(to_child)) + return -1; + pid = fork(); + if (pid < 0) + return -1; + if (pid == 0) { + close(to_parent[0]); + close(to_child[1]); + _exit(fn(a, to_parent[1], to_child[0])); + } + close(to_parent[1]); + close(to_child[0]); + if (read(to_parent[0], &c, 1) == 1) { + /* not a mountpoint in this namespace, so the file can go */ + EXPECT_EQ(unlink(victim), 0); + EXPECT_EQ(write(to_child[1], "", 1), 1); + } + close(to_parent[0]); + close(to_child[1]); + if (waitpid(pid, &status, 0) != pid || !WIFEXITED(status)) + return -1; + return WEXITSTATUS(status); +} + +/* + * An nsfs bind mount on @x, an fd on it and a bind mount of @s on top. Once + * the parent has unlinked @x underneath, the first mount is detached and the + * second one, which stayed attached to it, has been vacated. The tree may + * not be walked, the mount at its root may still be copied. + */ +static int vacant_child(const struct child_args *a, int to_parent, int from_parent) +{ + char link[PATH_LEN]; + int fd, tfd; + char c; + + if (own_mntns()) + return CHILD_NS; + if (mount("/proc/self/ns/net", a->x, NULL, MS_BIND, NULL)) + return CHILD_MOUNT; + fd = open(a->x, O_PATH | O_CLOEXEC); + if (fd < 0 || mount(a->s, a->x, NULL, MS_BIND, NULL)) + return CHILD_MOUNT; + + if (write(to_parent, "", 1) != 1 || read(from_parent, &c, 1) != 1) + return CHILD_PIPE; + + tfd = sys_open_tree(fd, "", AT_EMPTY_PATH | AT_RECURSIVE | OPEN_TREE_CLONE | + OPEN_TREE_CLOEXEC); + if (tfd >= 0) { + close(tfd); + return CHILD_WALKED; + } + if (errno != EINVAL) + return CHILD_ERRNO; + snprintf(link, sizeof(link), "/proc/self/fd/%d", fd); + if (!mount(link, a->y, NULL, MS_BIND | MS_REC, NULL)) + return CHILD_WALKED; + if (errno != EINVAL) + return CHILD_ERRNO; + + tfd = sys_open_tree(fd, "", AT_EMPTY_PATH | OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC); + if (tfd < 0) + return CHILD_ROOT_REFUSED; + close(tfd); + /* the last reference on the detached mount collects the vacant one */ + close(fd); + return CHILD_OK; +} + +/* + * unlink() of the child's mountpoint from here, where it is a plain file, + * detaches the child's nsfs bind mount with the mount on top left attached + * to it. The mount on top loses its last reference right there and is + * vacated. The child must not be able to walk that tree. + */ +TEST_F(unmounted_tree, vacant_child_not_copied) +{ + char x[PATH_LEN], s[PATH_LEN], y[PATH_LEN]; + struct child_args a = { .x = x, .s = s, .y = y }; + int ret; + + snprintf(x, sizeof(x), "%s/x", self->dir); + snprintf(s, sizeof(s), "%s/s", self->dir); + snprintf(y, sizeof(y), "%s/y", self->dir); + ASSERT_EQ(touch(x), 0); + ASSERT_EQ(touch(s), 0); + ASSERT_EQ(touch(y), 0); + + ret = run_child(_metadata, vacant_child, &a, x); + ASSERT_EQ(ret, CHILD_OK) + TH_LOG("child: %s", child_reason(ret)); +} + +/* + * An nsfs bind mount on @x, lazily unmounted but kept alive by an fd, and a + * copy of it on @y. That copy is a mount like any other: once the parent has + * unlinked @y underneath, it is unmounted and its attributes can't be changed + * anymore. A copy that took the flag along is only unhooked, stays in the + * namespace and still takes the change. + */ +static int ordinary_copy(const struct child_args *a, int to_parent, int from_parent) +{ + struct mount_attr attr = { + .attr_set = MOUNT_ATTR_NOSUID, + }; + char link[PATH_LEN]; + int fd, fdy; + char c; + + if (own_mntns()) + return CHILD_NS; + if (mount("/proc/self/ns/net", a->x, NULL, MS_BIND, NULL)) + return CHILD_MOUNT; + fd = open(a->x, O_PATH | O_CLOEXEC); + if (fd < 0 || umount2(a->x, MNT_DETACH)) + return CHILD_MOUNT; + snprintf(link, sizeof(link), "/proc/self/fd/%d", fd); + if (mount(link, a->y, NULL, MS_BIND, NULL)) + return CHILD_MOUNT; + fdy = open(a->y, O_PATH | O_CLOEXEC); + if (fdy < 0) + return CHILD_MOUNT; + + if (write(to_parent, "", 1) != 1 || read(from_parent, &c, 1) != 1) + return CHILD_PIPE; + + if (!sys_mount_setattr(fdy, "", AT_EMPTY_PATH, &attr, sizeof(attr))) + return CHILD_STILL_MOUNTED; + if (errno != EINVAL) + return CHILD_ERRNO; + close(fdy); + close(fd); + return CHILD_OK; +} + +/* + * A copy of a detached nsfs bind mount must not take after its source and + * read as unmounted. unlink() of its mountpoint from here unmounts it like + * any other mount, so the child can't change its attributes anymore. + */ +TEST_F(unmounted_tree, copy_of_detached_bind_is_ordinary) +{ + char x[PATH_LEN], y[PATH_LEN]; + struct child_args a = { .x = x, .y = y }; + int ret; + + snprintf(x, sizeof(x), "%s/x", self->dir); + snprintf(y, sizeof(y), "%s/y", self->dir); + ASSERT_EQ(touch(x), 0); + ASSERT_EQ(touch(y), 0); + + ret = run_child(_metadata, ordinary_copy, &a, y); + ASSERT_EQ(ret, CHILD_OK) + TH_LOG("child: %s", child_reason(ret)); +} + +/* + * mount_setattr() may change the propagation of a detached tree while that + * tree is still the root of an anonymous mount namespace, but not once the + * tree has been dissolved and only a second fd keeps its root alive. + */ +TEST_F(unmounted_tree, detached_tree_setattr_refused) +{ + struct mount_attr attr = { + .propagation = MS_SHARED, + }; + char a[PATH_LEN], b[PATH_LEN]; + int tfd, fd; + + snprintf(a, sizeof(a), "%s/a", self->dir); + snprintf(b, sizeof(b), "%s/a/b", self->dir); + ASSERT_EQ(mkdir(a, 0755), 0); + ASSERT_EQ(mount("tmpfs", a, "tmpfs", 0, NULL), 0); + ASSERT_EQ(mkdir(b, 0755), 0); + ASSERT_EQ(mount("tmpfs", b, "tmpfs", 0, NULL), 0); + + tfd = sys_open_tree(AT_FDCWD, self->dir, AT_RECURSIVE | OPEN_TREE_CLONE | + OPEN_TREE_CLOEXEC); + ASSERT_GE(tfd, 0); + ASSERT_EQ(sys_mount_setattr(tfd, "", AT_EMPTY_PATH | AT_RECURSIVE, &attr, sizeof(attr)), 0); + attr.propagation = MS_PRIVATE; + ASSERT_EQ(sys_mount_setattr(tfd, "", AT_EMPTY_PATH | AT_RECURSIVE, &attr, sizeof(attr)), 0); + + /* a second reference on the root, then dissolve the tree */ + fd = sys_open_tree(tfd, "", AT_EMPTY_PATH | OPEN_TREE_CLOEXEC); + ASSERT_GE(fd, 0); + close(tfd); + + attr.propagation = MS_SHARED; + ASSERT_EQ(sys_mount_setattr(fd, "", AT_EMPTY_PATH | AT_RECURSIVE, &attr, sizeof(attr)), -1); + ASSERT_EQ(errno, EINVAL); + attr.propagation = MS_PRIVATE; + ASSERT_EQ(sys_mount_setattr(fd, "", AT_EMPTY_PATH, &attr, sizeof(attr)), -1); + ASSERT_EQ(errno, EINVAL); + close(fd); +} + +TEST_HARNESS_MAIN From 7fbcc793f76e0f3bf78f7e41bde634107233714f Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Sat, 26 Sep 2026 15:44:33 +0200 Subject: [PATCH 040/367] selftests/filesystems: check that a dead mount's overmount isn't followed Add overmount_reparent_test: - a mount that a file descriptor keeps alive after propagate_umount() reparented its overmount and unmounted it must not be walked into the freed overmount by move_mount(MOVE_MOUNT_BENEATH) Link: https://patch.msgid.link/20260926-work-mount-fixes-2-v1-10-f357abf3d17b@kernel.org Signed-off-by: Christian Brauner (Amutable) --- .../filesystems/mount_cycle/.gitignore | 1 + .../filesystems/mount_cycle/Makefile | 2 +- .../mount_cycle/overmount_reparent_test.c | 181 ++++++++++++++++++ 3 files changed, 183 insertions(+), 1 deletion(-) create mode 100644 tools/testing/selftests/filesystems/mount_cycle/overmount_reparent_test.c diff --git a/tools/testing/selftests/filesystems/mount_cycle/.gitignore b/tools/testing/selftests/filesystems/mount_cycle/.gitignore index 7bdfe4268fb4..03ca95de7765 100644 --- a/tools/testing/selftests/filesystems/mount_cycle/.gitignore +++ b/tools/testing/selftests/filesystems/mount_cycle/.gitignore @@ -1,2 +1,3 @@ # SPDX-License-Identifier: GPL-2.0-only unmounted_tree_test +overmount_reparent_test diff --git a/tools/testing/selftests/filesystems/mount_cycle/Makefile b/tools/testing/selftests/filesystems/mount_cycle/Makefile index 46a68ca234ba..88271c82d1c1 100644 --- a/tools/testing/selftests/filesystems/mount_cycle/Makefile +++ b/tools/testing/selftests/filesystems/mount_cycle/Makefile @@ -1,5 +1,5 @@ # SPDX-License-Identifier: GPL-2.0 -TEST_GEN_PROGS := unmounted_tree_test +TEST_GEN_PROGS := unmounted_tree_test overmount_reparent_test CFLAGS += -Wall -O2 -g $(KHDR_INCLUDES) diff --git a/tools/testing/selftests/filesystems/mount_cycle/overmount_reparent_test.c b/tools/testing/selftests/filesystems/mount_cycle/overmount_reparent_test.c new file mode 100644 index 000000000000..46a56b722f3e --- /dev/null +++ b/tools/testing/selftests/filesystems/mount_cycle/overmount_reparent_test.c @@ -0,0 +1,181 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * propagate_umount() slides a surviving overmount off a mount that is being + * unmounted and unmounts that mount right after. If a file descriptor keeps + * the unmounted mount alive its ->overmount is left pointing at the moved + * mount, which is freed on its own schedule. move_mount(MOVE_MOUNT_BENEATH) + * with such a file descriptor as the target walks ->overmount in + * topmost_overmount() before it checks that the target is still mounted and + * must not step into the freed mount. + */ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../wrappers.h" +#include "../../kselftest_harness.h" + +#ifndef MOVE_MOUNT_BENEATH +#define MOVE_MOUNT_BENEATH 0x00000200 +#endif + +#ifndef FSCONFIG_CMD_CREATE +#define FSCONFIG_CMD_CREATE 6 +#endif + +#define DIR_LEN 64 +#define PATH_LEN 128 + +static int write_file(const char *path, const char *s) +{ + ssize_t n = -1; + int fd; + + fd = open(path, O_WRONLY | O_CLOEXEC); + if (fd >= 0) { + n = write(fd, s, strlen(s)); + close(fd); + } + return n == (ssize_t)strlen(s) ? 0 : -1; +} + +/* Become root in a new user namespace with a private mount namespace. */ +static int enter_userns(void) +{ + uid_t uid = getuid(); + gid_t gid = getgid(); + char map[32]; + + if (unshare(CLONE_NEWUSER | CLONE_NEWNS)) + return -1; + if (write_file("/proc/self/setgroups", "deny") && errno != ENOENT) + return -1; + snprintf(map, sizeof(map), "0 %d 1", uid); + if (write_file("/proc/self/uid_map", map)) + return -1; + snprintf(map, sizeof(map), "0 %d 1", gid); + if (write_file("/proc/self/gid_map", map)) + return -1; + if (setgid(0) || setuid(0)) + return -1; + return mount("", "/", NULL, MS_REC | MS_PRIVATE, NULL); +} + +/* A detached tmpfs mount to serve as the source of a move. */ +static int detached_tmpfs(void) +{ + int sfd, mfd; + + sfd = sys_fsopen("tmpfs", 0); + if (sfd < 0) + return -1; + if (sys_fsconfig(sfd, FSCONFIG_CMD_CREATE, NULL, NULL, 0)) { + close(sfd); + return -1; + } + mfd = sys_fsmount(sfd, 0, 0); + close(sfd); + return mfd; +} + +FIXTURE(overmount_reparent) { + char dir[DIR_LEN]; +}; + +FIXTURE_SETUP(overmount_reparent) +{ + snprintf(self->dir, sizeof(self->dir), "/tmp/overmount_reparent.XXXXXX"); + ASSERT_NE(mkdtemp(self->dir), NULL); + if (enter_userns()) { + rmdir(self->dir); + SKIP(return, "test requires user namespaces"); + } +} + +FIXTURE_TEARDOWN(overmount_reparent) +{ + char p[PATH_LEN]; + + snprintf(p, sizeof(p), "%s/b", self->dir); + umount2(p, MNT_DETACH); + snprintf(p, sizeof(p), "%s/a", self->dir); + umount2(p, MNT_DETACH); +} + +/* + * A shared mount base_a is bind-mounted to base_b as its peer. A mount X on + * base_a/mp propagates a copy X_b onto base_b/mp. A file descriptor pins X_b, + * then X_b is made private and an overmount is stacked on its root. + * + * A lazy unmount of X propagates to X_b: X_b is committed to the unmount while + * its overmount survives, so propagate_umount() reparents the overmount onto + * base_b and unmounts X_b, which the file descriptor keeps alive. Unmounting + * the reparented overmount frees it. X_b->overmount now dangles unless + * mnt_change_mountpoint() reset it. + * + * move_mount(MOVE_MOUNT_BENEATH) through the file descriptor makes + * do_lock_mount() follow X_b->overmount in topmost_overmount() before it + * notices that X_b is no longer mounted. With the pointer reset the move fails + * cleanly with ENOENT; otherwise it reads the freed overmount. + */ +TEST_F(overmount_reparent, dead_overmount_holder_not_followed) +{ + char a[PATH_LEN], b[PATH_LEN], amp[PATH_LEN], bmp[PATH_LEN]; + int fd, mfd, ret; + + snprintf(a, sizeof(a), "%s/a", self->dir); + snprintf(b, sizeof(b), "%s/b", self->dir); + ASSERT_EQ(mkdir(a, 0755), 0); + ASSERT_EQ(mkdir(b, 0755), 0); + + ASSERT_EQ(mount("tmpfs", a, "tmpfs", 0, NULL), 0); + ASSERT_EQ(mount(NULL, a, NULL, MS_SHARED, NULL), 0); + ASSERT_EQ(mount(a, b, NULL, MS_BIND, NULL), 0); + + snprintf(amp, sizeof(amp), "%s/a/mp", self->dir); + snprintf(bmp, sizeof(bmp), "%s/b/mp", self->dir); + /* base_a and base_b share one superblock, so this dir is in both. */ + ASSERT_EQ(mkdir(amp, 0755), 0); + + /* X on base_a/mp propagates a copy X_b onto base_b/mp. */ + ASSERT_EQ(mount("tmpfs", amp, "tmpfs", 0, NULL), 0); + + /* Pin X_b before anything is stacked on top of it. */ + fd = open(bmp, O_PATH | O_CLOEXEC); + ASSERT_GE(fd, 0); + + /* Keep the overmount local to X_b. */ + ASSERT_EQ(mount(NULL, bmp, NULL, MS_PRIVATE, NULL), 0); + + /* The overmount on X_b's root: X_b->overmount points at it. */ + ASSERT_EQ(mount("tmpfs", bmp, "tmpfs", 0, NULL), 0); + + /* Reparents the overmount onto base_b and unmounts X_b. */ + ASSERT_EQ(umount2(amp, MNT_DETACH), 0); + + /* Free the reparented overmount. */ + ASSERT_EQ(umount2(bmp, MNT_DETACH), 0); + usleep(100000); + + mfd = detached_tmpfs(); + ASSERT_GE(mfd, 0); + + ret = sys_move_mount(mfd, "", fd, "", + MOVE_MOUNT_F_EMPTY_PATH | MOVE_MOUNT_T_EMPTY_PATH | + MOVE_MOUNT_BENEATH); + EXPECT_EQ(ret, -1); + EXPECT_EQ(errno, ENOENT); + + close(mfd); + close(fd); +} + +TEST_HARNESS_MAIN From b816da5d5df1461446a0a5f3fe2b4948a659a790 Mon Sep 17 00:00:00 2001 From: Wentao Liang Date: Wed, 16 Sep 2026 15:40:15 +0000 Subject: [PATCH 041/367] drm/vc4: Fix firmware reference leak in vc4_drm_bind() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The firmware reference obtained via rpi_firmware_get() is leaked when aperture_remove_all_conflicting_devices() fails, as the error label returns without putting it. This happens before the firmware is consumed and put further down in the function. Put the firmware reference before jumping to err. Fixes: c406ad5e4a85 ("drm/vc4: Notify the firmware when DRM is in charge") Cc: stable@vger.kernel.org Signed-off-by: Wentao Liang Link: https://patch.msgid.link/20260916154015.2056797-1-vulab@iscas.ac.cn Reviewed-by: Maíra Canal Signed-off-by: Maíra Canal --- drivers/gpu/drm/vc4/vc4_drv.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/vc4/vc4_drv.c b/drivers/gpu/drm/vc4/vc4_drv.c index 616caf9d9915..7b64e1ed02d4 100644 --- a/drivers/gpu/drm/vc4/vc4_drv.c +++ b/drivers/gpu/drm/vc4/vc4_drv.c @@ -360,8 +360,11 @@ static int vc4_drm_bind(struct device *dev) } ret = aperture_remove_all_conflicting_devices(driver->name); - if (ret) + if (ret) { + if (firmware) + rpi_firmware_put(firmware); goto err; + } if (firmware) { ret = rpi_firmware_property(firmware, From add916cfcec5b71cb242db85b91b2437be7390cf Mon Sep 17 00:00:00 2001 From: Felix Gu Date: Sat, 21 Mar 2026 03:12:10 +0800 Subject: [PATCH 042/367] drm/bridge: th1520-dw-hdmi: Fix error check on dw_hdmi_probe() return value The error check after calling dw_hdmi_probe() was incorrectly checking the struct pointer hdmi instead of the probe result hdmi->dw_hdmi. Fix this by checking the correct variable. Fixes: 96f30ee0fb9d ("drm/bridge: add a driver for T-Head TH1520 HDMI controller") Signed-off-by: Felix Gu Reviewed-by: Dmitry Baryshkov Cc: stable@vger.kernel.org Signed-off-by: Icenowy Zheng Link: https://patch.msgid.link/20260321-th1520-v1-1-ec877197770d@gmail.com --- drivers/gpu/drm/bridge/th1520-dw-hdmi.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/bridge/th1520-dw-hdmi.c b/drivers/gpu/drm/bridge/th1520-dw-hdmi.c index cbea8b14cd4b..3464720b4e13 100644 --- a/drivers/gpu/drm/bridge/th1520-dw-hdmi.c +++ b/drivers/gpu/drm/bridge/th1520-dw-hdmi.c @@ -135,8 +135,8 @@ static int th1520_dw_hdmi_probe(struct platform_device *pdev) plat_data->priv_data = hdmi; hdmi->dw_hdmi = dw_hdmi_probe(pdev, plat_data); - if (IS_ERR(hdmi)) - return PTR_ERR(hdmi); + if (IS_ERR(hdmi->dw_hdmi)) + return PTR_ERR(hdmi->dw_hdmi); platform_set_drvdata(pdev, hdmi); From 01c83795d50ccdb441f60e89ff50767cb2f6045a Mon Sep 17 00:00:00 2001 From: Felix Gu Date: Sat, 21 Mar 2026 03:12:11 +0800 Subject: [PATCH 043/367] drm/bridge: th1520-dw-hdmi: Fix remove() callback This driver stores struct th1520_hdmi * in platform drvdata, but th1520_dw_hdmi_remove() was reading it back as struct dw_hdmi * and passing it to dw_hdmi_remove(), so teardown runs on the wrong pointer. Retrieve struct th1520_hdmi * from platform drvdata and pass hdmi->dw_hdmi to dw_hdmi_remove(). Fixes: 96f30ee0fb9d ("drm/bridge: add a driver for T-Head TH1520 HDMI controller") Signed-off-by: Felix Gu Reviewed-by: Dmitry Baryshkov Cc: stable@vger.kernel.org Signed-off-by: Icenowy Zheng Link: https://patch.msgid.link/20260321-th1520-v1-2-ec877197770d@gmail.com --- drivers/gpu/drm/bridge/th1520-dw-hdmi.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/bridge/th1520-dw-hdmi.c b/drivers/gpu/drm/bridge/th1520-dw-hdmi.c index 3464720b4e13..2fa7f2d46b3d 100644 --- a/drivers/gpu/drm/bridge/th1520-dw-hdmi.c +++ b/drivers/gpu/drm/bridge/th1520-dw-hdmi.c @@ -145,9 +145,9 @@ static int th1520_dw_hdmi_probe(struct platform_device *pdev) static void th1520_dw_hdmi_remove(struct platform_device *pdev) { - struct dw_hdmi *hdmi = platform_get_drvdata(pdev); + struct th1520_hdmi *hdmi = platform_get_drvdata(pdev); - dw_hdmi_remove(hdmi); + dw_hdmi_remove(hdmi->dw_hdmi); } static const struct of_device_id th1520_dw_hdmi_of_table[] = { From ff82fc3a4a1d417dee1229681cc5285986edb1fa Mon Sep 17 00:00:00 2001 From: Ridham Khurana Date: Wed, 23 Sep 2026 11:39:02 +0000 Subject: [PATCH 044/367] gpio: xilinx: fix runtime PM leak on request error path pm_runtime_get_sync() leaves the usage counter incremented even when it fails, and xgpio_request() returns the error without dropping it. gpiolib does not call ->free() when ->request() fails, so xgpio_free(), which holds the only matching pm_runtime_put(), never runs. The reference is leaked and the controller can no longer runtime-suspend, so its clock stays enabled. Switch to pm_runtime_resume_and_get(), which only increments the usage counter on success. Fixes: 26b04774621e ("gpio: gpio-xilinx: Add support for suspend and resume") Cc: stable@vger.kernel.org Signed-off-by: Ridham Khurana Link: https://patch.msgid.link/20260923113905.1409290-1-khurana.ridham222@gmail.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-xilinx.c | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/drivers/gpio/gpio-xilinx.c b/drivers/gpio/gpio-xilinx.c index 532205175827..9406ce85f933 100644 --- a/drivers/gpio/gpio-xilinx.c +++ b/drivers/gpio/gpio-xilinx.c @@ -271,14 +271,7 @@ static void xgpio_save_regs(struct xgpio_instance *chip) static int xgpio_request(struct gpio_chip *chip, unsigned int offset) { - int ret; - - ret = pm_runtime_get_sync(chip->parent); - /* - * If the device is already active pm_runtime_get() will return 1 on - * success, but gpio_request still needs to return 0. - */ - return ret < 0 ? ret : 0; + return pm_runtime_resume_and_get(chip->parent); } static void xgpio_free(struct gpio_chip *chip, unsigned int offset) From 4c0d69d677cf5b0065ebbb3ceb8ba66fddbec630 Mon Sep 17 00:00:00 2001 From: Shawn Lin Date: Mon, 21 Sep 2026 14:52:50 +0800 Subject: [PATCH 045/367] pmdomain: rockchip: propagate subdomain add errors rockchip_pm_add_subdomain() ignored the return value of its recursive call, so a failure deeper in the hierarchy was silently dropped and probe still succeeded with an incomplete domain tree. Fixes: 6be05b5ec161 ("soc: rockchip: power-domain: add support for sub-power domains") Signed-off-by: Shawn Lin Reviewed-by: Sebastian Reichel Cc: stable@vger.kernel.org Signed-off-by: Ulf Hansson --- drivers/pmdomain/rockchip/pm-domains.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/pmdomain/rockchip/pm-domains.c b/drivers/pmdomain/rockchip/pm-domains.c index ba66ae719428..068dd4b3705e 100644 --- a/drivers/pmdomain/rockchip/pm-domains.c +++ b/drivers/pmdomain/rockchip/pm-domains.c @@ -1018,7 +1018,9 @@ static int rockchip_pm_add_subdomain(struct rockchip_pmu *pmu, parent_domain->name, child_domain->name); } - rockchip_pm_add_subdomain(pmu, np); + error = rockchip_pm_add_subdomain(pmu, np); + if (error) + return error; } return 0; From d8b3847770661b7109146e6479eeadcdab9cecf0 Mon Sep 17 00:00:00 2001 From: Shawn Lin Date: Mon, 21 Sep 2026 14:52:51 +0800 Subject: [PATCH 046/367] pmdomain: rockchip: fix clock leak on domain probe failure When of_clk_get() failed in the middle of the loop, the references of the clocks acquired so far were leaked instead of being released by the error path. Fixes: d909072d0521 ("soc: rockchip: power-domain: use clk_bulk APIs") Signed-off-by: Shawn Lin Reviewed-by: Sebastian Reichel Cc: stable@vger.kernel.org Signed-off-by: Ulf Hansson --- drivers/pmdomain/rockchip/pm-domains.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/pmdomain/rockchip/pm-domains.c b/drivers/pmdomain/rockchip/pm-domains.c index 068dd4b3705e..b6b099b9ed5c 100644 --- a/drivers/pmdomain/rockchip/pm-domains.c +++ b/drivers/pmdomain/rockchip/pm-domains.c @@ -842,7 +842,7 @@ static int rockchip_pm_add_one_domain(struct rockchip_pmu *pmu, dev_err(pmu->dev, "%pOFn: failed to get clk at index %d: %d\n", node, i, error); - return error; + goto err_put_clocks; } } From 4c66c423593e26273ea0d644906ed007630c6f6d Mon Sep 17 00:00:00 2001 From: Shawn Lin Date: Mon, 21 Sep 2026 14:52:52 +0800 Subject: [PATCH 047/367] pmdomain: rockchip: don't ignore clock lookup errors on attach rockchip_pd_attach_dev() treated every of_clk_get() error as the end of the clock list, so a clock that exists but is not available yet (e.g. -EPROBE_DEFER) was silently skipped and the device ended up without pm_clk management. Only -ENOENT means there are no more clocks. Fixes: 7c696693a4f5 ("soc: rockchip: power-domain: Add power domain driver") Signed-off-by: Shawn Lin Reviewed-by: Sebastian Reichel Cc: stable@vger.kernel.org Signed-off-by: Ulf Hansson --- drivers/pmdomain/rockchip/pm-domains.c | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/drivers/pmdomain/rockchip/pm-domains.c b/drivers/pmdomain/rockchip/pm-domains.c index b6b099b9ed5c..e193f978d5f3 100644 --- a/drivers/pmdomain/rockchip/pm-domains.c +++ b/drivers/pmdomain/rockchip/pm-domains.c @@ -759,7 +759,19 @@ static int rockchip_pd_attach_dev(struct generic_pm_domain *genpd, } i = 0; - while ((clk = of_clk_get(dev->of_node, i++)) && !IS_ERR(clk)) { + while (1) { + clk = of_clk_get(dev->of_node, i++); + if (IS_ERR(clk)) { + error = PTR_ERR(clk); + if (error == -ENOENT) + break; + + dev_err(dev, "failed to get clock %d: %d\n", i - 1, + error); + pm_clk_destroy(dev); + return error; + } + dev_dbg(dev, "adding clock '%pC' to list of PM clocks\n", clk); error = pm_clk_add_clk(dev, clk); if (error) { From 21bc51cb89ced74d6645a138cc2f21184bc75094 Mon Sep 17 00:00:00 2001 From: Zack Rusin Date: Sun, 9 Aug 2026 16:45:15 -0400 Subject: [PATCH 048/367] drm/vmwgfx: validate pitch coming from userspace Validate the pitch, alongside the box dimensions before trying to copy data from the underlying surface. Fixes possible out of bounds reads with cursor snooping. Fixes: 2ac863719e51 ("vmwgfx: Snoop DMA transfers with non-covering sizes") Cc: stable@vger.kernel.org Reported-by: Youness HFA Signed-off-by: Zack Rusin Reviewed-by: Maaz Mombasawala Link: https://patch.msgid.link/20260809204515.211045-1-zack.rusin@broadcom.com --- drivers/gpu/drm/vmwgfx/vmwgfx_cursor_plane.c | 26 ++++++++++++++++---- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/drivers/gpu/drm/vmwgfx/vmwgfx_cursor_plane.c b/drivers/gpu/drm/vmwgfx/vmwgfx_cursor_plane.c index d1e7df500190..5a69606a0593 100644 --- a/drivers/gpu/drm/vmwgfx/vmwgfx_cursor_plane.c +++ b/drivers/gpu/drm/vmwgfx/vmwgfx_cursor_plane.c @@ -324,6 +324,7 @@ void vmw_kms_cursor_snoop(struct vmw_surface *srf, unsigned long kmap_num; SVGA3dCopyBox *box; u32 box_count; + u64 src_extent; void *virtual; bool is_iomem; struct vmw_dma_cmd { @@ -335,6 +336,8 @@ void vmw_kms_cursor_snoop(struct vmw_surface *srf, vmw_surface_get_desc(VMW_CURSOR_SNOOP_FORMAT); const u32 image_pitch = VMW_CURSOR_SNOOP_WIDTH * desc->pitchBytesPerBlock; + BUILD_BUG_ON(sizeof(*srf->snooper.image) != desc->pitchBytesPerBlock); + cmd = container_of(header, struct vmw_dma_cmd, header); /* No snooper installed, nothing to copy */ @@ -372,8 +375,19 @@ void vmw_kms_cursor_snoop(struct vmw_surface *srf, return; } + if (box->w == 0 || box->h == 0) + return; + + src_extent = (u64)(box->h - 1) * cmd->dma.guest.pitch + + (u64)box->w * desc->pitchBytesPerBlock; + if (src_extent > bo->base.size) { + DRM_ERROR("Cursor snoop source of %llu bytes exceeds the %zu byte buffer\n", + src_extent, bo->base.size); + return; + } + kmap_offset = cmd->dma.guest.ptr.offset >> PAGE_SHIFT; - kmap_num = (VMW_CURSOR_SNOOP_HEIGHT * image_pitch) >> PAGE_SHIFT; + kmap_num = PFN_UP(src_extent); ret = ttm_bo_reserve(bo, true, false, NULL); if (unlikely(ret != 0)) { @@ -387,14 +401,16 @@ void vmw_kms_cursor_snoop(struct vmw_surface *srf, virtual = ttm_kmap_obj_virtual(&map, &is_iomem); - if (box->w == VMW_CURSOR_SNOOP_WIDTH && cmd->dma.guest.pitch == image_pitch) { + if (box->w == VMW_CURSOR_SNOOP_WIDTH && + box->h == VMW_CURSOR_SNOOP_HEIGHT && + cmd->dma.guest.pitch == image_pitch) { memcpy(srf->snooper.image, virtual, VMW_CURSOR_SNOOP_HEIGHT * image_pitch); } else { - /* Image is unsigned pointer. */ + /* The destination stride is in pixels. */ for (i = 0; i < box->h; i++) - memcpy(srf->snooper.image + i * image_pitch, - virtual + i * cmd->dma.guest.pitch, + memcpy(srf->snooper.image + i * VMW_CURSOR_SNOOP_WIDTH, + virtual + (size_t)i * cmd->dma.guest.pitch, box->w * desc->pitchBytesPerBlock); } srf->snooper.id++; From e78a9fb7a40c55ec2a70ae39903dde4f898af2ef Mon Sep 17 00:00:00 2001 From: Ian Forbes Date: Wed, 16 Sep 2026 14:01:44 -0500 Subject: [PATCH 049/367] drm/vmwgfx: Add blend mode property This stops the log spamming introduced by the referenced fixes commit. Fixes: 860e748bddcc ("drm: ensure blend mode supported if pixel format with alpha exposed") Signed-off-by: Ian Forbes Reviewed-by: Zack Rusin Signed-off-by: Zack Rusin Link: https://patch.msgid.link/20260916190144.2080777-1-ian.forbes@broadcom.com --- drivers/gpu/drm/vmwgfx/vmwgfx_kms.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/gpu/drm/vmwgfx/vmwgfx_kms.c b/drivers/gpu/drm/vmwgfx/vmwgfx_kms.c index 1b407b61f683..31d3ee0825ca 100644 --- a/drivers/gpu/drm/vmwgfx/vmwgfx_kms.c +++ b/drivers/gpu/drm/vmwgfx/vmwgfx_kms.c @@ -20,10 +20,18 @@ #include #include #include +#include void vmw_du_init(struct vmw_display_unit *du) { vmw_vkms_crtc_init(&du->crtc); + drm_plane_create_blend_mode_property(&du->primary, + BIT(DRM_MODE_BLEND_PIXEL_NONE) | + BIT(DRM_MODE_BLEND_PREMULTI)); + // Check that the cursor plane was actually initialized + if (du->crtc.cursor == &du->cursor.base) + drm_plane_create_blend_mode_property(&du->cursor.base, + BIT(DRM_MODE_BLEND_PREMULTI)); } void vmw_du_cleanup(struct vmw_display_unit *du) From b38af5386a74416e898955443f2e26547b358cb2 Mon Sep 17 00:00:00 2001 From: Yibo Tan Date: Tue, 15 Sep 2026 22:44:57 +0800 Subject: [PATCH 050/367] mmc: mtk-sd: Cancel request timeout work on remove The driver queues req_timeout while a request is active. msdc_request_done() uses cancel_delayed_work(), which does not wait for a timeout callback that has already started. The timeout callback calls mmc_request_done(), which wakes the request waiter, and then continues to use host->dev_comp and check the SDIO IRQ. During unbind, msdc_drv_remove() can return and the managed mmc_host can be freed before the callback finishes. KASAN reported use-after-free accesses in msdc_request_done() and msdc_recheck_sdio_irq() in each of three unbind tests. The same tests completed without a kernel diagnostic after this change. Call cancel_delayed_work_sync() after mmc_remove_host() has stopped new requests and before the driver releases the host resources. Fixes: 208489032bdd ("mmc: mediatek: Add Mediatek MMC driver") Cc: stable@vger.kernel.org Assisted-by: Codex:GPT-5 Signed-off-by: Yibo Tan Signed-off-by: Ulf Hansson --- drivers/mmc/host/mtk-sd.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/mmc/host/mtk-sd.c b/drivers/mmc/host/mtk-sd.c index 01ea3adbdf3b..5b160fdad3f0 100644 --- a/drivers/mmc/host/mtk-sd.c +++ b/drivers/mmc/host/mtk-sd.c @@ -3216,6 +3216,7 @@ static void msdc_drv_remove(struct platform_device *pdev) platform_set_drvdata(pdev, NULL); mmc_remove_host(mmc); + cancel_delayed_work_sync(&host->req_timeout); msdc_deinit_hw(host); msdc_gate_clock(host); From e1c6c10a6a70a69e29b04fcc1ebe4b9bcbc7a49a Mon Sep 17 00:00:00 2001 From: Guangshuo Li Date: Wed, 16 Sep 2026 00:19:47 +0800 Subject: [PATCH 051/367] mmc: sdhci-sprd: disable runtime PM on remove sdhci_sprd_probe() enables runtime PM, while sdhci_sprd_remove() does not perform the corresponding runtime PM cleanup. The probe failure path disables runtime PM before disabling the clocks, but the normal remove path directly disables clocks that are also managed by the runtime PM callbacks. If the device is runtime suspended, those clocks may already be disabled. Resume the device before removal, disable runtime PM and drop the temporary runtime PM reference before disabling the clocks. This issue was found by manual code inspection. Fixes: fb8bd90f83c4 ("mmc: sdhci-sprd: Add Spreadtrum's initial host controller") Cc: stable@vger.kernel.org Signed-off-by: Guangshuo Li Reviewed-by: Baolin Wang Acked-by: Adrian Hunter Signed-off-by: Ulf Hansson --- drivers/mmc/host/sdhci-sprd.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/mmc/host/sdhci-sprd.c b/drivers/mmc/host/sdhci-sprd.c index 3584a2b314a9..0c8f389fe584 100644 --- a/drivers/mmc/host/sdhci-sprd.c +++ b/drivers/mmc/host/sdhci-sprd.c @@ -890,6 +890,10 @@ static void sdhci_sprd_remove(struct platform_device *pdev) struct sdhci_host *host = platform_get_drvdata(pdev); struct sdhci_sprd_host *sprd_host = TO_SPRD_HOST(host); + pm_runtime_get_sync(&pdev->dev); + pm_runtime_disable(&pdev->dev); + pm_runtime_put_noidle(&pdev->dev); + sdhci_remove_host(host, 0); clk_disable_unprepare(sprd_host->clk_sdio); From 9d1afbf60ed53c5b051cd124153e0ae3078c8b93 Mon Sep 17 00:00:00 2001 From: Maxim Skokov Date: Tue, 15 Sep 2026 10:00:46 +0300 Subject: [PATCH 052/367] Bluetooth: RFCOMM: free the skb when the DLC has no owner rfcomm_recv_data() hands the skb to d->data_ready() and returns 0 without freeing it, so the callback owns the skb from that point on. The tty implementation frees it when the DLC has been detached from its device: static void rfcomm_dev_data_ready(struct rfcomm_dlc *dlc, struct sk_buff *skb) { struct rfcomm_dev *dev = dlc->owner; if (!dev) { kfree_skb(skb); return; } The socket implementation returns without freeing it, and the skb is leaked. A connected DLC can have no owner: rfcomm_sock_destruct() clears d->owner, while the DLC itself stays on the session until it is closed. Data frames that arrive in that window still find d->state == BT_CONNECTED in rfcomm_recv_data() and are handed to the callback, one leaked skb each. The leak does not stop on its own either. The remote runs out of credits only once RFCOMM_RX_THROTTLED is set, and for a socket the only place that sets it is rfcomm_sk_data_ready() itself, below the return. rfcomm_process_dlcs() keeps granting credits, so the remote keeps sending and the leak keeps growing. Free the skb, as the tty side does. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Reported-by: Mikhail Gavrilov Closes: https://lore.kernel.org/linux-bluetooth/CABXGCsPe5wykd8Aj-hm+SJ7SGQyb8yoGNnx3XDpPmBathj+EGg@mail.gmail.com/ Cc: stable@vger.kernel.org Signed-off-by: Maxim Skokov Assisted-by: Claude:claude-opus-5 Reviewed-by: Mikhail Gavrilov Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/rfcomm/sock.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/net/bluetooth/rfcomm/sock.c b/net/bluetooth/rfcomm/sock.c index fb924d0e34ec..b26918dc9e9e 100644 --- a/net/bluetooth/rfcomm/sock.c +++ b/net/bluetooth/rfcomm/sock.c @@ -48,8 +48,11 @@ static void rfcomm_sock_kill(struct sock *sk); static void rfcomm_sk_data_ready(struct rfcomm_dlc *d, struct sk_buff *skb) { struct sock *sk = d->owner; - if (!sk) + + if (!sk) { + kfree_skb(skb); return; + } atomic_add(skb->len, &sk->sk_rmem_alloc); skb_queue_tail(&sk->sk_receive_queue, skb); From 0f4d4424fc1d651ec3fa63a0a8060ba9087eb0ec Mon Sep 17 00:00:00 2001 From: Guangshuo Li Date: Tue, 15 Sep 2026 23:57:24 +0800 Subject: [PATCH 053/367] mmc: cavium-octeon: destroy slot platform devices on remove octeon_mmc_probe() creates host->slot_pdev[i] with of_platform_device_create(), while octeon_mmc_remove() does not destroy the child platform devices. The probe failure path removes each MMC slot and destroys its associated platform device, but the normal remove path only removes the MMC slot. As a result, the child platform devices remain registered after the Octeon MMC controller is removed. Destroy each slot platform device during removal after cleaning up the corresponding MMC slot, matching the probe failure cleanup path. This issue was found by manual code inspection. Fixes: 01d95843335c ("mmc: cavium: Add MMC support for Octeon SOCs.") Cc: stable@vger.kernel.org Signed-off-by: Guangshuo Li Signed-off-by: Ulf Hansson --- drivers/mmc/host/cavium-octeon.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/mmc/host/cavium-octeon.c b/drivers/mmc/host/cavium-octeon.c index 1bfc1a4f9fe9..671dd1a36a74 100644 --- a/drivers/mmc/host/cavium-octeon.c +++ b/drivers/mmc/host/cavium-octeon.c @@ -295,9 +295,12 @@ static void octeon_mmc_remove(struct platform_device *pdev) u64 dma_cfg; int i; - for (i = 0; i < CAVIUM_MAX_MMC; i++) + for (i = 0; i < CAVIUM_MAX_MMC; i++) { if (host->slot[i]) cvm_mmc_of_slot_remove(host->slot[i]); + if (host->slot_pdev[i]) + of_platform_device_destroy(&host->slot_pdev[i]->dev, NULL); + } dma_cfg = readq(host->dma_base + MIO_EMM_DMA_CFG(host)); dma_cfg &= ~MIO_EMM_DMA_CFG_EN; From 29ee8cf18a21b02cd0b2e35f741b61cd1fe3e14e Mon Sep 17 00:00:00 2001 From: Guangshuo Li Date: Wed, 16 Sep 2026 00:13:10 +0800 Subject: [PATCH 054/367] mmc: cavium-thunderx: destroy slot platform devices on remove thunder_mmc_probe() creates host->slot_pdev[i] with of_platform_device_create(), while thunder_mmc_remove() does not destroy the child platform devices. The probe failure path removes each MMC slot and destroys its associated platform device, but the normal remove path only removes the MMC slot. As a result, the child platform devices remain registered after the ThunderX MMC controller is removed. Destroy each slot platform device during removal after cleaning up the corresponding MMC slot, matching the probe failure cleanup path. Keep an extra device reference around of_platform_device_destroy() as done in the existing error path. This issue was found by manual code inspection. Fixes: 166bac38c3c56 ("mmc: cavium: Add MMC PCI driver for ThunderX SOCs") Cc: stable@vger.kernel.org Signed-off-by: Guangshuo Li Signed-off-by: Ulf Hansson --- drivers/mmc/host/cavium-thunderx.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/mmc/host/cavium-thunderx.c b/drivers/mmc/host/cavium-thunderx.c index 84ff6d82ae3c..2635ac94ec5d 100644 --- a/drivers/mmc/host/cavium-thunderx.c +++ b/drivers/mmc/host/cavium-thunderx.c @@ -173,9 +173,12 @@ static void thunder_mmc_remove(struct pci_dev *pdev) u64 dma_cfg; int i; - for (i = 0; i < CAVIUM_MAX_MMC; i++) + for (i = 0; i < CAVIUM_MAX_MMC; i++) { if (host->slot[i]) cvm_mmc_of_slot_remove(host->slot[i]); + if (host->slot_pdev[i]) + of_platform_device_destroy(&host->slot_pdev[i]->dev, NULL); + } dma_cfg = readq(host->dma_base + MIO_EMM_DMA_CFG(host)); dma_cfg &= ~MIO_EMM_DMA_CFG_EN; From 74bbbc9359a592e3c0736b289c00489c1afa928c Mon Sep 17 00:00:00 2001 From: Jose Ignacio Tornos Martinez Date: Tue, 28 Apr 2026 17:26:44 +0200 Subject: [PATCH 055/367] i40e: skip unnecessary VF reset when setting trust The current implementation triggers a VF reset when changing the trust setting, causing a ~10 second delay during bonding setup. In all the cases, the reset causes a ~10 second delay during which: - VF must reinitialize completely - Any in-progress operations (like bonding enslave) fail with timeouts - VF is unavailable When granting trust, no reset is needed - we can just set the capability flag to allow privileged operations. When revoking trust, we only need to reset (conservative approach) if the VF has actually configured advanced features that require cleanup (ADQ/cloud filters, promiscuous mode). For VFs in a clean state, we can safely change the trust setting without the disruptive reset. When we don't reset, we manually handle capability flag via helper function, eliminating the delay. Signed-off-by: Jose Ignacio Tornos Martinez Tested-by: Rafal Romanowski Signed-off-by: Tony Nguyen --- .../ethernet/intel/i40e/i40e_virtchnl_pf.c | 35 +++++++++++++------ 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/drivers/net/ethernet/intel/i40e/i40e_virtchnl_pf.c b/drivers/net/ethernet/intel/i40e/i40e_virtchnl_pf.c index a26c3d47ec15..c6732a24b640 100644 --- a/drivers/net/ethernet/intel/i40e/i40e_virtchnl_pf.c +++ b/drivers/net/ethernet/intel/i40e/i40e_virtchnl_pf.c @@ -4943,6 +4943,20 @@ int i40e_ndo_set_vf_spoofchk(struct net_device *netdev, int vf_id, bool enable) return ret; } +/** + * i40e_setup_vf_trust - Enable/disable VF trust mode without reset + * @vf: VF to configure + * @setting: trust setting + * + * Update VF flags when changing trust without performing a VF reset. + * This is only called when it's safe to skip the reset (VF has no advanced + * features configured that need cleanup). + */ +static void i40e_setup_vf_trust(struct i40e_vf *vf, bool setting) +{ + assign_bit(I40E_VIRTCHNL_VF_CAP_PRIVILEGE, &vf->vf_caps, setting); +} + /** * i40e_ndo_set_vf_trust * @netdev: network interface device structure of the pf @@ -4987,19 +5001,20 @@ int i40e_ndo_set_vf_trust(struct net_device *netdev, int vf_id, bool setting) set_bit(__I40E_MACVLAN_SYNC_PENDING, pf->state); pf->vsi[vf->lan_vsi_idx]->flags |= I40E_VSI_FLAG_FILTER_CHANGED; - i40e_vc_reset_vf(vf, true); + /* Reset only if revoking trust and VF has advanced features configured */ + if (!setting && + (vf->adq_enabled || vf->num_cloud_filters > 0 || + test_bit(I40E_VF_STATE_UC_PROMISC, &vf->vf_states) || + test_bit(I40E_VF_STATE_MC_PROMISC, &vf->vf_states))) { + i40e_vc_reset_vf(vf, true); + i40e_del_all_cloud_filters(vf); + } else { + i40e_setup_vf_trust(vf, setting); + } + dev_info(&pf->pdev->dev, "VF %u is now %strusted\n", vf_id, setting ? "" : "un"); - if (vf->adq_enabled) { - if (!vf->trusted) { - dev_info(&pf->pdev->dev, - "VF %u no longer Trusted, deleting all cloud filters\n", - vf_id); - i40e_del_all_cloud_filters(vf); - } - } - out: clear_bit(__I40E_VIRTCHNL_OP_PENDING, pf->state); return ret; From 436729a56dd4dac611d10b569c5359d2cf705b19 Mon Sep 17 00:00:00 2001 From: Jose Ignacio Tornos Martinez Date: Mon, 22 Jun 2026 15:58:24 +0200 Subject: [PATCH 056/367] iavf: send MAC change request synchronously After commit ad7c7b2172c3 ("net: hold netdev instance lock during sysfs operations"), iavf_set_mac() is called with the netdev instance lock already held. The function queues a MAC address change request via iavf_replace_primary_mac() and then waits for completion. However, in the current flow, the actual virtchnl message is sent by the watchdog task, which also needs to acquire the netdev lock to run. Additionally, the adminq_task which processes virtchnl responses also needs the netdev lock. This creates a deadlock scenario: 1. iavf_set_mac() holds netdev lock and waits for MAC change 2. Watchdog needs netdev lock to send the request -> blocked 3. Even if request is sent, adminq_task needs netdev lock to process PF response -> blocked 4. MAC change times out after 2.5 seconds 5. iavf_set_mac() returns -EAGAIN This particularly affects VFs during bonding setup when multiple VFs are enslaved in quick succession. Fix by implementing a synchronous MAC change operation similar to the approach used in commit fdadbf6e84c4 ("iavf: fix incorrect reset handling in callbacks"). The solution: 1. Send the virtchnl ADD_ETH_ADDR message directly (not via watchdog) 2. Poll the admin queue hardware directly for responses 3. Process all received messages (including non-MAC messages) 4. Return when MAC change completes or times out A new generic function iavf_poll_virtchnl_response() is introduced that can be reused for any future synchronous virtchnl operations. It takes a callback to check completion, allowing flexible condition checking. This allows the operation to complete synchronously while holding netdev_lock, without relying on watchdog or adminq_task. The function can sleep for up to 2.5 seconds polling hardware, but this is acceptable since netdev_lock is per-device and only serializes operations on the same interface. To support this, change iavf_add_ether_addrs() to return an error code instead of void, allowing callers to detect failures. Additionally, export iavf_mac_add_reject() to enable proper rollback on local failures (timeouts, send errors) - PF rejections are already handled automatically by iavf_virtchnl_completion(). Remove vc_waitqueue entirely because iavf_set_mac was the only waiter on this waitqueue and after the changes it is not needed. Fixes: ad7c7b2172c3 ("net: hold netdev instance lock during sysfs operations") cc: stable@vger.kernel.org Signed-off-by: Jose Ignacio Tornos Martinez Reviewed-by: Przemek Kitszel Tested-by: Rafal Romanowski Signed-off-by: Tony Nguyen --- drivers/net/ethernet/intel/iavf/iavf.h | 11 ++- drivers/net/ethernet/intel/iavf/iavf_main.c | 85 ++++++++++++---- .../net/ethernet/intel/iavf/iavf_virtchnl.c | 99 +++++++++++++++++-- 3 files changed, 165 insertions(+), 30 deletions(-) diff --git a/drivers/net/ethernet/intel/iavf/iavf.h b/drivers/net/ethernet/intel/iavf/iavf.h index dc31202b2a94..8c45536fd502 100644 --- a/drivers/net/ethernet/intel/iavf/iavf.h +++ b/drivers/net/ethernet/intel/iavf/iavf.h @@ -259,7 +259,6 @@ struct iavf_adapter { struct work_struct adminq_task; struct work_struct finish_config; wait_queue_head_t down_waitqueue; - wait_queue_head_t vc_waitqueue; struct iavf_q_vector *q_vectors; struct list_head vlan_filter_list; int num_vlan_filters; @@ -588,8 +587,9 @@ void iavf_configure_queues(struct iavf_adapter *adapter); void iavf_enable_queues(struct iavf_adapter *adapter); void iavf_disable_queues(struct iavf_adapter *adapter); void iavf_map_queues(struct iavf_adapter *adapter); -void iavf_add_ether_addrs(struct iavf_adapter *adapter); +int iavf_add_ether_addrs(struct iavf_adapter *adapter); void iavf_del_ether_addrs(struct iavf_adapter *adapter); +void iavf_mac_add_reject(struct iavf_adapter *adapter); void iavf_add_vlans(struct iavf_adapter *adapter); void iavf_del_vlans(struct iavf_adapter *adapter); void iavf_set_promiscuous(struct iavf_adapter *adapter); @@ -606,6 +606,13 @@ void iavf_disable_vlan_stripping(struct iavf_adapter *adapter); void iavf_virtchnl_completion(struct iavf_adapter *adapter, enum virtchnl_ops v_opcode, enum iavf_status v_retval, u8 *msg, u16 msglen); +int iavf_poll_virtchnl_response(struct iavf_adapter *adapter, + struct iavf_arq_event_info *event, + bool (*condition)(struct iavf_adapter *adapter, + const void *data, + enum virtchnl_ops v_op), + const void *cond_data, + unsigned int timeout_ms); int iavf_config_rss(struct iavf_adapter *adapter); void iavf_cfg_queues_bw(struct iavf_adapter *adapter); void iavf_cfg_queues_quanta_size(struct iavf_adapter *adapter); diff --git a/drivers/net/ethernet/intel/iavf/iavf_main.c b/drivers/net/ethernet/intel/iavf/iavf_main.c index 29b8403a066b..2f8a80a11336 100644 --- a/drivers/net/ethernet/intel/iavf/iavf_main.c +++ b/drivers/net/ethernet/intel/iavf/iavf_main.c @@ -1029,6 +1029,60 @@ static bool iavf_is_mac_set_handled(struct net_device *netdev, return ret; } +/** + * iavf_mac_change_done - Check if MAC change completed + * @adapter: board private structure + * @data: MAC address being checked (as const void *) + * @v_op: virtchnl opcode from processed message + * + * Callback for iavf_poll_virtchnl_response() to check if MAC change completed. + * + * Return: true if MAC change completed, false otherwise + */ +static bool iavf_mac_change_done(struct iavf_adapter *adapter, + const void *data, enum virtchnl_ops v_op) +{ + const u8 *addr = data; + + return iavf_is_mac_set_handled(adapter->netdev, addr); +} + +/** + * iavf_set_mac_sync - Synchronously change MAC address + * @adapter: board private structure + * @addr: MAC address to set + * + * Send MAC change request to PF and poll admin queue for response. + * Caller must hold netdev_lock. This can sleep for up to 2.5 seconds. + * Event buffer is allocated before sending to avoid state mismatch if + * allocation fails after message is sent to PF. + * + * Return: 0 on success, negative on failure + */ +static int iavf_set_mac_sync(struct iavf_adapter *adapter, const u8 *addr) +{ + struct iavf_arq_event_info event; + int ret; + + netdev_assert_locked(adapter->netdev); + + event.buf_len = IAVF_MAX_AQ_BUF_SIZE; + event.msg_buf = kzalloc(event.buf_len, GFP_KERNEL); + if (!event.msg_buf) + return -ENOMEM; + + ret = iavf_add_ether_addrs(adapter); + if (ret) + goto out; + + ret = iavf_poll_virtchnl_response(adapter, &event, + iavf_mac_change_done, addr, 2500); + +out: + kfree(event.msg_buf); + return ret; +} + /** * iavf_set_mac - NDO callback to set port MAC address * @netdev: network interface device structure @@ -1046,25 +1100,23 @@ static int iavf_set_mac(struct net_device *netdev, void *p) return -EADDRNOTAVAIL; ret = iavf_replace_primary_mac(adapter, addr->sa_data); - if (ret) return ret; - ret = wait_event_interruptible_timeout(adapter->vc_waitqueue, - iavf_is_mac_set_handled(netdev, addr->sa_data), - msecs_to_jiffies(2500)); - - /* If ret < 0 then it means wait was interrupted. - * If ret == 0 then it means we got a timeout. - * else it means we got response for set MAC from PF, - * check if netdev MAC was updated to requested MAC, - * if yes then set MAC succeeded otherwise it failed return -EACCES - */ - if (ret < 0) + ret = iavf_set_mac_sync(adapter, addr->sa_data); + if (ret) { + /* Rollback only if send failed (message never reached PF). + * Don't rollback on timeout (-EAGAIN) because the message was + * sent and PF will eventually respond. When the response arrives, + * iavf_virtchnl_completion() will handle rollback (on PF error) + * or acceptance (on PF success) automatically. + */ + if (ret != -EAGAIN) { + iavf_mac_add_reject(adapter); + ether_addr_copy(adapter->hw.mac.addr, netdev->dev_addr); + } return ret; - - if (!ret) - return -EAGAIN; + } if (!ether_addr_equal(netdev->dev_addr, addr->sa_data)) return -EACCES; @@ -5394,9 +5446,6 @@ static int iavf_probe(struct pci_dev *pdev, const struct pci_device_id *ent) /* Setup the wait queue for indicating transition to down status */ init_waitqueue_head(&adapter->down_waitqueue); - /* Setup the wait queue for indicating virtchannel events */ - init_waitqueue_head(&adapter->vc_waitqueue); - INIT_LIST_HEAD(&adapter->ptp.aq_cmds); init_waitqueue_head(&adapter->ptp.phc_time_waitqueue); mutex_init(&adapter->ptp.aq_cmd_lock); diff --git a/drivers/net/ethernet/intel/iavf/iavf_virtchnl.c b/drivers/net/ethernet/intel/iavf/iavf_virtchnl.c index ec234cc8bd9d..e6b7e8f82c7c 100644 --- a/drivers/net/ethernet/intel/iavf/iavf_virtchnl.c +++ b/drivers/net/ethernet/intel/iavf/iavf_virtchnl.c @@ -2,6 +2,7 @@ /* Copyright(c) 2013 - 2018 Intel Corporation. */ #include +#include #include "iavf.h" #include "iavf_ptp.h" @@ -555,20 +556,23 @@ iavf_set_mac_addr_type(struct virtchnl_ether_addr *virtchnl_ether_addr, * @adapter: adapter structure * * Request that the PF add one or more addresses to our filters. - **/ -void iavf_add_ether_addrs(struct iavf_adapter *adapter) + * + * Return: 0 on success, negative on failure + */ +int iavf_add_ether_addrs(struct iavf_adapter *adapter) { struct virtchnl_ether_addr_list *veal; struct iavf_mac_filter *f; int i = 0, count = 0; bool more = false; size_t len; + int ret; if (adapter->current_op != VIRTCHNL_OP_UNKNOWN) { /* bail because we already have a command pending */ dev_err(&adapter->pdev->dev, "Cannot add filters, command %d pending\n", adapter->current_op); - return; + return -EBUSY; } spin_lock_bh(&adapter->mac_vlan_list_lock); @@ -580,7 +584,7 @@ void iavf_add_ether_addrs(struct iavf_adapter *adapter) if (!count) { adapter->aq_required &= ~IAVF_FLAG_AQ_ADD_MAC_FILTER; spin_unlock_bh(&adapter->mac_vlan_list_lock); - return; + return 0; } adapter->current_op = VIRTCHNL_OP_ADD_ETH_ADDR; @@ -594,8 +598,9 @@ void iavf_add_ether_addrs(struct iavf_adapter *adapter) veal = kzalloc(len, GFP_ATOMIC); if (!veal) { + adapter->current_op = VIRTCHNL_OP_UNKNOWN; spin_unlock_bh(&adapter->mac_vlan_list_lock); - return; + return -ENOMEM; } veal->vsi_id = adapter->vsi_res->vsi_id; @@ -615,8 +620,15 @@ void iavf_add_ether_addrs(struct iavf_adapter *adapter) spin_unlock_bh(&adapter->mac_vlan_list_lock); - iavf_send_pf_msg(adapter, VIRTCHNL_OP_ADD_ETH_ADDR, (u8 *)veal, len); + ret = iavf_send_pf_msg(adapter, VIRTCHNL_OP_ADD_ETH_ADDR, (u8 *)veal, len); kfree(veal); + if (ret) { + dev_err(&adapter->pdev->dev, + "Unable to send ADD_ETH_ADDR message to PF, error %d\n", ret); + adapter->current_op = VIRTCHNL_OP_UNKNOWN; + } + + return ret; } /** @@ -712,8 +724,8 @@ static void iavf_mac_add_ok(struct iavf_adapter *adapter) * @adapter: adapter structure * * Remove filters from list based on PF response. - **/ -static void iavf_mac_add_reject(struct iavf_adapter *adapter) + */ +void iavf_mac_add_reject(struct iavf_adapter *adapter) { struct net_device *netdev = adapter->netdev; struct iavf_mac_filter *f, *ftmp; @@ -2364,7 +2376,6 @@ void iavf_virtchnl_completion(struct iavf_adapter *adapter, iavf_mac_add_reject(adapter); /* restore administratively set MAC address */ ether_addr_copy(adapter->hw.mac.addr, netdev->dev_addr); - wake_up(&adapter->vc_waitqueue); break; case VIRTCHNL_OP_DEL_ETH_ADDR: dev_err(&adapter->pdev->dev, "Failed to delete MAC filter, error %s\n", @@ -2555,7 +2566,6 @@ void iavf_virtchnl_completion(struct iavf_adapter *adapter, eth_hw_addr_set(netdev, adapter->hw.mac.addr); netif_addr_unlock_bh(netdev); } - wake_up(&adapter->vc_waitqueue); break; case VIRTCHNL_OP_GET_STATS: { struct iavf_eth_stats *stats = @@ -2950,3 +2960,72 @@ void iavf_virtchnl_completion(struct iavf_adapter *adapter, } /* switch v_opcode */ adapter->current_op = VIRTCHNL_OP_UNKNOWN; } + +/** + * iavf_poll_virtchnl_response - Poll admin queue for virtchnl response + * @adapter: adapter structure + * @event: pre-allocated event buffer to use for polling + * @condition: callback to check if desired response received + * @cond_data: context data passed to condition callback + * @timeout_ms: maximum time to wait in milliseconds + * + * Polls the admin queue and processes all incoming virtchnl messages. + * After processing each valid message, calls the condition callback to check + * if the expected response has been received. The callback receives the opcode + * of the processed message to identify which response was received. Continues + * polling until the callback returns true or timeout expires. + * + * Caller must allocate event buffer before sending any messages to PF to avoid + * state mismatch if allocation fails after message is sent. + * + * Caller must hold netdev_lock. This can sleep for up to timeout_ms while + * polling hardware. + * + * Return: 0 on success (condition met), -EAGAIN on timeout, or error code + */ +int iavf_poll_virtchnl_response(struct iavf_adapter *adapter, + struct iavf_arq_event_info *event, + bool (*condition)(struct iavf_adapter *adapter, + const void *data, + enum virtchnl_ops v_op), + const void *cond_data, + unsigned int timeout_ms) +{ + struct iavf_hw *hw = &adapter->hw; + enum virtchnl_ops received_op; + unsigned long timeout; + int ret = -EAGAIN; + u16 pending = 0; + u32 v_retval; + + netdev_assert_locked(adapter->netdev); + + timeout = jiffies + msecs_to_jiffies(timeout_ms); + do { + if (!pending) + usleep_range(50, 75); + + if (iavf_clean_arq_element(hw, event, &pending) == IAVF_SUCCESS) { + received_op = (enum virtchnl_ops)le32_to_cpu(event->desc.cookie_high); + if (received_op != VIRTCHNL_OP_UNKNOWN) { + v_retval = le32_to_cpu(event->desc.cookie_low); + + iavf_virtchnl_completion(adapter, received_op, + (enum iavf_status)v_retval, + event->msg_buf, event->msg_len); + + if (condition(adapter, cond_data, received_op)) { + ret = 0; + break; + } + } + + memset(event->msg_buf, 0, IAVF_MAX_AQ_BUF_SIZE); + + if (pending) + continue; + } + } while (time_before(jiffies, timeout)); + + return ret; +} From 0a01e1df137fd7e51f045a5e97d40f42fb58ae0c Mon Sep 17 00:00:00 2001 From: Jose Ignacio Tornos Martinez Date: Mon, 15 Jun 2026 20:00:00 +0200 Subject: [PATCH 057/367] ice: skip unnecessary VF reset when setting trust Similar to the i40e fix, ice_set_vf_trust() unconditionally calls ice_reset_vf() when the trust setting changes. While the delay is smaller than i40e, this reset is still unnecessary in most cases. When granting trust, no reset is needed - we can just set the capability flag to allow privileged operations. When revoking trust, we only need to reset (conservative approach) if the VF has actually configured advanced features that require cleanup (MAC LLDP filters, promiscuous mode). For VFs in a clean state, we can safely change the trust setting without the disruptive reset. When we do reset, we maintain the original ice pattern that has been reliable in production: cleanup LLDP filters first, then set vf->trusted, then reset. This ensures the privilege capability bit is handled correctly during reset rebuild. When we don't reset, we manually handle the capability flag via helper function, eliminating the delay. Signed-off-by: Jose Ignacio Tornos Martinez Reviewed-by: Aleksandr Loktionov Tested-by: Rafal Romanowski Signed-off-by: Tony Nguyen --- drivers/net/ethernet/intel/ice/ice_sriov.c | 32 ++++++++++++++++++---- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/drivers/net/ethernet/intel/ice/ice_sriov.c b/drivers/net/ethernet/intel/ice/ice_sriov.c index e04de0215596..f893dff39aac 100644 --- a/drivers/net/ethernet/intel/ice/ice_sriov.c +++ b/drivers/net/ethernet/intel/ice/ice_sriov.c @@ -1366,6 +1366,20 @@ int ice_set_vf_mac(struct net_device *netdev, int vf_id, u8 *mac) return __ice_set_vf_mac(ice_netdev_to_pf(netdev), vf_id, mac); } +/** + * ice_setup_vf_trust - Enable/disable VF trust mode without reset + * @vf: VF to configure + * @setting: trust setting + * + * Update VF flags when changing trust without performing a VF reset. + * This is only called when it's safe to skip the reset (VF has no advanced + * features configured that need cleanup). + */ +static void ice_setup_vf_trust(struct ice_vf *vf, bool setting) +{ + assign_bit(ICE_VIRTCHNL_VF_CAP_PRIVILEGE, &vf->vf_caps, setting); +} + /** * ice_set_vf_trust * @netdev: network interface device structure @@ -1401,11 +1415,19 @@ int ice_set_vf_trust(struct net_device *netdev, int vf_id, bool trusted) mutex_lock(&vf->cfg_lock); - while (!trusted && vf->num_mac_lldp) - ice_vf_update_mac_lldp_num(vf, ice_get_vf_vsi(vf), false); - - vf->trusted = trusted; - ice_reset_vf(vf, ICE_VF_RESET_NOTIFY); + /* Reset only if revoking trust and VF has advanced features configured */ + if (!trusted && + (vf->num_mac_lldp > 0 || + test_bit(ICE_VF_STATE_UC_PROMISC, vf->vf_states) || + test_bit(ICE_VF_STATE_MC_PROMISC, vf->vf_states))) { + while (vf->num_mac_lldp) + ice_vf_update_mac_lldp_num(vf, ice_get_vf_vsi(vf), false); + vf->trusted = trusted; + ice_reset_vf(vf, ICE_VF_RESET_NOTIFY); + } else { + vf->trusted = trusted; + ice_setup_vf_trust(vf, trusted); + } dev_info(ice_pf_to_dev(pf), "VF %u is now %strusted\n", vf_id, trusted ? "" : "un"); From d35a535d3e3e71f92a051d94e415f43612c4edfc Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Wed, 23 Sep 2026 12:39:20 -1000 Subject: [PATCH 058/367] sched_ext: Fix CPU hotplug hang when a dying CPU's tasks sit in the BPF scheduler A CPU going down has to empty its own rq. Its hotplug thread waits in sched_cpu_wait_empty() until nothing else is left, and the only thing that wakes it is balance_push(), which runs from __schedule() on the dying CPU and pushes off the migratable tasks the CPU picks. sched_ext breaks this. A task sitting on a user DSQ or held by the BPF scheduler is still counted on its rq, but an inactive CPU no longer calls ops.dispatch() and can't pull the task back. The dying CPU goes idle with the task still counted, and one of two things happens: - Another CPU consumes the task. The rq empties without a __schedule() on the dying CPU, so the hotplug thread is never woken and cpu_down() hangs holding cpu_hotplug_lock. - The task is affine only to the dying CPU. Nothing can move it, and the offline stalls until the watchdog ejects the BPF scheduler. When the rq goes offline, re-enqueue every task on it that isn't already on the local DSQ. An enqueue on an offline rq lands on the local DSQ, so the dying CPU itself picks the tasks and balance_push() pushes them off, the same as for the other sched classes. From then on no sched_ext path on another CPU can pull a task off the rq. ops.dispatch() currently stops as soon as the CPU goes inactive, and CPU hotplug then waits for an RCU grace period before taking the rq offline. A BPF-held task affine only to the dying CPU and preempted inside an RCU read-side critical section would block that grace period, and the rq would never go offline. Test SCX_RQ_ONLINE directly so that ops.dispatch() keeps running until the rq goes offline. Only the dying CPU's own hotplug thread clears the flag during teardown, and task_can_run_on_remote_rq() still keeps other rqs' tasks off the CPU. Fixes: f0e1a0643a59 ("sched_ext: Implement BPF extensible scheduler class") Fixes: 991ef53a4832 ("sched_ext: Make scx_rq_online() also test cpu_active() in addition to SCX_RQ_ONLINE") Cc: stable@vger.kernel.org # v6.12+ Reported-by: Alap Mohan Reported-by: Joonwoo Park Signed-off-by: Tejun Heo Reviewed-by: Andrea Righi --- kernel/sched/ext/ext.c | 22 ++++++++++++++++++++-- kernel/sched/ext/inlines.h | 8 +++++++- kernel/sched/ext/sub.c | 4 ---- kernel/sched/sched.h | 5 +++-- 4 files changed, 30 insertions(+), 9 deletions(-) diff --git a/kernel/sched/ext/ext.c b/kernel/sched/ext/ext.c index ad391a8cbd05..493b7aac7087 100644 --- a/kernel/sched/ext/ext.c +++ b/kernel/sched/ext/ext.c @@ -2123,8 +2123,8 @@ static void set_task_runnable(struct rq *rq, struct task_struct *p) } /* - * list_add_tail() must be used. scx_bypass() depends on tasks being - * appended to the runnable_list. + * list_add_tail() must be used. scx_bypass() and rq_offline_scx() + * depend on tasks being appended to the runnable_list. */ list_add_tail(&p->scx.runnable_node, &rq->scx.runnable_list); @@ -3731,8 +3731,26 @@ static void rq_online_scx(struct rq *rq) static void rq_offline_scx(struct rq *rq) { + struct task_struct *p, *n; + rq->scx.flags &= ~SCX_RQ_ONLINE; + + /* sched domain rebuilds call rq_offline with the CPU staying alive */ + if (cpu_active(cpu_of(rq))) + return; + scx_rescue_flush(rq); + + /* + * An offline CPU no longer calls ops.dispatch(). Re-enqueue its tasks + * onto the local DSQ so that they run here and balance_push() moves + * them off. + */ + list_for_each_entry_safe_reverse(p, n, &rq->scx.runnable_list, scx.runnable_node) { + if (p->scx.dsq == &rq->scx.local_dsq) + continue; + guard(sched_change)(p, DEQUEUE_SAVE | DEQUEUE_MOVE | DEQUEUE_NOCLOCK); + } } static bool check_rq_for_timeouts(struct rq *rq) diff --git a/kernel/sched/ext/inlines.h b/kernel/sched/ext/inlines.h index 2ff5479334cb..8de9d8fb4b25 100644 --- a/kernel/sched/ext/inlines.h +++ b/kernel/sched/ext/inlines.h @@ -70,7 +70,13 @@ scx_dispatch_sched(struct scx_sched *sch, struct rq *rq, #endif /* CONFIG_EXT_SUB_SCHED */ } - if (unlikely(!SCX_HAS_OP(sch, dispatch)) || !scx_rq_online(rq)) + /* + * scx_rq_online() can't be used. Its cpu_active() test goes false + * before CPU hotplug waits for an RCU grace period, and + * rq_offline_scx() moves this CPU's tasks to the local DSQ only after + * the wait. The grace period can depend on those tasks running. + */ + if (unlikely(!SCX_HAS_OP(sch, dispatch)) || !(rq->scx.flags & SCX_RQ_ONLINE)) return SCX_DSP_NONE; dspc->rq = rq; diff --git a/kernel/sched/ext/sub.c b/kernel/sched/ext/sub.c index 3036190b0662..c9b44ab3bd0a 100644 --- a/kernel/sched/ext/sub.c +++ b/kernel/sched/ext/sub.c @@ -585,10 +585,6 @@ void scx_rescue_flush(struct rq *rq) lockdep_assert_rq_held(rq); - /* sched domain rebuilds call rq_offline with the CPU staying alive */ - if (cpu_active(cpu_of(rq))) - return; - /* end the current rescue */ if (rq->scx.rescue.curr) scx_task_slice_ended(rq, rq->scx.rescue.curr); diff --git a/kernel/sched/sched.h b/kernel/sched/sched.h index 7701a5a60972..742cce9da4e9 100644 --- a/kernel/sched/sched.h +++ b/kernel/sched/sched.h @@ -4205,8 +4205,9 @@ extern void balance_callbacks(struct rq *rq, struct balance_callback *head); * after which it is enqueued again. * * Typically this must be called while holding task_rq_lock, since most/all - * properties are serialized under those locks. There is currently one - * exception to this rule in sched/ext which only holds rq->lock. + * properties are serialized under those locks. There are currently two + * exceptions to this rule in sched/ext which only hold rq->lock: scx_bypass() + * and rq_offline_scx(). */ /* From 816fb1590a4c8cf8d05cd076057616be02e276f0 Mon Sep 17 00:00:00 2001 From: Aldo Ariel Panzardo Date: Sat, 26 Sep 2026 18:21:45 -0300 Subject: [PATCH 059/367] Bluetooth: SCO: serialise sco_conn lifetime against sco_recv_scodata() sco_recv_scodata() upgrades the weak hcon->sco_data back-pointer to a strong reference under hdev->lock: hci_dev_lock(hdev); hcon = hci_conn_hash_lookup_handle(hdev, handle); ... conn = sco_conn_hold_unless_zero(hcon->sco_data); hci_dev_unlock(hdev); but the pointer is cleared from the other side without that lock. When the last sco_conn reference is dropped, sco_conn_free() ran conn->hcon->sco_data = NULL; with no hdev->lock held, so the RX path could read hcon->sco_data and call kref_get_unless_zero() on an sco_conn that was concurrently freed: BUG: KASAN: slab-use-after-free in sco_conn_hold_unless_zero+0xbe/0x160 Write of size 4 by task kworker/u17:0 Workqueue: hci0 hci_rx_work Call Trace: sco_conn_hold_unless_zero+0xbe/0x160 sco_recv_scodata+0x13f/0x490 hci_rx_work+0x3af/0x730 kref_get_unless_zero() only guards against a zero refcount, not against the backing memory already being freed. Give hcon->sco_data an actual reference on the sco_conn it points to, so the object cannot be freed while the pointer is still installed, and only clear and drop it from sco_conn_del(), which runs under hdev->lock (its callers, sco_connect_cfm() and sco_disconn_cfm(), hold it). The reader in sco_recv_scodata() also takes hdev->lock, so the store and the read are now serialised: the RX path either observes NULL or a reference that is guaranteed to stay valid until it drops its own. sco_conn_add() no longer hands its kref_init() reference to the caller as the connection's only reference; that initial reference is the one owned by hcon->sco_data, and callers get their own via sco_conn_hold(). Because the sco_conn can now outlive the socket (the association keeps it alive until the link goes down), the hci_conn can no longer be dropped from sco_conn_free() without regressing socket close: closing a connected SCO socket must still tear the link down. Move hci_conn ownership to the socket instead: __sco_chan_add() takes an hci_conn reference and it is released from sco_chan_del() and sco_sock_destruct(), exactly once. The sco_conn no longer owns an hci_conn reference, so sco_conn_add() stops consuming one and sco_connect() drops the hci_connect_sco() reference on every path; sco_connect_cfm() no longer needs its hci_conn_hold() dance. Fixes: e6720779ae61 ("Bluetooth: SCO: Use kref to track lifetime of sco_conn") Cc: stable@vger.kernel.org Suggested-by: Pauli Virtanen Signed-off-by: Aldo Ariel Panzardo Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/sco.c | 87 +++++++++++++++++++++++++++++++++------------ 1 file changed, 65 insertions(+), 22 deletions(-) diff --git a/net/bluetooth/sco.c b/net/bluetooth/sco.c index 3d4362a09df4..f4a20d7b5f9c 100644 --- a/net/bluetooth/sco.c +++ b/net/bluetooth/sco.c @@ -84,12 +84,14 @@ static void sco_conn_free(struct kref *ref) if (conn->sk) sco_pi(conn->sk)->conn = NULL; - if (conn->hcon) { - conn->hcon->sco_data = NULL; - hci_conn_drop(conn->hcon); - } + /* hcon->sco_data is cleared and the association's reference on the + * sco_conn is dropped in sco_conn_del() under hdev->lock, and the + * hci_conn is now owned by the socket (held in __sco_chan_add() and + * dropped in sco_chan_del()/sco_sock_destruct()), so there is nothing + * left to release towards hcon here. + */ - /* Ensure no more work items will run since hci_conn has been dropped */ + /* Ensure no more work items will run before the connection is freed */ disable_delayed_work_sync(&conn->timeout_work); kfree(conn); @@ -188,25 +190,19 @@ static void sco_sock_clear_timer(struct sock *sk) } /* ---- SCO connections ---- */ -/* Consumes a reference on @hcon, which the returned sco_conn owns until it is - * freed. On failure (NULL return) the reference is left for the caller to drop. +/* Returns a new reference the caller must drop with sco_conn_put(). The + * hcon->sco_data association holds its own reference on the sco_conn for the + * connection's lifetime; it is dropped in sco_conn_del() under hdev->lock. + * @hcon is not consumed: the hci_conn reference is taken and owned by the + * socket in __sco_chan_add(). */ static struct sco_conn *sco_conn_add(struct hci_conn *hcon) { struct sco_conn *conn = hcon->sco_data; conn = sco_conn_hold_unless_zero(conn); - if (conn) { - if (!conn->hcon) { - sco_conn_lock(conn); - conn->hcon = hcon; - sco_conn_unlock(conn); - } else { - /* conn already owns a reference on hcon */ - hci_conn_drop(hcon); - } + if (conn) return conn; - } conn = kzalloc_obj(struct sco_conn); if (!conn) @@ -227,7 +223,10 @@ static struct sco_conn *sco_conn_add(struct hci_conn *hcon) BT_DBG("hcon %p conn %p", hcon, conn); - return conn; + /* kref_init() above set the association reference owned by + * hcon->sco_data; hand the caller its own reference. + */ + return sco_conn_hold(conn); } /* Delete channel. @@ -242,6 +241,19 @@ static void sco_chan_del(struct sock *sk, int err) BT_DBG("sk %p, conn %p, err %d", sk, conn, err); if (conn) { + struct hci_conn *hcon; + + sco_conn_lock(conn); + hcon = conn->hcon; + sco_conn_unlock(conn); + + /* Drop the socket's hci_conn reference BEFORE clearing + * conn->sk, so sco_conn_del() on another CPU cannot free + * the hci_conn while we still hold a pointer to it. + */ + if (hcon) + hci_conn_drop(hcon); + sco_conn_lock(conn); conn->sk = NULL; sco_conn_unlock(conn); @@ -266,6 +278,13 @@ static void sco_conn_del(struct hci_conn *hcon, int err) BT_DBG("hcon %p conn %p, err %d", hcon, conn, err); + /* Detach from the hci_conn and drop the association's reference. + * The caller holds hdev->lock, which serialises this against the + * read of hcon->sco_data in sco_recv_scodata(). + */ + hcon->sco_data = NULL; + sco_conn_put(conn); + sco_conn_lock(conn); sk = sco_sock_hold(conn); sco_conn_unlock(conn); @@ -290,6 +309,11 @@ static void __sco_chan_add(struct sco_conn *conn, struct sock *sk, sco_pi(sk)->conn = sco_conn_hold(conn); conn->sk = sk; + /* The socket owns an hci_conn reference for as long as it stays + * attached; it is dropped in sco_chan_del()/sco_sock_destruct(). + */ + hci_conn_hold(conn->hcon); + if (parent) bt_accept_enqueue(parent, sk, true); } @@ -371,6 +395,7 @@ static int sco_connect(struct sock *sk) if (sk->sk_state != BT_OPEN && sk->sk_state != BT_BOUND) { release_sock(sk); sco_conn_put(conn); + hci_conn_drop(hcon); err = -EBADFD; goto unlock; } @@ -379,9 +404,13 @@ static int sco_connect(struct sock *sk) sco_conn_put(conn); if (err) { release_sock(sk); + hci_conn_drop(hcon); goto unlock; } + /* __sco_chan_add() took its own hci_conn reference; drop ours. */ + hci_conn_drop(hcon); + /* Update source addr of the socket */ bacpy(&sco_pi(sk)->src, &hcon->src); @@ -495,9 +524,25 @@ static struct sock *sco_get_sock_listen(bdaddr_t *src) static void sco_sock_destruct(struct sock *sk) { + struct sco_conn *conn = sco_pi(sk)->conn; + BT_DBG("sk %p", sk); - sco_conn_put(sco_pi(sk)->conn); + /* If the channel was not already torn down via sco_chan_del(), drop + * the socket's own references here. + */ + if (conn) { + struct hci_conn *hcon; + + sco_conn_lock(conn); + hcon = conn->hcon; + sco_conn_unlock(conn); + + if (hcon) + hci_conn_drop(hcon); + sco_pi(sk)->conn = NULL; + sco_conn_put(conn); + } skb_queue_purge(&sk->sk_receive_queue); skb_queue_purge(&sk->sk_write_queue); @@ -1511,12 +1556,10 @@ static void sco_connect_cfm(struct hci_conn *hcon, __u8 status) if (!status) { struct sco_conn *conn; - conn = sco_conn_add(hci_conn_hold(hcon)); + conn = sco_conn_add(hcon); if (conn) { sco_conn_ready(conn); sco_conn_put(conn); - } else { - hci_conn_drop(hcon); } } else sco_conn_del(hcon, bt_to_errno(status)); From 980db94e3eeefdd8a1c46f86412c33a7f7806f51 Mon Sep 17 00:00:00 2001 From: Pavankumar Kondeti Date: Mon, 28 Sep 2026 09:18:56 +0530 Subject: [PATCH 060/367] workqueue: Fix NULL current_pwq deref in chained work check current_wq_worker() only tells us that %current is a kworker. It does not guarantee that the worker is currently executing a work item, as worker->current_pwq is populated only while process_one_work() is running the work function. is_chained_work() can be reached while queueing on a draining or destroying workqueue. If a kworker outside work-item execution reaches that path, current_wq_worker() returns a worker but worker->current_pwq is NULL, and the chained-work test can fault before it emits the intended warning. Guard the chained-work test with worker->current_pwq. A kworker without current_pwq is not executing work on the target workqueue, so the helper should return false and let the draining/destroying warning fire. Fixes: 8d03ecfe4718 ("workqueue: reimplement is_chained_work() using current_wq_worker()") Cc: stable@vger.kernel.org Signed-off-by: Pavankumar Kondeti Signed-off-by: Tejun Heo --- kernel/workqueue.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kernel/workqueue.c b/kernel/workqueue.c index 4a73d305d88a..acf5d4a97ecb 100644 --- a/kernel/workqueue.c +++ b/kernel/workqueue.c @@ -2290,7 +2290,7 @@ static bool is_chained_work(struct workqueue_struct *wq) * Return %true iff I'm a worker executing a work item on @wq. If * I'm @worker, it's safe to dereference it without locking. */ - return worker && worker->current_pwq->wq == wq; + return worker && worker->current_pwq && worker->current_pwq->wq == wq; } /* From aa0a37b5024d921e96ac4f8f487c8bfbf1f1a582 Mon Sep 17 00:00:00 2001 From: Raj Ojha Date: Fri, 25 Sep 2026 00:59:55 +0530 Subject: [PATCH 061/367] memstick: core: wait for request completion before freeing card In memstick_alloc_card() and memstick_set_rw_addr(), wait_for_completion_timeout() is used to wait for device identification and address configuration requests to complete. The memstick subsystem does not provide an abort or cancellation callback in struct memstick_host for in-flight requests. If wait_for_completion_timeout() expires (for instance, when a USB transfer or command retry is delayed), memstick_alloc_card() returns an error and frees the memstick_dev structure via kfree(card). However, the host controller driver (such as rtsx_usb_ms) is still running the request asynchronously in worker context. When the transfer eventually finishes, the driver writes into host->req->data, which points to memory inside the already freed card structure. This leads to a KASAN slab-use-after-free write: BUG: KASAN: slab-use-after-free in ms_read_bytes+0x8fe/0x990 Write of size 1 at addr ffff88813c188079 by task kworker/1:5/5239 Workqueue: events rtsx_usb_ms_handle_req Call Trace: ms_read_bytes+0x8fe/0x990 drivers/memstick/host/rtsx_usb_ms.c:450 rtsx_usb_ms_issue_cmd drivers/memstick/host/rtsx_usb_ms.c:477 [inline] rtsx_usb_ms_handle_req+0x557/0x1a00 drivers/memstick/host/rtsx_usb_ms.c:531 process_one_work+0xac7/0x1b10 kernel/workqueue.c:3396 worker_thread+0x5ef/0xe50 kernel/workqueue.c:3560 kthread+0x373/0x450 kernel/kthread.c:436 Allocated by task 1662: memstick_alloc_card drivers/memstick/core/memstick.c:383 [inline] memstick_check+0x2c1/0x10d0 drivers/memstick/core/memstick.c:452 process_one_work+0xac7/0x1b10 kernel/workqueue.c:3396 Freed by task 1662: kfree+0x20c/0x650 mm/slub.c:6792 memstick_alloc_card drivers/memstick/core/memstick.c:420 [inline] memstick_check+0x72b/0x10d0 drivers/memstick/core/memstick.c:452 process_one_work+0xac7/0x1b10 kernel/workqueue.c:3396 Host controller drivers (rtsx_usb_ms, jmb38x_ms, tifm_ms, r592) already implement their own hardware and bus timeouts, and callers in mspro_block and ms_block already rely on wait_for_completion() unconditionally. Replace wait_for_completion_timeout() with wait_for_completion() in memstick_set_rw_addr() and memstick_alloc_card() so that the card structure is never freed while an asynchronous request is in flight. Fixes: baf8532a147d ("memstick: initial commit for Sony MemoryStick support") Reported-by: syzbot+ccffdf05833ebdaeae70@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=ccffdf05833ebdaeae70 Cc: stable@vger.kernel.org Signed-off-by: Raj Ojha Signed-off-by: Ulf Hansson --- drivers/memstick/core/memstick.c | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/drivers/memstick/core/memstick.c b/drivers/memstick/core/memstick.c index e03989c4e99e..0d15f48ef51d 100644 --- a/drivers/memstick/core/memstick.c +++ b/drivers/memstick/core/memstick.c @@ -370,9 +370,7 @@ int memstick_set_rw_addr(struct memstick_dev *card) { card->next_request = h_memstick_set_rw_addr; memstick_new_req(card->host); - if (!wait_for_completion_timeout(&card->mrq_complete, - msecs_to_jiffies(500))) - card->current_mrq.error = -ETIMEDOUT; + wait_for_completion(&card->mrq_complete); return card->current_mrq.error; } @@ -405,9 +403,7 @@ static struct memstick_dev *memstick_alloc_card(struct memstick_host *host) card->next_request = h_memstick_read_dev_id; memstick_new_req(host); - if (!wait_for_completion_timeout(&card->mrq_complete, - msecs_to_jiffies(500))) - card->current_mrq.error = -ETIMEDOUT; + wait_for_completion(&card->mrq_complete); if (card->current_mrq.error) goto err_out; From baa53af641c9e3644e717b8dfd1b95a55a0c111b Mon Sep 17 00:00:00 2001 From: Ravindra Date: Tue, 29 Sep 2026 12:26:41 +0530 Subject: [PATCH 062/367] Bluetooth: btintel_pcie: fix TX descriptor bounds check btintel_pcie_prepare_tx() uses tfd_index to access the TFD and data buffer arrays before the index is advanced modulo txq->count. An index equal to txq->count is one past the end of both arrays, so reject it in the bounds check. Fixes: 6e65a09f9275 ("Bluetooth: btintel_pcie: Add *setup* function to download firmware") Signed-off-by: Ravindra Signed-off-by: Luiz Augusto von Dentz --- drivers/bluetooth/btintel_pcie.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/bluetooth/btintel_pcie.c b/drivers/bluetooth/btintel_pcie.c index f333b01f7465..d525269be501 100644 --- a/drivers/bluetooth/btintel_pcie.c +++ b/drivers/bluetooth/btintel_pcie.c @@ -401,7 +401,7 @@ static int btintel_pcie_send_sync(struct btintel_pcie_data *data, tfd_index = data->ia.tr_hia[BTINTEL_PCIE_TXQ_NUM]; - if (tfd_index > txq->count) + if (tfd_index >= txq->count) return -ERANGE; if (skb->len > BTINTEL_PCIE_BUFFER_SIZE - BTINTEL_PCIE_HCI_TYPE_LEN) { From ac022970e9fb0a5f7e7d1f6c8e104c1ed924dd1c Mon Sep 17 00:00:00 2001 From: Mikhail Gavrilov Date: Tue, 29 Sep 2026 17:22:26 +0500 Subject: [PATCH 063/367] Bluetooth: RFCOMM: connect the session socket without rfcomm_mutex An RFCOMM connect() issued while a BR/EDR link is being authenticated makes lockdep report a circular dependency, and the reported cycle is a real AB/BA between rfcomm_mutex and hdev->lock. rfcomm_security_cfm() is called from the HCI event path, which already holds hdev->lock: hci_rx_work() hci_event_packet() hci_cc_read_enc_key_size() [hdev->lock] hci_encrypt_cfm() [hci_cb_list_lock] rfcomm_security_cfm() [rfcomm_mutex] while an RFCOMM connect() from userspace takes the same two locks the other way round: rfcomm_sock_connect() rfcomm_dlc_open() [rfcomm_mutex] __rfcomm_dlc_open() rfcomm_session_create() kernel_connect() l2cap_sock_connect() l2cap_chan_connect() [hdev->lock] WARNING: possible circular locking dependency detected kworker/u131:1/1128 is trying to acquire lock: rfcomm_mutex, at: rfcomm_security_cfm+0x31/0x3e0 [rfcomm] but task is already holding lock: hci_cb_list_lock, at: hci_cc_read_enc_key_size+0x1d2/0xcc0 Chain exists of: rfcomm_mutex --> &hdev->lock --> hci_cb_list_lock hci_auth_complete_evt() and hci_encrypt_change_evt() reach the callback the same way. Both orders have to be seen in the same boot, which is why a BR/EDR connection alone is not enough to show it: a session set up by the remote side is created by rfcomm_accept_connection() in krfcommd, which calls kernel_accept() and never takes hdev->lock under rfcomm_mutex. Connecting a device that authenticates and encrypts the link and then calling connect() on an RFCOMM socket towards any address - the connect does not have to succeed, the order is recorded before the page timeout - reports it every time. Only the session socket has to be connected with the lock held, and it does not: nothing else can see the socket before it is put on the session list. So connect it first and take rfcomm_mutex afterwards, which removes the rfcomm_mutex -> hdev->lock order for good, rather than keeping the HCI event path out of rfcomm_mutex. rfcomm_session_create() becomes rfcomm_session_connect(), which returns the connected socket without touching the session list, and rfcomm_dlc_open() adds the session once it holds the lock again. If another opener added a session for the same pair while this socket was connecting, that session is used and this socket is dropped. __rfcomm_dlc_open() now takes the session it should use, and its state check runs after the lock is re-acquired, so a DLC that was opened or closed in the meantime is still handled. Over an existing ACL link the connection can complete before the session reaches the list, and the wakeup from the socket callback is then lost, so krfcommd is woken once the session is visible. Connecting without the lock opens a window in which the socket can be closed. The DLC is not on a session yet, so rfcomm_dlc_close() finds nothing to do and returns without touching it, and rfcomm_dlc_open() would then attach a DLC whose owner is gone. rfcomm_dlc_close() now marks such a DLC with RFCOMM_CLOSED, and rfcomm_dlc_open() checks the mark once it holds rfcomm_mutex again and drops the socket instead of attaching the DLC. The mark is cleared when an open starts, so a DLC that is reused is not affected. This covers both callers of rfcomm_dlc_open(), the socket and the tty layer. Fixes: 759c185d0bbd ("Bluetooth: RFCOMM: serialize security confirmation handling") Suggested-by: Pauli Virtanen Reported-by: Pauli Virtanen Closes: https://lore.kernel.org/linux-bluetooth/5e76a95e934e451e7006db28827c2d64af5a88be.camel@iki.fi/ Reported-by: syzbot+74071deb72339c215b2e@syzkaller.appspotmail.com Closes: https://lore.kernel.org/linux-bluetooth/6a92fadc.08e933ee.dbf97.008f.GAE@google.com/ Reported-by: Jiaming Zhang Closes: https://lore.kernel.org/all/CANypQFabseTuRiyz2pGkc_Qj0moGDnY7KEp1qdTOqkw6gAL3hw@mail.gmail.com/ Cc: stable@vger.kernel.org Signed-off-by: Mikhail Gavrilov Tested-by: syzbot+74071deb72339c215b2e@syzkaller.appspotmail.com Signed-off-by: Luiz Augusto von Dentz --- include/net/bluetooth/rfcomm.h | 1 + net/bluetooth/rfcomm/core.c | 134 +++++++++++++++++++++++---------- 2 files changed, 96 insertions(+), 39 deletions(-) diff --git a/include/net/bluetooth/rfcomm.h b/include/net/bluetooth/rfcomm.h index 102c278e3584..9dc5fd4cb8c8 100644 --- a/include/net/bluetooth/rfcomm.h +++ b/include/net/bluetooth/rfcomm.h @@ -207,6 +207,7 @@ struct rfcomm_dlc { #define RFCOMM_AUTH_REJECT 7 #define RFCOMM_DEFER_SETUP 8 #define RFCOMM_ENC_DROP 9 +#define RFCOMM_CLOSED 10 /* Scheduling flags and events */ #define RFCOMM_SCHED_WAKEUP 31 diff --git a/net/bluetooth/rfcomm/core.c b/net/bluetooth/rfcomm/core.c index d91e2a6ee26c..54ec1136f87e 100644 --- a/net/bluetooth/rfcomm/core.c +++ b/net/bluetooth/rfcomm/core.c @@ -62,10 +62,9 @@ static void rfcomm_make_uih(struct sk_buff *skb, u8 addr); static void rfcomm_process_connect(struct rfcomm_session *s); -static struct rfcomm_session *rfcomm_session_create(bdaddr_t *src, - bdaddr_t *dst, - u8 sec_level, - int *err); +static struct socket *rfcomm_session_connect(bdaddr_t *src, bdaddr_t *dst, + u8 sec_level, int *err); +static struct rfcomm_session *rfcomm_session_add(struct socket *sock, int state); static struct rfcomm_session *rfcomm_session_get(bdaddr_t *src, bdaddr_t *dst); static struct rfcomm_session *rfcomm_session_del(struct rfcomm_session *s); @@ -365,28 +364,17 @@ static int rfcomm_check_channel(u8 channel) return channel < 1 || channel > 30; } -static int __rfcomm_dlc_open(struct rfcomm_dlc *d, bdaddr_t *src, bdaddr_t *dst, u8 channel) +static int __rfcomm_dlc_open(struct rfcomm_dlc *d, struct rfcomm_session *s, + u8 channel) { - struct rfcomm_session *s; - int err = 0; u8 dlci; - BT_DBG("dlc %p state %ld %pMR -> %pMR channel %d", - d, d->state, src, dst, channel); - - if (rfcomm_check_channel(channel)) - return -EINVAL; + BT_DBG("dlc %p state %ld session %p channel %d", + d, d->state, s, channel); if (d->state != BT_OPEN && d->state != BT_CLOSED) return 0; - s = rfcomm_session_get(src, dst); - if (!s) { - s = rfcomm_session_create(src, dst, d->sec_level, &err); - if (!s) - return err; - } - dlci = __dlci(__session_dir(s), channel); /* Check if DLCI already exists */ @@ -421,14 +409,84 @@ static int __rfcomm_dlc_open(struct rfcomm_dlc *d, bdaddr_t *src, bdaddr_t *dst, int rfcomm_dlc_open(struct rfcomm_dlc *d, bdaddr_t *src, bdaddr_t *dst, u8 channel) { - int r; + struct rfcomm_session *s; + struct socket *sock; + int err; + + BT_DBG("dlc %p state %ld %pMR -> %pMR channel %d", + d, d->state, src, dst, channel); + + if (rfcomm_check_channel(channel)) + return -EINVAL; rfcomm_lock(); - r = __rfcomm_dlc_open(d, src, dst, channel); + clear_bit(RFCOMM_CLOSED, &d->flags); + + /* Do not page the remote device for a DLC that cannot be opened + * anyway. __rfcomm_dlc_open() looks at the state again once the + * lock has been re-acquired below. + */ + if (d->state != BT_OPEN && d->state != BT_CLOSED) { + rfcomm_unlock(); + return 0; + } + + s = rfcomm_session_get(src, dst); + if (s) { + err = __rfcomm_dlc_open(d, s, channel); + rfcomm_unlock(); + return err; + } + rfcomm_unlock(); + + /* There is no session for this pair yet. kernel_connect() ends up in + * l2cap_chan_connect(), which takes hdev->lock, and the HCI event + * path takes rfcomm_mutex while holding hdev->lock, so the socket has + * to be connected with rfcomm_mutex released. + */ + sock = rfcomm_session_connect(src, dst, d->sec_level, &err); + if (!sock) + return err; + + rfcomm_lock(); + + /* The DLC may have been closed while the socket was connecting. It + * was not on a session, so rfcomm_dlc_close() could only mark it; + * attaching it now would leave it with no owner. + */ + if (test_bit(RFCOMM_CLOSED, &d->flags)) { + rfcomm_unlock(); + sock_release(sock); + return -ECONNRESET; + } + + /* Another opener may have added a session for the same pair in the + * meantime; that one is used and this socket is dropped. + */ + s = rfcomm_session_get(src, dst); + if (!s) { + s = rfcomm_session_add(sock, BT_BOUND); + if (s) { + s->initiator = 1; + sock = NULL; + } + } + + err = s ? __rfcomm_dlc_open(d, s, channel) : -ENOMEM; rfcomm_unlock(); - return r; + + if (sock) + sock_release(sock); + + /* Over an existing ACL link the connection can complete before the + * session reaches the list, and that wakeup is then lost, so let + * krfcommd look at the socket state now. + */ + rfcomm_schedule(); + + return err; } static void __rfcomm_dlc_disconn(struct rfcomm_dlc *d) @@ -508,8 +566,14 @@ int rfcomm_dlc_close(struct rfcomm_dlc *d, int err) rfcomm_lock(); s = d->session; - if (!s) + if (!s) { + /* Not on a session yet: rfcomm_dlc_open() may be connecting a + * socket for it with rfcomm_mutex released. Leave a mark so + * that it does not attach the DLC once it holds the lock again. + */ + set_bit(RFCOMM_CLOSED, &d->flags); goto no_session; + } /* after waiting on the mutex check the session still exists * then check the dlc still exists @@ -757,12 +821,12 @@ static struct rfcomm_session *rfcomm_session_close(struct rfcomm_session *s, return rfcomm_session_del(s); } -static struct rfcomm_session *rfcomm_session_create(bdaddr_t *src, - bdaddr_t *dst, - u8 sec_level, - int *err) +/* Creates the L2CAP socket a new session will run on and starts connecting + * it. Must be called with rfcomm_mutex released. + */ +static struct socket *rfcomm_session_connect(bdaddr_t *src, bdaddr_t *dst, + u8 sec_level, int *err) { - struct rfcomm_session *s = NULL; struct sockaddr_l2 addr; struct socket *sock; struct sock *sk; @@ -792,24 +856,16 @@ static struct rfcomm_session *rfcomm_session_create(bdaddr_t *src, l2cap_pi(sk)->chan->mode = L2CAP_MODE_ERTM; release_sock(sk); - s = rfcomm_session_add(sock, BT_BOUND); - if (!s) { - *err = -ENOMEM; - goto failed; - } - - s->initiator = 1; - bacpy(&addr.l2_bdaddr, dst); addr.l2_family = AF_BLUETOOTH; addr.l2_psm = cpu_to_le16(L2CAP_PSM_RFCOMM); addr.l2_cid = 0; addr.l2_bdaddr_type = BDADDR_BREDR; *err = kernel_connect(sock, (struct sockaddr_unsized *)&addr, sizeof(addr), O_NONBLOCK); - if (*err == 0 || *err == -EINPROGRESS) - return s; + if (*err && *err != -EINPROGRESS) + goto failed; - return rfcomm_session_del(s); + return sock; failed: sock_release(sock); From 1465494e8d07426fa41c0021530cccb2a1ac38fa Mon Sep 17 00:00:00 2001 From: Sai Teja Aluvala Date: Tue, 29 Sep 2026 11:42:33 +0530 Subject: [PATCH 064/367] Bluetooth: btintel_pcie: Add shared HW reset for clean start Reset shared Bluetooth hardware after mapping the BAR and before setting up MSI-X interrupts, so stale device state is cleared before IRQ handlers and interrupt masks are configured. Propagate reset errors so probe aborts rather than continuing when the reset fails. Fixes: c2b636b3f788 ("Bluetooth: btintel_pcie: Add support for PCIe transport") Signed-off-by: Sai Teja Aluvala Signed-off-by: Luiz Augusto von Dentz --- drivers/bluetooth/btintel_pcie.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/bluetooth/btintel_pcie.c b/drivers/bluetooth/btintel_pcie.c index d525269be501..0194badeeff3 100644 --- a/drivers/bluetooth/btintel_pcie.c +++ b/drivers/bluetooth/btintel_pcie.c @@ -1877,6 +1877,13 @@ static int btintel_pcie_config_pcie(struct pci_dev *pdev, if (IS_ERR(data->base_addr)) return PTR_ERR(data->base_addr); + /* Do shared hardware reset to ensure a clean start before + * configuring interrupts. + */ + err = btintel_pcie_reset_bt(data); + if (err) + return err; + err = btintel_pcie_setup_irq(data); if (err) return err; From 86ef0f58bdecdedb3a1240971c56d71b7e4ce3fc Mon Sep 17 00:00:00 2001 From: Iaroslav Voitovych Date: Thu, 24 Sep 2026 19:11:58 +0200 Subject: [PATCH 065/367] Bluetooth: MGMT: Fix status of pending commands flushed on power off cmd_complete_rsp() receives a struct cmd_lookup and, for a pending command that has no cmd_complete callback, falls through to cmd_status_rsp(cmd, data). cmd_status_rsp() reads data as a u8 *, so the status sent comes from the first byte of match->sk's representation, not from match->mgmt_status. commit f53e1c9c726d ("Bluetooth: MGMT: Fix possible crash on mgmt_index_removed") changed the callers' data from &status to &match and updated the cmd_complete branch to match->mgmt_status, but left the fall-through passing data unchanged. In mgmt_index_removed() match->sk is NULL, so every such command is answered with status 0x00 (Success) instead of MGMT_STATUS_INVALID_INDEX. In __mgmt_power_off() it is NULL too, unless a Set Powered command was pending: then settings_rsp() has stored that command's socket in match->sk, and the byte sent comes from the socket pointer. Either way the status the caller set, MGMT_STATUS_NOT_POWERED (or MGMT_STATUS_INVALID_INDEX when the device is being unregistered), is lost. Pass the status the callers set. This was seen on a QCA9377 laptop: bluetoothd's Start Discovery, pending when the adapter was powered off, was answered with Command Status 0x00 and no parameters, and bluetoothd 5.72 crashed on the zero-length success. BlueZ master has since added the length check on that path ("adapter: Fix crash on short start discovery reply", BlueZ a734b06059cb), but userspace should still be told Not Powered, not Success. Tested on Ubuntu 7.0.0-31 with a virtual controller (hci_vhci): power off is held in HCI Write Scan Enable while a Start Discovery is submitted, and the flush answers it with status 0x00 before this change and 0x0f (Not Powered) after. Fixes: f53e1c9c726d ("Bluetooth: MGMT: Fix possible crash on mgmt_index_removed") Cc: stable@vger.kernel.org Signed-off-by: Iaroslav Voitovych Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/mgmt.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/bluetooth/mgmt.c b/net/bluetooth/mgmt.c index 41956cdde982..251a896babd5 100644 --- a/net/bluetooth/mgmt.c +++ b/net/bluetooth/mgmt.c @@ -1495,7 +1495,7 @@ static void cmd_complete_rsp(struct mgmt_pending_cmd *cmd, void *data) return; } - cmd_status_rsp(cmd, data); + cmd_status_rsp(cmd, &match->mgmt_status); } static int generic_cmd_complete(struct mgmt_pending_cmd *cmd, u8 status) From cfb3293c4573289f11f913611f530ea6b1926d67 Mon Sep 17 00:00:00 2001 From: Vladimir Murzin Date: Tue, 22 Sep 2026 11:37:56 +0100 Subject: [PATCH 066/367] usb: mtu3: Fix double dereference in TP_printk Paul reported kernel splat: [ 0.000000] TRACE EVENT ERROR: Event mtu3_gadget_ep_set_halt has double dereference in TP_printk: &REC->gpd_ring->dma [ 0.000000] ------------[ cut here ]------------ [ 0.000000] Event mtu3_gadget_ep_set_halt has double dereference in TP_printk: &REC->gpd_ring->dma [ 0.000000] WARNING: kernel/trace/trace_events.c:420 at test_double_dereference+0x144/0x14c, CPU#0: swapper/0/0 [ 0.000000] Modules linked in: [ 0.000000] CPU: 0 UID: 0 PID: 0 Comm: swapper/0 Not tainted 7.3.0-rc1 #15247 PREEMPT [ 0.000000] Hardware name: linux,dummy-virt (DT) [ 0.000000] pstate: 600000c5 (nZCv daIF -PAN -UAO -TCO -DIT -SSBS BTYPE=--) [ 0.000000] pc : test_double_dereference+0x144/0x14c [ 0.000000] lr : test_double_dereference+0x144/0x14c [ 0.000000] sp : ffffc80aa7633bf0 [ 0.000000] x29: ffffc80aa7633bf0 x28: ffffc80aa7c0f047 x27: 000508b58019388f [ 0.000000] x26: 0000000000000003 x25: 0000000000000007 x24: ffffc80aa5fceff8 [ 0.000000] x23: ffffc80aa7c0f05a x22: ffffc80aa64edfe8 x21: ffffc80aa7c0fce8 [ 0.000000] x20: ffffc80aa7c0f047 x19: 0000000000000013 x18: 0000000000000001 [ 0.000000] x17: 6572656420656c62 x16: 756f642073616820 x15: 746c61685f746573 [ 0.000000] x14: 0000000000000000 x13: ffff000139d90000 x12: 0000000000000045 [ 0.000000] x11: 00000000000000cf x10: ffff00013f546428 x9 : ffff000139d90000 [ 0.000000] x8 : 3fffffffffffc000 x7 : 0000000000000001 x6 : 0000000000000001 [ 0.000000] x5 : ffff00013f4e6440 x4 : 0000000000000000 x3 : 0000000000000000 [ 0.000000] x2 : 0000000000000000 x1 : 0000000000000000 x0 : ffffc80aa764a700 [ 0.000000] Call trace: [ 0.000000] test_double_dereference+0x144/0x14c (P) [ 0.000000] trace_event_raw_init+0x37c/0x5d8 [ 0.000000] event_init+0x34/0xc0 [ 0.000000] trace_event_init+0xec/0x588 [ 0.000000] trace_init+0x24/0x6e0 [ 0.000000] start_kernel+0x4a0/0x8ec [ 0.000000] __primary_switched+0x88/0x90 [ 0.000000] irq event stamp: 0 [ 0.000000] hardirqs last enabled at (0): [<0000000000000000>] 0x0 [ 0.000000] hardirqs last disabled at (0): [<0000000000000000>] 0x0 [ 0.000000] softirqs last enabled at (0): [<0000000000000000>] 0x0 [ 0.000000] softirqs last disabled at (0): [<0000000000000000>] 0x0 [ 0.000000] ---[ end trace 0000000000000000 ]--- [ 0.000000] TRACE EVENT ERROR: Event mtu3_gadget_ep_disable has double dereference in TP_printk: &REC->gpd_ring->dma [ 0.000000] TRACE EVENT ERROR: Event mtu3_gadget_ep_enable has double dereference in TP_printk: &REC->gpd_ring->dma Which also observed by Mark and myself. The splat it result of new check introduced by b5cc230af5e5 ("tracing: Warn when an event dereferences a pointer in TP_printk()") which correctly catches issue with %pad dereferencing the address saved in the ring buffer. TP_fast_assign() logic gets executed when the tracepoint is triggered, however the TP_printk() is executed when the user reads the trace buffer which could be seconds, minutes, hours, days, even months later and nothing guarantee that __entry->gpd_ring pointer will still be pointing to what it was when it was recorded. Fix the issue by capturing immediate value of gpd_ring.dma when trace point is triggered. Reported-by: Paul E. McKenney Tested-by: Mark Rutland Reviewed-by: Steven Rostedt Signed-off-by: Vladimir Murzin Signed-off-by: Paul E. McKenney --- drivers/usb/mtu3/mtu3_trace.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/usb/mtu3/mtu3_trace.h b/drivers/usb/mtu3/mtu3_trace.h index 89870175d635..9aaa167d69c1 100644 --- a/drivers/usb/mtu3/mtu3_trace.h +++ b/drivers/usb/mtu3/mtu3_trace.h @@ -224,6 +224,7 @@ DECLARE_EVENT_CLASS(mtu3_log_ep, __field(unsigned int, flags) __field(unsigned int, direction) __field(struct mtu3_gpd_ring *, gpd_ring) + __field(dma_addr_t, gpd_ring_dma) ), TP_fast_assign( __assign_str(name); @@ -235,12 +236,13 @@ DECLARE_EVENT_CLASS(mtu3_log_ep, __entry->flags = mep->flags; __entry->direction = mep->is_in; __entry->gpd_ring = &mep->gpd_ring; + __entry->gpd_ring_dma = mep->gpd_ring.dma ), TP_printk("%s: type %s maxp %d slot %d mult %d burst %d ring %p/%pad flags %c:%c%c%c:%c", __get_str(name), usb_ep_type_string(__entry->type), __entry->maxp, __entry->slot, __entry->mult, __entry->maxburst, - __entry->gpd_ring, &__entry->gpd_ring->dma, + __entry->gpd_ring, &__entry->gpd_ring_dma, __entry->flags & MTU3_EP_ENABLED ? 'E' : 'e', __entry->flags & MTU3_EP_STALL ? 'S' : 's', __entry->flags & MTU3_EP_WEDGE ? 'W' : 'w', From bc8ce2cea5f77c4bd1a5184705e300e83546c0c4 Mon Sep 17 00:00:00 2001 From: Hengyu Liang Date: Tue, 29 Sep 2026 12:41:35 -0400 Subject: [PATCH 067/367] ata: libata-scsi: do not lose CHECK CONDITION for failed ATAPI commands Since commit 2e1d2e65e773 ("ata: libata-scsi: terminate deferred commands on time out"), atapi_qc_complete() sets SAM_STAT_CHECK_CONDITION for a failed command only if cmd->result is zero, so that the DID_TIME_OUT or DID_REQUEUE host byte of a terminated command is preserved. However, cmd->result can also be non-zero for a regular failed ATAPI command completed through libata EH: ata_eh_analyze_tf() calls ata_eh_decide_disposition(), that is, scsi_check_sense(), on the sense data obtained with REQUEST SENSE, and scsi_check_sense() sets the SCSI midlayer internal byte of the result for some sense codes, e.g. SCSIML_STAT_TGT_FAILURE for ILLEGAL REQUEST with ASC 0x20 (INVALID COMMAND OPERATION CODE) or 0x24 (INVALID FIELD IN CDB), or SCSIML_STAT_MED_ERROR for MEDIUM ERROR with ASC 0x11 (UNRECOVERED READ ERROR). For such commands, atapi_qc_complete() keeps the result as is and the command completes with a GOOD status byte, even though it failed and valid sense data is available. As a result, passthrough users see these failed ATAPI commands as successful. For example, with the emulated IDE CD-ROM drive of QEMU and no medium loaded, a PLAY AUDIO MSF command issued with SG_IO completes with status 0x00 (and no DRIVER_SENSE) instead of 0x02 (CHECK CONDITION) with sense key ILLEGAL REQUEST, ASC/ASCQ 0x20/0x00, and the CDROM ioctls CDROMPLAYTRKIND, CDROMSUBCHNL, CDROMPAUSE, CDROMRESUME, CDROMPLAYMSF and CDROM_GET_MCN return 0 instead of -ENOMEDIUM or -EOPNOTSUPP. Fix this by setting the status byte to SAM_STAT_CHECK_CONDITION whenever the host byte is not set. Use set_status_byte() rather than overwriting the whole result, so that the SCSI midlayer internal byte set by scsi_check_sense() is preserved, as ata_scsi_qc_complete() already does for ATA commands. Fixes: 2e1d2e65e773 ("ata: libata-scsi: terminate deferred commands on time out") Cc: stable@vger.kernel.org Suggested-by: Niklas Cassel Signed-off-by: Hengyu Liang Reviewed-by: Damien Le Moal Link: https://lore.kernel.org/r/20260929164135.266708-1-hengyul@cs.unc.edu Signed-off-by: Niklas Cassel --- drivers/ata/libata-scsi.c | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/drivers/ata/libata-scsi.c b/drivers/ata/libata-scsi.c index 8f9aa97a519d..fb5517d51095 100644 --- a/drivers/ata/libata-scsi.c +++ b/drivers/ata/libata-scsi.c @@ -3059,10 +3059,15 @@ static void atapi_qc_complete(struct ata_queued_cmd *qc) if (qc->cdb[0] == ALLOW_MEDIUM_REMOVAL && qc->dev->sdev) qc->dev->sdev->locked = 0; - if (cmd->result) - ata_scsi_qc_done(qc, false, 0); - else - ata_scsi_qc_done(qc, true, SAM_STAT_CHECK_CONDITION); + /* + * Report CHECK CONDITION unless the command was terminated + * with a host byte set. Only set the status byte, so that the + * SCSI midlayer internal byte, which scsi_check_sense() may + * have set from the sense data, is preserved. + */ + if (get_host_byte(cmd) == DID_OK) + set_status_byte(cmd, SAM_STAT_CHECK_CONDITION); + ata_scsi_qc_done(qc, false, 0); goto schedule_deferred; } From b62e3db8cdc1680e1c3390591e872f485148541a Mon Sep 17 00:00:00 2001 From: Cristian Ciocaltea Date: Tue, 1 Sep 2026 21:50:36 +0300 Subject: [PATCH 068/367] drm/bridge: Fix unlocked list_del in drm_bridge_add() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When re-adding a bridge that was previously removed, drm_bridge_add() drops it from bridge_lingering_list without holding bridge_lock. Both bridge_list and bridge_lingering_list are protected by bridge_lock, as they are concurrently modified by drm_bridge_remove() and __drm_bridge_free(), and walked by the debugfs 'bridges' file. Running the list_empty() test and the list_del_init() outside of the lock may therefore corrupt either list. Perform both under bridge_lock. Fixes: 17805a15d175 ("drm/bridge: add list of removed refcounted bridges") Reported-by: Sashiko Closes: https://lore.kernel.org/all/20260723015004.1F5711F000E9@smtp.kernel.org/ Signed-off-by: Cristian Ciocaltea Reviewed-by: Luca Ceresoli Tested-by: Luca Ceresoli Tested-by: Igor Paunovic # RK3588 Orange Pi 5 Plus, dual HDMI Tested-by: Robin Räber Link: https://patch.msgid.link/20260901-dw-hdmi-qp-scramb-v11-12-bc12954a0688@collabora.com Signed-off-by: Luca Ceresoli --- drivers/gpu/drm/drm_bridge.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/gpu/drm/drm_bridge.c b/drivers/gpu/drm/drm_bridge.c index 83f1809a5d37..04b999305f30 100644 --- a/drivers/gpu/drm/drm_bridge.c +++ b/drivers/gpu/drm/drm_bridge.c @@ -454,8 +454,10 @@ void drm_bridge_add(struct drm_bridge *bridge) * in bridge_lingering_list. Remove it or bridge_lingering_list will be * corrupted when adding this bridge to bridge_list below. */ + mutex_lock(&bridge_lock); if (!list_empty(&bridge->list)) list_del_init(&bridge->list); + mutex_unlock(&bridge_lock); mutex_init(&bridge->hpd_state_mutex); mutex_init(&bridge->hpd_mutex); From 57889076afd1be890150c32ffde3b691fe5707c8 Mon Sep 17 00:00:00 2001 From: Cristian Ciocaltea Date: Tue, 1 Sep 2026 21:50:38 +0300 Subject: [PATCH 069/367] drm/bridge: Fix unlocked list access in drm_bridge_attach() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit drm_bridge_attach() inspects bridge->list to warn about bridges being attached without a previous drm_bridge_add() call, but it does so without holding bridge_lock. The list head can be concurrently written by drm_bridge_add(), drm_bridge_remove() and __drm_bridge_free() while they operate on neighbouring entries of bridge_list or bridge_lingering_list, all of them under bridge_lock. Hence the unlocked read is a data race, even though it is unlikely to change the outcome of list_empty() in practice. Perform the check with bridge_lock held. Fixes: 76f1a9711b83 ("drm/bridge: add warning for bridges attached without being added") Signed-off-by: Cristian Ciocaltea Reviewed-by: Luca Ceresoli Tested-by: Luca Ceresoli Tested-by: Igor Paunovic # RK3588 Orange Pi 5 Plus, dual HDMI Tested-by: Robin Räber Link: https://patch.msgid.link/20260901-dw-hdmi-qp-scramb-v11-14-bc12954a0688@collabora.com Signed-off-by: Luca Ceresoli --- drivers/gpu/drm/drm_bridge.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/drm_bridge.c b/drivers/gpu/drm/drm_bridge.c index 04b999305f30..a52d5f5b9f05 100644 --- a/drivers/gpu/drm/drm_bridge.c +++ b/drivers/gpu/drm/drm_bridge.c @@ -595,8 +595,10 @@ int drm_bridge_attach(struct drm_encoder *encoder, struct drm_bridge *bridge, if (!bridge->container) DRM_WARN("DRM bridge corrupted or not allocated by devm_drm_bridge_alloc()\n"); - if (list_empty(&bridge->list)) - DRM_WARN("Missing drm_bridge_add() before attach\n"); + scoped_guard(mutex, &bridge_lock) { + if (list_empty(&bridge->list)) + DRM_WARN("Missing drm_bridge_add() before attach\n"); + } drm_bridge_get(bridge); From c9417c1c5269e2bd0570fbe53e4ec03a8d31f008 Mon Sep 17 00:00:00 2001 From: Mikulas Patocka Date: Wed, 23 Sep 2026 12:40:16 +0200 Subject: [PATCH 070/367] dm: fix reading free memory in do_resume When do_resume calls dm_table_get_mode(new_map), the call is done without holding any locks (it only holds a reference to the md). It may be possible that another concurrent ioctl on the same device will swap table after dm_swap_table and before dm_table_get_mode. In this case, dm_table_get_mode(new_map) reads freed memory. This race condition was triggered by syzbot. This commit fixes the race by reading mode of the new table before dm_swap_table. Note that new_map is also passed to dm_ima_need_measure, but this function doesn't attempt to dereference it, so this call should be safe. Note that this is not a security bug because only root can trigger it and the commonly used tools such as lvm or cryptsetup do not call the ioctls concurrently, so that they can't trigger it acceidentally. Signed-off-by: Mikulas Patocka Reported-by: syzbot+bdff1ecf726d2af66afd@syzkaller.appspotmail.com Cc: stable@vger.kernel.org --- drivers/md/dm-ioctl.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/md/dm-ioctl.c b/drivers/md/dm-ioctl.c index a005f124d706..8f945f0aeda5 100644 --- a/drivers/md/dm-ioctl.c +++ b/drivers/md/dm-ioctl.c @@ -1273,6 +1273,7 @@ static int do_resume(struct dm_ioctl *param) /* Do we need to load a new map ? */ if (new_map) { sector_t old_size, new_size; + blk_mode_t new_map_mode; dm_ima_context_table_op(md, ima_context, DM_IMA_TABLE_SAVE); /* Suspend if it isn't already suspended */ @@ -1305,6 +1306,7 @@ static int do_resume(struct dm_ioctl *param) } } + new_map_mode = dm_table_get_mode(new_map); old_size = dm_get_size(md); old_map = dm_swap_table(md, new_map); if (IS_ERR(old_map)) { @@ -1320,7 +1322,7 @@ static int do_resume(struct dm_ioctl *param) if (old_size && new_size && old_size != new_size) need_resize_uevent = true; - if (dm_table_get_mode(new_map) & BLK_OPEN_WRITE) + if (new_map_mode & BLK_OPEN_WRITE) set_disk_ro(dm_disk(md), 0); else set_disk_ro(dm_disk(md), 1); From fcbde6227fcc53c73f08c56b2539e462d545cac6 Mon Sep 17 00:00:00 2001 From: Ma Xinmeng <1564938642@qq.com> Date: Fri, 25 Sep 2026 11:05:39 +0800 Subject: [PATCH 071/367] dm-snap: reject the transient exception store for snapshot-merge snapshot_merge_next_chunks() calls s->store->type->prepare_merge() without checking it. The transient store ("N") implements neither prepare_merge nor commit_merge, and snapshot_ctr() validates those callbacks only on the handover path (__validate_exception_handover()), so a directly constructed snapshot-merge calls a NULL prepare_merge on resume: snapshot_merge_resume -> snapshot_merge_next_chunks -> prepare_merge(...) commit_merge() is NULL for the same reason. Userspace only issues documented device-mapper ioctls (CAP_SYS_ADMIN); this is a misconfiguration the kernel must reject rather than oops on. Reject the combination in snapshot_ctr() at table-load time (-EINVAL), the same check the handover path already performs; this also covers the commit_merge() call site, which a guard local to the prepare_merge call would miss. Tested on Linux v6.6.157 KASAN: unpatched, the dmsetup reproducer oopses in snapshot_merge_next_chunks(); with this patch, the table load fails with -EINVAL and no oops. Also present on v6.6.145; still present on the linux-dm tree (555f3d7be91a) and on torvalds/linux master (62f4c998b297) as of 2026-09-22. Note that this is not a security bug because only root can trigger it. Signed-off-by: Ma Xinmeng <1564938642@qq.com> Signed-off-by: Mikulas Patocka Cc: stable@vger.kernel.org --- drivers/md/dm-snap.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/drivers/md/dm-snap.c b/drivers/md/dm-snap.c index c8a9ca1eeae7..7c3725e4ac54 100644 --- a/drivers/md/dm-snap.c +++ b/drivers/md/dm-snap.c @@ -1292,6 +1292,20 @@ static int snapshot_ctr(struct dm_target *ti, unsigned int argc, char **argv) goto bad_store; } + /* + * snapshot-merge requires the optional prepare_merge/commit_merge + * callbacks; the transient exception store does not implement them. + * The handover path checks this in __validate_exception_handover(), + * but a directly constructed snapshot-merge never goes through it. + */ + if (dm_target_is_snapshot_merge(ti) && + (!s->store->type->prepare_merge || + !s->store->type->commit_merge)) { + ti->error = "Snapshot exception store does not support snapshot-merge."; + r = -EINVAL; + goto bad_hash_tables; + } + argv += args_used; argc -= args_used; From 2f15518682c779b1014552cb2ccb4a2052f85f27 Mon Sep 17 00:00:00 2001 From: Ridham Khurana Date: Wed, 30 Sep 2026 10:53:25 +0000 Subject: [PATCH 072/367] regulator: tps6594-regulator: Fix n_linear_ranges for TPS65224 BUCK2-4 The TPS65224 BUCK2, BUCK3 and BUCK4 descriptors pass 4 as n_linear_ranges, but tps65224_bucks_2_3_4_ranges[] only has two entries, covering selectors 0x00-0x45. The linear range helpers loop over n_linear_ranges entries, so any lookup of a selector above 0x45 reads past the end of the array and treats whatever follows it as two more ranges. This happens on the normal registration path when the core checks the voltage constraints against every selector. TPS652G1 uses the same descriptors and is affected too. Use ARRAY_SIZE() so the count always matches the table. Fixes: 00c826525fba ("regulator: tps6594-regulator: Add TI TPS65224 PMIC regulators") Cc: stable@vger.kernel.org Signed-off-by: Ridham Khurana Link: https://patch.msgid.link/20260930105333.3522412-1-khurana.ridham222@gmail.com Signed-off-by: Mark Brown --- drivers/regulator/tps6594-regulator.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/regulator/tps6594-regulator.c b/drivers/regulator/tps6594-regulator.c index 31a5218d5510..503b89a2f5f8 100644 --- a/drivers/regulator/tps6594-regulator.c +++ b/drivers/regulator/tps6594-regulator.c @@ -241,21 +241,21 @@ static const struct regulator_desc tps65224_buck_regs[] = { TPS65224_MASK_BUCKS_VSET, TPS6594_REG_BUCKX_CTRL(1), TPS6594_BIT_BUCK_EN, 0, 0, tps65224_bucks_2_3_4_ranges, - 4, 0, 0, NULL, 0, 0), + ARRAY_SIZE(tps65224_bucks_2_3_4_ranges), 0, 0, NULL, 0, 0), TPS6594_REGULATOR("BUCK3", "buck3", TPS6594_BUCK_3, REGULATOR_VOLTAGE, tps6594_bucks_ops, TPS65224_MASK_BUCKS_VSET, TPS6594_REG_BUCKX_VOUT_1(2), TPS65224_MASK_BUCKS_VSET, TPS6594_REG_BUCKX_CTRL(2), TPS6594_BIT_BUCK_EN, 0, 0, tps65224_bucks_2_3_4_ranges, - 4, 0, 0, NULL, 0, 0), + ARRAY_SIZE(tps65224_bucks_2_3_4_ranges), 0, 0, NULL, 0, 0), TPS6594_REGULATOR("BUCK4", "buck4", TPS6594_BUCK_4, REGULATOR_VOLTAGE, tps6594_bucks_ops, TPS65224_MASK_BUCKS_VSET, TPS6594_REG_BUCKX_VOUT_1(3), TPS65224_MASK_BUCKS_VSET, TPS6594_REG_BUCKX_CTRL(3), TPS6594_BIT_BUCK_EN, 0, 0, tps65224_bucks_2_3_4_ranges, - 4, 0, 0, NULL, 0, 0), + ARRAY_SIZE(tps65224_bucks_2_3_4_ranges), 0, 0, NULL, 0, 0), }; static const struct tps6594_regulator_irq_type tps6594_buck1_irq_types[] = { From ec86c43dcfff68eab4db9bdafcd3e2b16e342e18 Mon Sep 17 00:00:00 2001 From: Osama Abdelkader Date: Tue, 4 Aug 2026 18:00:47 +0200 Subject: [PATCH 073/367] drm/panthor: validate userspace queue count against initialized firmware slot count In panthor_drv.c:panthor_ioctl_group_create(), the queue count is validated against the hardcoded maximum: if (!args->queues.count || args->queues.count > MAX_CS_PER_CSG) return -EINVAL; However, if the firmware reports fewer slots than MAX_CS_PER_CSG, panthor_fw_init_ifaces() will only initialize that smaller number of stream interfaces. If unprivileged userspace requests more queues than the firmware reported (but no more than MAX_CS_PER_CSG), cs_slot_prog_locked() fetches an uninitialized stream interface and may crash when dereferencing cs_iface->input->ringbuf_base. Fixes: de8548813824 ("drm/panthor: Add the scheduler logical block") Cc: stable@vger.kernel.org Reported-by: sashiko-bot@kernel.org Closes: https://sashiko.dev/#/patchset/20260803141149.68182-1-osama.abdelkader@gmail.com?part=1 Signed-off-by: Osama Abdelkader Reviewed-by: Steven Price Reviewed-by: Liviu Dudau Reviewed-by: Boris Brezillon Link: https://patch.msgid.link/20260804160048.174148-1-osama.abdelkader@gmail.com Signed-off-by: Steven Price --- drivers/gpu/drm/panthor/panthor_sched.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/gpu/drm/panthor/panthor_sched.c b/drivers/gpu/drm/panthor/panthor_sched.c index 5778e23e20a0..653c6755896c 100644 --- a/drivers/gpu/drm/panthor/panthor_sched.c +++ b/drivers/gpu/drm/panthor/panthor_sched.c @@ -3674,6 +3674,9 @@ int panthor_group_create(struct panthor_file *pfile, hweight64(group_args->tiler_core_mask) < group_args->max_tiler_cores) return -EINVAL; + if (group_args->queues.count > sched->cs_slot_count) + return -EINVAL; + group = kzalloc_obj(*group); if (!group) return -ENOMEM; From c96ca94425b076dc398d929366e378d1e22ec794 Mon Sep 17 00:00:00 2001 From: Nguyen Ngoc Thang Date: Wed, 30 Sep 2026 21:07:48 +0700 Subject: [PATCH 074/367] memstick: rtsx_usb_ms: complete requests after eject instead of dropping them memstick_check() can pass its host->removing check just before rtsx_usb_ms_drv_remove() sets eject/removing. Its next request is then silently dropped: rtsx_usb_ms_request() skips schedule_work() once eject is set, and drv_remove's cancel_work_sync() can also cancel a queued handle_req before it picks the request up. Nobody completes card->mrq_complete. Once memstick core waits for requests without a timeout ("memstick: core: wait for request completion before freeing card"), this hangs removal: memstick_check() never returns and memstick_remove_host() blocks in flush_workqueue(): INFO: task kworker/u10:3:65 blocked for more than 20 seconds. Workqueue: kmemstick memstick_check __wait_for_common memstick_check INFO: task kworker/1:1:33 blocked for more than 20 seconds. Workqueue: usb_hub_wq hub_event __flush_workqueue memstick_remove_host rtsx_usb_ms_drv_remove Never drop a request. rtsx_usb_ms_request() always schedules handle_req, and handle_req fails requests with -ENOMEDIUM once eject is set, without touching the device. drv_remove flushes handle_req instead of cancelling it, and cancels it only after memstick_remove_host(), when no new request can arrive, so it cannot run on a freed host. The host_mutex drain in drv_remove is removed: handle_req always leaves host->req NULL when it finishes, so the drain never had anything to do, and it would now race with handle_req. Fixes: 99451dceeb5f ("memstick: Add realtek USB memstick host driver") Cc: stable@vger.kernel.org Link: https://lore.kernel.org/all/20260924204142.607-1-rajojha047@gmail.com/ Signed-off-by: Nguyen Ngoc Thang Signed-off-by: Ulf Hansson --- drivers/memstick/host/rtsx_usb_ms.c | 31 ++++++++++------------------- 1 file changed, 11 insertions(+), 20 deletions(-) diff --git a/drivers/memstick/host/rtsx_usb_ms.c b/drivers/memstick/host/rtsx_usb_ms.c index beadc389f15f..d5b3a96fc609 100644 --- a/drivers/memstick/host/rtsx_usb_ms.c +++ b/drivers/memstick/host/rtsx_usb_ms.c @@ -27,7 +27,6 @@ struct rtsx_usb_ms { struct memstick_host *msh; struct memstick_request *req; - struct mutex host_mutex; struct work_struct handle_req; struct delayed_work poll_card; @@ -514,6 +513,13 @@ static void rtsx_usb_ms_handle_req(struct work_struct *work) struct memstick_host *msh = host->msh; int rc; + /* Fail requests after eject so their waiters are released. */ + if (host->eject) { + while (!memstick_next_req(msh, &host->req)) + host->req->error = -ENOMEDIUM; + return; + } + if (!host->req) { pm_runtime_get_sync(ms_dev(host)); do { @@ -547,8 +553,7 @@ static void rtsx_usb_ms_request(struct memstick_host *msh) dev_dbg(ms_dev(host), "--> %s\n", __func__); - if (!host->eject) - schedule_work(&host->handle_req); + schedule_work(&host->handle_req); } static int rtsx_usb_ms_set_param(struct memstick_host *msh, @@ -781,7 +786,6 @@ static int rtsx_usb_ms_drv_probe(struct platform_device *pdev) host->power_mode = MEMSTICK_POWER_OFF; platform_set_drvdata(pdev, host); - mutex_init(&host->host_mutex); INIT_WORK(&host->handle_req, rtsx_usb_ms_handle_req); INIT_DELAYED_WORK(&host->poll_card, rtsx_usb_ms_poll_card); @@ -812,27 +816,12 @@ static void rtsx_usb_ms_drv_remove(struct platform_device *pdev) { struct rtsx_usb_ms *host = platform_get_drvdata(pdev); struct memstick_host *msh = host->msh; - int err; host->eject = true; msh->removing = true; - cancel_work_sync(&host->handle_req); + flush_work(&host->handle_req); cancel_delayed_work_sync(&host->poll_card); - mutex_lock(&host->host_mutex); - if (host->req) { - dev_dbg(ms_dev(host), - "%s: Controller removed during transfer\n", - dev_name(&msh->dev)); - host->req->error = -ENOMEDIUM; - do { - err = memstick_next_req(msh, &host->req); - if (!err) - host->req->error = -ENOMEDIUM; - } while (!err); - } - mutex_unlock(&host->host_mutex); - /* Balance possible unbalanced usage count * e.g. unconditional module removal */ @@ -841,6 +830,8 @@ static void rtsx_usb_ms_drv_remove(struct platform_device *pdev) pm_runtime_disable(ms_dev(host)); memstick_remove_host(msh); + /* No card, no new requests; wait for the last failed one to finish. */ + cancel_work_sync(&host->handle_req); dev_dbg(ms_dev(host), ": Realtek USB Memstick controller has been removed\n"); memstick_free_host(msh); From e06b678e3b4e12fdaa0b51a7a5a54abc5dbd6469 Mon Sep 17 00:00:00 2001 From: Waiman Long Date: Tue, 29 Sep 2026 23:18:33 -0400 Subject: [PATCH 075/367] cgroup/cpuset: Don't access cpuset_cgrp_subsys.root in is_in_v2_mode() After seeing the patch [1] to guard is_in_v2_mode() with RCU, it makes me realize that is_in_v2_mode() may be called in a context where a new cgroup filesystem is being rebound with stale cpuset_cgrp_subsys.root pointer. Avoid this potential UaF situation by adding a new cpuset_v2_mode flag which is set when the cpuset_v2_mode mount option is used. This flag is written into only when cpuset_bind() is being called with a stable cpuset_cgrp_subsys.root value. The is_in_v2_mode() helper is modified to read the new cpuset_v2_mode flag instead of accessing cpuset_cgrp_subsys.root directly. [1] https://lore.kernel.org/lkml/20260929084124.626693-2-arighi@nvidia.com Fixes: b8d1b8ee93df ("cpuset: Allow v2 behavior in v1 cgroup") Signed-off-by: Waiman Long Reviewed-by: Ridong Chen Reviewed-by: Andrea Righi Signed-off-by: Tejun Heo --- kernel/cgroup/cpuset.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/kernel/cgroup/cpuset.c b/kernel/cgroup/cpuset.c index 1fcec89a28b9..98af44a7f399 100644 --- a/kernel/cgroup/cpuset.c +++ b/kernel/cgroup/cpuset.c @@ -152,6 +152,12 @@ static cpumask_var_t isolated_cpus; /* CSCB */ */ static bool update_housekeeping; /* RWCS */ +/* + * Set if "cpuset_v2_mode" mount option is used + * Cached at bind time and not lock protected; accessed via {READ,WRITE}_ONCE + */ +static bool cpuset_v2_mode; + /* * Copy of isolated_cpus to be passed to housekeeping_update() */ @@ -439,8 +445,7 @@ static inline bool cpuset_v2(void) */ static inline bool is_in_v2_mode(void) { - return cpuset_v2() || - (cpuset_cgrp_subsys.root->flags & CGRP_ROOT_CPUSET_V2_MODE); + return cpuset_v2() || READ_ONCE(cpuset_v2_mode); } /** @@ -3727,6 +3732,8 @@ static void cpuset_bind(struct cgroup_subsys_state *root_css) mutex_lock(&cpuset_mutex); spin_lock_irq(&callback_lock); + WRITE_ONCE(cpuset_v2_mode, + !!(cpuset_cgrp_subsys.root->flags & CGRP_ROOT_CPUSET_V2_MODE)); if (is_in_v2_mode()) { cpumask_copy(top_cpuset.cpus_allowed, cpu_possible_mask); cpumask_copy(top_cpuset.effective_xcpus, cpu_possible_mask); From af701b8d3238b9eb7cd450b0491300452c700bf5 Mon Sep 17 00:00:00 2001 From: Kuba Piecuch Date: Wed, 30 Sep 2026 14:23:58 +0000 Subject: [PATCH 076/367] sched_ext: Call ops.dequeue() when a task arrives on a remote local DSQ When a task in the BPF scheduler's custody is moved to another CPU's local DSQ, ops.dequeue() is only called once the task is picked for execution, from set_next_task_scx() with SCX_DEQ_CORE_SCHED_EXEC, even though no core-sched pick took place. It should instead be called without flags when the task is inserted into the destination DSQ, as it is for same-rq dispatches. move_remote_task_to_local_dsq() sets p->scx.sticky_cpu across the migration so that deactivate_task() on the source rq doesn't end custody. Since commit b75aaea24c9f ("sched_ext: Properly mark SCX-internal migrations via sticky_cpu"), enqueue_task_scx() only clears it after inserting the task, so task_leave_custody() still sees the migration in progress and skips the custody exit. Clear p->scx.sticky_cpu as soon as enqueue_task_scx() has read it, as was done before that commit. The source side is unaffected. Fixes: ebf1ccff79c4 ("sched_ext: Fix ops.dequeue() semantics") Cc: stable@vger.kernel.org # 7.1.x: 18d62044cda7: sched_ext: Preserve rq tracking across local DSQ dispatch Cc: stable@vger.kernel.org # 7.1.x Reviewed-by: Andrea Righi Assisted-by: Claude:claude-opus-5.5 Signed-off-by: Kuba Piecuch Signed-off-by: Tejun Heo --- kernel/sched/ext/ext.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/kernel/sched/ext/ext.c b/kernel/sched/ext/ext.c index 493b7aac7087..5209510d6a5e 100644 --- a/kernel/sched/ext/ext.c +++ b/kernel/sched/ext/ext.c @@ -2152,6 +2152,13 @@ static void enqueue_task_scx(struct rq *rq, struct task_struct *p, int core_enq_ int sticky_cpu = p->scx.sticky_cpu; u64 enq_flags = core_enq_flags | rq->scx.remote_activate_enq_flags; + /* + * An SCX-internal migration ends on arrival. Clear sticky_cpu so @p can + * leave custody when inserted into the destination DSQ. + */ + if (sticky_cpu >= 0) + p->scx.sticky_cpu = -1; + /* * SCX_RQ_IN_WAKEUP promises a task_woken_scx() call once this enqueue * returns. Only the core's wakeup path delivers one. The flags stashed @@ -2190,9 +2197,6 @@ static void enqueue_task_scx(struct rq *rq, struct task_struct *p, int core_enq_ dl_server_start(&rq->ext_server); scx_do_enqueue_task(rq, p, enq_flags, sticky_cpu); - - if (sticky_cpu >= 0) - p->scx.sticky_cpu = -1; out: rq->scx.flags &= ~SCX_RQ_IN_WAKEUP; From cc07fae1de5dbf514a9a3978050ee3cf5fe20d3c Mon Sep 17 00:00:00 2001 From: Kuba Piecuch Date: Wed, 30 Sep 2026 14:23:59 +0000 Subject: [PATCH 077/367] selftests/sched_ext: Add a test for ops.dequeue() on remote local DSQ moves Add a dequeue_remote test that makes moves of tasks in the BPF scheduler's custody to another CPU's local DSQ the common case, both via SCX_DSQ_LOCAL_ON dispatch and via scx_bpf_dsq_move_to_local(). The BPF scheduler tracks each task's custody state and triggers scx_bpf_error() if a custody period doesn't end with exactly one ops.dequeue() before the task runs, or if it ends with an SCX_DEQ_CORE_SCHED_EXEC dequeue of a task without a core cookie. Without the previous patch, the test fails with: sched_ext: dequeue_remote: dequeue_remote.bpf.c:141: 15 (rcu_preempt): late ops.dequeue() with SCX_DEQ_CORE_SCHED_EXEC (enq_cpu=3 cpu=2 seq=1) ... ops_dequeue+0x114/0x170 set_next_task_scx+0x104/0x1e0 __pick_next_task+0xc7/0x180 __schedule+0x154/0x1870 Reviewed-by: Andrea Righi Assisted-by: Claude:claude-opus-5.5 Signed-off-by: Kuba Piecuch Signed-off-by: Tejun Heo --- tools/testing/selftests/sched_ext/Makefile | 1 + .../selftests/sched_ext/dequeue_remote.bpf.c | 270 ++++++++++++++++++ .../selftests/sched_ext/dequeue_remote.c | 204 +++++++++++++ 3 files changed, 475 insertions(+) create mode 100644 tools/testing/selftests/sched_ext/dequeue_remote.bpf.c create mode 100644 tools/testing/selftests/sched_ext/dequeue_remote.c diff --git a/tools/testing/selftests/sched_ext/Makefile b/tools/testing/selftests/sched_ext/Makefile index 4e06d0baaeec..af919a56a4f4 100644 --- a/tools/testing/selftests/sched_ext/Makefile +++ b/tools/testing/selftests/sched_ext/Makefile @@ -165,6 +165,7 @@ auto-test-targets := \ create_dsq \ dequeue \ dequeue_iter \ + dequeue_remote \ enq_last_no_enq_fails \ ddsp_bogus_dsq_fail \ ddsp_vtimelocal_fail \ diff --git a/tools/testing/selftests/sched_ext/dequeue_remote.bpf.c b/tools/testing/selftests/sched_ext/dequeue_remote.bpf.c new file mode 100644 index 000000000000..983590c6d498 --- /dev/null +++ b/tools/testing/selftests/sched_ext/dequeue_remote.bpf.c @@ -0,0 +1,270 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Verify that ops.dequeue() is called when a task leaves the BPF scheduler's + * custody by being moved to the local DSQ of a CPU other than the one whose + * rq it's on (move_remote_task_to_local_dsq()). + * + * ops.enqueue() puts every task into custody and ops.dispatch() moves it to + * the dispatching CPU's local DSQ, so most moves cross CPUs. With + * @test_use_move_to_local, tasks are queued on a user DSQ and consumed with + * scx_bpf_dsq_move_to_local(). Otherwise, they are queued in a BPF queue and + * dispatched with SCX_DSQ_LOCAL_ON. + * + * Copyright (c) 2026 Google LLC. + */ + +#include + +#define SHARED_DSQ 0 +#define MAX_DISPATCH_POPS 8 + +char _license[] SEC("license") = "GPL"; + +UEI_DEFINE(uei); + +struct { + __uint(type, BPF_MAP_TYPE_QUEUE); + __uint(max_entries, 32768); + __type(value, s32); +} global_queue SEC(".maps"); + +enum task_state { + TASK_NONE = 0, + TASK_ENQUEUED, /* in BPF custody, waiting for ops.dequeue() */ + TASK_DISPATCHED, /* left custody */ +}; + +struct task_ctx { + enum task_state state; + s32 enq_cpu; /* scx_bpf_task_cpu() at ops.enqueue() */ + u64 enqueue_seq; +}; + +struct { + __uint(type, BPF_MAP_TYPE_TASK_STORAGE); + __uint(map_flags, BPF_F_NO_PREALLOC); + __type(key, int); + __type(value, struct task_ctx); +} task_ctx_stor SEC(".maps"); + +/* core_cookie only exists with CONFIG_SCHED_CORE */ +struct task_struct___core_sched { + unsigned long core_cookie; +} __attribute__((preserve_access_index)); + +bool test_use_move_to_local; + +u64 enqueue_cnt, dequeue_cnt, dispatch_dequeue_cnt, change_dequeue_cnt; +u64 remote_dispatch_cnt, remote_running_cnt, missed_dequeue_cnt; +u64 core_sched_exec_dequeue_cnt; + +static struct task_ctx *lookup_task_ctx(struct task_struct *p) +{ + return bpf_task_storage_get(&task_ctx_stor, p, 0, 0); +} + +static bool task_has_core_cookie(struct task_struct *p) +{ + struct task_struct___core_sched *t = (void *)p; + + if (!bpf_core_field_exists(t->core_cookie)) + return false; + return BPF_CORE_READ(t, core_cookie); +} + +s32 BPF_STRUCT_OPS(dequeue_remote_select_cpu, struct task_struct *p, + s32 prev_cpu, u64 wake_flags) +{ + /* no direct dispatch, always go through ops.enqueue() */ + return prev_cpu; +} + +void BPF_STRUCT_OPS(dequeue_remote_enqueue, struct task_struct *p, u64 enq_flags) +{ + struct task_ctx *tctx; + s32 pid = p->pid; + + tctx = lookup_task_ctx(p); + if (!tctx) { + scx_bpf_dsq_insert(p, SCX_DSQ_GLOBAL, SCX_SLICE_DFL, enq_flags); + return; + } + + /* the previous custody period must have ended with ops.dequeue() */ + if (tctx->state == TASK_ENQUEUED) + scx_bpf_error("%d (%s): enqueue while in ENQUEUED state seq=%llu", + p->pid, p->comm, tctx->enqueue_seq); + + /* + * Mark @p as enqueued before making it visible to ops.dispatch() on + * other CPUs, which skips queue entries of tasks not in ENQUEUED + * state as stale. + */ + tctx->state = TASK_ENQUEUED; + tctx->enq_cpu = scx_bpf_task_cpu(p); + tctx->enqueue_seq++; + + if (test_use_move_to_local) { + scx_bpf_dsq_insert(p, SHARED_DSQ, SCX_SLICE_DFL, enq_flags); + } else if (bpf_map_push_elem(&global_queue, &pid, 0)) { + scx_bpf_dsq_insert(p, SCX_DSQ_GLOBAL, SCX_SLICE_DFL, enq_flags); + tctx->state = TASK_DISPATCHED; + tctx->enq_cpu = -1; + goto out; + } + + __sync_fetch_and_add(&enqueue_cnt, 1); +out: + scx_bpf_kick_cpu(scx_bpf_task_cpu(p), SCX_KICK_IDLE); +} + +void BPF_STRUCT_OPS(dequeue_remote_dequeue, struct task_struct *p, u64 deq_flags) +{ + struct task_ctx *tctx; + + __sync_fetch_and_add(&dequeue_cnt, 1); + + tctx = lookup_task_ctx(p); + if (!tctx) + return; + + /* + * Only core scheduling can pick a task straight out of custody, and + * only if the task has a core cookie. Otherwise, the custody exit was + * missed when @p was inserted into a local DSQ and got deferred until + * @p was picked. + */ + if ((deq_flags & SCX_DEQ_CORE_SCHED_EXEC) && !task_has_core_cookie(p)) { + __sync_fetch_and_add(&core_sched_exec_dequeue_cnt, 1); + scx_bpf_error("%d (%s): late ops.dequeue() with SCX_DEQ_CORE_SCHED_EXEC (enq_cpu=%d cpu=%d seq=%llu)", + p->pid, p->comm, tctx->enq_cpu, + scx_bpf_task_cpu(p), tctx->enqueue_seq); + } + + /* ops.dequeue() ends the custody period started by ops.enqueue() */ + if (tctx->state != TASK_ENQUEUED) + scx_bpf_error("%d (%s): dequeue outside custody deq_flags=0x%llx state=%d seq=%llu", + p->pid, p->comm, deq_flags, tctx->state, + tctx->enqueue_seq); + + if (deq_flags & SCX_DEQ_SCHED_CHANGE) { + __sync_fetch_and_add(&change_dequeue_cnt, 1); + tctx->state = TASK_NONE; + } else { + __sync_fetch_and_add(&dispatch_dequeue_cnt, 1); + tctx->state = TASK_DISPATCHED; + } +} + +void BPF_STRUCT_OPS(dequeue_remote_dispatch, s32 cpu, struct task_struct *prev) +{ + struct task_ctx *tctx; + struct task_struct *p; + s32 pid; + int i; + + if (test_use_move_to_local) { + scx_bpf_dsq_move_to_local(SHARED_DSQ, 0); + return; + } + + /* pop past stale entries so that they don't leave this CPU idle */ + bpf_for(i, 0, MAX_DISPATCH_POPS) { + if (bpf_map_pop_elem(&global_queue, &pid)) + return; + + p = bpf_task_from_pid(pid); + if (!p) + continue; + + /* + * Entries are stale if @p left custody through a property + * change dequeue or was dispatched from a duplicate entry. + */ + tctx = lookup_task_ctx(p); + if (!tctx || tctx->state != TASK_ENQUEUED) { + bpf_task_release(p); + continue; + } + + if (bpf_cpumask_test_cpu(cpu, p->cpus_ptr)) { + if (scx_bpf_task_cpu(p) != cpu) + __sync_fetch_and_add(&remote_dispatch_cnt, 1); + scx_bpf_dsq_insert(p, SCX_DSQ_LOCAL_ON | cpu, + SCX_SLICE_DFL, 0); + } else { + scx_bpf_dsq_insert(p, SCX_DSQ_GLOBAL, SCX_SLICE_DFL, 0); + } + + bpf_task_release(p); + return; + } + + /* out of pops with entries left, retry instead of idling this CPU */ + if (!bpf_map_peek_elem(&global_queue, &pid)) + scx_bpf_kick_cpu(cpu, SCX_KICK_IDLE); +} + +void BPF_STRUCT_OPS(dequeue_remote_running, struct task_struct *p) +{ + struct task_ctx *tctx; + + tctx = lookup_task_ctx(p); + if (!tctx) + return; + + /* tasks can only run from a local DSQ, i.e. after leaving custody */ + if (tctx->state == TASK_ENQUEUED) { + __sync_fetch_and_add(&missed_dequeue_cnt, 1); + scx_bpf_error("%d (%s): running without ops.dequeue() (enq_cpu=%d cpu=%d seq=%llu)", + p->pid, p->comm, tctx->enq_cpu, + scx_bpf_task_cpu(p), tctx->enqueue_seq); + return; + } + + if (tctx->enq_cpu >= 0 && tctx->enq_cpu != scx_bpf_task_cpu(p)) + __sync_fetch_and_add(&remote_running_cnt, 1); + tctx->enq_cpu = -1; +} + +s32 BPF_STRUCT_OPS(dequeue_remote_init_task, struct task_struct *p, + struct scx_init_task_args *args) +{ + struct task_ctx *tctx; + + tctx = bpf_task_storage_get(&task_ctx_stor, p, 0, + BPF_LOCAL_STORAGE_GET_F_CREATE); + if (!tctx) + return -ENOMEM; + + /* task storage persists across attachments, start from scratch */ + tctx->state = TASK_NONE; + tctx->enq_cpu = -1; + tctx->enqueue_seq = 0; + + return 0; +} + +s32 BPF_STRUCT_OPS_SLEEPABLE(dequeue_remote_init) +{ + return scx_bpf_create_dsq(SHARED_DSQ, -1); +} + +void BPF_STRUCT_OPS(dequeue_remote_exit, struct scx_exit_info *ei) +{ + UEI_RECORD(uei, ei); +} + +SEC(".struct_ops.link") +struct sched_ext_ops dequeue_remote_ops = { + .select_cpu = (void *)dequeue_remote_select_cpu, + .enqueue = (void *)dequeue_remote_enqueue, + .dequeue = (void *)dequeue_remote_dequeue, + .dispatch = (void *)dequeue_remote_dispatch, + .running = (void *)dequeue_remote_running, + .init_task = (void *)dequeue_remote_init_task, + .init = (void *)dequeue_remote_init, + .exit = (void *)dequeue_remote_exit, + .flags = SCX_OPS_ENQ_LAST, + .name = "dequeue_remote", +}; diff --git a/tools/testing/selftests/sched_ext/dequeue_remote.c b/tools/testing/selftests/sched_ext/dequeue_remote.c new file mode 100644 index 000000000000..f2c1901030de --- /dev/null +++ b/tools/testing/selftests/sched_ext/dequeue_remote.c @@ -0,0 +1,204 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Verify that ops.dequeue() is called for tasks leaving BPF custody through + * an SCX-internal cross-CPU migration (move_remote_task_to_local_dsq()). + * + * Copyright (c) 2026 Google LLC. + */ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "scx_test.h" +#include "dequeue_remote.bpf.skel.h" + +#define MAX_WORKERS 64 +#define RUN_MS 2000 + +static int nr_cpus; + +static long long now_ms(void) +{ + struct timespec ts; + + clock_gettime(CLOCK_MONOTONIC, &ts); + return ts.tv_sec * 1000LL + ts.tv_nsec / 1000000; +} + +/* mix of short bursts and sleeps to generate lots of enqueues and wakeups */ +static void worker_fn(int id) +{ + long long end = now_ms() + RUN_MS; + volatile unsigned long sum = 0; + + while (now_ms() < end) { + unsigned long j; + + for (j = 0; j < 20000 + id * 1000; j++) + sum += j; + if (id & 1) + usleep(100); + else + sched_yield(); + } + + exit(0); +} + +static enum scx_test_status run_scenario(struct dequeue_remote *skel, + bool use_move_to_local, + const char *name) +{ + enum scx_test_status ret = SCX_TEST_PASS; + struct bpf_link *link; + pid_t pids[MAX_WORKERS]; + int nr_workers, nr_forked, i; + + nr_workers = 2 * nr_cpus; + if (nr_workers < 4) + nr_workers = 4; + if (nr_workers > MAX_WORKERS) + nr_workers = MAX_WORKERS; + + skel->bss->test_use_move_to_local = use_move_to_local; + skel->bss->enqueue_cnt = 0; + skel->bss->dequeue_cnt = 0; + skel->bss->dispatch_dequeue_cnt = 0; + skel->bss->change_dequeue_cnt = 0; + skel->bss->remote_dispatch_cnt = 0; + skel->bss->remote_running_cnt = 0; + skel->bss->missed_dequeue_cnt = 0; + skel->bss->core_sched_exec_dequeue_cnt = 0; + memset(&skel->data->uei, 0, sizeof(skel->data->uei)); + + link = bpf_map__attach_struct_ops(skel->maps.dequeue_remote_ops); + SCX_FAIL_IF(!link, "Failed to attach struct_ops for %s", name); + + fflush(stdout); + fflush(stderr); + + for (nr_forked = 0; nr_forked < nr_workers; nr_forked++) { + pid_t pid = fork(); + + if (pid < 0) { + SCX_ERR("Failed to fork worker %d", nr_forked); + ret = SCX_TEST_FAIL; + break; + } + if (pid == 0) + worker_fn(nr_forked); + pids[nr_forked] = pid; + } + + /* on failure, kill the remaining workers but still reap them */ + for (i = 0; i < nr_forked; i++) { + int status; + + if (ret != SCX_TEST_PASS) + kill(pids[i], SIGKILL); + + if (waitpid(pids[i], &status, 0) != pids[i]) { + SCX_ERR("Failed to wait for worker %d", i); + ret = SCX_TEST_FAIL; + } else if (ret == SCX_TEST_PASS && status != 0) { + SCX_ERR("Worker %d exited with status %d", i, status); + ret = SCX_TEST_FAIL; + } + } + + bpf_link__destroy(link); + + if (ret != SCX_TEST_PASS) + return ret; + + printf("%s:\n", name); + printf(" workers: %d\n", nr_workers); + printf(" enqueues: %lu\n", (unsigned long)skel->bss->enqueue_cnt); + printf(" dequeues: %lu (dispatch: %lu, property_change: %lu)\n", + (unsigned long)skel->bss->dequeue_cnt, + (unsigned long)skel->bss->dispatch_dequeue_cnt, + (unsigned long)skel->bss->change_dequeue_cnt); + if (!use_move_to_local) + printf(" remote SCX_DSQ_LOCAL_ON dispatches: %lu\n", + (unsigned long)skel->bss->remote_dispatch_cnt); + printf(" ran on a CPU other than the enqueue CPU: %lu\n", + (unsigned long)skel->bss->remote_running_cnt); + printf(" ran without ops.dequeue(): %lu\n", + (unsigned long)skel->bss->missed_dequeue_cnt); + printf(" late SCX_DEQ_CORE_SCHED_EXEC dequeues: %lu\n", + (unsigned long)skel->bss->core_sched_exec_dequeue_cnt); + + if (skel->data->uei.kind != EXIT_KIND(SCX_EXIT_UNREG)) + SCX_ERR("Scheduler exited with kind=%lld: %s", + (long long)skel->data->uei.kind, skel->data->uei.msg); + SCX_EQ(skel->data->uei.kind, EXIT_KIND(SCX_EXIT_UNREG)); + + /* the test is meaningless if no task was moved across CPUs */ + SCX_GT(skel->bss->remote_running_cnt, 0); + SCX_EQ(skel->bss->enqueue_cnt, skel->bss->dequeue_cnt); + + return SCX_TEST_PASS; +} + +static enum scx_test_status setup(void **ctx) +{ + struct dequeue_remote *skel; + cpu_set_t cpus; + + SCX_FAIL_IF(sched_getaffinity(0, sizeof(cpus), &cpus), + "Failed to get CPU affinity"); + nr_cpus = CPU_COUNT(&cpus); + if (nr_cpus < 2) { + fprintf(stderr, "Skipping: requires at least 2 usable CPUs\n"); + return SCX_TEST_SKIP; + } + + skel = SCX_OPS_OPEN(dequeue_remote_ops, dequeue_remote); + SCX_OPS_LOAD(skel, dequeue_remote_ops, dequeue_remote, uei); + + *ctx = skel; + + return SCX_TEST_PASS; +} + +static enum scx_test_status run(void *ctx) +{ + struct dequeue_remote *skel = ctx; + enum scx_test_status status; + + status = run_scenario(skel, false, + "BPF queue -> SCX_DSQ_LOCAL_ON | cpu"); + if (status != SCX_TEST_PASS) + return status; + + status = run_scenario(skel, true, + "user DSQ -> scx_bpf_dsq_move_to_local()"); + if (status != SCX_TEST_PASS) + return status; + + return SCX_TEST_PASS; +} + +static void cleanup(void *ctx) +{ + struct dequeue_remote *skel = ctx; + + dequeue_remote__destroy(skel); +} + +struct scx_test dequeue_remote_test = { + .name = "dequeue_remote", + .description = "Verify ops.dequeue() on SCX-internal cross-CPU migrations", + .setup = setup, + .run = run, + .cleanup = cleanup, +}; + +REGISTER_SCX_TEST(&dequeue_remote_test) From 28b7260548af3d35773477993ad4e4de41578dd7 Mon Sep 17 00:00:00 2001 From: Stephen Smalley Date: Mon, 28 Sep 2026 10:19:17 -0400 Subject: [PATCH 078/367] selinux: preserve NATIVE_LABELS on already-initialized sb in set_mnt_opts When an NFSv4 filesystem exported with security_label is mounted a second time, sget_fc() returns the existing superblock and nfs_get_root() calls security_sb_set_mnt_opts() again on it. Since the superblock is already SB_INITIALIZED, selinux_set_mnt_opts() takes an early exit without setting SECURITY_LSM_NATIVE_LABELS in *set_kern_flags. nfs_get_root() then sees kflags without SECURITY_LSM_NATIVE_LABELS set and clears NFS_CAP_SECURITY_LABEL on the shared nfs_server, silently disabling per-file labeling for both the new and the original mount. Commit 3815a245b501 ("security/selinux: fix SECURITY_LSM_NATIVE_LABELS on reused superblock") fixed the same problem in selinux_sb_clone_mnt_opts(). The reused-superblock path for a plain second mount now goes through sb_set_mnt_opts rather than sb_clone_mnt_opts, so it needs the same treatment: on both SE_SBINITIALIZED early exits, set SECURITY_LSM_NATIVE_LABELS in *set_kern_flags when the superblock was already using SECURITY_FS_USE_NATVE. Cc: stable@vger.kernel.org Closes: https://github.com/SELinuxProject/selinux-kernel/issues/69 Closes: https://github.com/SELinuxProject/selinux-kernel/issues/70 Closes: https://github.com/SELinuxProject/selinux-testsuite/issues/91 Closes: https://github.com/SELinuxProject/selinux-testsuite/issues/92 Fixes: eb9ae686507b ("SELinux: Add new labeling type native labels") Signed-off-by: Stephen Smalley Signed-off-by: Paul Moore --- security/selinux/hooks.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index 3f4a6ddee322..30776b0107f2 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -690,8 +690,12 @@ static int selinux_set_mnt_opts(struct super_block *sb, * will be used for both mounts) */ if ((sbsec->flags & SE_SBINITIALIZED) && (sb->s_type->fs_flags & FS_BINARY_MOUNTDATA) - && !opts) + && !opts) { + if ((kern_flags & SECURITY_LSM_NATIVE_LABELS) && + sbsec->behavior == SECURITY_FS_USE_NATIVE) + *set_kern_flags |= SECURITY_LSM_NATIVE_LABELS; goto out; + } root_isec = backing_inode_security_novalidate(root); @@ -735,6 +739,9 @@ static int selinux_set_mnt_opts(struct super_block *sb, /* previously mounted with options, but not on this attempt? */ if ((sbsec->flags & SE_MNTMASK) && !opts) goto out_double_mount; + if ((kern_flags & SECURITY_LSM_NATIVE_LABELS) && + sbsec->behavior == SECURITY_FS_USE_NATIVE) + *set_kern_flags |= SECURITY_LSM_NATIVE_LABELS; rc = 0; goto out; } From 707ce102521496c67860d81221b20c56d8255f2f Mon Sep 17 00:00:00 2001 From: Oleg Keri Date: Fri, 25 Sep 2026 15:25:58 +0200 Subject: [PATCH 079/367] drm/dp: don't mark the AUX backlight enabled when enabling failed If drm_edp_backlight_enable() fails, dp_aux_backlight_update_status() still marks the backlight as enabled. From then on every brightness change only updates the level and the enable is never retried, so the panel stays dark until something blanks and unblanks the backlight. Return the error instead, so the next update tries to enable again. Fixes: 10f7b40e4f30 ("drm/panel: add basic DP AUX backlight support") Signed-off-by: Oleg Keri Reviewed-by: Douglas Anderson Signed-off-by: Douglas Anderson Link: https://patch.msgid.link/20260925132558.27518-1-okerixx@gmail.com --- drivers/gpu/drm/display/drm_dp_helper.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/display/drm_dp_helper.c b/drivers/gpu/drm/display/drm_dp_helper.c index e623ccb4c1d8..488fd123a01a 100644 --- a/drivers/gpu/drm/display/drm_dp_helper.c +++ b/drivers/gpu/drm/display/drm_dp_helper.c @@ -4634,7 +4634,9 @@ static int dp_aux_backlight_update_status(struct backlight_device *bd) if (!backlight_is_blank(bd)) { if (!bl->enabled) { - drm_edp_backlight_enable(bl->aux, &bl->info, brightness); + ret = drm_edp_backlight_enable(bl->aux, &bl->info, brightness); + if (ret) + return ret; bl->enabled = true; return 0; } From 4d0e2704118511e37790641d591454354e7c0d6c Mon Sep 17 00:00:00 2001 From: Dave Airlie Date: Wed, 30 Sep 2026 17:36:15 +1000 Subject: [PATCH 080/367] nouveau: check gsp->rm as well as gsp pointer before gcx ready In turing systems where gsp exists, but can end up not being used, we need to add a second check. Fixes: cb4c7603678c ("drm/nouveau/gsp/r570: Add support for INTERNAL_GCX_ENTRY_PREREQUISITE") Reported-by: Woody Suwalski Tested-by: Woody Suwalski Reviewed-by: Lyude Paul Signed-off-by: Dave Airlie Link: https://lore.kernel.org/nouveau/20260930212151.3926202-1-airlied@gmail.com/T/#u --- drivers/gpu/drm/nouveau/nvkm/engine/device/user.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/device/user.c b/drivers/gpu/drm/nouveau/nvkm/engine/device/user.c index f78e6b9b4292..b841d66f8bb3 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/device/user.c +++ b/drivers/gpu/drm/nouveau/nvkm/engine/device/user.c @@ -201,7 +201,7 @@ nvkm_udevice_gcx_ready(struct nvkm_udevice *udev, void *data, u32 size) } *args = data; int ret = -ENOSYS; - if (!gsp) { + if (!gsp || !gsp->rm) { args->v0.ready = NV_DEVICE_GC6_READY | NV_DEVICE_GCOFF_READY; return 0; } From 0d6c5794fd633427f0d4ad8b51d94061295649da Mon Sep 17 00:00:00 2001 From: Alexey Charkov Date: Wed, 30 Sep 2026 20:10:21 +0400 Subject: [PATCH 081/367] power: sequencing: pcie-m2: Fix leaking array from of_regulator_bulk_get_all() of_regulator_bulk_get_all() allocates the consumers array and hands it to the caller, who is expected to free it at an appropriate time. pwrseq-pcie-m2.c never did, leading to a memory leak. Add an explicit kfree() for the consumers array. Cc: stable@vger.kernel.org Fixes: 52e7b5bd62ba ("power: sequencing: Add the Power Sequencing driver for the PCIe M.2 connectors") Signed-off-by: Alexey Charkov Link: https://patch.msgid.link/20260930-m2-pwrseq-kfree-v1-1-82678b02c126@flipper.net Signed-off-by: Bartosz Golaszewski --- drivers/power/sequencing/pwrseq-pcie-m2.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/power/sequencing/pwrseq-pcie-m2.c b/drivers/power/sequencing/pwrseq-pcie-m2.c index 0b72e3a6992b..e42e85f47629 100644 --- a/drivers/power/sequencing/pwrseq-pcie-m2.c +++ b/drivers/power/sequencing/pwrseq-pcie-m2.c @@ -572,6 +572,7 @@ static int pwrseq_pcie_m2_probe(struct platform_device *pdev) mutex_destroy(&ctx->list_lock); err_free_regulators: regulator_bulk_free(ctx->num_vregs, ctx->regs); + kfree(ctx->regs); return ret; } @@ -585,6 +586,7 @@ static void pwrseq_pcie_m2_remove(struct platform_device *pdev) mutex_destroy(&ctx->list_lock); regulator_bulk_free(ctx->num_vregs, ctx->regs); + kfree(ctx->regs); } static const struct of_device_id pwrseq_pcie_m2_of_match[] = { From 4d69e74034b208588636ecc177e0dd43a7696af3 Mon Sep 17 00:00:00 2001 From: Sang-Hoon Choi Date: Wed, 30 Sep 2026 02:37:31 +0900 Subject: [PATCH 082/367] gpio: exar: initialize the ID before registering its cleanup devm_add_action_or_reset() invokes the cleanup action immediately when registering the action fails. gpio_exar_probe() currently stores the allocated ID in exar_gpio only after registering the action, so that failure makes exar_devm_ida_free() release ID 0. If the allocated ID is nonzero, this leaks that ID and can free ID 0 belonging to another device. Store the ID before registering the cleanup action so both the immediate failure path and normal device teardown release the correct allocation. Fixes: 5300ebb695fa ("gpio: exar: use devm action for freeing the IDA and drop remove()") Reported-by: Changyul Lee Assisted-by: LLM Signed-off-by: Sang-Hoon Choi Reviewed-by: Linus Walleij Link: https://patch.msgid.link/179070265834.12871.48183786512201899.idr-bug-79@gmail.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-exar.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpio/gpio-exar.c b/drivers/gpio/gpio-exar.c index 9053662f1817..f629cc526f3e 100644 --- a/drivers/gpio/gpio-exar.c +++ b/drivers/gpio/gpio-exar.c @@ -199,6 +199,7 @@ static int gpio_exar_probe(struct platform_device *pdev) index = ida_alloc(&ida_index, GFP_KERNEL); if (index < 0) return index; + exar_gpio->index = index; ret = devm_add_action_or_reset(dev, exar_devm_ida_free, exar_gpio); if (ret) @@ -214,7 +215,6 @@ static int gpio_exar_probe(struct platform_device *pdev) exar_gpio->gpio_chip.set = exar_set_value; exar_gpio->gpio_chip.base = -1; exar_gpio->gpio_chip.ngpio = ngpios; - exar_gpio->index = index; exar_gpio->first_pin = first_pin; ret = devm_gpiochip_add_data(dev, &exar_gpio->gpio_chip, exar_gpio); From fc79aeb86a2c244c1f1894e2d8f2966fe2b60fa2 Mon Sep 17 00:00:00 2001 From: Mikulas Patocka Date: Wed, 30 Sep 2026 18:42:20 +0200 Subject: [PATCH 083/367] dm-integrity: validate the superblock on resume dm_integrity_resume() re-reads the superblock from the device so that it picks up the flags and the recalculate position. It performs no validation on the result, while the constructor validates the superblock it reads and sizes all the in-memory structures according to it. The user may modify the superblock on the underlying device while the dm-integrity device is suspended, so that the two no longer agree. In particular, access_journal_data() shifts the journal entry index by ic->sb->log2_sectors_per_block, while ic->journal_section_sectors and the journal page list were computed with the value that was present at constructor time. Increasing log2_sectors_per_block makes the index run past the end of the journal, and dm-integrity then writes 512 bytes through lowmem_page_address(NULL). Snapshot the validated superblock in the constructor and refuse to resume if any of the fields that describe the on-disk geometry changed. Reported-by: syzbot+675c91651049ad042c5f@syzkaller.appspotmail.com Signed-off-by: Mikulas Patocka Cc: stable@vger.kernel.org Fixes: 118ba36e446c ("dm-integrity: fix recalculation in bitmap mode") Assisted-by: Claude:claude-opus-5 --- drivers/md/dm-integrity.c | 50 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/drivers/md/dm-integrity.c b/drivers/md/dm-integrity.c index 0862f31b8498..802b377613b0 100644 --- a/drivers/md/dm-integrity.c +++ b/drivers/md/dm-integrity.c @@ -180,6 +180,7 @@ struct dm_integrity_c { struct dm_bufio_client *bufio; struct workqueue_struct *metadata_wq; struct superblock *sb; + struct superblock *sb_copy; unsigned int journal_pages; unsigned int n_bitmap_blocks; @@ -3858,6 +3859,35 @@ static void dm_integrity_postsuspend(struct dm_target *ti) ic->journal_uptodate = true; } +/* + * The superblock is re-read from the device on every resume, so that we pick + * up the flags and the recalculate position. The geometry described by the + * superblock must not change though - the in-memory structures (and the + * journal in particular) were sized according to the superblock that was + * validated in the constructor. Reject a superblock that was modified behind + * our back. + * + * Only the fields that the driver never rewrites may be tested here. In + * particular, "version" is recalculated by sb_set_version on every superblock + * write and it depends on SB_FLAG_RECALCULATING and SB_FLAG_DIRTY_BITMAP, and + * SB_FLAG_DISCARD_KEYED may be set by dm_integrity_resume itself. + */ +static bool superblock_changed(struct dm_integrity_c *ic) +{ + const __le32 immutable_flags = cpu_to_le32(SB_FLAG_HAVE_JOURNAL_MAC | + SB_FLAG_FIXED_PADDING | + SB_FLAG_FIXED_HMAC | + SB_FLAG_INLINE); + + return memcmp(ic->sb->magic, ic->sb_copy->magic, sizeof(ic->sb->magic)) != 0 || + ic->sb->log2_interleave_sectors != ic->sb_copy->log2_interleave_sectors || + ic->sb->integrity_tag_size != ic->sb_copy->integrity_tag_size || + ic->sb->journal_sections != ic->sb_copy->journal_sections || + ic->sb->log2_sectors_per_block != ic->sb_copy->log2_sectors_per_block || + ((ic->sb->flags ^ ic->sb_copy->flags) & immutable_flags) != 0 || + memcmp(ic->sb->salt, ic->sb_copy->salt, SALT_SIZE) != 0; +} + static void dm_integrity_resume(struct dm_target *ti) { struct dm_integrity_c *ic = ti->private; @@ -3876,6 +3906,18 @@ static void dm_integrity_resume(struct dm_target *ti) if (r) dm_integrity_io_error(ic, "reading superblock", r); + if (unlikely(superblock_changed(ic))) { + /* + * Restore the superblock that we validated in the constructor, + * so that the rest of the driver doesn't operate on values + * that don't match the in-memory structures. + */ + memcpy(ic->sb, ic->sb_copy, sizeof(struct superblock)); + DMERR("The superblock was changed while the device was suspended"); + dm_integrity_io_error(ic, "superblock check", -EINVAL); + goto skip_writes; + } + if (ic->mode == 'R') goto skip_writes; @@ -5427,6 +5469,13 @@ static int dm_integrity_ctr(struct dm_target *ti, unsigned int argc, char **argv ic->just_formatted = true; } + ic->sb_copy = kmemdup(ic->sb, sizeof(struct superblock), GFP_KERNEL); + if (!ic->sb_copy) { + ti->error = "Cannot allocate superblock copy"; + r = -ENOMEM; + goto bad; + } + if (!ic->meta_dev && ic->mode != 'I') { r = dm_set_target_max_io_len(ti, 1U << ic->sb->log2_interleave_sectors); if (r) @@ -5525,6 +5574,7 @@ static void dm_integrity_dtr(struct dm_target *ti) kvfree(ic->journal_tree); if (ic->sb) free_pages_exact(ic->sb, SB_SECTORS << SECTOR_SHIFT); + kfree(ic->sb_copy); if (ic->internal_shash) crypto_free_shash(ic->internal_shash); From bd2feb7796b6585938a4241e7b7c4148a6f7b762 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Wed, 30 Sep 2026 15:31:53 +0200 Subject: [PATCH 084/367] namespace: queue a mount only once for mount notifications mnt_notify_add() puts a mount on notify_list. It doesn't check whether the mount is on the list already. That's fine as long as every mount is queued at most once per namespace_sem hold. It isn't. propagate_umount() moves a surviving overmount off a stack of mounts that are going away and queues it as moved. shrink_submounts() and mark_mounts_for_expiry() call umount_tree() for several mounts under a single namespace_sem hold. So the second umount_tree() can take down exactly the mount the first one reparented, reparent it once more and so end up queueing it a second time. The second list_add_tail() cuts the mounts queued in between out of the list while the head still points at the last of them. notify_mnt_list() then only visits that one mount and the others are freed after the grace period with notify_list still pointing at them. Every later mount operation on the host walks freed memory: BUG: KASAN: slab-use-after-free in __list_add_valid_or_report Read of size 8 at addr ffff8881003d57e0 by task notify_dq/162 __list_add_valid_or_report+0x15c/0x1a0 mnt_add_to_ns+0x322/0x890 attach_recursive_mnt.isra.0+0xf2a/0x1a00 path_mount+0x139c/0x1d40 This needs a mount with MNT_SHRINKABLE to bind from. That's what finish_automount() creates (submounts of NFS, AFS and CIFS and the tracefs mount below debugfs). Bind mounts inherit the flag. A mount namespace with a FAN_MARK_MNTNS mark does the rest and root in a user namespace can have both. Initialize to_notify in alloc_vfsmnt() and leave a mount alone that is queued already. mnt_notify() looks at the state the mount has when the list is drained so a single entry per mount is enough. The move event for a mount that is unmounted under the same hold is lost. That's fine. The detach is what matters. Fixes: bf630c401641 ("vfs: add notifications for mount attach and detach") Cc: stable@vger.kernel.org # v6.15+ Link: https://patch.msgid.link/20260930-work-mount-fixes-3-v1-1-be34c83956ae@kernel.org Signed-off-by: Christian Brauner (Amutable) --- fs/mount.h | 3 +++ fs/namespace.c | 3 +++ 2 files changed, 6 insertions(+) diff --git a/fs/mount.h b/fs/mount.h index 85f136786bbc..2223fb141499 100644 --- a/fs/mount.h +++ b/fs/mount.h @@ -234,6 +234,9 @@ static inline struct mnt_namespace *to_mnt_ns(struct ns_common *ns) #ifdef CONFIG_FSNOTIFY static inline void mnt_notify_add(struct mount *m) { + /* queued already under this namespace_sem hold */ + if (!list_empty(&m->to_notify)) + return; /* Optimize the case where there are no watches */ if ((m->mnt_ns && m->mnt_ns->n_fsnotify_marks) || (m->prev_ns && m->prev_ns->n_fsnotify_marks)) diff --git a/fs/namespace.c b/fs/namespace.c index 5b44eee28f39..2a1e77c0cb6e 100644 --- a/fs/namespace.c +++ b/fs/namespace.c @@ -333,6 +333,9 @@ static struct mount *alloc_vfsmnt(const char *name) INIT_HLIST_NODE(&mnt->mnt_mp_list); INIT_HLIST_HEAD(&mnt->mnt_stuck_children); INIT_HLIST_NODE(&mnt->mnt_ns_visible); +#ifdef CONFIG_FSNOTIFY + INIT_LIST_HEAD(&mnt->to_notify); +#endif RB_CLEAR_NODE(&mnt->mnt_node); mnt->mnt.mnt_idmap = &nop_mnt_idmap; } From 32bf92416614a1e3ff3524fc6b780259c7aad3ef Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Wed, 30 Sep 2026 15:31:54 +0200 Subject: [PATCH 085/367] namespace: check a submount for references right before unmounting it shrink_submounts() and mark_mounts_for_expiry() first collect all the mounts they are allowed to unmount and then unmount them. Whether a mount is busy is decided by propagate_mount_busy() on the tree. But unmounting the first mount changes the tree that the second one propagates into. propagate_umount() moves a surviving overmount off a copy it unmounts and mounts it where the copy was mounted. If that's where the propagated copy of the second victim is looked up the overmount becomes a candidate of the second umount_tree(). It's childless and so trim_one() commits it without looking at its reference count. So a synchronous umount pulls out a busy mount that is in use somewhere else and marks it MNT_SYNC_UMOUNT while it has users: umount2(/tmp/plshrink/p1, 0) = 0 errno 0 cwd is (unreachable) The same two umounts requested one after the other fail with EBUSY. This needs a mount with MNT_SHRINKABLE, i.e., automounted submounts of NFS, AFS and CIFS or the tracefs mount below debugfs. Bind mounts inherit the flag. Check each mount right before it is unmounted under the same hold of namespace_sem and mount_lock as the umount itself. Make sure that the algorithm stays linear. Fixes: 1064f874abc0 ("mnt: Tuck mounts under others instead of creating shadow/side mounts.") Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260930-work-mount-fixes-3-v1-2-be34c83956ae@kernel.org Signed-off-by: Christian Brauner (Amutable) --- fs/namespace.c | 78 ++++++++++++++++++++++++++++++++++---------------- 1 file changed, 53 insertions(+), 25 deletions(-) diff --git a/fs/namespace.c b/fs/namespace.c index 2a1e77c0cb6e..23d3bfa9c14d 100644 --- a/fs/namespace.c +++ b/fs/namespace.c @@ -3987,6 +3987,11 @@ void mark_mounts_for_expiry(struct list_head *mounts) } while (!list_empty(&graveyard)) { mnt = list_first_entry(&graveyard, struct mount, mnt_expire); + /* an earlier umount_tree() may have moved a busy mount here */ + if (propagate_mount_busy(mnt, 1)) { + list_move(&mnt->mnt_expire, mounts); + continue; + } touch_mnt_namespace(mnt->mnt_ns); umount_tree(mnt, UMOUNT_PROPAGATE|UMOUNT_SYNC); } @@ -3994,17 +3999,37 @@ void mark_mounts_for_expiry(struct list_head *mounts) EXPORT_SYMBOL_GPL(mark_mounts_for_expiry); +/* + * Unmount @mnt if it's a shrinkable mount without children that nobody uses. + * + * mount_lock must be held for write + */ +static bool shrink_submount(struct mount *mnt) +{ + if (propagate_mount_busy(mnt, 1)) + return false; + touch_mnt_namespace(mnt->mnt_ns); + umount_tree(mnt, UMOUNT_PROPAGATE|UMOUNT_SYNC); + return true; +} + /* * Ripoff of 'select_parent()' * - * search the list of submounts for a given mountpoint, and move any - * shrinkable submounts to the 'graveyard' list. + * unmount the shrinkable submounts of @parent that aren't busy, children + * before their parent, and say whether anything went + * + * The cursor into the children of @this_parent survives the umount of a + * child mount without child mounts. The mounts that get umounted together with + * it are located under receiving mounts of @this_parent and never under + * @this_parent itself. The one exception is @this_parent getting unmounted + * then the walk starts over. */ -static int select_submounts(struct mount *parent, struct list_head *graveyard) +static bool __shrink_submounts(struct mount *parent) { struct mount *this_parent = parent; struct list_head *next; - int found = 0; + bool shrunk = false; repeat: next = this_parent->mnt_mounts.next; @@ -4023,42 +4048,45 @@ static int select_submounts(struct mount *parent, struct list_head *graveyard) this_parent = mnt; goto repeat; } - - if (!propagate_mount_busy(mnt, 1)) { - list_move_tail(&mnt->mnt_expire, graveyard); - found++; - } + if (!shrink_submount(mnt)) + continue; + shrunk = true; + if (unlikely(this_parent->mnt.mnt_flags & MNT_UMOUNT)) + return true; } /* * All done at this level ... ascend and resume the search */ if (this_parent != parent) { - next = this_parent->mnt_child.next; - this_parent = this_parent->mnt_parent; + struct mount *mnt = this_parent; + + next = mnt->mnt_child.next; + this_parent = mnt->mnt_parent; + /* its children are gone, maybe it can go as well */ + if (shrink_submount(mnt)) { + shrunk = true; + if (unlikely(this_parent->mnt.mnt_flags & MNT_UMOUNT)) + return true; + } goto resume; } - return found; + return shrunk; } /* - * process a list of expirable mountpoints with the intent of discarding any - * submounts of a specific parent mountpoint + * unmount the shrinkable submounts of @mnt that aren't busy + * + * The busy check and the umount of a mount are adjacent. An umount can + * still empty or move a mount in a part of the tree that was walked + * already, so walk again until nothing goes. * * mount_lock must be held for write */ static void shrink_submounts(struct mount *mnt) { - LIST_HEAD(graveyard); - struct mount *m; - - /* extract submounts of 'mountpoint' from the expiration list */ - while (select_submounts(mnt, &graveyard)) { - while (!list_empty(&graveyard)) { - m = list_first_entry(&graveyard, struct mount, - mnt_expire); - touch_mnt_namespace(m->mnt_ns); - umount_tree(m, UMOUNT_PROPAGATE|UMOUNT_SYNC); - } + for (;;) { + if (!__shrink_submounts(mnt)) + break; } } From 520b5a15fc9db81404e337e2904bebaf147b2b03 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Wed, 30 Sep 2026 15:31:55 +0200 Subject: [PATCH 086/367] selftests/filesystems: check that a busy submount survives a synchronous umount Add a test for the shrinkable submounts of a synchronous umount: - P shared, P1 a slave that is shared in turn, P2 its peer - B on P/options with copies on P1 and P2, R on top of the copy in P2 - P with B moved below P1, R is the working directory - umount(P1) slides R to where the copy of B is looked up The umount fails with EBUSY and R stays mounted. Needs the tracefs automount below debugfs for the shrinkable mounts. Link: https://patch.msgid.link/20260930-work-mount-fixes-3-v1-3-be34c83956ae@kernel.org Signed-off-by: Christian Brauner (Amutable) --- .../filesystems/umount_propagation/Makefile | 2 +- .../shrink_submounts_test.c | 211 ++++++++++++++++++ 2 files changed, 212 insertions(+), 1 deletion(-) create mode 100644 tools/testing/selftests/filesystems/umount_propagation/shrink_submounts_test.c diff --git a/tools/testing/selftests/filesystems/umount_propagation/Makefile b/tools/testing/selftests/filesystems/umount_propagation/Makefile index fc0a0783018b..eb85612abf8d 100644 --- a/tools/testing/selftests/filesystems/umount_propagation/Makefile +++ b/tools/testing/selftests/filesystems/umount_propagation/Makefile @@ -1,5 +1,5 @@ # SPDX-License-Identifier: GPL-2.0 -TEST_GEN_PROGS := umount_propagation_test +TEST_GEN_PROGS := umount_propagation_test shrink_submounts_test CFLAGS += -Wall -O2 -g $(KHDR_INCLUDES) diff --git a/tools/testing/selftests/filesystems/umount_propagation/shrink_submounts_test.c b/tools/testing/selftests/filesystems/umount_propagation/shrink_submounts_test.c new file mode 100644 index 000000000000..b024ae3417ce --- /dev/null +++ b/tools/testing/selftests/filesystems/umount_propagation/shrink_submounts_test.c @@ -0,0 +1,211 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * A synchronous umount first unmounts the shrinkable submounts of the + * victim that aren't busy. Every one of them has to be checked right + * before it is unmounted: unmounting one can slide a busy mount to where + * the propagated copy of the next one is looked up. + */ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../../kselftest_harness.h" + +#ifndef FAN_REPORT_MNT +#define FAN_REPORT_MNT 0x00004000 +#endif +#ifndef FAN_MARK_MNTNS +#define FAN_MARK_MNTNS 0x00000110 +#endif +#ifndef FAN_MNT_ATTACH +#define FAN_MNT_ATTACH 0x01000000 +#endif +#ifndef FAN_MNT_DETACH +#define FAN_MNT_DETACH 0x02000000 +#endif + +#define DIR_LEN 64 +#define PATH_LEN 128 + +FIXTURE(shrink_submounts) { + char base[DIR_LEN]; + char automount[PATH_LEN]; + bool mounted; + int fan; +}; + +/* + * Shrinkable mounts come from an automount. The tracefs mount below debugfs + * is one and bind mounts inherit the flag. + */ +FIXTURE_SETUP(shrink_submounts) +{ + struct stat st; + char p[PATH_LEN]; + + self->mounted = false; + self->fan = -1; + + if (geteuid() != 0) + SKIP(return, "test requires CAP_SYS_ADMIN"); + + ASSERT_EQ(unshare(CLONE_NEWNS), 0); + ASSERT_EQ(mount("", "/", NULL, MS_REC | MS_PRIVATE, NULL), 0); + + snprintf(self->base, sizeof(self->base), "/tmp/shrink_submounts.XXXXXX"); + ASSERT_NE(mkdtemp(self->base), NULL); + ASSERT_EQ(mount("tmpfs", self->base, "tmpfs", 0, NULL), 0); + self->mounted = true; + ASSERT_EQ(mount(NULL, self->base, NULL, MS_PRIVATE, NULL), 0); + + snprintf(p, sizeof(p), "%s/dbg", self->base); + ASSERT_EQ(mkdir(p, 0755), 0); + if (mount("debugfs", p, "debugfs", 0, NULL)) { + umount2(self->base, MNT_DETACH); + rmdir(self->base); + SKIP(return, "test requires debugfs"); + } + snprintf(self->automount, sizeof(self->automount), "%s/dbg/tracing", + self->base); + snprintf(p, sizeof(p), "%s/dbg/tracing/.", self->base); + if (stat(p, &st)) { + umount2(self->base, MNT_DETACH); + rmdir(self->base); + SKIP(return, "test requires the tracefs automount"); + } +} + +FIXTURE_TEARDOWN(shrink_submounts) +{ + if (self->fan >= 0) + close(self->fan); + chdir("/"); + if (self->mounted) + umount2(self->base, MNT_DETACH); + rmdir(self->base); +} + +static bool mounted_tmpfs(const char *path) +{ + struct statfs st; + + return !statfs(path, &st) && st.f_type == TMPFS_MAGIC; +} + +/* + * P is a shared bind mount of the automount, P1 a slave of P that is shared + * in turn and P2 its peer. B, another bind mount of the automount, goes on + * P/options and propagates copies Bc1 and Bc2 onto P1/options and + * P2/options. R, a tmpfs and our working directory, sits on top of Bc2 which + * is made private first. P, with B on it, is moved to P1/instances. + * + * A synchronous umount of P1 unmounts the shrinkable submounts Bc1 and B + * first. Unmounting Bc1 takes Bc2 along and slides R to P2/options where + * the propagated copy of B is looked up next. R is busy, so B has to stay + * and the umount fails with EBUSY. + */ +TEST_F(shrink_submounts, busy_mount_moved_into_reach) +{ + char p[PATH_LEN], p1[PATH_LEN], p2[PATH_LEN], r[PATH_LEN], cwd[PATH_LEN]; + int nsfd; + + snprintf(p, sizeof(p), "%s/p", self->base); + snprintf(p1, sizeof(p1), "%s/p1", self->base); + snprintf(p2, sizeof(p2), "%s/p2", self->base); + ASSERT_EQ(mkdir(p, 0755), 0); + ASSERT_EQ(mkdir(p1, 0755), 0); + ASSERT_EQ(mkdir(p2, 0755), 0); + + /* watch the mount namespace so that the detached mounts get queued */ + self->fan = fanotify_init(FAN_REPORT_MNT, O_RDONLY); + if (self->fan >= 0) { + nsfd = open("/proc/self/ns/mnt", O_RDONLY | O_CLOEXEC); + ASSERT_GE(nsfd, 0); + EXPECT_EQ(fanotify_mark(self->fan, FAN_MARK_ADD | FAN_MARK_MNTNS, + FAN_MNT_ATTACH | FAN_MNT_DETACH, nsfd, NULL), 0); + close(nsfd); + } + + ASSERT_EQ(mount(self->automount, p, NULL, MS_BIND, NULL), 0); + ASSERT_EQ(mount(NULL, p, NULL, MS_SHARED, NULL), 0); + ASSERT_EQ(mount(p, p1, NULL, MS_BIND, NULL), 0); + ASSERT_EQ(mount(NULL, p1, NULL, MS_SLAVE, NULL), 0); + ASSERT_EQ(mount(NULL, p1, NULL, MS_SHARED, NULL), 0); + ASSERT_EQ(mount(p1, p2, NULL, MS_BIND, NULL), 0); + + /* B on P/options, copies on P1/options and P2/options */ + snprintf(r, sizeof(r), "%s/p/options", self->base); + ASSERT_EQ(mount(self->automount, r, NULL, MS_BIND, NULL), 0); + + /* R on top of Bc2 */ + snprintf(r, sizeof(r), "%s/p2/options", self->base); + ASSERT_EQ(mount(NULL, r, NULL, MS_PRIVATE, NULL), 0); + ASSERT_EQ(mount("R", r, "tmpfs", 0, NULL), 0); + ASSERT_EQ(chdir(r), 0); + + /* P, with B on it, below the victim */ + snprintf(cwd, sizeof(cwd), "%s/p1/instances", self->base); + ASSERT_EQ(mount(p, cwd, NULL, MS_MOVE, NULL), 0); + + ASSERT_TRUE(mounted_tmpfs(r)); + ASSERT_EQ(umount2(p1, 0), -1); + EXPECT_EQ(errno, EBUSY); + + /* R is still mounted and still our working directory */ + EXPECT_TRUE(mounted_tmpfs(r)); + ASSERT_NE(getcwd(cwd, sizeof(cwd)), NULL); + EXPECT_STREQ(cwd, r); +} + +/* + * T is shared and Q, a slave of T, has T moved into it, so Q receives + * propagation from its own child. M, another bind mount of the automount, is + * on T/options and that is the dentry T sits on in Q. Unmounting M makes T + * the propagated victim at that dentry in Q and takes T along. The shrink + * walk of V has just unmounted M and continues in the children of T. + */ +TEST_F(shrink_submounts, parent_goes_with_child) +{ + char v[PATH_LEN], t[PATH_LEN], q[PATH_LEN], p[PATH_LEN]; + struct stat before, after; + + snprintf(v, sizeof(v), "%s/v", self->base); + snprintf(t, sizeof(t), "%s/v/t", self->base); + snprintf(q, sizeof(q), "%s/v/q", self->base); + ASSERT_EQ(mkdir(v, 0755), 0); + ASSERT_EQ(mount("V", v, "tmpfs", 0, NULL), 0); + ASSERT_EQ(mount(NULL, v, NULL, MS_PRIVATE, NULL), 0); + ASSERT_EQ(stat(v, &before), 0); + ASSERT_EQ(mkdir(t, 0755), 0); + ASSERT_EQ(mkdir(q, 0755), 0); + + /* T shared, M on T/options before anything receives from T */ + ASSERT_EQ(mount(self->automount, t, NULL, MS_BIND, NULL), 0); + ASSERT_EQ(mount(NULL, t, NULL, MS_SHARED, NULL), 0); + snprintf(p, sizeof(p), "%s/v/t/options", self->base); + ASSERT_EQ(mount(self->automount, p, NULL, MS_BIND, NULL), 0); + + /* Q, a slave of T, and T moved into Q at the dentry M sits on */ + ASSERT_EQ(mount(t, q, NULL, MS_BIND, NULL), 0); + ASSERT_EQ(mount(NULL, q, NULL, MS_SLAVE, NULL), 0); + snprintf(p, sizeof(p), "%s/v/q/options", self->base); + ASSERT_EQ(mount(t, p, NULL, MS_MOVE, NULL), 0); + + /* M, T and then Q go, V is empty and can be unmounted */ + ASSERT_EQ(umount2(v, 0), 0); + ASSERT_EQ(stat(v, &after), 0); + EXPECT_NE(before.st_dev, after.st_dev); +} + +TEST_HARNESS_MAIN From c640a01a78e42302879006ed7b7d5a9a943ba815 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Wed, 30 Sep 2026 15:31:56 +0200 Subject: [PATCH 087/367] namespace: check a recursive bind mount for mount namespace loops do_loopback() refuses to bind mount the file of a mount namespace that is as old as the caller's or older because a mount namespace that holds a mount of its own file, or of an ancestor's, would cause a cycle. But it only checks the dentry the bind mount starts from. With MS_REC everything below it is copied and do_loopback() passes CL_COPY_MNT_NS_FILE so mount namespace files below the source are copied. That's fine for a source in the caller's own mount namespace. Every mount namespace file in there passed the same check when it was bind-mounted. It isn't fine for a source in another mount namespace. may_copy_tree() accepts a bind mount of any nsfs or pidfs file no matter what mount namespace it lives in so that /proc//ns/ and pidfds can be bind mounted. If we create a bind-mount stack of mount namespaces file descriptors the kernel will copy them irrespective of their ancestoral relationship to the mount namespace in question: 151 146 0:7 net:[4026531833] /tmp/nrc/y rw - nsfs nsfs rw 152 151 0:7 mnt:[4026532293] /tmp/nrc/y rw - nsfs nsfs rw Mount 152 is a mount of the mount namespace file of mount namespace 4026532293 inside mount namespace 4026532293. That namespace and every mount in it are leaked... All it takes is a process in an older mount namespace that stacks the file and repeating it leaks without limit: after control: Shmem: 380 kB child: mount(/proc/self/fd/6, MS_BIND|MS_REC): ok after cycle: Shmem: 65916 kB do_move_mount() runs check_for_nsfs_mounts() over a detached tree for exactly that reason. Do the same for the copy before it is grafted. Fixes: e149ed2b805f ("take the targets of /proc/*/ns/* symlinks to separate fs") Fixes: ef4144ac2dec ("pidfs: allow bind-mounts") Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260930-work-mount-fixes-3-v1-4-be34c83956ae@kernel.org Signed-off-by: Christian Brauner (Amutable) --- fs/namespace.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/fs/namespace.c b/fs/namespace.c index 23d3bfa9c14d..c2f54636ec9d 100644 --- a/fs/namespace.c +++ b/fs/namespace.c @@ -3055,7 +3055,11 @@ static int do_loopback(const struct path *path, const char *old_name, if (IS_ERR(mnt)) return PTR_ERR(mnt); - err = graft_tree(mnt, &mp); + /* the copy may carry mount namespace files from below the source */ + if (recurse && !check_for_nsfs_mounts(mnt)) + err = -EINVAL; + else + err = graft_tree(mnt, &mp); if (err) { lock_mount_hash(); umount_tree(mnt, UMOUNT_SYNC); From 7c07b183000eb0ae21408581df88cffbcd38d8a0 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Wed, 30 Sep 2026 15:31:57 +0200 Subject: [PATCH 088/367] selftests/filesystems: check that a recursive bind mount can't pin the caller's mount namespace Add a test for a recursive bind mount of a namespace file in another mount namespace: - a bind mount of the network namespace file, held through a descriptor - the caller's own mount namespace file stacked on top of it there - the recursive bind mount through the descriptor fails with EINVAL - a plain bind mount of the file still works The copy would pin the namespace it is put in otherwise. Link: https://patch.msgid.link/20260930-work-mount-fixes-3-v1-5-be34c83956ae@kernel.org Signed-off-by: Christian Brauner (Amutable) --- .../filesystems/mount_cycle/.gitignore | 1 + .../filesystems/mount_cycle/Makefile | 1 + .../mount_cycle/nsfs_rbind_loop_test.c | 193 ++++++++++++++++++ 3 files changed, 195 insertions(+) create mode 100644 tools/testing/selftests/filesystems/mount_cycle/nsfs_rbind_loop_test.c diff --git a/tools/testing/selftests/filesystems/mount_cycle/.gitignore b/tools/testing/selftests/filesystems/mount_cycle/.gitignore index 03ca95de7765..8cd722977a32 100644 --- a/tools/testing/selftests/filesystems/mount_cycle/.gitignore +++ b/tools/testing/selftests/filesystems/mount_cycle/.gitignore @@ -1,3 +1,4 @@ # SPDX-License-Identifier: GPL-2.0-only unmounted_tree_test overmount_reparent_test +nsfs_rbind_loop_test diff --git a/tools/testing/selftests/filesystems/mount_cycle/Makefile b/tools/testing/selftests/filesystems/mount_cycle/Makefile index 88271c82d1c1..32e26336b132 100644 --- a/tools/testing/selftests/filesystems/mount_cycle/Makefile +++ b/tools/testing/selftests/filesystems/mount_cycle/Makefile @@ -1,5 +1,6 @@ # SPDX-License-Identifier: GPL-2.0 TEST_GEN_PROGS := unmounted_tree_test overmount_reparent_test +TEST_GEN_PROGS += nsfs_rbind_loop_test CFLAGS += -Wall -O2 -g $(KHDR_INCLUDES) diff --git a/tools/testing/selftests/filesystems/mount_cycle/nsfs_rbind_loop_test.c b/tools/testing/selftests/filesystems/mount_cycle/nsfs_rbind_loop_test.c new file mode 100644 index 000000000000..0928a584eddc --- /dev/null +++ b/tools/testing/selftests/filesystems/mount_cycle/nsfs_rbind_loop_test.c @@ -0,0 +1,193 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * A recursive bind mount of a namespace file that lives in another mount + * namespace copies whatever is stacked on top of it there. If that includes + * the file of the caller's own mount namespace, or of an older one, the copy + * would pin the namespace it is put in forever. The bind mount has to be + * refused, a plain bind mount of the file itself still works. + */ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../../kselftest_harness.h" + +#define DIR_LEN 64 +#define PATH_LEN 128 + +/* Child exit codes. */ +enum { + CHILD_OK, + CHILD_UNSHARE, /* could not create the newer mount namespace */ + CHILD_PIPE, /* the parent went away */ + CHILD_TMPFS, /* could not mount the tmpfs in the new namespace */ + CHILD_REC_ALLOWED, /* the recursive bind mount was not refused */ + CHILD_REC_ERRNO, /* it was refused with the wrong error */ + CHILD_PLAIN_REFUSED, /* the plain bind mount of the file was refused */ +}; + +static int write_file(const char *path, const char *s) +{ + ssize_t n = -1; + int fd; + + fd = open(path, O_WRONLY | O_CLOEXEC); + if (fd >= 0) { + n = write(fd, s, strlen(s)); + close(fd); + } + return n == (ssize_t)strlen(s) ? 0 : -1; +} + +static int create_file(const char *path) +{ + int fd = open(path, O_WRONLY | O_CREAT | O_CLOEXEC, 0644); + + if (fd < 0) + return -1; + close(fd); + return 0; +} + +/* Become root in a new user namespace with a private mount namespace. */ +static int enter_userns(void) +{ + uid_t uid = getuid(); + gid_t gid = getgid(); + char map[32]; + + if (unshare(CLONE_NEWUSER | CLONE_NEWNS)) + return -1; + if (write_file("/proc/self/setgroups", "deny") && errno != ENOENT) + return -1; + snprintf(map, sizeof(map), "0 %d 1", uid); + if (write_file("/proc/self/uid_map", map)) + return -1; + snprintf(map, sizeof(map), "0 %d 1", gid); + if (write_file("/proc/self/gid_map", map)) + return -1; + if (setgid(0) || setuid(0)) + return -1; + return mount("", "/", NULL, MS_REC | MS_PRIVATE, NULL); +} + +static int send_msg(int fd, char msg) +{ + return write(fd, &msg, 1) == 1 ? 0 : -1; +} + +static char recv_msg(int fd) +{ + char msg; + + if (read(fd, &msg, 1) != 1) + return 0; + return msg; +} + +FIXTURE(nsfs_rbind_loop) { + char dir[DIR_LEN]; + char x[PATH_LEN]; +}; + +FIXTURE_SETUP(nsfs_rbind_loop) +{ + snprintf(self->dir, sizeof(self->dir), "/tmp/nsfs_rbind_loop.XXXXXX"); + ASSERT_NE(mkdtemp(self->dir), NULL); + if (enter_userns()) { + rmdir(self->dir); + SKIP(return, "test requires user namespaces"); + } + ASSERT_EQ(mount("tmpfs", self->dir, "tmpfs", 0, NULL), 0); + snprintf(self->x, sizeof(self->x), "%s/x", self->dir); + ASSERT_EQ(create_file(self->x), 0); +} + +FIXTURE_TEARDOWN(nsfs_rbind_loop) +{ + umount2(self->dir, MNT_DETACH); + rmdir(self->dir); +} + +/* + * The child in the newer mount namespace binds the network namespace file + * mount of the parent through @fd. Recursively that would copy the mount of + * its own mount namespace file that the parent stacked on top. + */ +static int newer_ns_child(const char *dir, int fd, int to_parent, int from_parent) +{ + char src[32], y[PATH_LEN]; + + if (unshare(CLONE_NEWNS)) + return CHILD_UNSHARE; + if (send_msg(to_parent, 'r') || recv_msg(from_parent) != 'g') + return CHILD_PIPE; + + snprintf(y, sizeof(y), "%s/y", dir); + if (mount("tmpfs", y, "tmpfs", 0, NULL)) + return CHILD_TMPFS; + snprintf(src, sizeof(src), "/proc/self/fd/%d", fd); + snprintf(y, sizeof(y), "%s/y/f", dir); + if (create_file(y)) + return CHILD_TMPFS; + + if (!mount(src, y, NULL, MS_BIND | MS_REC, NULL)) + return CHILD_REC_ALLOWED; + if (errno != EINVAL) + return CHILD_REC_ERRNO; + if (mount(src, y, NULL, MS_BIND, NULL)) + return CHILD_PLAIN_REFUSED; + umount2(y, MNT_DETACH); + return CHILD_OK; +} + +TEST_F(nsfs_rbind_loop, own_ns_file_below_foreign_source) +{ + int to_child[2], to_parent[2], fd, status; + char p[PATH_LEN]; + pid_t pid; + + snprintf(p, sizeof(p), "%s/y", self->dir); + ASSERT_EQ(mkdir(p, 0755), 0); + + /* M, a mount of our network namespace file, held by a descriptor */ + ASSERT_EQ(mount("/proc/self/ns/net", self->x, NULL, MS_BIND, NULL), 0); + fd = open(self->x, O_PATH | O_CLOEXEC); + ASSERT_GE(fd, 0); + + ASSERT_EQ(pipe(to_child), 0); + ASSERT_EQ(pipe(to_parent), 0); + pid = fork(); + ASSERT_GE(pid, 0); + if (pid == 0) { + close(to_child[1]); + close(to_parent[0]); + _exit(newer_ns_child(self->dir, fd, to_parent[1], to_child[0])); + } + close(to_child[0]); + close(to_parent[1]); + ASSERT_EQ(recv_msg(to_parent[0]), 'r'); + + /* the child's mount namespace file on top of M */ + snprintf(p, sizeof(p), "/proc/%d/ns/mnt", pid); + ASSERT_EQ(mount(p, self->x, NULL, MS_BIND, NULL), 0); + + ASSERT_EQ(send_msg(to_child[1], 'g'), 0); + ASSERT_EQ(waitpid(pid, &status, 0), pid); + ASSERT_TRUE(WIFEXITED(status)); + ASSERT_EQ(WEXITSTATUS(status), CHILD_OK); + + close(fd); + ASSERT_EQ(umount2(self->x, MNT_DETACH), 0); + ASSERT_EQ(umount2(self->x, MNT_DETACH), 0); +} + +TEST_HARNESS_MAIN From 0febe278ec45b1ccdbe9bd5ff1ba365d8fd55e2a Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Wed, 30 Sep 2026 15:31:58 +0200 Subject: [PATCH 089/367] namespace: keep covered mounts covered in OPEN_TREE_NAMESPACE open_tree(OPEN_TREE_NAMESPACE) creates a new mount namespace from a copy of the tree at the given path. The caller needs to be privileged over its current user namespace because the new mount namespace will be owned by it. It doesn't need to be privileged over the mount namespace the tree is copied from. An unprivileged user just needs to create a user namespace first. The copy follows the rules of a detached bind mount. Without AT_RECURSIVE only the mount itself is cloned and its children are left out. With AT_RECURSIVE unbindable mounts are skipped. Both is fine when the caller has privileges over the source mount namespace because it could unmount those mounts anyway. Not so for an unprivileged user in that mount namespace. For them a mount namespace copy is what unshare() does. copy_mnt_ns() copies everything including unbindable mounts and lock_mnt_tree() makes sure nothing can be unmounted in the copy. Nothing that was covered gets revealed. create_new_namespace() only does the locking and so the covered content is right there in the new mount namespace: # mount -t tmpfs none /tmp/otn; mkdir /tmp/otn/covered # echo hidden > /tmp/otn/covered/under.txt # mount -t tmpfs none /tmp/otn/covered uid 1000, after unshare(CLONE_NEWUSER): fd = open_tree(AT_FDCWD, "/tmp/otn", OPEN_TREE_NAMESPACE); setns(fd, CLONE_NEWNS); open("/covered/under.txt", O_RDONLY) -> "hidden" Covering paths with mounts is how container runtimes mask parts of /proc and /sys and how admins hide things. When the caller's user namespace doesn't own the source mount namespace copy the way unshare() does and refuse a non-recursive copy of a mount that has anything mounted below the requested directory and copy unbindable mounts in a recursive copy. A caller that is privileged over the source mount namespace sees no change. Fixes: 9b8a0ba68246 ("mount: add OPEN_TREE_NAMESPACE") Cc: stable@vger.kernel.org # v7.0+ Link: https://patch.msgid.link/20260930-work-mount-fixes-3-v1-6-be34c83956ae@kernel.org Signed-off-by: Christian Brauner (Amutable) --- fs/namespace.c | 30 ++++++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/fs/namespace.c b/fs/namespace.c index c2f54636ec9d..de3900dd02f6 100644 --- a/fs/namespace.c +++ b/fs/namespace.c @@ -2369,6 +2369,18 @@ bool has_locked_children(struct mount *mnt, struct dentry *dentry) return __has_locked_children(mnt, dentry); } +/* locks: namespace_shared && pinned(mnt) || mount_locked_reader */ +static bool __has_children(struct mount *mnt, struct dentry *dentry) +{ + struct mount *child; + + list_for_each_entry(child, &mnt->mnt_mounts, mnt_child) { + if (is_subdir(child->mnt_mountpoint, dentry)) + return true; + } + return false; +} + /* * Check that there aren't references to earlier/same mount namespaces in the * specified subtree. Such references can act as pins for mount namespaces @@ -3141,12 +3153,18 @@ static struct mnt_namespace *create_new_namespace(struct path *path, struct mount *mnt; unsigned int copy_flags = 0; bool locked = false, recurse = flags & MOUNT_COPY_RECURSIVE; + bool foreign = user_ns != ns->user_ns; if (unlikely(!d_can_lookup(path->dentry))) return ERR_PTR(-ENOTDIR); - if (user_ns != ns->user_ns) - copy_flags |= CL_SLAVE; + /* + * Without privileges over the mount namespace the copy is made from + * nothing mounted below @path may be left out. It would reveal what + * it covers. That's what unshare() gives such a caller as well. + */ + if (foreign) + copy_flags |= CL_SLAVE | CL_COPY_UNBINDABLE; new_ns = alloc_mnt_ns(user_ns, false); if (IS_ERR(new_ns)) @@ -3180,10 +3198,14 @@ static struct mnt_namespace *create_new_namespace(struct path *path, /* * We don't emulate unshare()ing a mount namespace. We stick to * the restrictions of creating detached bind-mounts. It has a - * lot saner and simpler semantics. + * lot saner and simpler semantics. A caller without privileges + * over the mount namespace can't leave out any child though. */ if (flags & MOUNT_COPY_NEW) mnt = clone_mnt(real_mount(path->mnt), path->dentry, copy_flags); + else if (foreign && !recurse && + __has_children(real_mount(path->mnt), path->dentry)) + mnt = ERR_PTR(-EINVAL); else mnt = __do_loopback(path, recurse, copy_flags); scoped_guard(mount_writer) { @@ -3200,7 +3222,7 @@ static struct mnt_namespace *create_new_namespace(struct path *path, * of the real rootfs we created. */ attach_mnt(mnt, new_ns_root, mp.mp); - if (user_ns != ns->user_ns) + if (foreign) lock_mnt_tree(new_ns_root); } From fe187cbb48323aecb6fe58906341bc6478b6f2f0 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Wed, 30 Sep 2026 15:31:59 +0200 Subject: [PATCH 090/367] selftests/filesystems: check that OPEN_TREE_NAMESPACE keeps mounts covered Add a test for open_tree(OPEN_TREE_NAMESPACE) from a user namespace that doesn't own the mount namespace it copies from: - a file covered by a private or an unbindable tmpfs mount - the non-recursive copy of the parent mount fails with EINVAL - the recursive copy keeps the file covered in the new mount namespace - the owner of the mount namespace keeps the bind mount semantics Link: https://patch.msgid.link/20260930-work-mount-fixes-3-v1-7-be34c83956ae@kernel.org Signed-off-by: Christian Brauner (Amutable) --- .../filesystems/open_tree_ns/.gitignore | 1 + .../filesystems/open_tree_ns/Makefile | 2 +- .../open_tree_ns/open_tree_ns_covered_test.c | 195 ++++++++++++++++++ 3 files changed, 197 insertions(+), 1 deletion(-) create mode 100644 tools/testing/selftests/filesystems/open_tree_ns/open_tree_ns_covered_test.c diff --git a/tools/testing/selftests/filesystems/open_tree_ns/.gitignore b/tools/testing/selftests/filesystems/open_tree_ns/.gitignore index fb12b93fbcaa..76f95c0ae5ef 100644 --- a/tools/testing/selftests/filesystems/open_tree_ns/.gitignore +++ b/tools/testing/selftests/filesystems/open_tree_ns/.gitignore @@ -1 +1,2 @@ open_tree_ns_test +open_tree_ns_covered_test diff --git a/tools/testing/selftests/filesystems/open_tree_ns/Makefile b/tools/testing/selftests/filesystems/open_tree_ns/Makefile index 4976ed1d7d4a..fb2aa77b6edb 100644 --- a/tools/testing/selftests/filesystems/open_tree_ns/Makefile +++ b/tools/testing/selftests/filesystems/open_tree_ns/Makefile @@ -1,5 +1,5 @@ # SPDX-License-Identifier: GPL-2.0 -TEST_GEN_PROGS := open_tree_ns_test +TEST_GEN_PROGS := open_tree_ns_test open_tree_ns_covered_test CFLAGS += -Wall -O0 -g $(KHDR_INCLUDES) $(TOOLS_INCLUDES) LDLIBS := -lcap diff --git a/tools/testing/selftests/filesystems/open_tree_ns/open_tree_ns_covered_test.c b/tools/testing/selftests/filesystems/open_tree_ns/open_tree_ns_covered_test.c new file mode 100644 index 000000000000..27f5cddfb9ef --- /dev/null +++ b/tools/testing/selftests/filesystems/open_tree_ns/open_tree_ns_covered_test.c @@ -0,0 +1,195 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * open_tree(OPEN_TREE_NAMESPACE) by a caller that isn't privileged over the + * mount namespace it copies from must not reveal what the mounts below the + * copied mount cover. Without AT_RECURSIVE the copy is refused when there's + * anything mounted below the requested directory. With AT_RECURSIVE + * unbindable mounts are copied as well. + */ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../wrappers.h" +#include "../../kselftest_harness.h" + +#ifndef OPEN_TREE_NAMESPACE +#define OPEN_TREE_NAMESPACE (1 << 1) +#endif + +#define DIR_LEN 64 +#define PATH_LEN 128 + +/* Child exit codes. */ +enum { + CHILD_OK, + CHILD_USERNS, /* could not create the child user namespace */ + CHILD_NONREC_ALLOWED, /* the non-recursive copy was not refused */ + CHILD_NONREC_ERRNO, /* it was refused with the wrong error */ + CHILD_REC_REFUSED, /* the recursive copy failed */ + CHILD_SETNS, /* could not enter the new mount namespace */ + CHILD_NO_COVER, /* the covering mount is missing in the copy */ + CHILD_REVEALED, /* the covered file is visible in the copy */ +}; + +static int write_file(const char *path, const char *s) +{ + ssize_t n = -1; + int fd; + + fd = open(path, O_WRONLY | O_CLOEXEC); + if (fd >= 0) { + n = write(fd, s, strlen(s)); + close(fd); + } + return n == (ssize_t)strlen(s) ? 0 : -1; +} + +static int create_file(const char *path, const char *s) +{ + ssize_t n = -1; + int fd; + + fd = open(path, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0644); + if (fd >= 0) { + n = write(fd, s, strlen(s)); + close(fd); + } + return n == (ssize_t)strlen(s) ? 0 : -1; +} + +/* Become root in a new user namespace, the uid @uid is mapped to 0. */ +static int enter_userns(uid_t uid, gid_t gid) +{ + char map[32]; + + if (unshare(CLONE_NEWUSER)) + return -1; + if (write_file("/proc/self/setgroups", "deny") && errno != ENOENT) + return -1; + snprintf(map, sizeof(map), "0 %d 1", uid); + if (write_file("/proc/self/uid_map", map)) + return -1; + snprintf(map, sizeof(map), "0 %d 1", gid); + if (write_file("/proc/self/gid_map", map)) + return -1; + return setgid(0) || setuid(0) ? -1 : 0; +} + +FIXTURE(open_tree_ns_covered) { + char dir[DIR_LEN]; + char cover[PATH_LEN]; +}; + +FIXTURE_VARIANT(open_tree_ns_covered) { + int propagation; +}; + +FIXTURE_VARIANT_ADD(open_tree_ns_covered, private_cover) { + .propagation = MS_PRIVATE, +}; + +FIXTURE_VARIANT_ADD(open_tree_ns_covered, unbindable_cover) { + .propagation = MS_UNBINDABLE, +}; + +/* + * Root in a user namespace owns a private mount namespace with a tmpfs + * on @dir and a second tmpfs covering @dir/covered/under.txt. + */ +FIXTURE_SETUP(open_tree_ns_covered) +{ + char p[PATH_LEN]; + int fd; + + snprintf(self->dir, sizeof(self->dir), "/tmp/open_tree_ns_covered.XXXXXX"); + ASSERT_NE(mkdtemp(self->dir), NULL); + if (enter_userns(getuid(), getgid()) || unshare(CLONE_NEWNS)) { + rmdir(self->dir); + SKIP(return, "test requires user namespaces"); + } + ASSERT_EQ(mount("", "/", NULL, MS_REC | MS_PRIVATE, NULL), 0); + ASSERT_EQ(mount("tmpfs", self->dir, "tmpfs", 0, NULL), 0); + + /* the flag has to exist before its rules can be checked */ + fd = sys_open_tree(AT_FDCWD, self->dir, + OPEN_TREE_NAMESPACE | OPEN_TREE_CLOEXEC); + if (fd < 0 && (errno == EINVAL || errno == ENOSYS)) { + umount2(self->dir, MNT_DETACH); + rmdir(self->dir); + SKIP(return, "OPEN_TREE_NAMESPACE not supported"); + } + ASSERT_GE(fd, 0); + close(fd); + + snprintf(self->cover, sizeof(self->cover), "%s/covered", self->dir); + ASSERT_EQ(mkdir(self->cover, 0755), 0); + snprintf(p, sizeof(p), "%s/covered/under.txt", self->dir); + ASSERT_EQ(create_file(p, "hidden"), 0); + ASSERT_EQ(mount("tmpfs", self->cover, "tmpfs", 0, NULL), 0); + ASSERT_EQ(mount(NULL, self->cover, NULL, variant->propagation, NULL), 0); +} + +FIXTURE_TEARDOWN(open_tree_ns_covered) +{ + umount2(self->dir, MNT_DETACH); + rmdir(self->dir); +} + +/* A caller in a new user namespace that doesn't own the mount namespace. */ +static int foreign_child(const char *dir) +{ + struct stat st; + int fd; + + if (enter_userns(0, 0)) + return CHILD_USERNS; + + fd = sys_open_tree(AT_FDCWD, dir, OPEN_TREE_NAMESPACE | OPEN_TREE_CLOEXEC); + if (fd >= 0) + return CHILD_NONREC_ALLOWED; + if (errno != EINVAL) + return CHILD_NONREC_ERRNO; + + fd = sys_open_tree(AT_FDCWD, dir, + OPEN_TREE_NAMESPACE | OPEN_TREE_CLOEXEC | AT_RECURSIVE); + if (fd < 0) + return CHILD_REC_REFUSED; + if (setns(fd, CLONE_NEWNS)) + return CHILD_SETNS; + if (stat("/covered", &st)) + return CHILD_NO_COVER; + if (!access("/covered/under.txt", F_OK) || errno != ENOENT) + return CHILD_REVEALED; + return CHILD_OK; +} + +TEST_F(open_tree_ns_covered, foreign_user_namespace) +{ + int status, fd; + pid_t pid; + + pid = fork(); + ASSERT_GE(pid, 0); + if (pid == 0) + _exit(foreign_child(self->dir)); + ASSERT_EQ(waitpid(pid, &status, 0), pid); + ASSERT_TRUE(WIFEXITED(status)); + ASSERT_EQ(WEXITSTATUS(status), CHILD_OK); + + /* the owner of the mount namespace keeps bind mount semantics */ + fd = sys_open_tree(AT_FDCWD, self->dir, + OPEN_TREE_NAMESPACE | OPEN_TREE_CLOEXEC); + ASSERT_GE(fd, 0); + close(fd); +} + +TEST_HARNESS_MAIN From 8714b44adac7b10506e1db3ef1710689751f0f10 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Wed, 30 Sep 2026 15:32:00 +0200 Subject: [PATCH 091/367] namespace: look at the topmost mount for a mount namespace file attach_recursive_mnt() preallocates a mountpoint for the root of the topmost mount of the source so that a mount that already sits at the destination can be put on top of the source or on top of one of its propagated copies. The copies are made without CL_COPY_MNT_NS_FILE so their chain of overmounts is shorter than the source's. It ends below the first bind mount of a mount namespace file. So while the loop walks up the chain of the source it remembers the mountpoint of that mount in "shorter" and the existing mount is put there for the copies. But the loop ends at the topmost mount without ever looking at it. If the topmost mount is the only mount namespace file in the chain "shorter" stays NULL and mnt_change_mountpoint() attaches the existing mount of the copy below the root of the mount namespace file. That's a dentry of nsfs. No path walk in the copy's mount namespace ever gets there. The mount is gone until its parent goes and the copy that took its place can't be unmounted synchronously because it has a child: S bind mount of a file N bind mount of a mount namespace file on top of S A shared mount, B a slave of A, Q mounted on B/file move_mount(S, A/file) cat B/file -> the content of S instead of Q umount B/file -> EBUSY Look at every mount of the chain including the topmost one. Fixes: 96f5d2e05165 ("attach_recursive_mnt(): unify the mnt_change_mountpoint() logics") Cc: stable@vger.kernel.org # v6.17+ Link: https://patch.msgid.link/20260930-work-mount-fixes-3-v1-8-be34c83956ae@kernel.org Signed-off-by: Christian Brauner (Amutable) --- fs/namespace.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/fs/namespace.c b/fs/namespace.c index de3900dd02f6..f66f4609ef9d 100644 --- a/fs/namespace.c +++ b/fs/namespace.c @@ -2617,9 +2617,11 @@ static int attach_recursive_mnt(struct mount *source_mnt, * Preallocate a mountpoint in case the new mounts need to be * mounted beneath mounts on the same mountpoint. */ - for (top = source_mnt; unlikely(top->overmount); top = top->overmount) { + for (top = source_mnt; ; top = top->overmount) { if (!shorter && is_mnt_ns_file(top->mnt.mnt_root)) shorter = top->mnt_mp; + if (likely(!top->overmount)) + break; } err = get_mountpoint(top->mnt.mnt_root, &root); if (err) From c518a195f631cbf03444078d564d378cd9fa4a32 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Wed, 30 Sep 2026 15:32:01 +0200 Subject: [PATCH 092/367] selftests/filesystems: check that a mount namespace file on top doesn't bury a mount Add a test for moving a mount with a mount namespace file on top of it: - S, a bind mount of a file, with a newer mount namespace's file on top - A shared with a slave B that has Q on B/file - S moved onto A/file, its copy lands on B/file below Q B/file keeps reading Q and once Q is unmounted it reads the copy. Link: https://patch.msgid.link/20260930-work-mount-fixes-3-v1-9-be34c83956ae@kernel.org Signed-off-by: Christian Brauner (Amutable) --- .../filesystems/mount_cycle/.gitignore | 1 + .../filesystems/mount_cycle/Makefile | 2 +- .../mount_cycle/overmount_ns_file_test.c | 190 ++++++++++++++++++ 3 files changed, 192 insertions(+), 1 deletion(-) create mode 100644 tools/testing/selftests/filesystems/mount_cycle/overmount_ns_file_test.c diff --git a/tools/testing/selftests/filesystems/mount_cycle/.gitignore b/tools/testing/selftests/filesystems/mount_cycle/.gitignore index 8cd722977a32..d11f5b720d5b 100644 --- a/tools/testing/selftests/filesystems/mount_cycle/.gitignore +++ b/tools/testing/selftests/filesystems/mount_cycle/.gitignore @@ -2,3 +2,4 @@ unmounted_tree_test overmount_reparent_test nsfs_rbind_loop_test +overmount_ns_file_test diff --git a/tools/testing/selftests/filesystems/mount_cycle/Makefile b/tools/testing/selftests/filesystems/mount_cycle/Makefile index 32e26336b132..49a8402ca858 100644 --- a/tools/testing/selftests/filesystems/mount_cycle/Makefile +++ b/tools/testing/selftests/filesystems/mount_cycle/Makefile @@ -1,6 +1,6 @@ # SPDX-License-Identifier: GPL-2.0 TEST_GEN_PROGS := unmounted_tree_test overmount_reparent_test -TEST_GEN_PROGS += nsfs_rbind_loop_test +TEST_GEN_PROGS += nsfs_rbind_loop_test overmount_ns_file_test CFLAGS += -Wall -O2 -g $(KHDR_INCLUDES) diff --git a/tools/testing/selftests/filesystems/mount_cycle/overmount_ns_file_test.c b/tools/testing/selftests/filesystems/mount_cycle/overmount_ns_file_test.c new file mode 100644 index 000000000000..ea1b33fa0bf8 --- /dev/null +++ b/tools/testing/selftests/filesystems/mount_cycle/overmount_ns_file_test.c @@ -0,0 +1,190 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * A mount namespace file bind-mounted on top of the mount that is moved + * onto a shared mount isn't copied to the peers and slaves. The mount that + * already sits at the destination in a slave has to end up on top of the + * propagated copy, not below the root of the mount namespace file where no + * path walk ever finds it. + */ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../wrappers.h" +#include "../../kselftest_harness.h" + +#define DIR_LEN 64 +#define PATH_LEN 128 + +static int write_file(const char *path, const char *s) +{ + ssize_t n = -1; + int fd; + + fd = open(path, O_WRONLY | O_CLOEXEC); + if (fd >= 0) { + n = write(fd, s, strlen(s)); + close(fd); + } + return n == (ssize_t)strlen(s) ? 0 : -1; +} + +static int create_file(const char *path, const char *s) +{ + ssize_t n = -1; + int fd; + + fd = open(path, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0644); + if (fd >= 0) { + n = write(fd, s, strlen(s)); + close(fd); + } + return n == (ssize_t)strlen(s) ? 0 : -1; +} + +/* the first bytes of the file at @path, "" if it can't be read */ +static const char *read_file(const char *path, char *buf, size_t len) +{ + ssize_t n = -1; + int fd; + + fd = open(path, O_RDONLY | O_CLOEXEC); + if (fd >= 0) { + n = read(fd, buf, len - 1); + close(fd); + } + buf[n > 0 ? n : 0] = '\0'; + return buf; +} + +/* Become root in a new user namespace with a private mount namespace. */ +static int enter_userns(void) +{ + uid_t uid = getuid(); + gid_t gid = getgid(); + char map[32]; + + if (unshare(CLONE_NEWUSER | CLONE_NEWNS)) + return -1; + if (write_file("/proc/self/setgroups", "deny") && errno != ENOENT) + return -1; + snprintf(map, sizeof(map), "0 %d 1", uid); + if (write_file("/proc/self/uid_map", map)) + return -1; + snprintf(map, sizeof(map), "0 %d 1", gid); + if (write_file("/proc/self/gid_map", map)) + return -1; + if (setgid(0) || setuid(0)) + return -1; + return mount("", "/", NULL, MS_REC | MS_PRIVATE, NULL); +} + +FIXTURE(overmount_ns_file) { + char dir[DIR_LEN]; + pid_t child; +}; + +FIXTURE_SETUP(overmount_ns_file) +{ + self->child = -1; + snprintf(self->dir, sizeof(self->dir), "/tmp/overmount_ns_file.XXXXXX"); + ASSERT_NE(mkdtemp(self->dir), NULL); + if (enter_userns()) { + rmdir(self->dir); + SKIP(return, "test requires user namespaces"); + } + ASSERT_EQ(mount("tmpfs", self->dir, "tmpfs", 0, NULL), 0); +} + +FIXTURE_TEARDOWN(overmount_ns_file) +{ + if (self->child > 0) { + kill(self->child, SIGKILL); + waitpid(self->child, NULL, 0); + } + umount2(self->dir, MNT_DETACH); + rmdir(self->dir); +} + +/* + * A is a shared tmpfs and B its slave with Q, a bind mount of a file, on + * B/file. S is a bind mount of a file with N, a bind mount of a newer mount + * namespace's file, on top of it. S is moved onto A/file. Its copy S' lands + * on B/file below Q, without N. B/file keeps reading Q and once Q is + * unmounted it reads S'. + */ +TEST_F(overmount_ns_file, existing_mount_stays_on_top) +{ + char a[PATH_LEN], b[PATH_LEN], s[PATH_LEN], p[PATH_LEN], buf[16]; + int fd, pfd[2]; + char c; + + snprintf(a, sizeof(a), "%s/A", self->dir); + snprintf(b, sizeof(b), "%s/B", self->dir); + snprintf(s, sizeof(s), "%s/s", self->dir); + ASSERT_EQ(mkdir(a, 0755), 0); + ASSERT_EQ(mkdir(b, 0755), 0); + ASSERT_EQ(mkdir(s, 0755), 0); + + /* A shared, B its slave, Q on B/file */ + ASSERT_EQ(mount("tmpfs", a, "tmpfs", 0, NULL), 0); + ASSERT_EQ(mount(NULL, a, NULL, MS_SHARED, NULL), 0); + snprintf(p, sizeof(p), "%s/A/file", self->dir); + ASSERT_EQ(create_file(p, "A"), 0); + ASSERT_EQ(mount(a, b, NULL, MS_BIND, NULL), 0); + ASSERT_EQ(mount(NULL, b, NULL, MS_SLAVE, NULL), 0); + snprintf(p, sizeof(p), "%s/Q", self->dir); + ASSERT_EQ(create_file(p, "Q"), 0); + snprintf(b, sizeof(b), "%s/B/file", self->dir); + ASSERT_EQ(mount(p, b, NULL, MS_BIND, NULL), 0); + + /* S on s/f, pinned by a file descriptor before N goes on top */ + ASSERT_EQ(mount("tmpfs", s, "tmpfs", 0, NULL), 0); + snprintf(p, sizeof(p), "%s/s/f", self->dir); + ASSERT_EQ(create_file(p, "f"), 0); + snprintf(s, sizeof(s), "%s/s/S", self->dir); + ASSERT_EQ(create_file(s, "S"), 0); + ASSERT_EQ(mount(s, p, NULL, MS_BIND, NULL), 0); + fd = open(p, O_PATH | O_CLOEXEC); + ASSERT_GE(fd, 0); + + /* a newer mount namespace whose file can be bound */ + ASSERT_EQ(pipe(pfd), 0); + self->child = fork(); + ASSERT_GE(self->child, 0); + if (self->child == 0) { + close(pfd[0]); + if (unshare(CLONE_NEWNS) || write(pfd[1], "r", 1) != 1) + _exit(1); + pause(); + _exit(0); + } + close(pfd[1]); + ASSERT_EQ(read(pfd[0], &c, 1), 1); + close(pfd[0]); + snprintf(s, sizeof(s), "/proc/%d/ns/mnt", self->child); + ASSERT_EQ(mount(s, p, NULL, MS_BIND, NULL), 0); + + ASSERT_STREQ(read_file(b, buf, sizeof(buf)), "Q"); + + snprintf(a, sizeof(a), "%s/A/file", self->dir); + ASSERT_EQ(sys_move_mount(fd, "", AT_FDCWD, a, MOVE_MOUNT_F_EMPTY_PATH), 0); + close(fd); + + /* Q is still on top of the copy in B and can be unmounted */ + EXPECT_STREQ(read_file(b, buf, sizeof(buf)), "Q"); + EXPECT_EQ(umount2(b, 0), 0); + EXPECT_STREQ(read_file(b, buf, sizeof(buf)), "S"); +} + +TEST_HARNESS_MAIN From 6b7c001eb5857fa09041f47673666060a2e5fc1c Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Wed, 30 Sep 2026 15:32:02 +0200 Subject: [PATCH 093/367] namespace: check the mounts before reading their parents in pivot_root() path_pivot_root() reads the parents of new_root and of the caller's root and checks whether they are shared before it checks that either mount is in the caller's mount namespace. Only namespace_sem is held. That's fine for a mount that is in the caller's mount namespace. But new_root can be a file descriptor to a mount that has been unmounted and that only the file descriptor keeps alive. If that mount stayed attached to its parent when it was unmounted nothing pins the parent for it. The final mntput() of the parent unhooks the children under mount_lock alone and frees the parent afterwards: pivot_root() close(fd), last ref on the parent ------------ --------------------------------- ex_parent = new_mnt->mnt_parent mntput_no_expire_slowpath() __umount_mnt(new_mnt) cleanup_mnt() call_rcu() IS_MNT_SHARED(ex_parent) BUG: KASAN: slab-use-after-free in path_pivot_root+0xf1a/0x1840 Read of size 4 at addr ffff8881047382f8 by task pivot_widen/157 path_pivot_root+0xf1a/0x1840 __x64_sys_pivot_root+0x165/0x190 The same goes for the caller's root via chroot(). Both outcomes of the check end in EINVAL so nothing but the read itself goes wrong. It's the same thing commit bb4609405752 ("statmount: read the parent of an unmounted mount under mount_lock") fixed for statmount(). Check that both mounts are in the caller's namespace before their parents are read. Every path returns EINVAL either way. Fixes: e0c9c0afd2fc ("mnt: Update detach_mounts to leave mounts connected") Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260930-work-mount-fixes-3-v1-10-be34c83956ae@kernel.org Signed-off-by: Christian Brauner (Amutable) --- fs/namespace.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/fs/namespace.c b/fs/namespace.c index f66f4609ef9d..0a7d50db228d 100644 --- a/fs/namespace.c +++ b/fs/namespace.c @@ -4780,14 +4780,15 @@ int path_pivot_root(struct path *new, struct path *old) new_mnt = real_mount(new->mnt); root_mnt = real_mount(root.mnt); + /* only a mounted mount has a parent that namespace_sem pins */ + if (!check_mnt(root_mnt) || !check_mnt(new_mnt)) + return -EINVAL; ex_parent = new_mnt->mnt_parent; root_parent = root_mnt->mnt_parent; if (IS_MNT_SHARED(old_mnt) || IS_MNT_SHARED(ex_parent) || IS_MNT_SHARED(root_parent)) return -EINVAL; - if (!check_mnt(root_mnt) || !check_mnt(new_mnt)) - return -EINVAL; if (new_mnt->mnt.mnt_flags & MNT_LOCKED) return -EINVAL; if (d_unlinked(new->dentry)) From d791606bb915a8f27e36657696fd1d9a216c5c08 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Wed, 30 Sep 2026 15:32:03 +0200 Subject: [PATCH 094/367] namespace: don't reconfigure internal superblocks via remount and umount Commit 02587a4af82a ("fs: refuse fspick() on internal superblocks") blocked fspick() on SB_NOUSER superblocks. But that's not the only way to reconfigure_super(). mount(MS_REMOUNT) and umount() of the caller's root without MNT_DETACH. The root of an empty mount namespace is a nullfs mount and all mount namespaces share that superblock: nullfs: fspick: FAIL errno=22 (Invalid argument) nullfs: mount(MS_REMOUNT|MS_RDONLY): ok(0) nullfs after remount: statfs(/): magic=0x4e554c4c flags=0x21 RDONLY nullfs: umount2("/", 0): ok(0) Refuse both like fspick() does. Only root in the initial user namespace can do this and nullfs is empty and immutable so the flags don't buy anything. But they show up in statfs() for the root of every mount namespace on the host. Fixes: 9d4e752a24f7 ("namespace: allow creating empty mount namespaces") Cc: stable@vger.kernel.org # v7.1+ Link: https://patch.msgid.link/20260930-work-mount-fixes-3-v1-11-be34c83956ae@kernel.org Signed-off-by: Christian Brauner (Amutable) --- fs/fs_context.c | 4 ++++ fs/fsopen.c | 3 --- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/fs/fs_context.c b/fs/fs_context.c index 23ad66cd94e1..a50584df97be 100644 --- a/fs/fs_context.c +++ b/fs/fs_context.c @@ -315,6 +315,10 @@ struct fs_context *fs_context_for_reconfigure(struct dentry *dentry, unsigned int sb_flags, unsigned int sb_flags_mask) { + /* kernel-internal superblocks are nobody's to reconfigure */ + if (dentry->d_sb->s_flags & SB_NOUSER) + return ERR_PTR(-EINVAL); + return alloc_fs_context(dentry->d_sb->s_type, dentry, sb_flags, sb_flags_mask, FS_CONTEXT_FOR_RECONFIGURE); } diff --git a/fs/fsopen.c b/fs/fsopen.c index 9d5a7a22b529..ae19e5136598 100644 --- a/fs/fsopen.c +++ b/fs/fsopen.c @@ -190,9 +190,6 @@ SYSCALL_DEFINE3(fspick, int, dfd, const char __user *, path, unsigned int, flags ret = -EINVAL; if (target.mnt->mnt_root != target.dentry) goto err_path; - /* kernel-internal superblocks are nobody's to reconfigure */ - if (target.dentry->d_sb->s_flags & SB_NOUSER) - goto err_path; fc = fs_context_for_reconfigure(target.dentry, 0, 0); if (IS_ERR(fc)) { From 5e83e3a38b4d1ee99b044b968b78e3ca0935bab3 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Wed, 30 Sep 2026 15:32:04 +0200 Subject: [PATCH 095/367] selftests/filesystems: check that the nullfs root can't be reconfigured Add a test for the root of an empty mount namespace: - fspick() of the root fails with EINVAL - mount(MS_REMOUNT) of the root fails with EINVAL - umount() of the root fails and doesn't remount it read-only Link: https://patch.msgid.link/20260930-work-mount-fixes-3-v1-12-be34c83956ae@kernel.org Signed-off-by: Christian Brauner (Amutable) --- .../filesystems/empty_mntns/.gitignore | 1 + .../filesystems/empty_mntns/Makefile | 2 + .../internal_sb_reconfigure_test.c | 108 ++++++++++++++++++ 3 files changed, 111 insertions(+) create mode 100644 tools/testing/selftests/filesystems/empty_mntns/internal_sb_reconfigure_test.c diff --git a/tools/testing/selftests/filesystems/empty_mntns/.gitignore b/tools/testing/selftests/filesystems/empty_mntns/.gitignore index 99f89d329db2..32125b3eaa80 100644 --- a/tools/testing/selftests/filesystems/empty_mntns/.gitignore +++ b/tools/testing/selftests/filesystems/empty_mntns/.gitignore @@ -2,3 +2,4 @@ clone3_empty_mntns_test empty_mntns_test overmount_chroot_test +internal_sb_reconfigure_test diff --git a/tools/testing/selftests/filesystems/empty_mntns/Makefile b/tools/testing/selftests/filesystems/empty_mntns/Makefile index 22e3fb915e81..b64818b962ca 100644 --- a/tools/testing/selftests/filesystems/empty_mntns/Makefile +++ b/tools/testing/selftests/filesystems/empty_mntns/Makefile @@ -4,9 +4,11 @@ CFLAGS += -Wall -O2 -g $(KHDR_INCLUDES) $(TOOLS_INCLUDES) LDLIBS += -lcap TEST_GEN_PROGS := empty_mntns_test overmount_chroot_test clone3_empty_mntns_test +TEST_GEN_PROGS += internal_sb_reconfigure_test include ../../lib.mk $(OUTPUT)/empty_mntns_test: ../utils.c $(OUTPUT)/overmount_chroot_test: ../utils.c $(OUTPUT)/clone3_empty_mntns_test: ../utils.c +$(OUTPUT)/internal_sb_reconfigure_test: ../utils.c diff --git a/tools/testing/selftests/filesystems/empty_mntns/internal_sb_reconfigure_test.c b/tools/testing/selftests/filesystems/empty_mntns/internal_sb_reconfigure_test.c new file mode 100644 index 000000000000..cb645d1e5a9a --- /dev/null +++ b/tools/testing/selftests/filesystems/empty_mntns/internal_sb_reconfigure_test.c @@ -0,0 +1,108 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * The root of an empty mount namespace is a nullfs mount. Its superblock is + * kernel-internal and shared by every mount namespace. It can't be + * reconfigured, neither through fspick() nor through mount(MS_REMOUNT) nor + * through umount() of the root which remounts it read-only. + */ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../utils.h" +#include "../wrappers.h" +#include "empty_mntns.h" +#include "kselftest_harness.h" + +#ifndef __NR_fspick +#define __NR_fspick 433 +#endif + +static int sys_fspick(int dfd, const char *path, unsigned int flags) +{ + return syscall(__NR_fspick, dfd, path, flags); +} + +/* Child exit codes. */ +enum { + CHILD_OK, + CHILD_USERNS, /* could not create the user namespace */ + CHILD_UNSHARE, /* could not create the empty mount namespace */ + CHILD_FSPICK, /* fspick() of the root was not refused with EINVAL */ + CHILD_REMOUNT, /* mount(MS_REMOUNT) was not refused with EINVAL */ + CHILD_UMOUNT, /* umount() of the root succeeded */ + CHILD_STATFS, /* statfs() of the root failed */ + CHILD_RDONLY, /* the root ended up read-only */ +}; + +static int empty_mntns_child(void) +{ + struct statfs st; + + if (enter_userns()) + return CHILD_USERNS; + if (unshare(UNSHARE_EMPTY_MNTNS)) + return CHILD_UNSHARE; + + if (sys_fspick(AT_FDCWD, "/", 0) >= 0 || errno != EINVAL) + return CHILD_FSPICK; + if (!mount(NULL, "/", NULL, MS_REMOUNT | MS_RDONLY, NULL) || + errno != EINVAL) + return CHILD_REMOUNT; + if (!umount2("/", 0)) + return CHILD_UMOUNT; + if (statfs("/", &st)) + return CHILD_STATFS; + if (st.f_flags & ST_RDONLY) + return CHILD_RDONLY; + return CHILD_OK; +} + +FIXTURE(internal_sb_reconfigure) {}; + +FIXTURE_SETUP(internal_sb_reconfigure) +{ + pid_t pid; + int status; + + pid = fork(); + ASSERT_GE(pid, 0); + if (pid == 0) { + if (enter_userns()) + _exit(1); + if (unshare(UNSHARE_EMPTY_MNTNS)) + _exit(1); + _exit(0); + } + ASSERT_EQ(waitpid(pid, &status, 0), pid); + if (!WIFEXITED(status) || WEXITSTATUS(status)) + SKIP(return, "UNSHARE_EMPTY_MNTNS not supported"); +} + +FIXTURE_TEARDOWN(internal_sb_reconfigure) {} + +TEST_F(internal_sb_reconfigure, nullfs_root) +{ + pid_t pid; + int status; + + pid = fork(); + ASSERT_GE(pid, 0); + if (pid == 0) + _exit(empty_mntns_child()); + ASSERT_EQ(waitpid(pid, &status, 0), pid); + ASSERT_TRUE(WIFEXITED(status)); + ASSERT_EQ(WEXITSTATUS(status), CHILD_OK); +} + +TEST_HARNESS_MAIN From 1a116222acdcc1bc1a8350727ae6f28907d44594 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Wed, 30 Sep 2026 15:32:05 +0200 Subject: [PATCH 096/367] namespace: remove the fsnotify marks of a mount namespace in process context mnt_ns_release() drops the last passive reference of a mount namespace and removes its fanotify marks via fsnotify_mntns_delete(). That takes the mutex of every group with a mark on the namespace and the spinlock of the connector. Fine from process context. But mnt_ns_tree_remove() hands the reference the namespace was allocated with to call_rcu() and so the marks are removed from the RCU softirq: BUG: sleeping function called from invalid context at kernel/locking/mutex.c:623 in_atomic(): 1, irqs_disabled(): 0, non_block: 0, pid: 0, name: swapper/3 __mutex_lock+0x113/0x24b0 fsnotify_destroy_marks+0x11b/0x3d0 mnt_ns_release_rcu+0x57/0xa0 rcu_core+0x6b6/0x1e40 and lockdep complains about the connector lock being taken from softirq context. A fanotify group with a FAN_MARK_MNTNS mark on the mount namespace of another task is all that's needed. Root in a user namespace can do that for a mount namespace it owns. The task exits and the namespace is freed with the mark still on it. Remove the marks in free_mnt_ns() before the namespace is handed to RCU. That runs in process context once the last active reference is gone. A mark is added through a file descriptor to the namespace which holds an active reference so no mark can show up after that. Fixes: bf630c401641 ("vfs: add notifications for mount attach and detach") Cc: stable@vger.kernel.org # v6.15+ Link: https://patch.msgid.link/20260930-work-mount-fixes-3-v1-13-be34c83956ae@kernel.org Reviewed-by: Amir Goldstein Signed-off-by: Christian Brauner (Amutable) --- fs/namespace.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/fs/namespace.c b/fs/namespace.c index 0a7d50db228d..3c90d853e091 100644 --- a/fs/namespace.c +++ b/fs/namespace.c @@ -130,7 +130,6 @@ static void mnt_ns_release(struct mnt_namespace *ns) { /* keep alive for {list,stat}mount() */ if (ns && refcount_dec_and_test(&ns->passive)) { - fsnotify_mntns_delete(ns); put_user_ns(ns->user_ns); kfree(ns); } @@ -4279,6 +4278,8 @@ static void free_mnt_ns(struct mnt_namespace *ns) if (!is_anon_ns(ns)) ns_common_free(ns); dec_mnt_namespaces(ns->ucounts); + /* the last active reference is gone, no mark can show up anymore */ + fsnotify_mntns_delete(ns); mnt_ns_tree_remove(ns); } From 75f4197f1e11d2eb75c2d6f916a7d4764db621e5 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Wed, 30 Sep 2026 15:32:07 +0200 Subject: [PATCH 097/367] dcache: don't put a mountpoint on a dentry that's being removed rmdir(), unlink() and rename() call dont_mount() on the victim and then detach_mounts() with the victim's inode locked. do_lock_mount() takes the inode lock of the mountpoint and checks cant_mount() so a mount can't show up after detach_mounts(). But attach_recursive_mnt() makes a second mountpoint for the root of the source mount so that the mounts already located at the destination can be put on top of it. No inode is locked for that one and d_set_mounted() only refuses a dentry that is unlinked. Between detach_mounts() and d_delete() the victim is still hashed: rmrace: b passed dont_mount() and detach_mounts(), sleeping T2: move_mount(S, /tmp/plcant/x, BENEATH) = 0 errno 0 () T1: rmdir(/tmp/plcant/d/b) = 0 errno 0 109 107 0:61 /d/b//deleted /tmp/plcant/x rw,relatime - tmpfs tmpfs rw 108 109 0:63 / /tmp/plcant/x rw,relatime - tmpfs T rw A bind mount of the directory that's being removed is moved beneath an existing mount while the rmdir() is located between the two calls. The mount ends up on the removed directory and nothing will ever detach it. Check cant_mount() in d_set_mounted() as well. dont_mount() raises the flag under d_lock before detach_mounts() runs so either the mountpoint is set first and detach_mounts() finds the mount or the flag is seen and the mount is refused. Fixes: 1064f874abc0 ("mnt: Tuck mounts under others instead of creating shadow/side mounts.") Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260930-work-mount-fixes-3-v1-15-be34c83956ae@kernel.org Signed-off-by: Christian Brauner (Amutable) --- fs/dcache.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/fs/dcache.c b/fs/dcache.c index a66be85f9d01..f44c7b39c3b3 100644 --- a/fs/dcache.c +++ b/fs/dcache.c @@ -1583,6 +1583,8 @@ EXPORT_SYMBOL(path_has_submounts); * * Only one of d_invalidate() and d_set_mounted() must succeed. For * this reason take rename_lock and d_lock on dentry and ancestors. + * Likewise for dont_mount() which marks a dentry that is being removed + * under d_lock. */ int d_set_mounted(struct dentry *dentry) { @@ -1599,7 +1601,7 @@ int d_set_mounted(struct dentry *dentry) spin_unlock(&p->d_lock); } spin_lock(&dentry->d_lock); - if (!d_unlinked(dentry)) { + if (!d_unlinked(dentry) && !cant_mount(dentry)) { ret = -EBUSY; if (!d_mountpoint(dentry)) { dentry->d_flags |= DCACHE_MOUNTED; From 0b6f31a3c0b16066f812e94b52a704174a6f23cd Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Wed, 30 Sep 2026 15:32:08 +0200 Subject: [PATCH 098/367] unshare: don't drop active namespace references that were never taken Active references on the namespaces of an nsproxy are taken when the nsproxy is installed into a task in switch_task_namespaces() and copy_namespaces() and dropped again by put_nsproxy() through deactivate_nsproxy(). But ksys_unshare() calls put_nsproxy() on an nsproxy that was never installed when set_cred_ucounts() fails. The new namespaces of that nsproxy go from zero to minus one and the namespaces shared with the caller lose a reference that belongs to the nsproxy the caller keeps using: WARNING: kernel/nscommon.c:171 at __ns_ref_active_put+0x1cd/0x230 nsproxy_ns_active_put deactivate_nsproxy ksys_unshare Afterwards the namespaces the caller lives in aren't listed by listns() anymore. set_cred_ucounts() only fails when alloc_ucounts() can't allocate and it only allocates when the real uid of the caller differs from its effective uid. Commit cefd55bd2159 ("nsproxy: fix free_nsproxy() and simplify create_new_namespaces()") separated the two cases on purpose. nsproxy_free() frees an nsproxy that was prepared but never installed and that's what a failed setns() uses in put_nsset(). Export it and use it for a failed unshare() as well. Fixes: a98621a0f187 ("unshare: fix nsproxy leak in ksys_unshare() on set_cred_ucounts() failure") Cc: stable@vger.kernel.org # v7.1+ Link: https://patch.msgid.link/20260930-work-mount-fixes-3-v1-16-be34c83956ae@kernel.org Signed-off-by: Christian Brauner (Amutable) --- include/linux/nsproxy.h | 1 + kernel/fork.c | 3 ++- kernel/nsproxy.c | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/include/linux/nsproxy.h b/include/linux/nsproxy.h index 5a67648721c7..dc2447f5f092 100644 --- a/include/linux/nsproxy.h +++ b/include/linux/nsproxy.h @@ -100,6 +100,7 @@ void exit_cred_namespaces(struct task_struct *tsk); void switch_task_namespaces(struct task_struct *tsk, struct nsproxy *new); int exec_task_namespaces(void); void deactivate_nsproxy(struct nsproxy *ns); +void nsproxy_free(struct nsproxy *ns); int unshare_nsproxy_namespaces(unsigned long, struct nsproxy **, struct cred *, struct fs_struct *); int __init nsproxy_cache_init(void); diff --git a/kernel/fork.c b/kernel/fork.c index da48168c504f..d442cd68a37a 100644 --- a/kernel/fork.c +++ b/kernel/fork.c @@ -3338,8 +3338,9 @@ int ksys_unshare(unsigned long unshare_flags) perf_event_namespaces(current); bad_unshare_cleanup_nsproxy: + /* never installed, so no active references to drop */ if (new_nsproxy) - put_nsproxy(new_nsproxy); + nsproxy_free(new_nsproxy); bad_unshare_cleanup_cred: if (new_cred) put_cred(new_cred); diff --git a/kernel/nsproxy.c b/kernel/nsproxy.c index d9d3d5973bf5..3fb1595a4a59 100644 --- a/kernel/nsproxy.c +++ b/kernel/nsproxy.c @@ -61,7 +61,7 @@ static inline struct nsproxy *create_nsproxy(void) return nsproxy; } -static inline void nsproxy_free(struct nsproxy *ns) +void nsproxy_free(struct nsproxy *ns) { put_mnt_ns(ns->mnt_ns); put_uts_ns(ns->uts_ns); From b138e16fb1e81376cea89f727dda7bfccd140de4 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Wed, 30 Sep 2026 15:32:09 +0200 Subject: [PATCH 099/367] namespace: don't let a pseudo dentry become the root of a mount d_alloc_pseudo() hands out dentries for pipes, sockets and other files that are never anyone's child or parent. They carry DCACHE_NORCU and dentry_free() frees them right away because no lockless path walk can ever reach them. That holds as long as such a dentry isn't the root of a mount. But bind mounting /proc/self/fd/ of such a file does exactly that. Most callers of alloc_file_pseudo() put their files on kernel-internal mounts and may_copy_tree() refuses those. bpf_token_create() doesn't. It places the token file on the bpffs mount the caller handed it and that mount is in the caller's mount namespace so the clone goes through: mount --bind /proc/self/fd/ open_tree(tokfd, "", AT_EMPTY_PATH | OPEN_TREE_CLONE) + move_mount() __follow_mount_rcu() then loads ->mnt_root of that mount, reads d_seq and d_flags of the dentry and only then checks mount_lock. The dentry can be gone by then. The final mntput() of an unmounted parent unhooks a child that stayed attached to it under mount_lock alone. When the child's holder does the final put right after that the root is dput() and freed immediately while a walker that found the mount hashed still looks at it. Refuse to clone a mount with a DCACHE_NORCU dentry as its root. Nothing sensible can be done with a bind mount of a bpf token anyway. Fixes: 35f96de04127 ("bpf: Introduce BPF token object") Cc: stable@vger.kernel.org # v6.9+ Link: https://patch.msgid.link/20260930-work-mount-fixes-3-v1-17-be34c83956ae@kernel.org Signed-off-by: Christian Brauner (Amutable) --- fs/namespace.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/fs/namespace.c b/fs/namespace.c index 3c90d853e091..fcf42f192aae 100644 --- a/fs/namespace.c +++ b/fs/namespace.c @@ -3026,6 +3026,10 @@ static struct mount *__do_loopback(const struct path *old_path, if (!may_copy_tree(old_path)) return ERR_PTR(-EINVAL); + /* a pseudo dentry is freed without an RCU delay, no walk may find it */ + if (old_path->dentry->d_flags & DCACHE_NORCU) + return ERR_PTR(-EINVAL); + if (recurse && !old->mnt_ns) return ERR_PTR(-EINVAL); From 7efa1a1953cd6b20b23a3524d5b4b81789c6ef25 Mon Sep 17 00:00:00 2001 From: Fan Wu Date: Tue, 29 Sep 2026 09:36:46 +0000 Subject: [PATCH 100/367] gpio: mpsse: fix race when arming the IRQ poll worker gpio_mpsse_irq_enable() arms the poll worker before publishing it: schedule_work() runs before the worker is added to priv->workers. If gpio_mpsse_disconnect() walks the list in that window it misses the worker, and once disconnect returns, the USB core frees mpsse_priv while the orphaned gpio_mpsse_poll() work keeps accessing it, causing a use-after-free. Fix this by publishing and arming the worker in one irq_spin critical section. Teardown walks the same list under irq_spin, so a worker found on the list is guaranteed to be armed, and cancel_work_sync() handles it whether it is queued or running. A worker armed after the disconnect walk would still be missed, so also set a new priv->dying flag under irq_spin before the teardown walk, and check it in the same critical section, freeing it via kfree_rcu() instead when the device is going away, since kfree() would be called with the IRQ core's raw spinlock held and may sleep on PREEMPT_RT. schedule_work() is safe to call with irq_spin held, and the next probe gets a fresh mpsse_priv, so the flag never needs to be cleared. This issue was found by an in-house static analysis tool. Fixes: 179ef1127d7a ("gpio: mpsse: ensure worker is torn down") Cc: stable@vger.kernel.org Co-developed-by: Song Li Signed-off-by: Song Li Signed-off-by: Fan Wu Link: https://patch.msgid.link/20260929093646.18419-1-fanwu01@zju.edu.cn Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-mpsse.c | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/drivers/gpio/gpio-mpsse.c b/drivers/gpio/gpio-mpsse.c index 30e8009b5fcc..61c5ca96314b 100644 --- a/drivers/gpio/gpio-mpsse.c +++ b/drivers/gpio/gpio-mpsse.c @@ -10,6 +10,7 @@ #include #include #include +#include #include #include @@ -24,6 +25,7 @@ struct mpsse_priv { raw_spinlock_t irq_spin; /* protects worker list */ atomic_t irq_type[16]; /* pin -> edge detection type */ atomic_t irq_enabled; + atomic_t dying; /* no new workers after disconnect */ int id; u8 gpio_outputs[2]; /* Output states for GPIOs [L, H] */ @@ -46,6 +48,7 @@ struct mpsse_worker { atomic_t cancelled; struct list_head list; /* linked list */ struct list_head destroy; /* teardown linked list */ + struct rcu_head rcu; /* deferred free for dying path */ }; struct bulk_desc { @@ -525,10 +528,24 @@ static void gpio_mpsse_irq_enable(struct irq_data *irqd) worker->priv = priv; INIT_LIST_HEAD(&worker->list); INIT_WORK(&worker->work, gpio_mpsse_poll); - schedule_work(&worker->work); - scoped_guard(raw_spinlock_irqsave, &priv->irq_spin) + scoped_guard(raw_spinlock_irqsave, &priv->irq_spin) { + if (atomic_read(&priv->dying)) { + /* + * Cannot kfree() here: this callback + * runs with the IRQ descriptor's raw + * spinlock held and kfree() may sleep + * on PREEMPT_RT. The worker is not + * yet published, so the deferred free + * is unobservable. + */ + kfree_rcu(worker, rcu); + return; + } + list_add(&worker->list, &priv->workers); + schedule_work(&worker->work); + } } } @@ -704,6 +721,9 @@ static void gpio_mpsse_disconnect(struct usb_interface *intf) { struct mpsse_priv *priv = usb_get_intfdata(intf); + scoped_guard(raw_spinlock_irqsave, &priv->irq_spin) + atomic_set(&priv->dying, 1); + /* * Lock prevents double-free of worker from here and the teardown * step at the beginning of gpio_mpsse_poll From 755c94d178b7e972bb766199d34e4ba4f63a89eb Mon Sep 17 00:00:00 2001 From: Nguyen Ngoc Thang Date: Sun, 27 Sep 2026 14:54:34 +0700 Subject: [PATCH 101/367] drm/fb-helper: defer setup when fbdev_probe() fails, not just on -EAGAIN __drm_fb_helper_initial_config_and_unlock() calls drm_client_modeset_probe() first, which already fills in each mode_set->mode/num_connectors for every connected output, and only afterwards calls drm_fb_helper_single_fb_probe() to create fb_helper->fb and wire it into those mode_sets via drm_setup_crtcs_fb(). If drm_fb_helper_single_fb_probe() fails with anything other than -EAGAIN (e.g. dev->driver->fbdev_probe() itself fails), the function bails out before drm_setup_crtcs_fb() runs and before setting fb_helper->deferred_setup. The client is left with mode_sets that have a mode and connectors but mode_set->fb == NULL, and nothing marks this fb_helper as needing a retry. __drm_fb_helper_restore_fbdev_mode_unlocked() only skips committing when fb_helper->deferred_setup is set, so a later .restore() call (drm_lastclose() on process exit, in this report) commits the stale mode_set through drm_client_modeset_commit() -> drm_client_modeset_commit_atomic() -> __drm_atomic_helper_set_config(), which hits WARN_ON(!set->fb). Mark fb_helper->deferred_setup on any drm_fb_helper_single_fb_probe() failure, not just -EAGAIN, so restore keeps skipping the commit until a later hotplug event successfully re-probes and re-populates mode_set->fb. The real error code is still returned to the caller unchanged. Reported-by: syzbot+ca23c8570669ead78867@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=ca23c8570669ead78867 Fixes: ca91a2758fce ("drm/fb-helper: Support deferred setup") Cc: stable@vger.kernel.org Signed-off-by: Nguyen Ngoc Thang Signed-off-by: Thomas Zimmermann Link: https://patch.msgid.link/20260927075434.72658-1-ngocthang2710.1999@gmail.com Link: https://patch.msgid.link/20260917154743.1621-1-shechenglong@xfusion.com --- drivers/gpu/drm/drm_fb_helper.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/drm_fb_helper.c b/drivers/gpu/drm/drm_fb_helper.c index d4664ed468b2..0817b639b3fa 100644 --- a/drivers/gpu/drm/drm_fb_helper.c +++ b/drivers/gpu/drm/drm_fb_helper.c @@ -1724,10 +1724,10 @@ __drm_fb_helper_initial_config_and_unlock(struct drm_fb_helper *fb_helper) ret = drm_fb_helper_single_fb_probe(fb_helper); if (ret < 0) { - if (ret == -EAGAIN) { - fb_helper->deferred_setup = true; + /* modesets are probed but fb_helper->fb isn't; defer restore too */ + fb_helper->deferred_setup = true; + if (ret == -EAGAIN) ret = 0; - } mutex_unlock(&fb_helper->lock); goto err_drm_fb_helper_release_info; From ecce445c6489895463b7ddec2af66338287fee4c Mon Sep 17 00:00:00 2001 From: Myeonghun Pak Date: Sun, 13 Sep 2026 18:30:19 -0400 Subject: [PATCH 102/367] drm/aspeed: Balance the display clock enable on teardown aspeed_gfx_load() enables the display clock without checking the return value and never disables it on probe failure or removal. devm_clk_get() only releases the clock reference, leaving the enable unbalanced. Use devm_clk_get_enabled() to handle enable failures and automatically disable and unprepare the clock on probe failure or removal. This issue was identified during our ongoing static-analysis research while reviewing kernel code. Fixes: 4f2a8f5898ec ("drm: Add ASPEED GFX driver") Co-developed-by: Ijae Kim Signed-off-by: Ijae Kim Signed-off-by: Myeonghun Pak Assisted-by: OpenAI:GPT-5.6 Reviewed-by: Thomas Zimmermann Signed-off-by: Thomas Zimmermann Link: https://patch.msgid.link/20260913223019.89651-1-mhun512@gmail.com Link: https://patch.msgid.link/20260905080426.34224-1-kmehltretter@gmail.com --- drivers/gpu/drm/aspeed/aspeed_gfx_drv.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/aspeed/aspeed_gfx_drv.c b/drivers/gpu/drm/aspeed/aspeed_gfx_drv.c index d4577663a1f0..aa36b79d0f6c 100644 --- a/drivers/gpu/drm/aspeed/aspeed_gfx_drv.c +++ b/drivers/gpu/drm/aspeed/aspeed_gfx_drv.c @@ -189,13 +189,12 @@ static int aspeed_gfx_load(struct drm_device *drm) } reset_control_deassert(priv->rst); - priv->clk = devm_clk_get(drm->dev, NULL); + priv->clk = devm_clk_get_enabled(drm->dev, NULL); if (IS_ERR(priv->clk)) { dev_err(&pdev->dev, - "missing or invalid clk device tree entry"); + "failed to get and enable clock\n"); return PTR_ERR(priv->clk); } - clk_prepare_enable(priv->clk); /* Sanitize control registers */ writel(0, priv->base + CRT_CTRL1); From b41ff8b529278216cba22a5dcdc93fea2a3a9619 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ADra=20Canal?= Date: Wed, 30 Sep 2026 16:06:50 -0300 Subject: [PATCH 103/367] drm/vc4: Free the BO cache size list on teardown MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit vc4_bo_cache_destroy() purges the cached BOs but never frees the size list, leaking it on every unbind. Fixes: c826a6e10644 ("drm/vc4: Add a BO cache.") Assisted-by: LLM Reviewed-by: Thadeu Lima de Souza Cascardo Link: https://patch.msgid.link/20260930190721.1897264-1-mcanal@igalia.com Signed-off-by: Maíra Canal --- drivers/gpu/drm/vc4/vc4_bo.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/gpu/drm/vc4/vc4_bo.c b/drivers/gpu/drm/vc4/vc4_bo.c index 49ea2ed0996b..ea1be891ac78 100644 --- a/drivers/gpu/drm/vc4/vc4_bo.c +++ b/drivers/gpu/drm/vc4/vc4_bo.c @@ -1052,6 +1052,7 @@ static void vc4_bo_cache_destroy(struct drm_device *dev, void *unused) cancel_work_sync(&vc4->bo_cache.time_work); vc4_bo_cache_purge(dev); + kfree(vc4->bo_cache.size_list); for (i = 0; i < vc4->num_labels; i++) { if (vc4->bo_labels[i].num_allocated) { From bca45af5998a05f34b13a2ef11e639bac9c62643 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ADra=20Canal?= Date: Wed, 30 Sep 2026 16:06:51 -0300 Subject: [PATCH 104/367] drm/vc4: Use kvmalloc_objs() for the BO cache size list MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The BO cache size list has one entry per BO page count, so its size scales with the largest BO ever freed. A 64 MiB BO already needs at least an order-6 allocation, which fails under fragmentation and triggers a warn_alloc() splat on BO close: page allocation failure: order:7, mode:0x40cc0(GFP_KERNEL|__GFP_COMP) Call trace: ... warn_alloc+0x118/0x190 __alloc_pages_slowpath+0x4a8/0x520 ... __kmalloc_noprof+0x3ac/0x5d8 vc4_free_object+0x128/0x370 [vc4] drm_gem_object_handle_put_unlocked+0xb8/0x158 [drm] drm_gem_object_release_handle+0x74/0xe8 [drm] drm_gem_handle_delete+0x6c/0xa8 [drm] drm_gem_close_ioctl+0x34/0x50 [drm] The list doesn't need physically contiguous memory. Use kvmalloc_objs() so large lists can fall back to vmalloc(), avoiding the allocation failure. Fixes: c826a6e10644 ("drm/vc4: Add a BO cache.") Reviewed-by: Thadeu Lima de Souza Cascardo Link: https://patch.msgid.link/20260930190721.1897264-2-mcanal@igalia.com Signed-off-by: Maíra Canal --- drivers/gpu/drm/vc4/vc4_bo.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/vc4/vc4_bo.c b/drivers/gpu/drm/vc4/vc4_bo.c index ea1be891ac78..7b8b3364000f 100644 --- a/drivers/gpu/drm/vc4/vc4_bo.c +++ b/drivers/gpu/drm/vc4/vc4_bo.c @@ -204,7 +204,7 @@ static struct list_head *vc4_get_cache_list_for_size(struct drm_device *dev, struct list_head *new_list; uint32_t i; - new_list = kmalloc_objs(struct list_head, new_size); + new_list = kvmalloc_objs(struct list_head, new_size); if (!new_list) return NULL; @@ -224,7 +224,7 @@ static struct list_head *vc4_get_cache_list_for_size(struct drm_device *dev, for (i = vc4->bo_cache.size_list_size; i < new_size; i++) INIT_LIST_HEAD(&new_list[i]); - kfree(vc4->bo_cache.size_list); + kvfree(vc4->bo_cache.size_list); vc4->bo_cache.size_list = new_list; vc4->bo_cache.size_list_size = new_size; } @@ -1052,7 +1052,7 @@ static void vc4_bo_cache_destroy(struct drm_device *dev, void *unused) cancel_work_sync(&vc4->bo_cache.time_work); vc4_bo_cache_purge(dev); - kfree(vc4->bo_cache.size_list); + kvfree(vc4->bo_cache.size_list); for (i = 0; i < vc4->num_labels; i++) { if (vc4->bo_labels[i].num_allocated) { From 0a04c0cb8606ff88399489d03ef255afe90d0d5f Mon Sep 17 00:00:00 2001 From: zjamg Date: Thu, 17 Sep 2026 20:37:16 +0800 Subject: [PATCH 105/367] can: dev: init_can_skb(): restore skb header initialization Commit 9f10374bb024 ("can: remove private CAN skb headroom infrastructure") removed the skb_reset_mac_header()/skb_reset_network_header()/ skb_reset_transport_header() calls from init_can_skb(). As a result, RX skbs from alloc_can_skb() and friends again carry mac_header = 0xFFFF. When such an skb reaches packet_rcv_spkt() (SOCK_PACKET), the push length calculation overflows and triggers skb_under_panic -> kernel BUG -> full machine panic. The same issue was originally reported in 2014 on linux-can and fixed by commit 969439016d2c ("can: add missing initialisations in CAN related skbuffs"). packet_rcv_spkt() itself has never been hardened: only packet_rcv() and tpacket_rcv() gained dev_has_header() checks in commit d549699048b4 ("net/packet: fix packet receive on L3 devices without visible hard header"). Fixes: 9f10374bb024 ("can: remove private CAN skb headroom infrastructure") Cc: stable@vger.kernel.org Reviewed-by: Oliver Hartkopp Acked-by: Oliver Hartkopp Signed-off-by: zjamg Tested-by: Quchaosheng Link: https://patch.msgid.link/20260917123716.63116-1-ndaugoing@gmail.com Reported-by: Shaunak Datar Closes: https://lore.kernel.org/all/20260918151606.765490-1-shaunakkdatar@gmail.com Signed-off-by: Marc Kleine-Budde --- drivers/net/can/dev/skb.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/net/can/dev/skb.c b/drivers/net/can/dev/skb.c index 95fcdc1026f8..14edec5afb57 100644 --- a/drivers/net/can/dev/skb.c +++ b/drivers/net/can/dev/skb.c @@ -210,6 +210,10 @@ static void init_can_skb(struct sk_buff *skb) { skb->pkt_type = PACKET_BROADCAST; skb->ip_summed = CHECKSUM_UNNECESSARY; + + skb_reset_mac_header(skb); + skb_reset_network_header(skb); + skb_reset_transport_header(skb); } struct sk_buff *alloc_can_skb(struct net_device *dev, struct can_frame **cf) From 7093c3b8314a4ef7405c4066a2521398c455efe7 Mon Sep 17 00:00:00 2001 From: Oliver Hartkopp Date: Thu, 1 Oct 2026 13:57:24 +0200 Subject: [PATCH 106/367] can: fix unique skb identifier regression under RPS Commit d4fb6514ff8e ("can: use skb hash instead of private variable in headroom") moved the per-skb unique identifier used for raw_rcv() duplicate detection into skb->hash. With RPS enabled, get_rps_cpu() calls skb_get_hash() before the frame reaches the CAN subsystem. Since CAN skbs have no L3/L4 headers, the flow dissector assigns every CAN frame the same non-zero software hash. can_set_skb_uid() only generated a new identifier when skb->hash was 0, so it kept this constant hash. When the SLAB allocator later reused the same skb address, raw_rcv() mistook the next legitimate frame for a duplicate and dropped it. Fix this by storing the CAN UID in the CAN skb extension (struct can_skb_ext::can_skb_uid) instead of skb->hash, decoupling it from any hash the network stack may compute. can_set_skb_uid() keeps its "assign only if unset" behaviour, which preserves UIDs set before transmission (e.g. by isotp to identify echo frames) across the local loopback path. Frames whose extension is shared with another clone (e.g. via tc mirred or netem duplicate) are given a private extension copy before the UID is assigned, so that independently received skb clones from real CAN interfaces (can_skb_uid = 0) don't end up with the same UID. The limitation that tc mirred/netem skb duplicates might be dropped in raw_rcv for looped back isotp echo frame skbs is accepted. There is no valid use-case for tc mirred or netem together with isotp which is not capable to operate on different CAN interfaces simultaneously. For routing and modifying CAN frames together with isotp use can-gw. can-gw and vxcan additionally clear the UID of forwarded/duplicated frames so each newly routed frame gets its own unique identifier. Fixes: d4fb6514ff8e ("can: use skb hash instead of private variable in headroom") Reported-by: Joerg Willmann Closes: https://lore.kernel.org/linux-can/2859AD3D-C805-41A0-9036-C5E8EE152419@clnt.de/ Cc: stable@vger.kernel.org Tested-by: Oliver Hartkopp Tested-by: Joerg Willmann Signed-off-by: Oliver Hartkopp Link: https://patch.msgid.link/20261001115724.27192-1-socketcan@hartkopp.net Signed-off-by: Marc Kleine-Budde --- drivers/net/can/vxcan.c | 3 +++ include/linux/can/core.h | 3 ++- include/linux/can/skb.h | 4 +++- include/net/can.h | 2 ++ net/can/af_can.c | 47 ++++++++++++++++++++++++++++++---------- net/can/gw.c | 3 +++ net/can/isotp.c | 16 +++++++++----- net/can/raw.c | 10 ++++++--- 8 files changed, 66 insertions(+), 22 deletions(-) diff --git a/drivers/net/can/vxcan.c b/drivers/net/can/vxcan.c index 9e2e25d02471..51148a81d1d9 100644 --- a/drivers/net/can/vxcan.c +++ b/drivers/net/can/vxcan.c @@ -79,6 +79,9 @@ static netdev_tx_t vxcan_xmit(struct sk_buff *oskb, struct net_device *dev) /* reset CAN GW hop counter */ csx->can_gw_hops = 0; + /* start with new CAN skb UID in the other namespace */ + csx->can_skb_uid = 0; + skb->pkt_type = PACKET_BROADCAST; skb->dev = peer; skb->ip_summed = CHECKSUM_UNNECESSARY; diff --git a/include/linux/can/core.h b/include/linux/can/core.h index 3287232e3cad..2de74c2b78b6 100644 --- a/include/linux/can/core.h +++ b/include/linux/can/core.h @@ -17,6 +17,7 @@ #include #include #include +#include #define DNAME(dev) ((dev) ? (dev)->name : "any") @@ -58,7 +59,7 @@ extern void can_rx_unregister(struct net *net, struct net_device *dev, void *data); extern int can_send(struct sk_buff *skb, int loop); -void can_set_skb_uid(struct sk_buff *skb); +void can_set_skb_uid(struct can_skb_ext *csx); void can_sock_destruct(struct sock *sk); #endif /* !_CAN_CORE_H */ diff --git a/include/linux/can/skb.h b/include/linux/can/skb.h index a70a02967071..5d27843862fc 100644 --- a/include/linux/can/skb.h +++ b/include/linux/can/skb.h @@ -43,8 +43,10 @@ static inline struct can_skb_ext *can_skb_ext_add(struct sk_buff *skb) struct can_skb_ext *csx = skb_ext_add(skb, SKB_EXT_CAN); /* skb_ext_add() returns uninitialized space */ - if (csx) + if (csx) { csx->can_gw_hops = 0; + csx->can_skb_uid = 0; + } return csx; } diff --git a/include/net/can.h b/include/net/can.h index 6db9e826f0e0..2b8af2598732 100644 --- a/include/net/can.h +++ b/include/net/can.h @@ -17,12 +17,14 @@ * @can_framelen: cached echo CAN frame length for bql * @can_gw_hops: can-gw CAN frame time-to-live counter * @can_ext_flags: CAN skb extensions flags + * @can_skb_uid: CAN skb UID for raw_rcv and isotp echo handling */ struct can_skb_ext { int can_iif; u16 can_framelen; u8 can_gw_hops; u8 can_ext_flags; + u32 can_skb_uid; }; #endif /* _NET_CAN_H */ diff --git a/net/can/af_can.c b/net/can/af_can.c index 7bc86b176b4d..34fe3b28d576 100644 --- a/net/can/af_can.c +++ b/net/can/af_can.c @@ -641,13 +641,10 @@ static int can_rcv_filter(struct can_dev_rcv_lists *dev_rcv_lists, struct sk_buf return matches; } -void can_set_skb_uid(struct sk_buff *skb) +void can_set_skb_uid(struct can_skb_ext *csx) { - /* create non-zero unique skb identifier together with *skb */ - while (!(skb->hash)) - skb->hash = atomic_inc_return(&skbcounter); - - skb->sw_hash = 1; + while (!(csx->can_skb_uid)) + csx->can_skb_uid = atomic_inc_return(&skbcounter); } EXPORT_SYMBOL(can_set_skb_uid); @@ -662,8 +659,6 @@ static void can_receive(struct sk_buff *skb, struct net_device *dev) atomic_long_inc(&pkg_stats->rx_frames); atomic_long_inc(&pkg_stats->rx_frames_delta); - can_set_skb_uid(skb); - rcu_read_lock(); /* deliver the packet to sockets listening on all devices */ @@ -687,8 +682,10 @@ static void can_receive(struct sk_buff *skb, struct net_device *dev) static int can_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt, struct net_device *orig_dev) { + struct can_skb_ext *csx = can_skb_ext_find(skb); + if (unlikely(dev->type != ARPHRD_CAN || !can_get_ml_priv(dev) || - !can_skb_ext_find(skb) || !can_is_can_skb(skb))) { + !csx || !can_is_can_skb(skb))) { pr_warn_once("PF_CAN: dropped non conform CAN skbuff: dev type %d, len %d\n", dev->type, skb->len); @@ -696,6 +693,14 @@ static int can_rcv(struct sk_buff *skb, struct net_device *dev, return NET_RX_DROP; } + /* create unshared CAN skb_extension for netem/mirred skb clones */ + csx = skb_ext_add(skb, SKB_EXT_CAN); + if (unlikely(!csx)) { + kfree_skb_reason(skb, SKB_DROP_REASON_NOMEM); + return NET_RX_DROP; + } + + can_set_skb_uid(csx); can_receive(skb, dev); return NET_RX_SUCCESS; } @@ -703,8 +708,10 @@ static int can_rcv(struct sk_buff *skb, struct net_device *dev, static int canfd_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt, struct net_device *orig_dev) { + struct can_skb_ext *csx = can_skb_ext_find(skb); + if (unlikely(dev->type != ARPHRD_CAN || !can_get_ml_priv(dev) || - !can_skb_ext_find(skb) || !can_is_canfd_skb(skb))) { + !csx || !can_is_canfd_skb(skb))) { pr_warn_once("PF_CAN: dropped non conform CAN FD skbuff: dev type %d, len %d\n", dev->type, skb->len); @@ -712,6 +719,14 @@ static int canfd_rcv(struct sk_buff *skb, struct net_device *dev, return NET_RX_DROP; } + /* create unshared CAN skb_extension for netem/mirred skb clones */ + csx = skb_ext_add(skb, SKB_EXT_CAN); + if (unlikely(!csx)) { + kfree_skb_reason(skb, SKB_DROP_REASON_NOMEM); + return NET_RX_DROP; + } + + can_set_skb_uid(csx); can_receive(skb, dev); return NET_RX_SUCCESS; } @@ -719,8 +734,10 @@ static int canfd_rcv(struct sk_buff *skb, struct net_device *dev, static int canxl_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt, struct net_device *orig_dev) { + struct can_skb_ext *csx = can_skb_ext_find(skb); + if (unlikely(dev->type != ARPHRD_CAN || !can_get_ml_priv(dev) || - !can_skb_ext_find(skb) || !can_is_canxl_skb(skb))) { + !csx || !can_is_canxl_skb(skb))) { pr_warn_once("PF_CAN: dropped non conform CAN XL skbuff: dev type %d, len %d\n", dev->type, skb->len); @@ -728,6 +745,14 @@ static int canxl_rcv(struct sk_buff *skb, struct net_device *dev, return NET_RX_DROP; } + /* create unshared CAN skb_extension for netem/mirred skb clones */ + csx = skb_ext_add(skb, SKB_EXT_CAN); + if (unlikely(!csx)) { + kfree_skb_reason(skb, SKB_DROP_REASON_NOMEM); + return NET_RX_DROP; + } + + can_set_skb_uid(csx); can_receive(skb, dev); return NET_RX_SUCCESS; } diff --git a/net/can/gw.c b/net/can/gw.c index 0ec99f68aa45..5793cb2420ed 100644 --- a/net/can/gw.c +++ b/net/can/gw.c @@ -527,6 +527,9 @@ static void can_can_gw_rcv(struct sk_buff *skb, void *data) /* put the incremented hop counter in the cloned skb */ ncsx->can_gw_hops = csx->can_gw_hops + 1; + /* force a new CAN UID generation for the routed frame */ + ncsx->can_skb_uid = 0; + /* first processing of this CAN frame -> adjust to private hop limit */ if (gwj->limit_hops && ncsx->can_gw_hops == 1) ncsx->can_gw_hops = max_hops - gwj->limit_hops + 1; diff --git a/net/can/isotp.c b/net/can/isotp.c index 155530aedce2..f5dc9d04bd68 100644 --- a/net/can/isotp.c +++ b/net/can/isotp.c @@ -891,7 +891,7 @@ static void isotp_send_cframe(struct isotp_sock *so) csx->can_iif = dev->ifindex; /* set uid in tx skb to identify CF echo frames */ - can_set_skb_uid(skb); + can_set_skb_uid(csx); cf = (struct canfd_frame *)skb->data; skb_put_zero(skb, so->ll.mtu); @@ -917,7 +917,7 @@ static void isotp_send_cframe(struct isotp_sock *so) pr_notice_once("can-isotp: cfecho is %08X != 0\n", old_cfecho); /* set consecutive frame echo tag */ - WRITE_ONCE(so->cfecho, skb->hash); + WRITE_ONCE(so->cfecho, csx->can_skb_uid); /* send frame with local echo enabled */ can_send_ret = can_send(skb, 1); @@ -969,18 +969,22 @@ static void isotp_rcv_echo(struct sk_buff *skb, void *data) { struct sock *sk = (struct sock *)data; struct isotp_sock *so = isotp_sk(sk); + struct can_skb_ext *csx = can_skb_ext_find(skb); /* only handle my own local echo CF/SF skb's (no FF!) */ if (skb->sk != sk) return; + if (WARN_ON_ONCE(!csx)) + return; + /* unlike isotp_rcv_fc()/isotp_rcv_cf(), not already under so->rx_lock * (no isotp_rcv() caller here), so take it ourselves */ spin_lock(&so->rx_lock); /* so->cfecho may since belong to a new transfer; recheck under lock */ - if (READ_ONCE(so->cfecho) != skb->hash) + if (READ_ONCE(so->cfecho) != csx->can_skb_uid) goto out_unlock; /* cancel local echo timeout */ @@ -1222,7 +1226,7 @@ static int isotp_sendmsg(struct socket *sock, struct msghdr *msg, size_t size) csx->can_iif = dev->ifindex; /* set uid in tx skb to identify CF echo frames */ - can_set_skb_uid(skb); + can_set_skb_uid(csx); so->tx.len = size; so->tx.idx = 0; @@ -1261,7 +1265,7 @@ static int isotp_sendmsg(struct socket *sock, struct msghdr *msg, size_t size) cf->data[ae] |= size; /* set CF echo tag for isotp_rcv_echo() (SF-mode) */ - WRITE_ONCE(so->cfecho, skb->hash); + WRITE_ONCE(so->cfecho, csx->can_skb_uid); } else { /* send first frame */ @@ -1278,7 +1282,7 @@ static int isotp_sendmsg(struct socket *sock, struct msghdr *msg, size_t size) so->txfc.bs = 0; /* set CF echo tag for isotp_rcv_echo() (CF-mode) */ - WRITE_ONCE(so->cfecho, skb->hash); + WRITE_ONCE(so->cfecho, csx->can_skb_uid); } else { /* standard flow control check */ new_state = ISOTP_WAIT_FIRST_FC; diff --git a/net/can/raw.c b/net/can/raw.c index 82d9c0499c95..0acd4f6c6dd6 100644 --- a/net/can/raw.c +++ b/net/can/raw.c @@ -77,7 +77,7 @@ MODULE_ALIAS("can-proto-1"); struct uniqframe { const struct sk_buff *skb; - u32 hash; + u32 can_skb_uid; unsigned int join_rx_count; }; @@ -133,12 +133,16 @@ static void raw_rcv(struct sk_buff *oskb, void *data) enum skb_drop_reason reason; struct sockaddr_can *addr; struct sk_buff *skb; + struct can_skb_ext *csx = can_skb_ext_find(oskb); unsigned int *pflags; /* check the received tx sock reference */ if (!ro->recv_own_msgs && oskb->sk == sk) return; + if (WARN_ON_ONCE(!csx)) + return; + /* make sure to not pass oversized frames to the socket */ if (!ro->fd_frames && can_is_canfd_skb(oskb)) return; @@ -165,7 +169,7 @@ static void raw_rcv(struct sk_buff *oskb, void *data) /* eliminate multiple filter matches for the same skb */ if (this_cpu_ptr(ro->uniq)->skb == oskb && - this_cpu_ptr(ro->uniq)->hash == oskb->hash) { + this_cpu_ptr(ro->uniq)->can_skb_uid == csx->can_skb_uid) { if (!ro->join_filters) return; @@ -175,7 +179,7 @@ static void raw_rcv(struct sk_buff *oskb, void *data) return; } else { this_cpu_ptr(ro->uniq)->skb = oskb; - this_cpu_ptr(ro->uniq)->hash = oskb->hash; + this_cpu_ptr(ro->uniq)->can_skb_uid = csx->can_skb_uid; this_cpu_ptr(ro->uniq)->join_rx_count = 1; /* drop first frame to check all enabled filters? */ if (ro->join_filters && ro->count > 1) From 7f85d1170575645f9f327062d78017a23b2714eb Mon Sep 17 00:00:00 2001 From: "Ji-Ze Hong (Peter Hong)" Date: Mon, 24 Aug 2026 21:18:14 +0800 Subject: [PATCH 107/367] usb: f81604: fix struct f81604_int_data size mismatch The struct f81604_int_data defines 9 bytes of interrupt data: - Byte 0: Status register (sr) - Byte 1: Interrupt register (isrc) - Byte 2: Interrupt enable register (ier) - Byte 3: Arbitration lost capture (alc) - Byte 4: Error code capture (ecc) - Byte 5: Error warning limit register (ewlr) - Byte 6: RX error counter (rxerr) - Byte 7: TX error counter (txerr) - Byte 8: Reserved (val) The hardware sends exactly 9 bytes for the interrupt endpoint. However, the struct was defined with __aligned(4) attribute which caused the compiler to pad the struct to 12 bytes. This causes a problem in f81604_read_int_callback() where the short URB check compares urb->actual_length against sizeof(*data). When sizeof(struct f81604_int_data) is 12 but the hardware only sends 9 bytes, the check fails and valid interrupt messages are discarded. This results in the driver only being able to transmit once because the TX complete interrupt is never processed. Fix this by removing the __aligned(4) attribute so the struct size matches the actual hardware data size of 9 bytes. Fixes: 7299b1b39a25 ("can: usb: f81604: handle short interrupt urb messages properly") Cc: stable@vger.kernel.org Reported-by: Dynetrex, Admin Closes: https://lore.kernel.org/all/A3834A07-5639-4779-844F-C5843DFC3928@dynetrex.com/ Signed-off-by: Ji-Ze Hong (Peter Hong) Acked-by: Greg Kroah-Hartman Tested-by: Drew Willey Link: https://patch.msgid.link/20260824-f81604-fix-v2-1-fc9be5581394@fintek.com.tw Signed-off-by: Marc Kleine-Budde --- drivers/net/can/usb/f81604.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/can/usb/f81604.c b/drivers/net/can/usb/f81604.c index f12318268e46..4c147b9d6d69 100644 --- a/drivers/net/can/usb/f81604.c +++ b/drivers/net/can/usb/f81604.c @@ -169,7 +169,7 @@ struct f81604_int_data { u8 rxerr; u8 txerr; u8 val; -} __packed __aligned(4); +} __packed; struct f81604_sff { __be16 id; From af0524bf4ce1a4cbd2946aa81751582951cfcab5 Mon Sep 17 00:00:00 2001 From: Mingming Cao Date: Fri, 25 Sep 2026 11:05:29 -0700 Subject: [PATCH 108/367] ibmveth: h_free logical LAN on open-fail after register ibmveth_open() registers the logical LAN, then allocates the RX buffer pools. A pool failure jumps to out_free_buffer_pools without h_free_logical_lan(), so PHYP still owns the buffer-list page when it is unmapped. request_irq() failure already issued the hypercall before taking the same label. Move that h_free to the shared post-register unwind so both paths deregister before the buffer list is unmapped. Fixes: d43732ce021f ("ibmveth: properly unwind on init errors") Cc: stable@vger.kernel.org Signed-off-by: Mingming Cao Reviewed-by: Dave Marquardt Link: https://patch.msgid.link/7a56fbfb9e0674f011f4e310fbd7dfd8e67c30aa.1790357373.git.mmc@linux.ibm.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/ibm/ibmveth.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/drivers/net/ethernet/ibm/ibmveth.c b/drivers/net/ethernet/ibm/ibmveth.c index 73e051d26b9d..3e44f6b714d4 100644 --- a/drivers/net/ethernet/ibm/ibmveth.c +++ b/drivers/net/ethernet/ibm/ibmveth.c @@ -718,10 +718,6 @@ static int ibmveth_open(struct net_device *netdev) if (rc != 0) { netdev_err(netdev, "unable to request irq 0x%x, rc %d\n", netdev->irq, rc); - do { - lpar_rc = h_free_logical_lan(adapter->vdev->unit_address); - } while (H_IS_LONG_BUSY(lpar_rc) || (lpar_rc == H_BUSY)); - goto out_free_buffer_pools; } @@ -737,6 +733,9 @@ static int ibmveth_open(struct net_device *netdev) return 0; out_free_buffer_pools: + do { + lpar_rc = h_free_logical_lan(adapter->vdev->unit_address); + } while (H_IS_LONG_BUSY(lpar_rc) || (lpar_rc == H_BUSY)); while (--i >= 0) { if (adapter->rx_buff_pool[i].active) ibmveth_free_buffer_pool(adapter, From 84bec0bf035258a1073ffd6f6dc8ea61806aa7d4 Mon Sep 17 00:00:00 2001 From: Mingming Cao Date: Fri, 25 Sep 2026 11:05:30 -0700 Subject: [PATCH 109/367] ibmveth: fix TX LTB and filter unwind on open-fail out_free_buffer_pools reuses open()'s loop index for the TX LTB walk. After the pool unwind i is -1, so the TX buffers allocated earlier are leaked. request_irq() failure has the same leak. The TX-fail goto on that walk also skips dma_unmap of the filter list (pre-existing since the LTB was added). Rewrite the TX walk to real_num_tx_queues with a pointer check, and unmap the filter list before those frees. Fixes: d926793c1de9 ("ibmveth: Implement multi queue on xmit") Cc: stable@vger.kernel.org Signed-off-by: Mingming Cao Reviewed-by: Dave Marquardt Link: https://patch.msgid.link/d14314c2b10f125f5e578782484ba3cc50564d53.1790357373.git.mmc@linux.ibm.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/ibm/ibmveth.c | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/drivers/net/ethernet/ibm/ibmveth.c b/drivers/net/ethernet/ibm/ibmveth.c index 3e44f6b714d4..abebdb1fc262 100644 --- a/drivers/net/ethernet/ibm/ibmveth.c +++ b/drivers/net/ethernet/ibm/ibmveth.c @@ -553,10 +553,15 @@ static int ibmveth_rxq_harvest_buffer(struct ibmveth_adapter *adapter, static void ibmveth_free_tx_ltb(struct ibmveth_adapter *adapter, int idx) { + void *ptr = adapter->tx_ltb_ptr[idx]; + + if (!ptr) + return; + + adapter->tx_ltb_ptr[idx] = NULL; dma_unmap_single(&adapter->vdev->dev, adapter->tx_ltb_dma[idx], adapter->tx_ltb_size, DMA_TO_DEVICE); - kfree(adapter->tx_ltb_ptr[idx]); - adapter->tx_ltb_ptr[idx] = NULL; + kfree(ptr); } static int ibmveth_allocate_tx_ltb(struct ibmveth_adapter *adapter, int idx) @@ -667,7 +672,7 @@ static int ibmveth_open(struct net_device *netdev) for (i = 0; i < netdev->real_num_tx_queues; i++) { if (ibmveth_allocate_tx_ltb(adapter, i)) - goto out_free_tx_ltb; + goto out_unmap_filter_list; } adapter->rx_queue.index = 0; @@ -745,10 +750,8 @@ static int ibmveth_open(struct net_device *netdev) dma_unmap_single(dev, adapter->filter_list_dma, 4096, DMA_BIDIRECTIONAL); -out_free_tx_ltb: - while (--i >= 0) { + for (i = netdev->real_num_tx_queues - 1; i >= 0; i--) ibmveth_free_tx_ltb(adapter, i); - } out_unmap_buffer_list: dma_unmap_single(dev, adapter->buffer_list_dma, 4096, From 17e73bae400f6a434dc006e3bee92c94cf781954 Mon Sep 17 00:00:00 2001 From: Chengfeng Ye Date: Tue, 29 Sep 2026 00:12:46 +0800 Subject: [PATCH 110/367] tipc: hold a reference to nodes found by link name tipc_node_find_by_name() returns a node after dropping its RCU read lock without taking a reference. The LINK_SET, LINK_GET and LINK_RESET_STATS handlers then lock and access the node, racing with timer-driven cleanup of a down peer. Generic netlink serialization does not exclude the node timer. The following interleaving can leave a handler using a freed node: CPU 0: find the node under RCU and release the node read lock CPU 1: tipc_node_timeout() clears the links and unlinks the down node CPU 1: drop the list and timer references, queuing tipc_node_free() CPU 0: leave the RCU read-side critical section CPU 1: complete the grace period and free the node CPU 0: acquire the node lock through the stale pointer LINK_SET also uses the node's media address after releasing the node lock, when passing queued packets to tipc_bearer_xmit(). KASAN on v7.3-rc5 reported: BUG: KASAN: slab-use-after-free in _raw_read_lock_bh Write of size 4 at addr ffff88807e06f008 by task poc/92 Call Trace: _raw_read_lock_bh kernel/locking/spinlock.c:287 tipc_nl_node_set_link net/tipc/node.c:2475 genl_family_rcv_msg_doit net/netlink/genetlink.c:1114 genl_rcv_msg net/netlink/genetlink.c:1209 netlink_rcv_skb net/netlink/af_netlink.c:2575 Allocated by task 0: tipc_node_create net/tipc/node.c:539 tipc_node_check_dest net/tipc/node.c:1196 tipc_disc_rcv net/tipc/discover.c:252 Freed by task 92: kfree mm/slub.c:6801 rcu_core kernel/rcu/tree.c:2919 Last potentially related work creation: __call_rcu_common.constprop.0 kernel/rcu/tree.c:3181 tipc_node_timeout net/tipc/node.c:814 Acquire a reference to the selected node with kref_get_unless_zero() before leaving RCU, returning NULL if the node has already been released. Release that reference on every caller exit after the last node access, including transmission in LINK_SET. Keep the existing link lookup order and locking so concurrent link removal still takes the existing error paths. Fixes: 6a939f365bdb ("tipc: Auto removal of peer down node instance") Cc: stable@vger.kernel.org Signed-off-by: Chengfeng Ye Link: https://patch.msgid.link/20260928161246.1895273-1-nicoyip.dev@gmail.com Signed-off-by: Jakub Kicinski --- net/tipc/node.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/net/tipc/node.c b/net/tipc/node.c index bd91378b7540..d7cbfa786c13 100644 --- a/net/tipc/node.c +++ b/net/tipc/node.c @@ -2421,8 +2421,11 @@ static struct tipc_node *tipc_node_find_by_name(struct net *net, } } tipc_node_read_unlock(n); - if (found_node) + if (found_node) { + if (!kref_get_unless_zero(&found_node->kref)) + found_node = NULL; break; + } } rcu_read_unlock(); @@ -2507,6 +2510,7 @@ int tipc_nl_node_set_link(struct sk_buff *skb, struct genl_info *info) tipc_node_read_unlock(node); tipc_bearer_xmit(net, bearer_id, &xmitq, &node->links[bearer_id].maddr, NULL); + tipc_node_put(node); return res; } @@ -2558,12 +2562,14 @@ int tipc_nl_node_get_link(struct sk_buff *skb, struct genl_info *info) link = node->links[bearer_id].link; if (!link) { tipc_node_read_unlock(node); + tipc_node_put(node); err = -EINVAL; goto err_free; } err = __tipc_nl_add_link(net, &msg, link, 0); tipc_node_read_unlock(node); + tipc_node_put(node); if (err) goto err_free; } @@ -2634,11 +2640,13 @@ int tipc_nl_node_reset_link_stats(struct sk_buff *skb, struct genl_info *info) if (!link) { spin_unlock_bh(&le->lock); tipc_node_read_unlock(node); + tipc_node_put(node); return -EINVAL; } tipc_link_reset_stats(link); spin_unlock_bh(&le->lock); tipc_node_read_unlock(node); + tipc_node_put(node); return 0; } From 26f90559a77cac4b8fdfa3491732b7c7e08210ac Mon Sep 17 00:00:00 2001 From: Sunho Park Date: Sun, 30 Aug 2026 18:56:05 +0900 Subject: [PATCH 111/367] srcu: Fix WARN_ON() for rcu_segcblist_n_cbs() in cleanup_srcu_struct() The WARN_ON() added by commit 78a38cbf6f20 ("srcu: Queue sdp->work when the delay timer is successfully deleted") uses rcu_segcblist_n_cbs() to detect callbacks that srcu_barrier() failed to wait for. However, the ->len counter is decremented only at the end of srcu_invoke_callbacks(), after the invoking loop has finished. Since srcu_barrier() can return right after the barrier callback is invoked, cleanup_srcu_struct() can see a non-zero n_cbs even though the cblist is already physically empty, falsely triggering the WARN_ON() together with a still-pending delay_work timer. This can be triggered as follows, as seen in the syzbot report against kvm_destroy_vm() -> cleanup_srcu_struct(&kvm->srcu): 1. call_srcu(&kvm->srcu, &bus->rcu, __free_bus) starts SRCU grace period GP1. 2. GP1 ends: a delay timer is armed and sdp->work is queued, but sdp->work has not run yet. 3. Another call_srcu(&kvm->srcu, &bus->rcu, __free_bus) call invokes srcu_segcblist_advance(), which moves the GP1 callback to RCU_DONE_TAIL, and starts SRCU grace period GP2. 4. srcu_barrier() is called. It queues its barrier callback after the GP2 callback and waits for srcu_invoke_callbacks() to invoke it. 5. GP2 ends: another delay timer is armed, and the sdp->work queued in step 2 begins to run. Its srcu_invoke_callbacks() call invokes srcu_segcblist_advance() again, moving the GP2 and barrier callbacks to RCU_DONE_TAIL as well, and then invokes all of them. However, rcu_segcblist_add_len(), which updates srcu_cblist's ->len, has not run yet at this point. 6. srcu_barrier() returns once its callback has been invoked, and cleanup_srcu_struct() starts running. It finds the delay timer armed in step 5 still pending and srcu_cblist's ->len still non-zero (because step 5 has not reached rcu_segcblist_add_len() yet), and WARN_ON() fires even though every callback has actually been invoked. Use rcu_segcblist_empty(), which checks the actual head of the cblist, instead of rcu_segcblist_n_cbs(), which checks the racy ->len counter. Callbacks that have genuinely not been invoked yet still leave the list non-empty, so the WARN_ON() still catches callers that skip srcu_barrier() or queue callbacks after it. Link: https://lore.kernel.org/rcu/e6350377085ddd85d6ef00d8e9a67bd50c762d3c@linux.dev/T/#t Reported-by: syzbot+d4faf7db59e11f6fd1ab@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=d4faf7db59e11f6fd1ab Fixes: 78a38cbf6f20 ("srcu: Queue sdp->work when the delay timer is successfully deleted") Suggested-by: Zqiang Signed-off-by: Sunho Park Signed-off-by: Paul E. McKenney Tested-by: Sean Christopherson --- kernel/rcu/srcutree.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kernel/rcu/srcutree.c b/kernel/rcu/srcutree.c index ed204b3f4b84..ad27880dd690 100644 --- a/kernel/rcu/srcutree.c +++ b/kernel/rcu/srcutree.c @@ -704,7 +704,7 @@ void cleanup_srcu_struct(struct srcu_struct *ssp) // Call srcu_barrier() before this cleanup_srcu_struct() // to avoid triggering this WARN_ON(). if (WARN_ON(timer_delete_sync(&sdp->delay_work) && - rcu_segcblist_n_cbs(&sdp->srcu_cblist)) && + !rcu_segcblist_empty(&sdp->srcu_cblist)) && rcu_cpu_beenfullyonline(sdp->cpu)) queue_work_on(sdp->cpu, rcu_gp_wq, &sdp->work); flush_work(&sdp->work); From c9728fcf2e7fb4a7a8d08ebd9524c3b02ae1a021 Mon Sep 17 00:00:00 2001 From: Jamal Hadi Salim Date: Wed, 30 Sep 2026 03:31:02 -0400 Subject: [PATCH 112/367] net/sched: em_text: reject unterminated algo before autoload This is a follow-up to commit 9c572a83037a ("net/sched: fix potential stack infoleak in em_text_dump()"), which zero-initialised the dump-side struct. Sashiko pointed at a pre-existing bug that exposed the change-side input-validation gap open. em_text_change() casts the raw netlink attribute payload to struct tcf_em_text and passes conf->algo, a 16-byte array with no enforced NUL terminator, to textsearch_prepare(). On the TS_AUTOLOAD retry textsearch_prepare() calls request_module("ts_%s", algo); vsnprintf() walks %s until it finds a NUL, so an algo[] with no NUL reads past the array into the adjacent struct fields and payload and feeds those bytes to the usermode helper command line. This is an out-of-bounds read and a kernel memory disclosure. Reject the attribute when no NUL exists within TC_EM_TEXT_ALGOSIZ bytes, matching the check xt_string has had since 3ab720881b6e. Conditions to recreate the bug: - CAP_NET_ADMIN on the target netns (namespace-local via unshare -Urn is enough) - install clsact on an interface, then add a basic filter with one text ematch whose algo[] is 16 bytes with no NUL (e.g. "A"*16) and pattern_len at least 1 - the add fails with -ENOENT and the kernel invokes modprobe with a module name made of "ts_" plus the 16 bytes and the adjacent payload Fixes: d675c989ed2d4 ("[PKT_SCHED]: Packet classification based on textsearch (ematch)") Reported-by: Sashiko (gemini) Closes: https://sashiko.dev/#/patchset/20260918133953.12494-1-bernard.ladenthin%40gmail.com Signed-off-by: Jamal Hadi Salim Link: https://patch.msgid.link/QDISC-2DIU.v1.20260929183844@mojatatu.com Signed-off-by: Jakub Kicinski --- net/sched/em_text.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/net/sched/em_text.c b/net/sched/em_text.c index 4132f8c3c5fc..bc45edb8c03b 100644 --- a/net/sched/em_text.c +++ b/net/sched/em_text.c @@ -58,6 +58,9 @@ static int em_text_change(struct net *net, void *data, int len, if (len < sizeof(*conf) || len < (sizeof(*conf) + conf->pattern_len)) return -EINVAL; + if (!memchr(conf->algo, '\0', sizeof(conf->algo))) + return -EINVAL; + if (conf->from_layer > conf->to_layer) return -EINVAL; From afae89de73dd693cfa329580ba0fba63bd3f0a11 Mon Sep 17 00:00:00 2001 From: Omar Ramadan Date: Mon, 28 Sep 2026 23:23:11 +0300 Subject: [PATCH 113/367] amt: send the relay's General Query directly from the receive path An skb queued in a qdisc can outlive the tunnel it references through a raw pointer in skb->cb. For example, with igmp_qrv set to 1 on the relay a tunnel lives for 135s, so a netem delay of 180s on the amt device outlives it; when the tunnel expires and is freed, the subsequent dequeue triggers a use-after-free in amt_dev_xmit(). BUG: KASAN: slab-use-after-free in amt_dev_xmit+0x2763/0x2e20 Call Trace: amt_dev_xmit+0x2763/0x2e20 [drivers/net/amt.c:1262] dev_hard_start_xmit+0x22f/0x620 sch_direct_xmit+0x12e/0xac0 netem_dequeue+0x333/0xc50 net_tx_action+0x35c/0xa60 amt_send_igmp_gq() and amt_send_mld_gq() are only called from amt_request_handler(), inside the rcu_read_lock_bh() section of amt_rcv(). amt_request_handler() already has the tunnel the query is for: it found or created it inside that section. Queuing the query with dev_queue_xmit() only leads back into amt_dev_xmit(), which strips the Ethernet header and calls amt_send_membership_query() for that tunnel. Make that call directly from the two senders instead, the same way amt_send_advertisement() transmits from the receive path. The query never waits in a qdisc, the tunnel is only dereferenced inside the RCU section that found or created it, and nothing is stored in skb->cb, so no lookup or refcount is needed. Remove the query branch of amt_dev_xmit(), amt_skb_cb() and struct amt_skb_cb, which have no users left. Behaviour changes: - The relay's own General Queries no longer pass through the amt device's egress path: its qdisc, tc egress (clsact/tcx), the netfilter egress hook and packet taps. They are still visible as UDP on the underlay. - A query that is sent successfully is no longer counted as tx_dropped. The old query branch left through the unlock label, which counted every sent query as dropped. - A query that reaches amt_dev_xmit() on a relay from elsewhere, such as a userspace querier, is now dropped at the IGMP/MLD type switch. Before, it trusted whatever skb->cb held, and a NULL tunnel hit the WARN_ON(1). Fixes: cbc21dc1cfe9 ("amt: add data plane of amt interface") Reported-by: AutonomousCodeSecurity@microsoft.com Reported-by: Xiang Mei (Microsoft) Reported-by: Cen Zhang (Microsoft Security FORGE Labs) Signed-off-by: Omar Ramadan Link: https://patch.msgid.link/20260928202312.74574-2-omar@blockcast.net Signed-off-by: Jakub Kicinski --- drivers/net/amt.c | 48 +++++++++++++++-------------------------------- include/net/amt.h | 4 ---- 2 files changed, 15 insertions(+), 37 deletions(-) diff --git a/drivers/net/amt.c b/drivers/net/amt.c index bddc24e1856d..b53f8ec55661 100644 --- a/drivers/net/amt.c +++ b/drivers/net/amt.c @@ -80,15 +80,6 @@ static struct in6_addr mld2_all_node = MLD2_ALL_NODE_INIT; static struct mld2_grec mldv2_zero_grec; #endif -static struct amt_skb_cb *amt_skb_cb(struct sk_buff *skb) -{ - BUILD_BUG_ON(sizeof(struct amt_skb_cb) + sizeof(struct tc_skb_cb) > - sizeof_field(struct sk_buff, cb)); - - return (struct amt_skb_cb *)((void *)skb->cb + - sizeof(struct tc_skb_cb)); -} - static void __amt_source_gc_work(void) { struct amt_source_node *snode; @@ -791,6 +782,11 @@ static void amt_send_request(struct amt_dev *amt, bool v6) rcu_read_unlock(); } +static bool amt_send_membership_query(struct amt_dev *amt, + struct sk_buff *skb, + struct amt_tunnel_list *tunnel, + bool v6); + static void amt_send_igmp_gq(struct amt_dev *amt, struct amt_tunnel_list *tunnel) { @@ -800,8 +796,11 @@ static void amt_send_igmp_gq(struct amt_dev *amt, if (!skb) return; - amt_skb_cb(skb)->tunnel = tunnel; - dev_queue_xmit(skb); + skb_pull(skb, sizeof(struct ethhdr)); + if (amt_send_membership_query(amt, skb, tunnel, false)) { + amt->dev->stats.tx_dropped++; + kfree_skb(skb); + } } #if IS_ENABLED(CONFIG_IPV6) @@ -885,8 +884,11 @@ static void amt_send_mld_gq(struct amt_dev *amt, struct amt_tunnel_list *tunnel) if (!skb) return; - amt_skb_cb(skb)->tunnel = tunnel; - dev_queue_xmit(skb); + skb_pull(skb, sizeof(struct ethhdr)); + if (amt_send_membership_query(amt, skb, tunnel, true)) { + amt->dev->stats.tx_dropped++; + kfree_skb(skb); + } } #else static void amt_send_mld_gq(struct amt_dev *amt, struct amt_tunnel_list *tunnel) @@ -1186,7 +1188,6 @@ static netdev_tx_t amt_dev_xmit(struct sk_buff *skb, struct net_device *dev) #endif bool report = false; struct igmphdr *ih; - bool query = false; struct iphdr *iph; bool data = false; bool v6 = false; @@ -1204,9 +1205,6 @@ static netdev_tx_t amt_dev_xmit(struct sk_buff *skb, struct net_device *dev) case IGMP_HOST_MEMBERSHIP_REPORT: report = true; break; - case IGMP_HOST_MEMBERSHIP_QUERY: - query = true; - break; default: goto free; } @@ -1228,9 +1226,6 @@ static netdev_tx_t amt_dev_xmit(struct sk_buff *skb, struct net_device *dev) case ICMPV6_MLD2_REPORT: report = true; break; - case ICMPV6_MGM_QUERY: - query = true; - break; default: goto free; } @@ -1261,19 +1256,6 @@ static netdev_tx_t amt_dev_xmit(struct sk_buff *skb, struct net_device *dev) goto free; goto unlock; } else if (amt->mode == AMT_MODE_RELAY) { - if (query) { - tunnel = amt_skb_cb(skb)->tunnel; - if (!tunnel) { - WARN_ON(1); - goto free; - } - - /* Do not forward unexpected query */ - if (amt_send_membership_query(amt, skb, tunnel, v6)) - goto free; - goto unlock; - } - if (!data) goto free; list_for_each_entry_rcu(tunnel, &amt->tunnel_list, list) { diff --git a/include/net/amt.h b/include/net/amt.h index a0255491f5b0..2846dde0cadc 100644 --- a/include/net/amt.h +++ b/include/net/amt.h @@ -231,10 +231,6 @@ struct amt_relay_headers { }; } __packed; -struct amt_skb_cb { - struct amt_tunnel_list *tunnel; -}; - struct amt_tunnel_list { struct list_head list; /* Protect All resources under an amt_tunne_list */ From c41817fcaf397f6ff7c530b682581f5eb5465844 Mon Sep 17 00:00:00 2001 From: Omar Ramadan Date: Mon, 28 Sep 2026 23:23:12 +0300 Subject: [PATCH 114/367] selftests: net: amt: check that the relay's queries bypass the amt device The relay used to hand its General Queries to dev_queue_xmit() on the amt device, where a query could wait in a qdisc and outlive the tunnel it pointed to. The previous patch sends them directly from the receive path instead. Count the IGMP and MLD queries that leave the relay through amtr with tc flower filters on its egress, installed before the gateway comes up, and check that there are none. The forwarding tests before it already show that the gateway received its queries, since it cannot join without one. Without the previous patch the new test fails (one run counted 7 IGMP and 6 MLD queries); with it, all of amt.sh passes. Signed-off-by: Omar Ramadan Link: https://patch.msgid.link/20260928202312.74574-3-omar@blockcast.net Signed-off-by: Jakub Kicinski --- tools/testing/selftests/net/amt.sh | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/tools/testing/selftests/net/amt.sh b/tools/testing/selftests/net/amt.sh index 663744305e52..d13b20cccba9 100755 --- a/tools/testing/selftests/net/amt.sh +++ b/tools/testing/selftests/net/amt.sh @@ -150,6 +150,13 @@ setup_interface() ip netns exec "${RELAY}" ip a a 10.0.0.2/24 dev relay_gw ip netns exec "${RELAY}" ip link add amtr type amt mode relay \ local 10.0.0.2 dev relay_gw relay_port 2268 max_tunnels 4 + # Count the IGMP and MLD queries that leave the relay through its own + # amt device; test_query_egress expects none. + ip netns exec "${RELAY}" tc qdisc add dev amtr clsact + ip netns exec "${RELAY}" tc filter add dev amtr egress pref 1 \ + protocol ip flower ip_proto 0x2 action pass + ip netns exec "${RELAY}" tc filter add dev amtr egress pref 2 \ + protocol ipv6 flower ip_proto icmpv6 type 130 action pass ip netns exec "${RELAY}" ip a a 172.17.0.1/24 dev relay_src ip netns exec "${RELAY}" ip a a 2001:db8:3::1/64 dev relay_src ip netns exec "${SOURCE}" ip a a 172.17.0.2/24 dev src_relay @@ -246,6 +253,27 @@ test_ipv6_forward() fi } +# The relay sends its General Queries straight from the receive path, in +# the same context that found the tunnel. A query queued on the amt device +# instead could outlive the tunnel it was built for. The forwarding tests +# above show that the gateway got its queries. +test_query_egress() +{ + local n4 n6 + + n4=$(ip netns exec "${RELAY}" tc -s -j filter show dev amtr egress \ + pref 1 | jq '[.[].options.actions[0].stats.packets // empty] | add // 0') + n6=$(ip netns exec "${RELAY}" tc -s -j filter show dev amtr egress \ + pref 2 | jq '[.[].options.actions[0].stats.packets // empty] | add // 0') + if [ "$n4" -eq 0 ] && [ "$n6" -eq 0 ]; then + printf "TEST: %-60s [ OK ]\n" "amt relay queries bypass the amt device" + else + printf "TEST: %-60s [FAIL]\n" "amt relay queries bypass the amt device" + echo "IGMP queries on amtr egress: $n4, MLD queries: $n6" >&2 + ERR=1 + fi +} + send_mcast4() { sleep 5 @@ -287,6 +315,7 @@ wait $pid || err=$? if [ $err -eq 1 ]; then ERR=1 fi +test_query_egress printf "TEST: %-50s" "IPv4 amt traffic forwarding torture" send_mcast_torture4 printf " [ OK ]\n" From 28bc1ef699610ee09ce3d46a00552f5a0a0144bd Mon Sep 17 00:00:00 2001 From: Quchaosheng Date: Mon, 28 Sep 2026 19:31:08 +0800 Subject: [PATCH 115/367] net/packet: guard the ll header push in packet_rcv_spkt() packet_rcv_spkt() restores the link layer header with skb_push(skb, skb->data - skb_mac_header(skb)); That subtraction is only meaningful when the device actually has a link layer header. packet_rcv() and tpacket_rcv() both wrap it in dev_has_header(), which is also the predicate the block comment at the top of the file states the restore in terms of; packet_rcv_spkt() does not. commit d549699048b4 ("net/packet: fix packet receive on L3 devices without visible hard header") introduced the helper and changed the two call sites, and this one stayed behind. A device without a visible ll header can leave skb->mac_header at the 0xFFFF sentinel that __alloc_skb() initialises it to. A CAN skb does: init_can_skb() sets pkt_type and ip_summed but does not reset the headers, and commit 9f10374bb024 ("can: remove private CAN skb headroom infrastructure") dropped the skb_reset_*_header() calls that used to be there. skb_mac_header() is then 0xFFFF, the length becomes a large negative number and skb_push() reports it through skb_under_panic() -- from softirq context, so it is a full system panic even with panic_on_oops=0: skbuff: skb_under_panic: text:ffffffff8bd21bc1 len:-65455 put:-65471 head:... data:... tail:0x50 end:0x180 dev:can0 kernel BUG at net/core/skbuff.c:214! RIP: 0010:skb_panic+0x50/0x60 Call Trace: skb_push+0x38/0x40 packet_rcv_spkt+0xe1/0x170 __netif_receive_skb_core.constprop.0+0x7e8/0xd30 ... Kernel panic - not syncing: Fatal exception in interrupt The socket type is reachable: packet_create() accepts SOCK_PACKET alongside SOCK_RAW and SOCK_DGRAM behind the same CAP_NET_RAW check, and neither the socket length nor a capability check keeps it away from a CAN interface. The missing skb_reset_*_header() calls in init_can_skb() are a regression in their own right and are being fixed separately, but a packet socket should not turn a link layer that did not initialise its mac header into a kernel panic. Guard the push the way the other two receive paths do. Tested on v7.3-rc5 under QEMU with a slcan device on a pty, which is the driver RX path: vcan does not reproduce it, because can_send() resets the headers on the way out. One SOCK_PACKET socket bound to can0 and one frame written into the line discipline panics an unpatched kernel with the trace above; the same image with this patch prints no panic and powers off normally. Both kernels are this tree, defconfig plus CONFIG_CAN_SLCAN=y, differing only in this hunk. Fixes: d549699048b4 ("net/packet: fix packet receive on L3 devices without visible hard header") Cc: stable@vger.kernel.org Signed-off-by: Quchaosheng Reviewed-by: Willem de Bruijn Link: https://patch.msgid.link/20260928113108.2127215-1-quchaosheng000406@163.com Signed-off-by: Jakub Kicinski --- net/packet/af_packet.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/net/packet/af_packet.c b/net/packet/af_packet.c index 8865c1a42667..6ff16eef24f4 100644 --- a/net/packet/af_packet.c +++ b/net/packet/af_packet.c @@ -1911,7 +1911,8 @@ static int packet_rcv_spkt(struct sk_buff *skb, struct net_device *dev, spkt = &PACKET_SKB_CB(skb)->sa.pkt; - skb_push(skb, skb->data - skb_mac_header(skb)); + if (dev_has_header(dev)) + skb_push(skb, skb->data - skb_mac_header(skb)); /* * The SOCK_PACKET socket receives _all_ frames. From 0843b23890642a02ff85c36406224e8874e30664 Mon Sep 17 00:00:00 2001 From: Shashank Mohan Jain Date: Sun, 27 Sep 2026 10:47:42 +0530 Subject: [PATCH 116/367] lib/dim: fix 32-bit overflow in dim_calc_stats() rates dim_calc_stats() computes the per-millisecond rates as DIV_ROUND_UP(nbytes * USEC_PER_MSEC, delta_us) where nbytes is a u32 and USEC_PER_MSEC is 1000L. On 64-bit the product is done in 64-bit long arithmetic, but on 32-bit architectures long is 32 bits wide and the product wraps as soon as a measurement window carries more than 4294967 bytes (about 4.3 MB). The same applies to the packet and completion counts, although those need more than 4.29 million packets or completions per window. A DIM window spans DIM_NEVENTS (64) events. Drivers count events per interrupt or per NAPI poll, so under sustained load a window can easily carry more than 4.3 MB: 64 full NAPI polls of 64 MTU-sized frames are already 6.2 MB, and drivers such as mtk_eth_soc count one event per interrupt while NAPI keeps polling with the interrupt masked. On 32-bit users of the library (for example mtk_eth_soc on MT7621, bcmgenet and bcmsysport on 32-bit ARM, or virtio_net in a 32-bit guest) bpms then becomes the product modulo 2^32 divided by the window length, and net_dim_stats_compare() makes its BETTER/WORSE decisions on a value that has little to do with the real throughput. For example, a 1 Gbit/s link at line rate that moves 5 MB in a 40 ms window gives bpms = 125000 on 64-bit but 17626 on 32-bit, and 5 million packets in 2 s gives ppms = 353 instead of 2500. Widen the products to 64 bits and divide with DIV_ROUND_UP_ULL(). The results are unchanged on 64-bit. Fixes: cb3c7fd4f839 ("net/mlx5e: Support adaptive RX coalescing") Fixes: 4c4dbb4a7363 ("net/mlx5e: Move dynamic interrupt coalescing code to include/linux") Assisted-by: LLM Signed-off-by: Shashank Mohan Jain Signed-off-by: David S. Miller --- lib/dim/dim.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/lib/dim/dim.c b/lib/dim/dim.c index 97c3d084ebf0..7f3eebae73cb 100644 --- a/lib/dim/dim.c +++ b/lib/dim/dim.c @@ -69,11 +69,15 @@ bool dim_calc_stats(const struct dim_sample *start, if (!delta_us) return false; - curr_stats->ppms = DIV_ROUND_UP(npkts * USEC_PER_MSEC, delta_us); - curr_stats->bpms = DIV_ROUND_UP(nbytes * USEC_PER_MSEC, delta_us); + /* u32 * USEC_PER_MSEC overflows a 32-bit long */ + curr_stats->ppms = DIV_ROUND_UP_ULL((u64)npkts * USEC_PER_MSEC, + delta_us); + curr_stats->bpms = DIV_ROUND_UP_ULL((u64)nbytes * USEC_PER_MSEC, + delta_us); curr_stats->epms = DIV_ROUND_UP(DIM_NEVENTS * USEC_PER_MSEC, delta_us); - curr_stats->cpms = DIV_ROUND_UP(ncomps * USEC_PER_MSEC, delta_us); + curr_stats->cpms = DIV_ROUND_UP_ULL((u64)ncomps * USEC_PER_MSEC, + delta_us); if (curr_stats->epms != 0) curr_stats->cpe_ratio = DIV_ROUND_DOWN_ULL( curr_stats->cpms * 100, curr_stats->epms); From 636035157a814f289f293fd0edea1a367a637797 Mon Sep 17 00:00:00 2001 From: Shashank Mohan Jain Date: Sun, 27 Sep 2026 10:47:43 +0530 Subject: [PATCH 117/367] lib/dim: add KUnit test for dim_calc_stats() Add a KUnit suite for the DIM library that checks the packet, byte, event and completion rates computed by dim_calc_stats(), including counter wraparound and windows whose byte or packet count times USEC_PER_MSEC does not fit in 32 bits. The latter cases fail on 32-bit architectures without the previous commit. Assisted-by: LLM Signed-off-by: Shashank Mohan Jain Signed-off-by: David S. Miller --- lib/Kconfig.debug | 15 ++++++ lib/dim/Makefile | 2 + lib/dim/dim_kunit.c | 126 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 143 insertions(+) create mode 100644 lib/dim/dim_kunit.c diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug index 134b15a44625..6b24a2c89b84 100644 --- a/lib/Kconfig.debug +++ b/lib/Kconfig.debug @@ -2906,6 +2906,21 @@ config LIVEUPDATE_TEST If unsure, say N +config DIMLIB_KUNIT_TEST + tristate "KUnit test for the DIM library" if !KUNIT_ALL_TESTS + depends on KUNIT && NET + select DIMLIB + default KUNIT_ALL_TESTS + help + This builds the KUnit test for the Dynamic Interrupt Moderation + library (lib/dim/), which checks the rate statistics computed by + dim_calc_stats(). + + For more information on KUnit and unit tests in general please refer + to the KUnit documentation in Documentation/dev-tools/kunit/. + + If unsure, say N. + config CMDLINE_KUNIT_TEST tristate "KUnit test for cmdline API" if !KUNIT_ALL_TESTS depends on KUNIT diff --git a/lib/dim/Makefile b/lib/dim/Makefile index 5b9bfaac7ac1..47e1c36230e4 100644 --- a/lib/dim/Makefile +++ b/lib/dim/Makefile @@ -5,3 +5,5 @@ obj-$(CONFIG_DIMLIB) += dimlib.o dimlib-y := dim.o net_dim.o rdma_dim.o + +obj-$(CONFIG_DIMLIB_KUNIT_TEST) += dim_kunit.o diff --git a/lib/dim/dim_kunit.c b/lib/dim/dim_kunit.c new file mode 100644 index 000000000000..2e984f6b9fe4 --- /dev/null +++ b/lib/dim/dim_kunit.c @@ -0,0 +1,126 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * KUnit tests for the Dynamic Interrupt Moderation library + */ + +#include +#include +#include +#include + +struct dim_calc_stats_case { + const char *name; + u32 delta_us; + u32 start_pkts, end_pkts; + u32 start_bytes, end_bytes; + u32 start_comps, end_comps; + int ppms, bpms, cpms; +}; + +static const struct dim_calc_stats_case dim_calc_stats_cases[] = { + { + .name = "small", + .delta_us = 1000, + .end_pkts = 640, .end_bytes = 640 * 1500, .end_comps = 64, + .ppms = 640, .bpms = 960000, .cpms = 64, + }, + { + .name = "round_up", + .delta_us = 3000, + .end_pkts = 10, .end_bytes = 10, .end_comps = 1, + .ppms = 4, .bpms = 4, .cpms = 1, + }, + { + .name = "counter_wrap", + .delta_us = 1000, + .start_pkts = 0xffffff00, .end_pkts = 0x100, + .start_bytes = 0xfffff000, .end_bytes = 0x1000, + .start_comps = 0xfffffff0, .end_comps = 0x10, + .ppms = 0x200, .bpms = 0x2000, .cpms = 0x20, + }, + { + /* 30 MB in 10 ms (24 Gbit/s): nbytes * 1000 exceeds 32 bits */ + .name = "many_bytes", + .delta_us = 10000, + .end_pkts = 20000, .end_bytes = 30000000, .end_comps = 64, + .ppms = 2000, .bpms = 3000000, .cpms = 7, + }, + { + /* 4.3 MB in 16 ms (2.15 Gbit/s), just above the 32-bit limit */ + .name = "bytes_32bit_limit", + .delta_us = 16000, + .end_pkts = 2900, .end_bytes = 4300000, .end_comps = 64, + .ppms = 182, .bpms = 268750, .cpms = 4, + }, + { + /* 5 MB in 40 ms: a 1 Gbit/s link at line rate */ + .name = "gigabit", + .delta_us = 40000, + .end_pkts = 3300, .end_bytes = 5000000, .end_comps = 64, + .ppms = 83, .bpms = 125000, .cpms = 2, + }, + { + /* 5 million packets and completions in 2 s */ + .name = "many_packets", + .delta_us = 2000000, + .end_pkts = 5000000, .end_bytes = 5000000, .end_comps = 5000000, + .ppms = 2500, .bpms = 2500, .cpms = 2500, + }, +}; + +static void dim_calc_stats_case_desc(const struct dim_calc_stats_case *t, + char *desc) +{ + strscpy(desc, t->name, KUNIT_PARAM_DESC_SIZE); +} + +KUNIT_ARRAY_PARAM(dim_calc_stats, dim_calc_stats_cases, + dim_calc_stats_case_desc); + +static void dim_calc_stats_test(struct kunit *test) +{ + const struct dim_calc_stats_case *t = test->param_value; + struct dim_sample start = {}, end = {}; + struct dim_stats stats = {}; + + dim_update_sample_with_comps(0, t->start_pkts, t->start_bytes, + t->start_comps, &start); + dim_update_sample_with_comps(DIM_NEVENTS, t->end_pkts, t->end_bytes, + t->end_comps, &end); + /* dim_update_sample() stamps ktime_get(); use fixed times instead */ + start.time = ktime_set(1000, 0); + end.time = ktime_add_us(start.time, t->delta_us); + + KUNIT_ASSERT_TRUE(test, dim_calc_stats(&start, &end, &stats)); + KUNIT_EXPECT_EQ(test, stats.ppms, t->ppms); + KUNIT_EXPECT_EQ(test, stats.bpms, t->bpms); + KUNIT_EXPECT_EQ(test, stats.cpms, t->cpms); + KUNIT_EXPECT_EQ(test, stats.epms, + (int)DIV_ROUND_UP(DIM_NEVENTS * USEC_PER_MSEC, + t->delta_us)); +} + +static void dim_calc_stats_no_time_test(struct kunit *test) +{ + struct dim_sample sample = {}; + struct dim_stats stats = {}; + + dim_update_sample_with_comps(0, 100, 1000, 10, &sample); + KUNIT_EXPECT_FALSE(test, dim_calc_stats(&sample, &sample, &stats)); +} + +static struct kunit_case dim_test_cases[] = { + KUNIT_CASE_PARAM(dim_calc_stats_test, dim_calc_stats_gen_params), + KUNIT_CASE(dim_calc_stats_no_time_test), + {} +}; + +static struct kunit_suite dim_test_suite = { + .name = "dim", + .test_cases = dim_test_cases, +}; + +kunit_test_suite(dim_test_suite); + +MODULE_DESCRIPTION("KUnit tests for the DIM library"); +MODULE_LICENSE("GPL"); From 3acdd44385bc7e2e57605e073691ce69cc5a960d Mon Sep 17 00:00:00 2001 From: Yuqi Xu Date: Mon, 28 Sep 2026 16:24:38 +0800 Subject: [PATCH 118/367] tipc: destroy topsrv workqueues before closing connections tipc_topsrv_stop() closed subscriber connections while the topology server's send and receive workqueues were still running. Socket callbacks and subscription events could then queue more work, and in-flight send/recv work could drop the last connection reference during the conn_idr walk. That race produced several teardown failures: refcount_t addition on 0 from conn_get() on a connection whose release was blocked on idr_lock, a subsequent use-after-free in tipc_conn_close(), queue_work() on an already destroyed workqueue from the listener data-ready callback, and an RCU stall in tipc_topsrv_exit_net() while the walk spun under idr_lock. Clear srv->listener under idr_lock so it acts as a shutdown flag, skip queue_work() once it is NULL, destroy the workqueues to flush in-flight work, and only then close the remaining connections. Refuse tipc_conn_lookup() after that flag is cleared, and drop idr_lock when the teardown walk finds no connection, so an in-flight subscription event cannot pin idr_in_use while the walk holds the lock. v3 supersedes the narrower in-thread diff Tung Quang Nguyen posted on 2026-09-23. It keeps his teardown order and adds the lookup refusal and the empty-idr unlock. Fixes: c5fa7b3cf3cb ("tipc: introduce new TIPC server infrastructure") Cc: stable@vger.kernel.org Reported-by: Vega Assisted-by: LLM Signed-off-by: Yuqi Xu Reviewed-by: Ren Wei Signed-off-by: David S. Miller --- net/tipc/topsrv.c | 80 ++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 65 insertions(+), 15 deletions(-) diff --git a/net/tipc/topsrv.c b/net/tipc/topsrv.c index af530c9ed840..82e9f44e2fe4 100644 --- a/net/tipc/topsrv.c +++ b/net/tipc/topsrv.c @@ -55,7 +55,7 @@ /** * struct tipc_topsrv - TIPC server structure * @conn_idr: identifier set of connection - * @idr_lock: protect the connection identifier set + * @idr_lock: protect the connection identifier set and listener * @idr_in_use: amount of allocated identifier entry * @net: network namespace instance * @awork: accept work item @@ -218,6 +218,10 @@ static struct tipc_conn *tipc_conn_lookup(struct tipc_topsrv *s, int conid) struct tipc_conn *con; spin_lock_bh(&s->idr_lock); + if (!s->listener) { + spin_unlock_bh(&s->idr_lock); + return NULL; + } con = idr_find(&s->conn_idr, conid); if (!connected(con) || !kref_get_unless_zero(&con->kref)) con = NULL; @@ -301,10 +305,20 @@ static void tipc_conn_send_to_sock(struct tipc_conn *con) static void tipc_conn_send_work(struct work_struct *work) { struct tipc_conn *con = container_of(work, struct tipc_conn, swork); + struct tipc_topsrv *srv; + + srv = con->server; + spin_lock_bh(&srv->idr_lock); + if (!srv->listener) { + spin_unlock_bh(&srv->idr_lock); + goto out; + } + spin_unlock_bh(&srv->idr_lock); if (connected(con)) tipc_conn_send_to_sock(con); +out: conn_put(con); } @@ -334,8 +348,14 @@ void tipc_topsrv_queue_evt(struct net *net, int conid, list_add_tail(&e->list, &con->outqueue); spin_unlock_bh(&con->outqueue_lock); - if (queue_work(srv->send_wq, &con->swork)) - return; + spin_lock_bh(&srv->idr_lock); + if (srv->listener) { + if (queue_work(srv->send_wq, &con->swork)) { + spin_unlock_bh(&srv->idr_lock); + return; + } + } + spin_unlock_bh(&srv->idr_lock); err: conn_put(con); } @@ -346,14 +366,20 @@ void tipc_topsrv_queue_evt(struct net *net, int conid, */ static void tipc_conn_write_space(struct sock *sk) { + struct tipc_topsrv *srv; struct tipc_conn *con; read_lock_bh(&sk->sk_callback_lock); con = sk->sk_user_data; if (connected(con)) { - conn_get(con); - if (!queue_work(con->server->send_wq, &con->swork)) - conn_put(con); + srv = con->server; + spin_lock_bh(&srv->idr_lock); + if (srv->listener) { + conn_get(con); + if (!queue_work(srv->send_wq, &con->swork)) + conn_put(con); + } + spin_unlock_bh(&srv->idr_lock); } read_unlock_bh(&sk->sk_callback_lock); } @@ -418,8 +444,17 @@ static int tipc_conn_rcv_from_sock(struct tipc_conn *con) static void tipc_conn_recv_work(struct work_struct *work) { struct tipc_conn *con = container_of(work, struct tipc_conn, rwork); + struct tipc_topsrv *srv; int count = 0; + srv = con->server; + spin_lock_bh(&srv->idr_lock); + if (!srv->listener) { + spin_unlock_bh(&srv->idr_lock); + goto out; + } + spin_unlock_bh(&srv->idr_lock); + while (connected(con)) { if (tipc_conn_rcv_from_sock(con)) break; @@ -430,6 +465,7 @@ static void tipc_conn_recv_work(struct work_struct *work) count = 0; } } +out: conn_put(con); } @@ -438,6 +474,7 @@ static void tipc_conn_recv_work(struct work_struct *work) */ static void tipc_conn_data_ready(struct sock *sk) { + struct tipc_topsrv *srv; struct tipc_conn *con; trace_sk_data_ready(sk); @@ -445,9 +482,14 @@ static void tipc_conn_data_ready(struct sock *sk) read_lock_bh(&sk->sk_callback_lock); con = sk->sk_user_data; if (connected(con)) { - conn_get(con); - if (!queue_work(con->server->rcv_wq, &con->rwork)) - conn_put(con); + srv = con->server; + spin_lock_bh(&srv->idr_lock); + if (srv->listener) { + conn_get(con); + if (!queue_work(srv->rcv_wq, &con->rwork)) + conn_put(con); + } + spin_unlock_bh(&srv->idr_lock); } read_unlock_bh(&sk->sk_callback_lock); } @@ -503,8 +545,12 @@ static void tipc_topsrv_listener_data_ready(struct sock *sk) read_lock_bh(&sk->sk_callback_lock); srv = sk->sk_user_data; - if (srv) - queue_work(srv->rcv_wq, &srv->awork); + if (srv) { + spin_lock_bh(&srv->idr_lock); + if (srv->listener) + queue_work(srv->rcv_wq, &srv->awork); + spin_unlock_bh(&srv->idr_lock); + } read_unlock_bh(&sk->sk_callback_lock); } @@ -700,23 +746,27 @@ static void tipc_topsrv_stop(struct net *net) struct tipc_conn *con; int id; + spin_lock_bh(&srv->idr_lock); + srv->listener = NULL; + spin_unlock_bh(&srv->idr_lock); + tipc_topsrv_work_stop(srv); + spin_lock_bh(&srv->idr_lock); for (id = 0; srv->idr_in_use; id++) { con = idr_find(&srv->conn_idr, id); if (con) { - conn_get(con); spin_unlock_bh(&srv->idr_lock); tipc_conn_close(con); - conn_put(con); spin_lock_bh(&srv->idr_lock); + continue; } + spin_unlock_bh(&srv->idr_lock); + spin_lock_bh(&srv->idr_lock); } __module_get(lsock->ops->owner); __module_get(lsock->sk->sk_prot_creator->owner); - srv->listener = NULL; spin_unlock_bh(&srv->idr_lock); - tipc_topsrv_work_stop(srv); sock_release(lsock); idr_destroy(&srv->conn_idr); kfree(srv); From c83e3d2912432107148cc5e288ddd153a84c0f43 Mon Sep 17 00:00:00 2001 From: Petr Mladek Date: Thu, 1 Oct 2026 16:07:27 +0200 Subject: [PATCH 119/367] braille: nbcon: Allow to use a serial console with NBCON API as Braille console The Braille console is not registered in console_list. Instead, it is integrated with the virtual terminal (VT) and shows what is displayed on the terminal. It writes the data using con->write*() callback of the associated serial console driver. braille_write() is called from the VT code under console_lock(). The associated serial console driver can be converted to the NBCON API though. The situation is similar to flushing nbcon consoles in the legacy loop when some boot consoles are still registered, see nbcon_legacy_emit_next_record(). But there is a big difference though. braille_write() is not directly called from the code paths flushing registered consoles. The VT code expects that braille_write() succeeds. It does not replay the message when it can't acquire the ownership. As a result, braille_write(): + must try harder to get the ownership. + has to be synchronized only against non-printk serial console which depend nbcon_device_try_acquire() using NBCON_PRIO_NORMAL. Let's look at it from another side and try to simulate the original locking using the NBCON API: 1. Take con->device_lock(), aka the port->lock in the legacy serial console driver. 2. Acquire nbcon context to provide some synchronization for a panic() context. Use NBCON_PRIO_NORMAL because it contends only with nbcon_device_try_acquire() users. Do it in a busy loop. It should always succeed when con->device_lock() succeeded because all other users do the same. The only exception is when the context get acquired by a CPU handling panic. 3. In panic, disable interrupts and try to acquire the nbcon context. Use NBCON_PRIO_PANIC. And try even an unsafe takeover because otherwise the Braille console won't see the text shown during panic(). It is similar to the oops_in_progress/trylock handling in the legacy serial console driver. Finally, avoid the newline prepending logic in the existing serial console drivers when they are used as a Braille console. As explained above, the Braille console shows the last modified line on the terminal (VT). braille_write() is called when single characters are added. Most messages are not ended by newline. Anyway, the VT code does not have logic to reply partially printed messages. Fixes: 13189fa73afa ("printk: nbcon: Rely on kthreads for normal operation") Reviewed-by: John Ogness [pmladek@suse.com: Remove white space changes in braille_register_console(). Typo fix.] Link: https://patch.msgid.link/20261001140727.124398-2-pmladek@suse.com Signed-off-by: Petr Mladek --- .../accessibility/braille/braille_console.c | 57 ++++++++++++- drivers/tty/serial/8250/8250_port.c | 5 +- drivers/tty/serial/amba-pl011.c | 2 +- drivers/tty/serial/imx.c | 2 +- drivers/tty/serial/sifive.c | 2 +- include/linux/console.h | 15 ++++ kernel/printk/nbcon.c | 79 +++++++++++++++++++ 7 files changed, 156 insertions(+), 6 deletions(-) diff --git a/drivers/accessibility/braille/braille_console.c b/drivers/accessibility/braille/braille_console.c index 06b43b678d6e..5a3e375f4800 100644 --- a/drivers/accessibility/braille/braille_console.c +++ b/drivers/accessibility/braille/braille_console.c @@ -62,14 +62,50 @@ static void braille_write(u16 *buf) { static u16 lastwrite[WIDTH]; unsigned char data[1 + 1 + 2*WIDTH + 2 + 1], csum = 0, *c; + struct nbcon_write_context wctxt = { }; + unsigned long flags; u16 out; int i; if (!braille_co) return; + /* + * Braille console is not registered in console_list. Instead, it + * is integrated with VT and shows what appears on the graphical + * console under console_lock(). From this POV it is a legacy + * console. But it calls serial console driver which might be + * converted to the NBCON API. It is similar to + * nbcon_legacy_emit_next_record() except that we should try + * harder to get the lock. Otherwise, the Braille device won't show + * everything what is displayed on the terminal. + * + * In short, simulate the original locking using NBCON API. + */ + if (braille_co->flags & CON_NBCON) { + if (panic_on_this_cpu()) { + /* + * This should be good enough in practice. Most/all + * serial console drivers have the atomic callback. + */ + if (!braille_co->write_atomic) + return; + + local_irq_save(flags); + /* NBCON API strictly requires the ownership. */ + if (!nbcon_braille_try_acquire(braille_co, &wctxt)) { + local_irq_restore(flags); + return; + } + } else { + braille_co->device_lock(braille_co, &flags); + while (!nbcon_braille_try_acquire(braille_co, &wctxt)) + cpu_relax(); + } + } + if (!memcmp(lastwrite, buf, WIDTH * sizeof(*buf))) - return; + goto unlock_nbcon; memcpy(lastwrite, buf, WIDTH * sizeof(*buf)); #define SOH 1 @@ -102,7 +138,24 @@ static void braille_write(u16 *buf) *c++ = csum; *c++ = ETX; - braille_co->write(braille_co, data, c - data); + if (braille_co->flags & CON_NBCON) { + nbcon_write_context_set_buf(&wctxt, (char *)data, c - data); + if (panic_on_this_cpu()) + braille_co->write_atomic(braille_co, &wctxt); + else + braille_co->write_thread(braille_co, &wctxt); + } else { + braille_co->write(braille_co, data, c - data); + } + +unlock_nbcon: + if (braille_co->flags & CON_NBCON) { + nbcon_braille_release(&wctxt); + if (panic_on_this_cpu()) + local_irq_restore(flags); + else + braille_co->device_unlock(braille_co, flags); + } } /* Follow the VC cursor*/ diff --git a/drivers/tty/serial/8250/8250_port.c b/drivers/tty/serial/8250/8250_port.c index 38fa45e74a37..548fd68ad1fa 100644 --- a/drivers/tty/serial/8250/8250_port.c +++ b/drivers/tty/serial/8250/8250_port.c @@ -3417,8 +3417,11 @@ static void __serial8250_console_write(struct uart_8250_port *up, * If the console printer did not fully output the previous line, it * must have been handed or taken over. Insert a newline in order to * maintain clean output. + * + * Braille consoles are an exception. The serial port is not used + * for printk(). The driver is supposed to write exactly what it gets. */ - if (!up->console_line_ended) { + if (unlikely(!up->console_line_ended && !nbcon_write_context_is_braille(wctxt))) { if (use_fifo) __serial8250_console_fifo_write(up, wctxt, "\n", 1); else diff --git a/drivers/tty/serial/amba-pl011.c b/drivers/tty/serial/amba-pl011.c index c4824c201e1c..e67f2ac454b6 100644 --- a/drivers/tty/serial/amba-pl011.c +++ b/drivers/tty/serial/amba-pl011.c @@ -2656,7 +2656,7 @@ pl011_console_write_atomic(struct console *co, struct nbcon_write_context *wctxt uap, REG_CR); } - if (!uap->console_line_ended) + if (unlikely(!uap->console_line_ended && !nbcon_is_braille(co))) uart_console_write(&uap->port, "\n", 1, pl011_console_putchar); uart_console_write(&uap->port, wctxt->outbuf, wctxt->len, pl011_console_putchar); diff --git a/drivers/tty/serial/imx.c b/drivers/tty/serial/imx.c index 513dbe95f7e9..986fefb45c63 100644 --- a/drivers/tty/serial/imx.c +++ b/drivers/tty/serial/imx.c @@ -2136,7 +2136,7 @@ static void imx_uart_console_write_atomic(struct console *co, imx_uart_writel(sport, ucr1, UCR1); imx_uart_writel(sport, old_ucr.ucr2 | UCR2_TXEN, UCR2); - if (!sport->last_putchar_was_newline) + if (unlikely(!sport->last_putchar_was_newline && !nbcon_is_braille(co))) uart_console_write(port, "\n", 1, imx_uart_console_putchar); uart_console_write(port, wctxt->outbuf, wctxt->len, imx_uart_console_putchar); diff --git a/drivers/tty/serial/sifive.c b/drivers/tty/serial/sifive.c index 110d67613192..f615701fceb7 100644 --- a/drivers/tty/serial/sifive.c +++ b/drivers/tty/serial/sifive.c @@ -821,7 +821,7 @@ static void sifive_serial_console_write_atomic(struct console *co, ier = __ssp_readl(ssp, SIFIVE_SERIAL_IE_OFFS); __ssp_writel(0, SIFIVE_SERIAL_IE_OFFS, ssp); - if (!ssp->console_line_ended) + if (unlikely(!ssp->console_line_ended && !nbcon_is_braille(co))) uart_console_write(port, "\n", 1, sifive_serial_console_putchar); uart_console_write(port, wctxt->outbuf, wctxt->len, sifive_serial_console_putchar); diff --git a/include/linux/console.h b/include/linux/console.h index 502d1abe3f50..df23148987ee 100644 --- a/include/linux/console.h +++ b/include/linux/console.h @@ -615,6 +615,15 @@ extern bool nbcon_allow_unsafe_takeover(void); extern bool nbcon_kdb_try_acquire(struct console *con, struct nbcon_write_context *wctxt); extern void nbcon_kdb_release(struct nbcon_write_context *wctxt); +extern bool nbcon_write_context_is_braille(struct nbcon_write_context *wctxt); +extern bool nbcon_braille_try_acquire(struct console *con, + struct nbcon_write_context *wctxt); +extern void nbcon_braille_release(struct nbcon_write_context *wctxt); + +static inline bool nbcon_is_braille(struct console *con) +{ + return con && (con->flags & CON_BRL); +} /* * Check if the given console is currently capable and allowed to print @@ -678,8 +687,14 @@ static inline void nbcon_reacquire_nobuf(struct nbcon_write_context *wctxt) { } static inline bool nbcon_kdb_try_acquire(struct console *con, struct nbcon_write_context *wctxt) { return false; } static inline void nbcon_kdb_release(struct nbcon_write_context *wctxt) { } +static inline +bool nbcon_write_context_is_braille(struct nbcon_write_context *wctxt) { return false; } static inline bool console_is_usable(struct console *con, short flags, bool use_atomic) { return false; } +static inline bool nbcon_braille_try_acquire(struct console *con, + struct nbcon_write_context *wctxt) { return false; } +static inline void nbcon_braille_release(struct nbcon_write_context *wctxt) { } +static inline bool nbcon_is_braille(struct console *con) { return false; } #endif extern int console_set_on_cmdline; diff --git a/kernel/printk/nbcon.c b/kernel/printk/nbcon.c index d17704fe93ae..fb1b37741952 100644 --- a/kernel/printk/nbcon.c +++ b/kernel/printk/nbcon.c @@ -1887,6 +1887,7 @@ bool nbcon_device_try_acquire(struct console *con) memset(ctxt, 0, sizeof(*ctxt)); ctxt->console = con; + /* Keep in sync with nbcon_braille_try_acquire(). */ ctxt->prio = NBCON_PRIO_NORMAL; if (!nbcon_context_try_acquire(ctxt, false)) @@ -2002,3 +2003,81 @@ void nbcon_kdb_release(struct nbcon_write_context *wctxt) */ __nbcon_atomic_flush_pending_con(ctxt->console, prb_next_reserve_seq(prb)); } + +/** + * nbcon_is_braille - Checks whether the nbcon write context is using Braille console + * + * @wctxt: checked nbcon write context + * + * Return: True when the write context is associated with a Braille console. + * Otherwise, return false. + * + * Context: Can be called in any context but only when Braille console is + * registered and the struct console could not disappear. + */ +bool nbcon_write_context_is_braille(struct nbcon_write_context *wctxt) +{ + struct nbcon_context *ctxt = &ACCESS_PRIVATE(wctxt, ctxt); + struct console *con = ctxt->console; + + return con && con->flags & CON_BRL; +} +EXPORT_SYMBOL_GPL(nbcon_write_context_is_braille); + +/** + * nbcon_braille_try_acquire - Try to acquire nbcon console for braille_write() + * + * @con: The nbcon console to acquire + * @wctxt: The nbcon write context to be used on success + * + * Context: braille_write() for emitting a single buffer on Braille console. + * + * Return: True if the console was acquired. False otherwise. + * + * Braille console is not registered as a proper printk consoles. Instead, + * it is integrated with the graphical virtual terminal. + * + * This function is going to synchronize the Braille write against other + * operations on the used serial port. The port can be used also for a user + * input but printk() won't emit the messages there directly. It means + * the other operations will get synchronized using nbcon_device_try_acquire(). + */ +bool nbcon_braille_try_acquire(struct console *con, + struct nbcon_write_context *wctxt) +{ + struct nbcon_context *ctxt = &ACCESS_PRIVATE(wctxt, ctxt); + bool success; + + memset(ctxt, 0, sizeof(*ctxt)); + ctxt->console = con; + /* Keep in sync with nbcon_device_try_acquire(). */ + if (panic_on_this_cpu()) + ctxt->prio = NBCON_PRIO_PANIC; + else + ctxt->prio = NBCON_PRIO_NORMAL; + + success = nbcon_context_try_acquire(ctxt, false); + /* + * Try hard in panic, otherwise the Braille console would + * miss a text on the related VT. + */ + if (panic_on_this_cpu() && !success) { + ctxt->allow_unsafe_takeover = true; + success = nbcon_context_try_acquire(ctxt, false); + } + + return success; +} + +/** + * nbcon_braille_release - Release the nbcon console + * + * @wctxt: The nbcon write context initialized by a successful + * nbcon_braille_try_acquire() + */ +void nbcon_braille_release(struct nbcon_write_context *wctxt) +{ + struct nbcon_context *ctxt = &ACCESS_PRIVATE(wctxt, ctxt); + + nbcon_context_release(ctxt); +} From 4c9dc0faca98084922caeb689579d338cf3e1c29 Mon Sep 17 00:00:00 2001 From: Sang-Hoon Choi Date: Wed, 30 Sep 2026 02:37:34 +0900 Subject: [PATCH 120/367] cxgb4/ch_ktls: disable softirqs around tid_list erases chcr_ktls_cpl_act_open_rpl() inserts into tid_list from the receive path using xa_insert_bh(). The array is shared by the adapter's connections and initialized with XA_FLAGS_LOCK_BH. However, chcr_ktls_dev_del() and the chcr_ktls_dev_add() error path use xa_erase(), which takes the XArray lock without disabling softirqs. When cleanup runs with softirqs enabled, a receive softirq for another connection on the same CPU can interrupt the erase and spin on the lock held by the interrupted task. Use xa_erase_bh() at both sites to match the insertion path. Fixes: 65e302a9bd57 ("cxgb4/ch_ktls: Clear resources when pf4 device is removed") Reported-by: Changyul Lee Assisted-by: LLM Signed-off-by: Sang-Hoon Choi Reviewed-by: Ayush Sawal Signed-off-by: David S. Miller --- .../net/ethernet/chelsio/inline_crypto/ch_ktls/chcr_ktls.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/net/ethernet/chelsio/inline_crypto/ch_ktls/chcr_ktls.c b/drivers/net/ethernet/chelsio/inline_crypto/ch_ktls/chcr_ktls.c index f5acd4be1e69..dbec95b49777 100644 --- a/drivers/net/ethernet/chelsio/inline_crypto/ch_ktls/chcr_ktls.c +++ b/drivers/net/ethernet/chelsio/inline_crypto/ch_ktls/chcr_ktls.c @@ -389,7 +389,7 @@ static void chcr_ktls_dev_del(struct net_device *netdev, cxgb4_remove_tid(&tx_info->adap->tids, tx_info->tx_chan, tx_info->tid, tx_info->ip_family); - xa_erase(&u_ctx->tid_list, tx_info->tid); + xa_erase_bh(&u_ctx->tid_list, tx_info->tid); } port_stats = &tx_info->adap->ch_ktls_stats.ktls_port[tx_info->port_id]; @@ -577,7 +577,7 @@ static int chcr_ktls_dev_add(struct net_device *netdev, struct sock *sk, cxgb4_remove_tid(&tx_info->adap->tids, tx_info->tx_chan, tx_info->tid, tx_info->ip_family); - xa_erase(&u_ctx->tid_list, tx_info->tid); + xa_erase_bh(&u_ctx->tid_list, tx_info->tid); put_module: /* release module refcount */ From f5e0b20f6c6d548d985acdd620c5110776c915c2 Mon Sep 17 00:00:00 2001 From: Felix Fietkau Date: Tue, 18 Aug 2026 12:58:21 +0000 Subject: [PATCH 121/367] wifi: mt76: mt7996: program a link again if the driver holds it The firmware needs the driver to remove active links from the BSSINFO table when it destroys the interface. A link therefore stays registered from its first chanctx assignment until the interface goes away. If the driver holds the link already, a second add does nothing. mac80211 gives a non-AP MLD a new address each time it sets a link up. A link that comes back after a failed attempt thus carries an address that the hardware does not know, on a BSS that the driver never programs again. The station then authenticates from an address that nothing answers. The failure repeats until the driver tears the interface down for good. Program the address and the BSS again. This is the pair that the initial add sets up. For a station it does what mt7996_mcu_mld_link_oper() does for an AP. Fixes: 08813703ac41 ("wifi: mt76: mt7996: Destroy vif active links in mt7996_remove_interface()") Link: https://patch.msgid.link/20260818125825.395538-1-nbd@nbd.name Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7996/main.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/main.c b/drivers/net/wireless/mediatek/mt76/mt7996/main.c index e218856b0c45..d17d1dbb2038 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/main.c @@ -310,7 +310,14 @@ int mt7996_vif_link_add(struct mt76_phy *mphy, struct ieee80211_vif *vif, if (vif->type == NL80211_IFTYPE_AP) return mt7996_mcu_mld_link_oper(dev, link_conf, link, true); - return 0; + + /* update the link address */ + ret = mt7996_mcu_add_dev_info(phy, vif, link_conf, mlink, true); + if (ret) + return ret; + + return mt7996_mcu_add_bss_info(phy, vif, link_conf, mlink, + msta_link, true); } mlink->idx = __ffs64(~dev->mt76.vif_mask); From 232d49dd4b40a666283de9e722899f088ed581b2 Mon Sep 17 00:00:00 2001 From: Lorenzo Bianconi Date: Tue, 29 Sep 2026 15:10:11 +0200 Subject: [PATCH 122/367] net: stmmac: propagate PTP addend and system time programming errors stmmac_update_subsecond_increment() ignores the error returned by stmmac_config_addend(), and stmmac_init_tstamp_counter() discards the addend and system time programming errors, always returning success. A failure to program the addend (PTP_TCR_TSADDREG) or to initialize the system time counter (PTP_TCR_TSINIT) is therefore silently swallowed, leaving the hardware timestamp counter in a non-running or partially configured state while the driver keeps operating as if timestamping were up. This matters for TAPRIO/EST offloading, which derives the gate base time from the hardware timestamp counter. The same hooks are also called from the PHC callbacks: settime64 and adjfine drop the error and report success to clock_settime() and clock_adjtime(), so a dead PTP reference clock goes unnoticed by ptp4l/phc2sys. Return error codes from stmmac_update_subsecond_increment(), stmmac_init_tstamp_counter(), stmmac_dl_ts_coarse_set() and the settime64/adjfine callbacks instead of silently returning success. On failure, roll back the partially applied configuration so the hardware and the driver bookkeeping stay consistent, and report the reason through the devlink extack. Also guard against a zero sub-second increment, which would otherwise divide by zero when computing the addend. Reset the persistent timestamping state (hwts_tx_en, hwts_rx_en, tstamp_config, systime_flags and tsfupdt_coarse) when (re)initializing timestamping, so a failed init does not leave TX/RX timestamping enabled on a counter that never started. Fixes: cc4c9001ce31 ("net: stmmac: Switch stmmac_hwtimestamp to generic HW Interface Helpers") Signed-off-by: Lorenzo Bianconi Reviewed-by: Maxime Chevallier Signed-off-by: David S. Miller --- .../net/ethernet/stmicro/stmmac/stmmac_main.c | 106 +++++++++++++----- .../net/ethernet/stmicro/stmmac/stmmac_ptp.c | 10 +- 2 files changed, 84 insertions(+), 32 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index ec62fa7418f4..9741f97fa37a 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -601,31 +601,64 @@ static void stmmac_get_rx_hwtstamp(struct stmmac_priv *priv, struct dma_desc *p, } } -static void stmmac_update_subsecond_increment(struct stmmac_priv *priv) +static void stmmac_restore_subsecond_increment(struct stmmac_priv *priv, + u32 default_addend) { bool xmac = dwmac_is_xmac(priv->plat->core_type); u32 sec_inc = 0; - u64 temp = 0; + stmmac_config_addend(priv, priv->ptpaddr, default_addend); stmmac_config_hw_tstamping(priv, priv->ptpaddr, priv->systime_flags); + stmmac_config_sub_second_increment(priv, priv->ptpaddr, + priv->plat->clk_ptp_rate, + xmac, &sec_inc); + priv->default_addend = default_addend; + priv->sub_second_inc = sec_inc; +} + +static int stmmac_update_subsecond_increment(struct stmmac_priv *priv, + u32 systime_flags) +{ + bool xmac = dwmac_is_xmac(priv->plat->core_type); + u32 sec_inc = 0, val; + u64 temp = 0; + int ret; + + stmmac_config_hw_tstamping(priv, priv->ptpaddr, systime_flags); /* program Sub Second Increment reg */ stmmac_config_sub_second_increment(priv, priv->ptpaddr, priv->plat->clk_ptp_rate, xmac, &sec_inc); - temp = div_u64(1000000000ULL, sec_inc); - - /* Store sub second increment for later use */ - priv->sub_second_inc = sec_inc; + if (!sec_inc) { + ret = -EINVAL; + goto error; + } /* calculate default added value: * formula is : * addend = (2^32)/freq_div_ratio; * where, freq_div_ratio = 1e9ns/sec_inc */ + temp = div_u64(1000000000ULL, sec_inc); temp = (u64)(temp << 32); - priv->default_addend = div_u64(temp, priv->plat->clk_ptp_rate); - stmmac_config_addend(priv, priv->ptpaddr, priv->default_addend); + val = div_u64(temp, priv->plat->clk_ptp_rate); + + ret = stmmac_config_addend(priv, priv->ptpaddr, val); + if (ret) + goto error; + + priv->sub_second_inc = sec_inc; + priv->default_addend = val; + + return 0; +error: + /* Restore previous configuration */ + stmmac_config_hw_tstamping(priv, priv->ptpaddr, priv->systime_flags); + stmmac_config_sub_second_increment(priv, priv->ptpaddr, + priv->plat->clk_ptp_rate, xmac, + NULL); + return ret; } /** @@ -854,35 +887,42 @@ static int stmmac_hwtstamp_get(struct net_device *dev, /** * stmmac_init_tstamp_counter - init hardware timestamping counter * @priv: driver private structure - * @systime_flags: timestamping flags * Description: * Initialize hardware counter for packet timestamping. * This is valid as long as the interface is open and not suspended. * Will be rerun after resuming from suspend, case in which the timestamping * flags updated by stmmac_hwtstamp_set() also need to be restored. */ -static int stmmac_init_tstamp_counter(struct stmmac_priv *priv, - u32 systime_flags) +static int stmmac_init_tstamp_counter(struct stmmac_priv *priv) { + u32 default_addend = priv->default_addend; struct timespec64 now; + int ret; if (!priv->plat->clk_ptp_rate) { netdev_err(priv->dev, "Invalid PTP clock rate"); return -EINVAL; } - stmmac_config_hw_tstamping(priv, priv->ptpaddr, systime_flags); - priv->systime_flags = systime_flags; - - stmmac_update_subsecond_increment(priv); + ret = stmmac_update_subsecond_increment(priv, priv->systime_flags); + if (ret) + return ret; /* initialize system time */ ktime_get_real_ts64(&now); /* lower 32 bits of tv_sec are safe until y2106 */ - stmmac_init_systime(priv, priv->ptpaddr, (u32)now.tv_sec, now.tv_nsec); + ret = stmmac_init_systime(priv, priv->ptpaddr, (u32)now.tv_sec, + now.tv_nsec); + if (ret) + goto error; return 0; +error: + /* Restore previous configuration */ + stmmac_restore_subsecond_increment(priv, default_addend); + + return ret; } /** @@ -905,8 +945,14 @@ static int stmmac_init_timestamping(struct stmmac_priv *priv) return -EOPNOTSUPP; } - ret = stmmac_init_tstamp_counter(priv, STMMAC_HWTS_ACTIVE | - PTP_TCR_TSCFUPDT); + /* Reset hw ts configuration */ + memset(&priv->tstamp_config, 0, sizeof(priv->tstamp_config)); + priv->systime_flags = STMMAC_HWTS_ACTIVE | PTP_TCR_TSCFUPDT; + priv->tsfupdt_coarse = false; + priv->hwts_tx_en = 0; + priv->hwts_rx_en = 0; + + ret = stmmac_init_tstamp_counter(priv); if (ret) { netdev_warn(priv->dev, "PTP init failed\n"); return ret; @@ -927,10 +973,6 @@ static int stmmac_init_timestamping(struct stmmac_priv *priv) netdev_info(priv->dev, "IEEE 1588-2008 Advanced Timestamp supported\n"); - memset(&priv->tstamp_config, 0, sizeof(priv->tstamp_config)); - priv->hwts_tx_en = 0; - priv->hwts_rx_en = 0; - if (priv->plat->flags & STMMAC_FLAG_HWTSTAMP_CORRECT_LATENCY) stmmac_hwtstamp_correct_latency(priv, priv); @@ -7711,18 +7753,26 @@ static int stmmac_dl_ts_coarse_set(struct devlink *dl, u32 id, { struct stmmac_devlink_priv *dl_priv = devlink_priv(dl); struct stmmac_priv *priv = dl_priv->stmmac_priv; + u32 systime_flags = priv->systime_flags; + int ret; - priv->tsfupdt_coarse = ctx->val.vbool; - - if (priv->tsfupdt_coarse) - priv->systime_flags &= ~PTP_TCR_TSCFUPDT; + if (ctx->val.vbool) + systime_flags &= ~PTP_TCR_TSCFUPDT; else - priv->systime_flags |= PTP_TCR_TSCFUPDT; + systime_flags |= PTP_TCR_TSCFUPDT; /* In Coarse mode, we can use a smaller subsecond increment, let's * reconfigure the systime, subsecond increment and addend. */ - stmmac_update_subsecond_increment(priv); + ret = stmmac_update_subsecond_increment(priv, systime_flags); + if (ret) { + NL_SET_ERR_MSG_MOD(extack, + "failed to reconfigure PTP adjustment"); + return ret; + } + + priv->tsfupdt_coarse = ctx->val.vbool; + priv->systime_flags = systime_flags; return 0; } diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_ptp.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_ptp.c index 3bfcc9760dce..493c5d81a36b 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_ptp.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_ptp.c @@ -28,14 +28,15 @@ static int stmmac_adjust_freq(struct ptp_clock_info *ptp, long scaled_ppm) container_of(ptp, struct stmmac_priv, ptp_clock_ops); unsigned long flags; u32 addend; + int ret; addend = adjust_by_scaled_ppm(priv->default_addend, scaled_ppm); write_lock_irqsave(&priv->ptp_lock, flags); - stmmac_config_addend(priv, priv->ptpaddr, addend); + ret = stmmac_config_addend(priv, priv->ptpaddr, addend); write_unlock_irqrestore(&priv->ptp_lock, flags); - return 0; + return ret; } /** @@ -153,12 +154,13 @@ static int stmmac_set_time(struct ptp_clock_info *ptp, struct stmmac_priv *priv = container_of(ptp, struct stmmac_priv, ptp_clock_ops); unsigned long flags; + int ret; write_lock_irqsave(&priv->ptp_lock, flags); - stmmac_init_systime(priv, priv->ptpaddr, ts->tv_sec, ts->tv_nsec); + ret = stmmac_init_systime(priv, priv->ptpaddr, ts->tv_sec, ts->tv_nsec); write_unlock_irqrestore(&priv->ptp_lock, flags); - return 0; + return ret; } static int stmmac_enable(struct ptp_clock_info *ptp, From 71a77ab76e74131a101f4d2d2afb0dcbf81b4e3c Mon Sep 17 00:00:00 2001 From: Frank Li Date: Tue, 29 Sep 2026 15:06:36 -0400 Subject: [PATCH 123/367] net: allwinner: remove dmaengine_desc_free() dmaengine_desc_free() is designed for DMA_CTRL_REUSE. If DMA_CTRL_REUSE is not set, dmaengine_desc_free() will return -EPERM. if (!dmaengine_desc_test_reuse(desc)) return -EPERM; So calling dmaengine_desc_free() does nothing. dmaengine_terminate_(a)sync() will free all already allocated descriptors. Signed-off-by: Frank Li Reviewed-by: Joe Damato Signed-off-by: David S. Miller --- drivers/net/ethernet/allwinner/sun4i-emac.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/drivers/net/ethernet/allwinner/sun4i-emac.c b/drivers/net/ethernet/allwinner/sun4i-emac.c index 0ba67a55705d..09b2abdfc9e8 100644 --- a/drivers/net/ethernet/allwinner/sun4i-emac.c +++ b/drivers/net/ethernet/allwinner/sun4i-emac.c @@ -300,7 +300,7 @@ static int emac_dma_inblk_32bit(struct emac_board_info *db, if (!req) { dev_err(db->dev, "alloc emac dma req error.\n"); ret = -ENOMEM; - goto alloc_req_err; + goto prepare_err; } desc->callback_param = req; @@ -319,9 +319,6 @@ static int emac_dma_inblk_32bit(struct emac_board_info *db, submit_err: emac_free_dma_req(req); -alloc_req_err: - dmaengine_desc_free(desc); - prepare_err: dma_unmap_single(db->dev, rxbuf, count, DMA_FROM_DEVICE); return ret; From b7f925fd5226deb953c8dcdc9677d3fcc2717c00 Mon Sep 17 00:00:00 2001 From: Georgiy Osokin Date: Mon, 28 Sep 2026 17:31:13 +0300 Subject: [PATCH 124/367] tee: shm: reject zero-sized allocations in tee_dyn_shm_alloc_helper() tee_dyn_shm_alloc_helper() derives nr_pages from a caller-supplied size and passes it to alloc_pages_exact() without checking it. For size == 0 nr_pages is 0, and alloc_pages_exact(0) calls get_order(0), which is documented as undefined and returns BITS_PER_LONG - PAGE_SHIFT. The page allocator then trips its order > MAX_PAGE_ORDER warning and fails the allocation; on a panic_on_warn kernel that ends the boot. This can be triggered by TEE_IOC_SHM_ALLOC with struct tee_ioctl_shm_alloc_data where size is 0. Reject a zero page count, as register_shm_helper() already does for the register path. Fixes: cf4441503e20 ("tee: optee: Move pool_op helper functions") Cc: stable@vger.kernel.org Cc: lvc-project@linuxtesting.org Signed-off-by: Georgiy Osokin Reviewed-by: Sumit Garg Signed-off-by: Jens Wiklander --- drivers/tee/tee_shm.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/tee/tee_shm.c b/drivers/tee/tee_shm.c index 6742b3579c86..daa4af1e089e 100644 --- a/drivers/tee/tee_shm.c +++ b/drivers/tee/tee_shm.c @@ -343,6 +343,10 @@ int tee_dyn_shm_alloc_helper(struct tee_shm *shm, size_t size, size_t align, unsigned int i; int rc = 0; + /* get_order(0) is undefined and exceeds MAX_PAGE_ORDER. */ + if (!nr_pages) + return -EINVAL; + /* * Ignore alignment since this is already going to be page aligned * and there's no need for any larger alignment. From c108feca0a096f8db25d1b69130035025e9e936d Mon Sep 17 00:00:00 2001 From: Shao-Fu Chen Date: Fri, 18 Sep 2026 18:10:46 +0800 Subject: [PATCH 125/367] optee: register TEE devices only once fully initialized optee_probe() called tee_device_register() on both the client and the supplicant device before the rest of struct optee had been initialized. tee_device_register() calls cdev_device_add(), which does two things at once: it creates /dev/tee0 and /dev/teepriv0, and it links the device into the tee class so that class_find_device() can find it. From that moment on the device is reachable both from user space via tee_open() and from kernel space via tee_client_open_context(). The only gate in teedev_open() is tee_device_get(), which merely checks that teedev->desc is non-NULL, which was already set by tee_device_alloc(). Therefore, there is effectively no gate at all. A context opened in that window can run against a struct optee where - optee->call_queue.mutex is not initialized by optee_cq_init() - optee->supp mutex and completions is not initialized by optee_supp_init() - optee->rpmb_dev_mutex is not initialized yet, - the message argument cache is not initialized by optee_shm_arg_cache_init() - optee->ctx is still NULL Any open session or invoke during this window takes uninitialized mutexes and dereferences a NULL pointer. Fix it by moving both tee_device_register() calls down to the point where all of struct optee is set up. Fixes: 4fb0a5eb364d ("tee: add OP-TEE driver") Cc: stable@vger.kernel.org Signed-off-by: Shao-Fu Chen [JW: adding fixes and cc] Signed-off-by: Jens Wiklander --- drivers/tee/optee/ffa_abi.c | 16 ++++++++-------- drivers/tee/optee/smc_abi.c | 17 ++++++++--------- 2 files changed, 16 insertions(+), 17 deletions(-) diff --git a/drivers/tee/optee/ffa_abi.c b/drivers/tee/optee/ffa_abi.c index 633715b98625..d3cc7c5fc1e9 100644 --- a/drivers/tee/optee/ffa_abi.c +++ b/drivers/tee/optee/ffa_abi.c @@ -1123,14 +1123,6 @@ static int optee_ffa_probe(struct ffa_device *ffa_dev) optee_set_dev_group(optee); - rc = tee_device_register(optee->teedev); - if (rc) - goto err_unreg_supp_teedev; - - rc = tee_device_register(optee->supp_teedev); - if (rc) - goto err_unreg_supp_teedev; - rc = rhashtable_init(&optee->ffa.global_ids, &shm_rhash_params); if (rc) goto err_unreg_supp_teedev; @@ -1159,6 +1151,14 @@ static int optee_ffa_probe(struct ffa_device *ffa_dev) if (optee_ffa_protmem_pool_init(optee, sec_caps)) pr_info("Protected memory service not available\n"); + rc = tee_device_register(optee->teedev); + if (rc) + goto err_unregister_devices; + + rc = tee_device_register(optee->supp_teedev); + if (rc) + goto err_unregister_devices; + rc = optee_enumerate_devices(PTA_CMD_GET_DEVICES); if (rc) goto err_unregister_devices; diff --git a/drivers/tee/optee/smc_abi.c b/drivers/tee/optee/smc_abi.c index b8a2bdac3208..51624443359b 100644 --- a/drivers/tee/optee/smc_abi.c +++ b/drivers/tee/optee/smc_abi.c @@ -1849,14 +1849,6 @@ static int optee_probe(struct platform_device *pdev) optee_set_dev_group(optee); - rc = tee_device_register(optee->teedev); - if (rc) - goto err_unreg_supp_teedev; - - rc = tee_device_register(optee->supp_teedev); - if (rc) - goto err_unreg_supp_teedev; - optee_cq_init(&optee->call_queue, thread_count); optee_supp_init(&optee->supp); optee->smc.memremaped_shm = memremaped_shm; @@ -1916,6 +1908,14 @@ static int optee_probe(struct platform_device *pdev) if (optee->smc.sec_caps & OPTEE_SMC_SEC_CAP_DYNAMIC_SHM) pr_info("dynamic shared memory is enabled\n"); + rc = tee_device_register(optee->teedev); + if (rc) + goto err_disable_shm_cache; + + rc = tee_device_register(optee->supp_teedev); + if (rc) + goto err_disable_shm_cache; + rc = optee_enumerate_devices(PTA_CMD_GET_DEVICES); if (rc) goto err_disable_shm_cache; @@ -1942,7 +1942,6 @@ static int optee_probe(struct platform_device *pdev) optee_shm_arg_cache_uninit(optee); optee_supp_uninit(&optee->supp); mutex_destroy(&optee->call_queue.mutex); -err_unreg_supp_teedev: tee_device_unregister(optee->supp_teedev); err_unreg_teedev: tee_device_unregister(optee->teedev); From 3c0d2f1f431c51a11253f28763be723ac782fd7a Mon Sep 17 00:00:00 2001 From: Mahantesh Salimath Date: Thu, 10 Sep 2026 16:11:24 +0000 Subject: [PATCH 126/367] tee: optee: ffa: support shared memory offsets on large-page kernels OP-TEE FF-A memory objects use 4 KiB pages, while the kernel page size may be larger. Consequently, tee_shm->offset can be greater than or equal to FFA_PAGE_SIZE, but OP-TEE rejects such a value in internal_offs. Do not encode the excess page offset in offs_low/offs_high. Those fields describe the logical memref offset and are copied back into tee_param->shm_offs on return. Folding the page offset into them breaks parameter round trips when a memref is reused. They are also ignored by the OPTEE_RPC_CMD_SHM_ALLOC response path, which uses only global_id and internal_offs to construct the shared-memory mobj. Instead, start the FF-A descriptor at the 4 KiB page containing the shared buffer, the same approach as optee_fill_pages_list() in the SMC ABI. Store the remaining in-page offset in internal_offs and preserve shm_offs in offs_low/offs_high. This keeps internal_offs within the FF-A page size, maps RPC allocations at the correct address, and preserves normal memref offsets across repeated invocations. Tested on ARMv8-A with 64 KiB PAGE_SIZE. OP-TEE OS ran as a secure partition under Hafnium (SPMC) over FF-A. Verified registered shared memory with tee_shm->offset >= 4 KiB, memref reuse on the same TEEC_Operation, and RPC OPTEE_RPC_CMD_SHM_ALLOC (xtest regression 6007-6009). optee_hello_world, optee_aes, and xtest regression 1005, 1007, 1008, 4001-4003 and 6001-6003 also passed. Fixes: 4615e5a34b95 ("optee: add FF-A support") Acked-by: Liming Sun Acked-by: James Hurley Acked-by: Dave Thompson Signed-off-by: Mahantesh Salimath Reviewed-by: Sumit Garg Signed-off-by: Jens Wiklander --- drivers/tee/optee/ffa_abi.c | 22 ++++++++++++++++++---- drivers/tee/optee/optee_msg.h | 4 ++-- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/drivers/tee/optee/ffa_abi.c b/drivers/tee/optee/ffa_abi.c index d3cc7c5fc1e9..d027fe7ce86f 100644 --- a/drivers/tee/optee/ffa_abi.c +++ b/drivers/tee/optee/ffa_abi.c @@ -198,7 +198,8 @@ static int to_msg_param_ffa_mem(struct optee_msg_param *mp, if (shm) { u64 shm_offs = p->u.memref.shm_offs; - mp->u.fmem.internal_offs = shm->offset; + mp->u.fmem.internal_offs = tee_shm_get_page_offset(shm) & + (FFA_PAGE_SIZE - 1); mp->u.fmem.offs_low = shm_offs; mp->u.fmem.offs_high = shm_offs >> 32; @@ -284,14 +285,26 @@ static int optee_ffa_shm_register(struct tee_context *ctx, struct tee_shm *shm, .nattrs = 1, }; struct sg_table sgt; + size_t ffa_offs; int rc; + if (!num_pages) + return -EINVAL; + rc = optee_check_mem_type(start, num_pages); if (rc) return rc; - rc = sg_alloc_table_from_pages(&sgt, pages, num_pages, 0, - num_pages * PAGE_SIZE, GFP_KERNEL); + /* + * Start the FF-A descriptor at the 4 KiB page containing the shared + * buffer, skipping unused leading 4 KiB pages when PAGE_SIZE is + * larger. Same approach as optee_fill_pages_list() in the SMC ABI. + * This leaves only the offset into that 4 KiB page for internal_offs. + */ + ffa_offs = round_down(tee_shm_get_page_offset(shm), FFA_PAGE_SIZE); + rc = sg_alloc_table_from_pages(&sgt, pages, num_pages, ffa_offs, + num_pages * PAGE_SIZE - ffa_offs, + GFP_KERNEL); if (rc) return rc; args.sg = sgt.sgl; @@ -458,7 +471,8 @@ static void handle_ffa_rpc_func_cmd_shm_alloc(struct tee_context *ctx, .attr = OPTEE_MSG_ATTR_TYPE_FMEM_OUTPUT, .u.fmem.size = tee_shm_get_size(shm), .u.fmem.global_id = shm->sec_world_id, - .u.fmem.internal_offs = shm->offset, + .u.fmem.internal_offs = tee_shm_get_page_offset(shm) & + (FFA_PAGE_SIZE - 1), }; arg->ret = TEEC_SUCCESS; diff --git a/drivers/tee/optee/optee_msg.h b/drivers/tee/optee/optee_msg.h index 7d9b12e71c03..6c3043f8da33 100644 --- a/drivers/tee/optee/optee_msg.h +++ b/drivers/tee/optee/optee_msg.h @@ -136,8 +136,8 @@ struct optee_msg_param_rmem { * struct optee_msg_param_fmem - FF-A memory reference parameter * @offs_low: lower bits of offset into shared memory reference * @offs_high: higher bits of offset into shared memory reference - * @internal_offs: internal offset into the first page of shared memory - * reference + * @internal_offs: offset into the first 4 KiB page of the FF-A shared + * memory region * @size: size of the buffer * @global_id: global identifier of the shared memory */ From 25016fe8c1ed0fccae107bd629089139ffbafe08 Mon Sep 17 00:00:00 2001 From: Runyu Xiao Date: Wed, 30 Sep 2026 15:44:53 +0800 Subject: [PATCH 127/367] Bluetooth: btintel_pcie: use managed IRQ teardown The PCIe driver registers its MSI-X handlers with devm_request_threaded_irq(), but remove() uses free_irq() directly. The devres actions then retain registrations and attempt to release them again during device cleanup. Use devm_free_irq() for the remove path and for already registered vectors when a later setup request fails. This removes the managed resource at the same time as the IRQ is released and keeps MSI-X vector teardown ordered after IRQ ownership is gone. Fixes: c2b636b3f788 ("Bluetooth: btintel_pcie: Add support for PCIe transport") Cc: stable@vger.kernel.org Assisted-by: LLM Signed-off-by: Runyu Xiao Signed-off-by: Luiz Augusto von Dentz --- drivers/bluetooth/btintel_pcie.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/drivers/bluetooth/btintel_pcie.c b/drivers/bluetooth/btintel_pcie.c index 0194badeeff3..ac7193400769 100644 --- a/drivers/bluetooth/btintel_pcie.c +++ b/drivers/bluetooth/btintel_pcie.c @@ -1777,7 +1777,7 @@ static irqreturn_t btintel_pcie_irq_msix_handler(int irq, void *dev_id) static int btintel_pcie_setup_irq(struct btintel_pcie_data *data) { int err; - int num_irqs, i; + int num_irqs, i, j; for (i = 0; i < BTINTEL_PCIE_MSIX_VEC_MAX; i++) data->msix_entries[i].entry = i; @@ -1806,6 +1806,10 @@ static int btintel_pcie_setup_irq(struct btintel_pcie_data *data) KBUILD_MODNAME, msix_entry); if (err) { + for (j = 0; j < i; j++) + devm_free_irq(&data->pdev->dev, + data->msix_entries[j].vector, + &data->msix_entries[j]); pci_free_irq_vectors(data->pdev); data->alloc_vecs = 0; return err; @@ -3145,7 +3149,7 @@ static void btintel_pcie_remove(struct pci_dev *pdev) struct msix_entry *msix_entry; msix_entry = &data->msix_entries[i]; - free_irq(msix_entry->vector, msix_entry); + devm_free_irq(&pdev->dev, msix_entry->vector, msix_entry); } pci_free_irq_vectors(pdev); From 08e90633377f1b2567ab5ad6810b74c552246a07 Mon Sep 17 00:00:00 2001 From: Palla Raghunath Date: Fri, 2 Oct 2026 08:07:52 +0100 Subject: [PATCH 128/367] Bluetooth: RFCOMM: Fix NULL tty_dev dereference in rfcomm_dev_shutdown syzbot hit a NULL pointer dereference in rfcomm_dev_shutdown() when an RFCOMM tty is closed. rfcomm_dev_add() puts the new device on rfcomm_dev_list first and only then registers the tty device and saves it in dev->tty_dev. If that registration fails, dev->tty_dev is never set at all. The device can be looked up in the meantime, so an open() of /dev/rfcommN gets through rfcomm_tty_install() and attaches to it. When that tty is closed again, rfcomm_dev_shutdown() uses dev->tty_dev without checking it: Oops: general protection fault, probably for non-canonical address KASAN: null-ptr-deref in range [0x0000000000000040-0x0000000000000047] RIP: 0010:rfcomm_dev_shutdown+0x48/0xb0 net/bluetooth/rfcomm/tty.c:131 Call Trace: tty_port_shutdown+0x1bf/0x220 drivers/tty/tty_port.c:372 tty_port_close+0x4e/0x150 drivers/tty/tty_port.c:705 tty_release+0x359/0x1670 drivers/tty/tty_io.c:1718 __fput+0x418/0xa50 fs/file_table.c:512 rfcomm_dev_destruct() already copes with a NULL dev->tty_dev. Add the same check to rfcomm_dev_shutdown(), so the tty device is only moved when it was actually registered. Fixes: cad348a17e17 ("Bluetooth: Implement .activate, .shutdown and .carrier_raised methods") Reported-by: syzbot+4a807caf7680652d53b6@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=4a807caf7680652d53b6 Cc: stable@vger.kernel.org Signed-off-by: Palla Raghunath Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/rfcomm/tty.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/bluetooth/rfcomm/tty.c b/net/bluetooth/rfcomm/tty.c index dc3cdf614def..198c7dd1a95b 100644 --- a/net/bluetooth/rfcomm/tty.c +++ b/net/bluetooth/rfcomm/tty.c @@ -128,7 +128,7 @@ static void rfcomm_dev_shutdown(struct tty_port *port) { struct rfcomm_dev *dev = container_of(port, struct rfcomm_dev, port); - if (dev->tty_dev->parent) + if (dev->tty_dev && dev->tty_dev->parent) device_move(dev->tty_dev, NULL, DPM_ORDER_DEV_LAST); /* close the dlc */ From 23b1ab15ca91f2cffaf149d9bb0fafeb700c7db2 Mon Sep 17 00:00:00 2001 From: Waiman Long Date: Fri, 2 Oct 2026 12:54:38 -0400 Subject: [PATCH 129/367] cgroup/cpuset: Handle cpu hotplug race in guarantee_active_cpus() With commit 2125c0034c5d ("cgroup/cpuset: Make cpuset hotplug processing synchronous"), the cpuset hotplug operation becomes synchronous. That commit also removes the code that handles race between cpuset_hotplug_work and cpu hotplug notifier with the assumption that race is now gone. Later commit 7a0aabd9ce69 ("cgroup/cpuset: Always use cpu_active_mask") updates the cpuset code to always use cpu_active_mask instead of cpu_online_mask in some places including guarantee_online_cpus() which is renamed to guarantee_active_cpus() in that commit. In the case of CPU offline operation, cpu_active_mask is updated first in sched_cpu_deactivate() to remove the offline CPU before cpuset_handle_hotplug() is called to update the effective_cpus of the affected cpusets. The cpu_online_mask is updated after that near the end of the offline operation to remove the offline CPU. As a result, the race comes back and the top cpuset may not have any active CPU leading to NULL pointer dereference during the race window when guarantee_active_cpus() is called after cpu_active_mask is updated to remove the CPU to be torn down but before cpuset_handle_hotplug() is able to properly update the effective_cpus of the top cpuset. Fix this by adding back the NULL cs check to avoid this problem. Fixes: 7a0aabd9ce69 ("cgroup/cpuset: Always use cpu_active_mask") Cc: stable@vger.kernel.org # v6.16+ Reported-by: Farhad Alemi Link: https://lore.kernel.org/lkml/CA+0ovChh3VjsKN1g+ZGjwwY2fGTpP7uD+aCCByLj5Qbymw=bfQ@mail.gmail.com Signed-off-by: Waiman Long Tested-by: Farhad Alemi Signed-off-by: Tejun Heo --- kernel/cgroup/cpuset.c | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/kernel/cgroup/cpuset.c b/kernel/cgroup/cpuset.c index 98af44a7f399..d2ec7b128960 100644 --- a/kernel/cgroup/cpuset.c +++ b/kernel/cgroup/cpuset.c @@ -518,10 +518,26 @@ static void guarantee_active_cpus(struct task_struct *tsk, rcu_read_lock(); cs = task_cs(tsk); - while (!cpumask_intersects(cs->effective_cpus, pmask)) + while (!cpumask_intersects(cs->effective_cpus, pmask)) { cs = parent_cs(cs); - + if (unlikely(!cs)) { + /* + * The top cpuset doesn't have any active cpu as a + * consequence of a race between its caller and the cpu + * hotplug operation where cpu_active_mask is updated + * asynchronously before cpuset_handle_hotplug() is + * being called to adjust the effective_cpus of the + * affected cpusets. But we know the top cpuset's + * effective_cpus is on its way to be identical to + * cpu_active_mask minus the exclusive CPUs dedicated + * to other valid cpuset partitions. Just pass back + * the filtered cpu_active_mask in this case. + */ + goto out_unlock; + } + } cpumask_and(pmask, pmask, cs->effective_cpus); +out_unlock: rcu_read_unlock(); } From 184b5ddf82d45d6f75cc83719a2e453392512bd2 Mon Sep 17 00:00:00 2001 From: Jakub Kicinski Date: Wed, 30 Sep 2026 18:26:39 -0700 Subject: [PATCH 130/367] bonding: fix the program leak when XDP is replaced We need to release the reference on the old BPF prog, whether we are going to no-prog, or replacing the prog with another. Spotted by AI while running tests for the propagation series (leaked programs were accumulating). Selftest will come in the net-next series to avoid conflicts. Fixes: 9e2ee5c7e7c3 ("net, bonding: Add XDP support to the bonding driver") Fixes: 6d5f1ef83868 ("bonding: Fix negative jump label count on nested bonding") Acked-by: Daniel Borkmann Reviewed-by: Jiayuan Chen Link: https://patch.msgid.link/20261001012639.298690-1-kuba@kernel.org Signed-off-by: Jakub Kicinski --- drivers/net/bonding/bond_main.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/net/bonding/bond_main.c b/drivers/net/bonding/bond_main.c index de2489c3d9bf..09aa45d71fab 100644 --- a/drivers/net/bonding/bond_main.c +++ b/drivers/net/bonding/bond_main.c @@ -5732,12 +5732,12 @@ static int bond_xdp_set(struct net_device *dev, struct bpf_prog *prog, bpf_prog_inc(prog); } - if (prog) { + if (prog && !old_prog) static_branch_inc(&bpf_master_redirect_enabled_key); - } else if (old_prog) { - bpf_prog_put(old_prog); + else if (!prog && old_prog) static_branch_dec(&bpf_master_redirect_enabled_key); - } + if (old_prog) + bpf_prog_put(old_prog); return 0; From bcf2573a68a808a5b54cadafa72b6672e662c2bf Mon Sep 17 00:00:00 2001 From: Suraj Gupta Date: Tue, 29 Sep 2026 00:12:07 +0530 Subject: [PATCH 131/367] net: xilinx: axienet: Free outstanding DMA buffers on dmaengine stop In the dmaengine path the driver pre-submits RX buffers and holds in-flight TX buffers whose SKBs are DMA-mapped by the driver and freed only in the completion callbacks. On ndo_stop() dmaengine_terminate_sync() aborts these descriptors without running their callbacks, and the driver then frees only the ring shells, leaking every SKB still owned by the engine and its DMA mapping on each ifdown. With 128 RX buffers pre-posted per channel, the mapping leak can eventually exhaust a limited IOMMU aperture. Clear the slot's skb in the TX and RX callbacks so a non-NULL skb marks a slot that still owns a live, DMA-mapped buffer, and on stop unmap and free every such buffer. axienet_dma_rx_cb() runs from the DMA tasklet and re-arms the RX ring on each completion, so it can race axienet_stop(): a completion may submit a fresh buffer after dmaengine_terminate_sync() has returned, leaving the channel armed with a buffer the teardown then frees while the engine may still write into it (dma_release_channel() does not stop it either). Add a lock that axienet_dma_rx_cb() holds across the @stopping check and the resubmit, and axienet_stop() holds to set @stopping before terminating. Once @stopping is set no callback can arm a new buffer, and any armed just before is aborted by the terminate, so teardown only frees buffers the engine no longer owns. Fixes: 6a91b846af85 ("net: axienet: Introduce dmaengine support") Cc: stable@vger.kernel.org Signed-off-by: Suraj Gupta Link: https://patch.msgid.link/20260928184207.2361931-1-suraj.gupta2@amd.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/xilinx/xilinx_axienet.h | 7 ++- .../net/ethernet/xilinx/xilinx_axienet_main.c | 53 +++++++++++++++---- 2 files changed, 48 insertions(+), 12 deletions(-) diff --git a/drivers/net/ethernet/xilinx/xilinx_axienet.h b/drivers/net/ethernet/xilinx/xilinx_axienet.h index fcd3aaef27fc..c63d99686d0b 100644 --- a/drivers/net/ethernet/xilinx/xilinx_axienet.h +++ b/drivers/net/ethernet/xilinx/xilinx_axienet.h @@ -523,8 +523,9 @@ struct skbuf_dma_descriptor { * @stats_work: Work for reading the hardware statistics counters often enough * to catch overflows. * @dma_err_task: Work structure to process Axi DMA errors - * @stopping: Set when @dma_err_task shouldn't do anything because we are - * about to stop the device. + * @stopping: Set when we are about to stop the device: makes @dma_err_task + * a no-op (legacy DMA path) and fences RX descriptor + * resubmission in axienet_dma_rx_cb() (dmaengine path). * @tx_irq: Axidma TX IRQ number * @rx_irq: Axidma RX IRQ number * @eth_irq: Ethernet core IRQ number @@ -545,6 +546,7 @@ struct skbuf_dma_descriptor { * @tx_ring_tail: TX skb ring buffer tail index. * @rx_ring_head: RX skb ring buffer head index. * @rx_ring_tail: RX skb ring buffer tail index. + * @rx_submit_lock: Protects RX ring resubmission vs teardown in dmaengine path. */ struct axienet_local { struct net_device *ndev; @@ -626,6 +628,7 @@ struct axienet_local { int tx_ring_tail; int rx_ring_head; int rx_ring_tail; + spinlock_t rx_submit_lock; }; /** diff --git a/drivers/net/ethernet/xilinx/xilinx_axienet_main.c b/drivers/net/ethernet/xilinx/xilinx_axienet_main.c index 1722b7038f34..09443623a3e2 100644 --- a/drivers/net/ethernet/xilinx/xilinx_axienet_main.c +++ b/drivers/net/ethernet/xilinx/xilinx_axienet_main.c @@ -881,6 +881,7 @@ static void axienet_dma_tx_cb(void *data, const struct dmaengine_result *result) u64_stats_update_end(&lp->tx_stat_sync); dma_unmap_sg(lp->dev, skbuf_dma->sgl, skbuf_dma->sg_len, DMA_TO_DEVICE); dev_consume_skb_any(skbuf_dma->skb); + skbuf_dma->skb = NULL; netif_txq_completed_wake(txq, 1, len, CIRC_SPACE(lp->tx_ring_head, lp->tx_ring_tail, TX_BD_NUM_MAX), 2); @@ -1171,6 +1172,7 @@ static void axienet_dma_rx_cb(void *data, const struct dmaengine_result *result) &meta_max_len); dma_unmap_single(lp->dev, skbuf_dma->dma_address, lp->max_frm_size, DMA_FROM_DEVICE); + skbuf_dma->skb = NULL; if (IS_ERR(app_metadata)) { if (net_ratelimit()) @@ -1193,10 +1195,17 @@ static void axienet_dma_rx_cb(void *data, const struct dmaengine_result *result) u64_stats_update_end(&lp->rx_stat_sync); rx_submit: + spin_lock(&lp->rx_submit_lock); + if (lp->stopping) { + spin_unlock(&lp->rx_submit_lock); + return; + } + for (i = 0; i < CIRC_SPACE(lp->rx_ring_head, lp->rx_ring_tail, RX_BUF_NUM_DEFAULT); i++) axienet_rx_submit_desc(lp->ndev); dma_async_issue_pending(lp->rx_chan); + spin_unlock(&lp->rx_submit_lock); } /** @@ -1541,6 +1550,7 @@ static int axienet_init_dmaengine(struct net_device *ndev) lp->tx_ring_head = 0; lp->rx_ring_tail = 0; lp->rx_ring_head = 0; + lp->stopping = false; lp->tx_skb_ring = kzalloc_objs(*lp->tx_skb_ring, TX_BD_NUM_MAX); if (!lp->tx_skb_ring) { ret = -ENOMEM; @@ -1752,20 +1762,42 @@ static int axienet_stop(struct net_device *ndev) free_irq(lp->rx_irq, ndev); axienet_dma_bd_release(ndev); } else { - dmaengine_terminate_sync(lp->tx_chan); - dmaengine_synchronize(lp->tx_chan); - dmaengine_terminate_sync(lp->rx_chan); - dmaengine_synchronize(lp->rx_chan); + struct skbuf_dma_descriptor *skbuf_dma; - for (i = 0; i < TX_BD_NUM_MAX; i++) - kfree(lp->tx_skb_ring[i]); - kfree(lp->tx_skb_ring); - for (i = 0; i < RX_BUF_NUM_DEFAULT; i++) - kfree(lp->rx_skb_ring[i]); - kfree(lp->rx_skb_ring); + spin_lock_bh(&lp->rx_submit_lock); + lp->stopping = true; + spin_unlock_bh(&lp->rx_submit_lock); + + dmaengine_terminate_sync(lp->tx_chan); + dmaengine_terminate_sync(lp->rx_chan); dma_release_channel(lp->rx_chan); dma_release_channel(lp->tx_chan); + + /* Unmap and free any buffer the terminate did not reclaim, so it + * is not leaked; a non-NULL skb marks such a slot. + */ + for (i = 0; i < TX_BD_NUM_MAX; i++) { + skbuf_dma = lp->tx_skb_ring[i]; + if (skbuf_dma && skbuf_dma->skb) { + dma_unmap_sg(lp->dev, skbuf_dma->sgl, + skbuf_dma->sg_len, DMA_TO_DEVICE); + dev_kfree_skb_any(skbuf_dma->skb); + } + kfree(skbuf_dma); + } + kfree(lp->tx_skb_ring); + + for (i = 0; i < RX_BUF_NUM_DEFAULT; i++) { + skbuf_dma = lp->rx_skb_ring[i]; + if (skbuf_dma && skbuf_dma->skb) { + dma_unmap_single(lp->dev, skbuf_dma->dma_address, + lp->max_frm_size, DMA_FROM_DEVICE); + dev_kfree_skb_any(skbuf_dma->skb); + } + kfree(skbuf_dma); + } + kfree(lp->rx_skb_ring); } netdev_reset_queue(ndev); @@ -3065,6 +3097,7 @@ static int axienet_probe(struct platform_device *pdev) spin_lock_init(&lp->rx_cr_lock); spin_lock_init(&lp->tx_cr_lock); + spin_lock_init(&lp->rx_submit_lock); INIT_WORK(&lp->rx_dim.work, axienet_rx_dim_work); lp->rx_dim_enabled = true; lp->rx_dim.profile_ix = 1; From 8b1f0af1fd5fd113432233ed8a3862ee7c30a84f Mon Sep 17 00:00:00 2001 From: Daehyeon Ko <4ncienth@gmail.com> Date: Wed, 30 Sep 2026 19:25:24 +0900 Subject: [PATCH 132/367] tcp: reject net_iov in zerocopy receive mapping hints After copying a readable prefix, receive_fallback_to_copy() asks tcp_zerocopy_set_hint_for_skb() where page mapping can resume. If the next skb is unreadable, find_next_mappable_frag() passes its net_iov fragment to can_map_frag(). skb_frag_page() returns NULL for a net_iov, but can_map_frag() dereferences it in PageCompound(). A v7.2 KASAN run on a connected TCP socket with 64 readable bytes followed by a 4096-byte NET_IOV_DMABUF fragment reported: BUG: KASAN: null-ptr-deref in can_map_frag tcp_zerocopy_receive -> can_map_frag Kernel panic - not syncing: KASAN: panic_on_warn set The diagnostic inserted the net_iov directly because the test host has no devmem-capable NIC. Hardware end-to-end reachability remains untested and requires CONFIG_NET_DEVMEM plus a supported DMA-buf-bound RX queue. Reject all net_iov fragments before skb_frag_page(). This covers both DMABUF and IOURING net_iov types while leaving page-backed checks unchanged. With the guard, the same queue copied the readable prefix, returned a 4096-byte skip hint, and completed without a fault. Fixes: 9f6b619edf2e ("net: support non paged skb frags") Cc: stable@vger.kernel.org Signed-off-by: Daehyeon Ko <4ncienth@gmail.com> Reviewed-by: Mina Almasry Link: https://patch.msgid.link/20260930102524.1659847-1-4ncienth@gmail.com Signed-off-by: Jakub Kicinski --- net/ipv4/tcp.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/net/ipv4/tcp.c b/net/ipv4/tcp.c index 562752352afe..87ef6d5cbfeb 100644 --- a/net/ipv4/tcp.c +++ b/net/ipv4/tcp.c @@ -1908,6 +1908,8 @@ static bool can_map_frag(const skb_frag_t *frag) if (skb_frag_size(frag) != PAGE_SIZE || skb_frag_off(frag)) return false; + if (skb_frag_is_net_iov(frag)) + return false; page = skb_frag_page(frag); From 1402fc67d2f4e2e1fc7bf8ee0825b8ad994557ff Mon Sep 17 00:00:00 2001 From: Xuanqiang Luo Date: Mon, 28 Sep 2026 16:04:23 -0700 Subject: [PATCH 133/367] ice: fix use-after-free in dynamic port cleanup ice_dealloc_dynamic_port() uses dyn_port->vsi->idx to erase the dynamic port from pf->dyn_ports. However, it frees the VSI before reading the index for the erase, resulting in a use-after-free. Follow the reverse of the allocation order in ice_alloc_dynamic_port() by erasing the xarray entry before freeing the VSI. Fixes: eda69d654c7e ("ice: add basic devlink subfunctions support") Cc: stable@vger.kernel.org Signed-off-by: Xuanqiang Luo Reviewed-by: Marcin Szycik Reviewed-by: Aleksandr Loktionov Tested-by: Patryk Holda Signed-off-by: Tony Nguyen Link: https://patch.msgid.link/20260928230429.495442-3-anthony.l.nguyen@intel.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/intel/ice/devlink/port.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/ethernet/intel/ice/devlink/port.c b/drivers/net/ethernet/intel/ice/devlink/port.c index 2a2e56777f9f..3ede24649002 100644 --- a/drivers/net/ethernet/intel/ice/devlink/port.c +++ b/drivers/net/ethernet/intel/ice/devlink/port.c @@ -590,8 +590,8 @@ static void ice_dealloc_dynamic_port(struct ice_dynamic_port *dyn_port) xa_erase(&pf->sf_nums, devlink_port->attrs.pci_sf.sf); ice_eswitch_detach_sf(pf, dyn_port); - ice_vsi_free(dyn_port->vsi); xa_erase(&pf->dyn_ports, dyn_port->vsi->idx); + ice_vsi_free(dyn_port->vsi); kfree(dyn_port); } From 7a579dec8423bba7bec96fcfcb2e30838b0858ee Mon Sep 17 00:00:00 2001 From: Bryan Fraschetti Date: Mon, 28 Sep 2026 16:04:24 -0700 Subject: [PATCH 134/367] ice: Restore Ordered MMIO Writes for Tx Doorbells The DQL accounting state which tracks the number of bytes queued for the NIC to transmit, namely dql->num_queued, is updated by the ICE driver when it invokes __netdev_tx_sent_queue(). Subsequently the driver updates the hardware queue tail allowing the NIC to begin processing the work queue. The queue accounting update should be ordered before the NIC begins transmitting descriptors. The transmit path currently updates the hardware doorbell using writel_relaxed(), which (on arm64) does not provide the same ordering guarantees between writes to normal memory and writes to MMIO registers that writel() does. This introduces a potential race where the NIC begins transmitting descriptors before the dql->num_queued update is globally visible. If the NIC finishes before the update is observed by dql_completed(), it detects an invalid state where more bytes have been completed than have been queued. When this happens the following BUG_ON is triggered. BUG_ON(count > num_queued - dql->num_completed); This has been observed and manifests as the following crash (note that the trace has been trimmed) in an environment with sustained network load that uses an Intel Corporation Ethernet Controller E810-XXV for SFP (rev 02) on an arm64 machine. Replacing writel_relaxed() with writel() in a test kernel eliminated the crash in the user's workload, which previously reproduced the issue reliably. kernel BUG at lib/dynamic_queue_limits.c:99 Internal error: Oops - BUG: 00000000f2000800 [#1] SMP pc : dql_completed+0x268/0x2a0 lr : ice_clean_tx_irq+0x1d4/0x620 [ice] Call trace: dql_completed+0x268/0x2a0 (P) ice_napi_poll+0x94/0x520 [ice] __napi_poll+0x48/0x3f0 net_rx_action+0x194/0x420 This restores the behaviour prior to commit ccde82e90946 ("ice: add E830 Earliest TxTime First Offload support"), which changed the notification mechanism from writel() to writel_relaxed(). Link: https://bugs.launchpad.net/ubuntu/+source/linux/+bug/2161572 Fixes: ccde82e90946 ("ice: add E830 Earliest TxTime First Offload support") Signed-off-by: Bryan Fraschetti Tested-by: Alexander Nowlin Signed-off-by: Tony Nguyen Link: https://patch.msgid.link/20260928230429.495442-4-anthony.l.nguyen@intel.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/intel/ice/ice_txrx.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/net/ethernet/intel/ice/ice_txrx.c b/drivers/net/ethernet/intel/ice/ice_txrx.c index 31303ab5be17..a2c7c4962882 100644 --- a/drivers/net/ethernet/intel/ice/ice_txrx.c +++ b/drivers/net/ethernet/intel/ice/ice_txrx.c @@ -1561,10 +1561,10 @@ ice_tx_map(struct ice_tx_ring *tx_ring, struct ice_tx_buf *first, } } tstamp_ring->next_to_use = j; - writel_relaxed(j, tstamp_ring->tail); + writel(j, tstamp_ring->tail); } else { ring_kick: - writel_relaxed(i, tx_ring->tail); + writel(i, tx_ring->tail); } return; From 58858b8f14803a7eeac297b3ce20f3b21bf2ff66 Mon Sep 17 00:00:00 2001 From: Tristan Madani Date: Mon, 28 Sep 2026 16:04:25 -0700 Subject: [PATCH 135/367] ice: fix metadata_dst refcount handling on representor teardown ice_eswitch_release_repr() uses metadata_dst_free() to release the representor's metadata_dst. metadata_dst_free() directly frees the underlying memory without checking the dst_entry refcount. When ice_eswitch_port_start_xmit() processes a packet, it takes a reference via dst_hold() and attaches the metadata_dst to the skb. If the representor is torn down while packets are still queued on the lower device (e.g. in a qdisc), the metadata_dst is freed while references are still held. Use dst_release() instead, which correctly decrements the refcount and only frees the object when all references are dropped. The dst subsystem already handles metadata_dst cleanup in dst_destroy() when DST_METADATA is set. Other drivers sharing this pattern (nfp, airoha, bnxt) already use dst_release() for their metadata_dst lifecycle. Fixes: f5396b8a663f7 ("ice: switchdev slow path") Cc: stable@vger.kernel.org Signed-off-by: Tristan Madani Reviewed-by: Simon Horman Signed-off-by: Tony Nguyen Link: https://patch.msgid.link/20260928230429.495442-5-anthony.l.nguyen@intel.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/intel/ice/ice_eswitch.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/ethernet/intel/ice/ice_eswitch.c b/drivers/net/ethernet/intel/ice/ice_eswitch.c index b069e6c514fb..6e7bba473898 100644 --- a/drivers/net/ethernet/intel/ice/ice_eswitch.c +++ b/drivers/net/ethernet/intel/ice/ice_eswitch.c @@ -95,7 +95,7 @@ ice_eswitch_release_repr(struct ice_pf *pf, struct ice_repr *repr) return; ice_vsi_update_security(vsi, ice_vsi_ctx_set_antispoof); - metadata_dst_free(repr->dst); + dst_release(&repr->dst->dst); repr->dst = NULL; ice_fltr_add_mac_and_broadcast(vsi, repr->parent_mac, ICE_FWD_TO_VSI); From f2e695a6c27241374c9b007b718361badccb2f63 Mon Sep 17 00:00:00 2001 From: Imre Kaloz Date: Thu, 24 Sep 2026 12:12:45 +0200 Subject: [PATCH 136/367] MAINTAINERS: update my email address Switch my contact address to kaloz@kernel.org and update .mailmap. Signed-off-by: Imre Kaloz Link: https://lore.kernel.org/r/20260924101245.5011-1-kaloz@kernel.org Signed-off-by: Arnd Bergmann --- .mailmap | 1 + MAINTAINERS | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.mailmap b/.mailmap index 389b94a0124e..2b43f748d4f7 100644 --- a/.mailmap +++ b/.mailmap @@ -484,6 +484,7 @@ Juha Yrjola Juha Yrjola Julien Thierry Justin Iurman +Imre Kaloz Ira Weiny Iskren Chernev Kalle Valo diff --git a/MAINTAINERS b/MAINTAINERS index 72294ddfa5b7..7270b031d40d 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -3012,7 +3012,7 @@ F: arch/arm/boot/dts/ti/omap/omap3-igep* ARM/INTEL IXP4XX ARM ARCHITECTURE M: Linus Walleij -M: Imre Kaloz +M: Imre Kaloz L: linux-arm-kernel@lists.infradead.org (moderated for non-subscribers) S: Maintained F: Documentation/devicetree/bindings/arm/intel-ixp4xx.yaml From d99bc5b456fd63be46c8bda20dbf6ede93d2fc11 Mon Sep 17 00:00:00 2001 From: Guixin Liu Date: Sun, 20 Sep 2026 11:34:59 +0800 Subject: [PATCH 137/367] nvmet: return Invalid Field for invalid NSIDs feature 82h The Reservation Notification Mask feature (FID 82h) defines its own status code for namespaces that do not support reservations: "If a Set Features command or a Get Features command attempts to access the Reservation Notification Mask on a namespace that does not support reservations or is invalid, then that command is aborted with status code of Invalid Field in Command." -- NVMe Base Specification, section 5.2.26.1.33 The Set Features handler routes the namespace lookup through nvmet_req_find_ns(), which returns Invalid Namespace or Format for an unknown NSID. That is the correct status for the generic namespace-scoped admin commands, but not for this feature: a Set Features 82h naming a nonexistent NSID is aborted with Invalid Namespace or Format instead of Invalid Field in Command. Translate the lookup failure to Invalid Field in Command. The broadcast NSID, the namespace-without-reservations case and everything else keep their current behavior. Fixes: 5a47c2080a73 ("nvmet: support reservation feature") Reviewed-by: Christoph Hellwig Reviewed-by: Nilay Shroff Signed-off-by: Guixin Liu Signed-off-by: Keith Busch --- drivers/nvme/target/pr.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/nvme/target/pr.c b/drivers/nvme/target/pr.c index 09d8c63f5680..81f48cb00aee 100644 --- a/drivers/nvme/target/pr.c +++ b/drivers/nvme/target/pr.c @@ -51,6 +51,8 @@ u16 nvmet_set_feat_resv_notif_mask(struct nvmet_req *req, u32 mask) if (nsid != U32_MAX) { status = nvmet_req_find_ns(req); + if (status == (NVME_SC_INVALID_NS | NVME_STATUS_DNR)) + return NVME_SC_INVALID_FIELD | NVME_STATUS_DNR; if (status) return status; if (!req->ns->pr.enable) @@ -75,6 +77,8 @@ u16 nvmet_get_feat_resv_notif_mask(struct nvmet_req *req) u16 status; status = nvmet_req_find_ns(req); + if (status == (NVME_SC_INVALID_NS | NVME_STATUS_DNR)) + return NVME_SC_INVALID_FIELD | NVME_STATUS_DNR; if (status) return status; From 564e46d9f6e87912effa03f123d37a511d520f09 Mon Sep 17 00:00:00 2001 From: Karl Bonde Torp Date: Tue, 29 Sep 2026 16:25:00 +0200 Subject: [PATCH 138/367] nvme: fix swapped ZRASF zone state values for full and read only The Zone Management Receive command Zone Receive Action Specific (ZRASF) reporting option values for the Full and Read Only zone states are swapped compared to the NVM Express Zoned Namespace Command Set Specification, Revision 1.5, Figure 35: - 5h lists the zones in the ZSF:Full state. - 6h lists the zones in the ZSRO:Read Only state. The nvmet ZNS backend uses these constants to map the ZRASF value received from the host to a block layer zone condition when filtering zones in Report Zones. Because of the swap, a host asking for full zones was given the read only zones, and a host asking for read only zones was given the full zones. Swap the values of NVME_ZRASF_ZONE_STATE_FULL and NVME_ZRASF_ZONE_STATE_READONLY to match the specification. Fixes: aaf2e048af27 ("nvmet: add ZBD over ZNS backend support") Cc: stable@vger.kernel.org Reviewed-by: Damien Le Moal Signed-off-by: Karl Bonde Torp Signed-off-by: Andreas Hindborg Signed-off-by: Keith Busch --- include/linux/nvme.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/linux/nvme.h b/include/linux/nvme.h index 91ce434a7e8d..6248711784b3 100644 --- a/include/linux/nvme.h +++ b/include/linux/nvme.h @@ -1266,8 +1266,8 @@ enum { NVME_ZRASF_ZONE_STATE_IMP_OPEN = 0x02, NVME_ZRASF_ZONE_STATE_EXP_OPEN = 0x03, NVME_ZRASF_ZONE_STATE_CLOSED = 0x04, - NVME_ZRASF_ZONE_STATE_READONLY = 0x05, - NVME_ZRASF_ZONE_STATE_FULL = 0x06, + NVME_ZRASF_ZONE_STATE_FULL = 0x05, + NVME_ZRASF_ZONE_STATE_READONLY = 0x06, NVME_ZRASF_ZONE_STATE_OFFLINE = 0x07, NVME_REPORT_ZONE_PARTIAL = 1, }; From b3ce7572130f7bdc71581d798df3fbfc3b64e7a5 Mon Sep 17 00:00:00 2001 From: Palla Raghunath Date: Wed, 30 Sep 2026 07:26:47 +0100 Subject: [PATCH 139/367] nvme-multipath: revalidate head zones after unfreezing the head queue When a namespace on a multipath controller is updated, nvme_update_ns_info() freezes the head disk queue, commits the new limits, and then calls nvme_mpath_revalidate_zones() before it unfreezes the queue again. That is the wrong way round for blk_revalidate_disk_zones(). It starts a limits update, which takes q->limits_lock, and it freezes the queue itself while updating the zone resources. The block layer takes limits_lock before freezing the queue, never the other way around, so calling it with the head queue already frozen reverses that order. syzbot has hit this twice. One report goes through q->limits_lock. The other one is on linux-next, where blk_revalidate_disk_zones() also takes disk->zone_revalidate_mutex and holds it across alloc_workqueue() the first time a disk's zone resources are set up. Lockdep then sees: q_usage_counter(io) (frozen head queue, nvme_update_ns_info()) --> &disk->zone_revalidate_mutex --> wq_pool_mutex --> fs_reclaim --> q_usage_counter(io) WARNING: possible circular locking dependency detected kworker/u8:10/3352 is trying to acquire lock: (&disk->zone_revalidate_mutex){+.+.}-{4:4}, at: blk_revalidate_disk_zones+0x1c5/0x1650 but task is already holding lock: (&q->q_usage_counter(io)#75){++++}-{0:0}, at: nvme_update_ns_info+0x3ac/0x1200 ... blk_revalidate_disk_zones+0x1c5/0x1650 block/blk-zoned.c:2560 nvme_mpath_revalidate_zones+0x106/0x1c0 drivers/nvme/host/multipath.c:301 nvme_update_ns_info+0x984/0x1200 drivers/nvme/host/core.c:2620 The rest of the driver already does this correctly: nvme_update_ns_info_block() unfreezes ns->disk->queue before calling blk_revalidate_disk_zones(), and nvme_mpath_set_live() revalidates the head zones without freezing the queue. Do the same here, and only revalidate the head zones once the queue is unfrozen and the limits update has succeeded. Fixes: 224041412693 ("nvme-multipath: revalidate zones for namespace heads") Reported-by: syzbot+b0910be96b7c31314822@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=b0910be96b7c31314822 Reported-by: syzbot+2e02ccadb3c5522a5c59@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=2e02ccadb3c5522a5c59 Link: https://lore.kernel.org/all/2bfc96f2-7d0d-47e0-936e-8810abb31a9f@acm.org/ Cc: Shuah Khan Cc: Brigham Campbell Reviewed-by: Damien Le Moal Reviewed-by: Yao Sang Tested-by: Yao Sang Signed-off-by: Palla Raghunath Signed-off-by: Keith Busch --- drivers/nvme/host/core.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/drivers/nvme/host/core.c b/drivers/nvme/host/core.c index ee7d09030c18..5d7dfd7a63d4 100644 --- a/drivers/nvme/host/core.c +++ b/drivers/nvme/host/core.c @@ -2617,10 +2617,19 @@ static int nvme_update_ns_info(struct nvme_ns *ns, struct nvme_ns_info *info) set_capacity_and_notify(ns->head->disk, get_capacity(ns->disk)); set_disk_ro(ns->head->disk, nvme_ns_is_readonly(ns, info)); nvme_mpath_revalidate_paths(ns->head); - ret = nvme_mpath_revalidate_zones(ns->head); unfreeze_head_queue: blk_mq_unfreeze_queue(ns->head->disk->queue, memflags); + + /* + * Wait until the head queue is unfrozen before revalidating + * its zones. blk_revalidate_disk_zones() takes the queue limits + * lock and then freezes the queue on its own, so it must not be + * called with the queue already frozen. This is also what + * nvme_update_ns_info_block() does for ns->disk. + */ + if (!ret) + ret = nvme_mpath_revalidate_zones(ns->head); } return ret; From 4aa9c0f377a4de5e8fddce76b34ee4529639a1ff Mon Sep 17 00:00:00 2001 From: Sang-Hoon Choi Date: Wed, 30 Sep 2026 16:08:58 +0900 Subject: [PATCH 140/367] nvmet: use the local P2P device on metadata allocation failure nvmet_req_alloc_p2pmem_sgls() assigns req->p2p_dev only after the data and metadata SGL allocations succeed. If the data allocation succeeds but the metadata allocation fails, out_free_sg passes req->p2p_dev to pci_p2pmem_free_sgl() before that assignment has taken place. Pass the local p2p_dev argument instead, so cleanup uses the same PCI device as the successful data allocation. Fixes: bcd9a0797d73 ("nvmet: fix freeing unallocated p2pmem") Reported-by: Changyul Lee Reviewed-by: Maurizio Lombardi Signed-off-by: Sang-Hoon Choi Signed-off-by: Keith Busch --- drivers/nvme/target/core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/nvme/target/core.c b/drivers/nvme/target/core.c index 8eea0a504308..07a74c2e1f58 100644 --- a/drivers/nvme/target/core.c +++ b/drivers/nvme/target/core.c @@ -1325,7 +1325,7 @@ static int nvmet_req_alloc_p2pmem_sgls(struct pci_dev *p2p_dev, return 0; out_free_sg: - pci_p2pmem_free_sgl(req->p2p_dev, req->sg); + pci_p2pmem_free_sgl(p2p_dev, req->sg); out_err: return -ENOMEM; } From 4e1462e7cd6a9b920f441842726cc2b9d232673e Mon Sep 17 00:00:00 2001 From: Matus Gajdos Date: Fri, 2 Oct 2026 14:52:37 +0200 Subject: [PATCH 141/367] ALSA: usb-audio: Add native DSD support for Cambridge Audio streamers The Cambridge Audio streamer advertises the UAC2 RAW_DATA format on altsetting 2 of its playback interface, but without the DSD_RAW quirk flag the driver exposes it only as a SPECIAL format that cannot be used. Add QUIRK_FLAG_DSD_RAW for the device (USB ID 22e8:ca10), so the altsetting is exposed as DSD_U32_BE, enabling native DSD64/128/256 playback. Tested with: gst-launch-1.0 filesrc location=file.dsf ! \ avdemux_dsf ! dsdconvert ! \ alsasink device=hw:2,0 Signed-off-by: Matus Gajdos Link: https://patch.msgid.link/20261002125237.82188-1-matuszpd@gmail.com Signed-off-by: Takashi Iwai --- sound/usb/quirks.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sound/usb/quirks.c b/sound/usb/quirks.c index 3afc49565963..a54fabce7920 100644 --- a/sound/usb/quirks.c +++ b/sound/usb/quirks.c @@ -2510,6 +2510,8 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { QUIRK_FLAG_DSD_RAW), DEVICE_FLG(0x21b4, 0x0232, /* Ayre QX-5 Twenty */ QUIRK_FLAG_DSD_RAW), + DEVICE_FLG(0x22e8, 0xca10, /* Cambridge Audio streamers */ + QUIRK_FLAG_DSD_RAW), DEVICE_FLG(0x2522, 0x0007, /* LH Labs Geek Out HD Audio 1V5 */ QUIRK_FLAG_SET_IFACE_FIRST), DEVICE_FLG(0x25aa, 0x600b, /* TAE1159 */ From 9a44b828dd7c0be1d0d6d6cf178d9727ceb6da6e Mon Sep 17 00:00:00 2001 From: Eckhart Mohr Date: Thu, 1 Oct 2026 20:34:00 +0200 Subject: [PATCH 142/367] ALSA: hda/realtek: Add quirk for ASRock NUC BOX-358H Fix headphone jack detection for some devices Signed-off-by: Eckhart Mohr Signed-off-by: Werner Sembach Link: https://patch.msgid.link/20261001183403.103990-1-wse@tuxedocomputers.com Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc269.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index be5482530aa2..0b5e0b45b6b8 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -8346,6 +8346,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x17aa, 0x9e56, "Lenovo ZhaoYang CF4620Z", ALC286_FIXUP_SONY_MIC_NO_PRESENCE), SND_PCI_QUIRK(0x1849, 0x0269, "Positivo Master C6400", ALC269VB_FIXUP_ASUS_ZENBOOK), SND_PCI_QUIRK(0x1849, 0x1233, "ASRock NUC Box 1100", ALC233_FIXUP_NO_AUDIO_JACK), + SND_PCI_QUIRK(0x1849, 0x6256, "ASRock NUC Box 358H", ALC233_FIXUP_NO_AUDIO_JACK), SND_PCI_QUIRK(0x1849, 0xa233, "Positivo Master C6300", ALC269_FIXUP_HEADSET_MIC), SND_PCI_QUIRK(0x1854, 0x0440, "LG CQ6", ALC256_FIXUP_HEADPHONE_AMP_VOL), SND_PCI_QUIRK(0x1854, 0x0441, "LG CQ6 AIO", ALC256_FIXUP_HEADPHONE_AMP_VOL), From 593d46fc6af9a0178a46e77d6d24b20d2314165f Mon Sep 17 00:00:00 2001 From: Harin Lee Date: Thu, 1 Oct 2026 23:45:01 +0900 Subject: [PATCH 143/367] ALSA: ctxfi: Fix memory leakage when clearing DAO input mappers Commit e576e7843c0d ("ALSA: ctxfi: Simplify dao_clear_{left,right}_input() functions") centralized the DAO input mapper clearing logic in dao_clear_input(), but omitted the kfree() calls. It removes the mapper entries from the list and sets their pointers to NULL without freeing the allocated array. This leaks memory each time dao_clear_input() is called. Store the base pointer of the allocation before clearing the mapper entries and free the array after removing entries from the list. Fixes: e576e7843c0d ("ALSA: ctxfi: Simplify dao_clear_{left,right}_input() functions") Cc: stable@vger.kernel.org Signed-off-by: Harin Lee Link: https://patch.msgid.link/20261001144501.3050667-2-me@harin.net Signed-off-by: Takashi Iwai --- sound/pci/ctxfi/ctdaio.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/sound/pci/ctxfi/ctdaio.c b/sound/pci/ctxfi/ctdaio.c index 9be70c6862ab..27e502fa493e 100644 --- a/sound/pci/ctxfi/ctdaio.c +++ b/sound/pci/ctxfi/ctdaio.c @@ -221,14 +221,18 @@ static int dao_set_right_input(struct dao *dao, struct rsc *input) static int dao_clear_input(struct dao *dao, unsigned int start, unsigned int end) { + struct imapper *entry; unsigned int i; - if (!dao->imappers[start]) + entry = dao->imappers[start]; + if (!entry) return 0; + for (i = start; i < end; i++) { dao->mgr->imap_delete(dao->mgr, dao->imappers[i]); dao->imappers[i] = NULL; } + kfree(entry); return 0; } From 408249089c48df1a4d23f60b831be3f3c311e6d1 Mon Sep 17 00:00:00 2001 From: Mohamed Yassine Jebabli Date: Thu, 1 Oct 2026 12:24:42 +0200 Subject: [PATCH 144/367] ALSA: hda/tas2781: Accept V1 calibration data without CRC Some Lenovo BIOSes (e.g. Legion Pro 7 16IRX9H, SSID 17aa:38cd) store valid V1 calibration data in the CALI_DATA EFI variable but leave the TimeStamp and CRC fields zeroed. The driver rejects such data, so the amplifiers always run with default parameters on these machines. The rejection was silent until commit 4fe238513407 ("ALSA: hda/tas2781: Move and unified the calibrated-data getting function for SPI and I2C into the tas2781_hda lib") turned it into "V1 CRC error". The stored values are consistent (R0 = 3.82/3.83 ohm, 1/R0 matching within 1e-4), so accept V1 data when both TimeStamp and CRC are zero and the first device entry is non-zero, and emit a warning. Fixes: 5be27f1e3ec9 ("ALSA: hda/tas2781: Add tas2781 HDA driver") Cc: stable@vger.kernel.org Signed-off-by: Mohamed Yassine Jebabli Link: https://patch.msgid.link/20261001-tas2781-v1-nocrc-v1-1-ff0c1032ce76@gmail.com Signed-off-by: Takashi Iwai --- sound/hda/codecs/side-codecs/tas2781_hda.c | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/sound/hda/codecs/side-codecs/tas2781_hda.c b/sound/hda/codecs/side-codecs/tas2781_hda.c index b22f93424c62..e0da299df780 100644 --- a/sound/hda/codecs/side-codecs/tas2781_hda.c +++ b/sound/hda/codecs/side-codecs/tas2781_hda.c @@ -139,9 +139,21 @@ static void tas2781_apply_calib(struct tasdevice_priv *p) */ crc = crc32(~0, data, 84) ^ ~0; if (crc != tmp_val[21]) { - cali_data->total_sz = 0; - dev_err(p->dev, "%s: V1 CRC error\n", __func__); - return; + /* + * Some Lenovo BIOSes (e.g. Legion Pro 7 16IRX9H, + * SSID 17aa:38cd) store valid V1 calibration data + * but leave TimeStamp and CRC zeroed. + */ + if (!tmp_val[20] && !tmp_val[21] && + tmp_val[0] && tmp_val[1]) { + dev_warn(p->dev, + "%s: V1 data without CRC, accepting\n", + __func__); + } else { + cali_data->total_sz = 0; + dev_err(p->dev, "%s: V1 CRC error\n", __func__); + return; + } } for (j = p->ndev - 1; j >= 0; j--) { From 13dffca9043500ed6033d770b491a043fb952bc5 Mon Sep 17 00:00:00 2001 From: Jaidheer Sirigineedi Date: Fri, 2 Oct 2026 13:59:51 -0500 Subject: [PATCH 145/367] ALSA: hda/realtek: Add mute LED quirk for HP Pavilion 15-eg3xxx (103c:8bfd) The mute LED on the HP Pavilion Laptop 15-eg3xxx (PCI SSID 103c:8bfd, Realtek ALC287) does not work because the machine has no entry in the quirk table. No fixup is applied, so the mute LED is never driven, even though the mute function itself works. The LED is controlled via GPIO pin 4 (mask 0x10), which is exactly what ALC287_FIXUP_HP_GPIO_LED configures. This was verified on the hardware with hda-verb: setting the GPIO data bit lights the mute LED, and clearing it turns the LED off. hda-verb /dev/snd/hwC0D0 0x01 SET_GPIO_MASK 0x10 hda-verb /dev/snd/hwC0D0 0x01 SET_GPIO_DIRECTION 0x10 hda-verb /dev/snd/hwC0D0 0x01 SET_GPIO_DATA 0x10 # LED on hda-verb /dev/snd/hwC0D0 0x01 SET_GPIO_DATA 0x00 # LED off Add a quirk entry for 103c:8bfd using ALC287_FIXUP_HP_GPIO_LED. Signed-off-by: Jaidheer Sirigineedi Link: https://patch.msgid.link/20261002185951.33855-1-jaidheer7@gmail.com Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc269.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index 0b5e0b45b6b8..1623cf926c71 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -7646,6 +7646,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x103c, 0x8be8, "HP Envy 17", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x103c, 0x8be9, "HP Envy x360 2-in-1 Laptop 15-fh0xxx", ALC245_FIXUP_HP_ENVY_X360_15_FH0XXX), SND_PCI_QUIRK(0x103c, 0x8bf0, "HP", ALC236_FIXUP_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x8bfd, "HP Pavilion 15-eg3xxx", ALC287_FIXUP_HP_GPIO_LED), SND_PCI_QUIRK(0x103c, 0x8c15, "HP Spectre x360 2-in-1 Laptop 14-eu0xxx", ALC245_FIXUP_HP_SPECTRE_X360_EU0XXX), SND_PCI_QUIRK(0x103c, 0x8c16, "HP Spectre x360 2-in-1 Laptop 16-aa0xxx", ALC245_FIXUP_HP_SPECTRE_X360_16_AA0XXX), SND_PCI_QUIRK(0x103c, 0x8c17, "HP Spectre x360 2-in-1 Laptop 16-aa0xxx", ALC245_FIXUP_HP_SPECTRE_X360_16_AA0XXX), From e6ac89b8b1c12e9104df45a14a26e2dfa96ded06 Mon Sep 17 00:00:00 2001 From: Kuba Piecuch Date: Sat, 3 Oct 2026 11:53:17 +0000 Subject: [PATCH 146/367] sched_ext: Generate qseq from a per-task counter finish_dispatch() uses the qseq embedded in p->scx.ops_state to tell whether the QUEUED instance of a task it's about to claim is the one scx_bpf_dsq_insert() saw. qseq is generated from rq->scx.ops_qseq, but the counters of different rqs are independent, so if a task is dequeued and re-enqueued on a different rq between scx_bpf_dsq_insert() and finish_dispatch(), the new QUEUED instance can end up with the same qseq as the old one: CPU X CPU Z ----- ----- enqueue p on rq A, qseq = N ops.dispatch() scx_bpf_dsq_insert(p) records qseq N sched_setaffinity(p) dequeue p from rq A enqueue p on rq B, qseq = N finish_dispatch(p, N) qseq matches, p is claimed The claim itself is still atomic so the core stays consistent, but an insert issued for a previous QUEUED instance gets applied to a new one which the BPF scheduler has just received through ops.enqueue(). This breaks the guarantee that dispatches targeting a stale instance are ignored. Generate qseq from a per-task counter, p->scx.ops_qseq, instead so that consecutive QUEUED instances of a task never share a qseq regardless of which rq they're on. The counter is only updated in scx_do_enqueue_task() with the task's rq locked, so no additional synchronization is needed, and it fits in an existing hole in struct sched_ext_entity on 64bit. Remove the now unused rq->scx.ops_qseq. Never generate qseq 0. NONE and DISPATCHING don't carry a qseq, so scx_bpf_dsq_insert() on a task in either state records 0. With a per-task counter, every task's first QUEUED instance would otherwise get qseq 0 and could be claimed by such an insert. Wrap the counter where the QSEQ field wraps so that it can't reach a value that shifts to 0 on 32bit either. Fixes: f0e1a0643a59 ("sched_ext: Implement BPF extensible scheduler class") Cc: stable@vger.kernel.org # v6.12+ Assisted-by: Claude:claude-opus-5.5 Signed-off-by: Kuba Piecuch Signed-off-by: Tejun Heo --- include/linux/sched/ext.h | 1 + kernel/sched/ext/ext.c | 14 ++++++++++---- kernel/sched/ext/internal.h | 5 +++++ kernel/sched/sched.h | 1 - 4 files changed, 16 insertions(+), 5 deletions(-) diff --git a/include/linux/sched/ext.h b/include/linux/sched/ext.h index 582d7cd4a983..36c04797436c 100644 --- a/include/linux/sched/ext.h +++ b/include/linux/sched/ext.h @@ -207,6 +207,7 @@ struct sched_ext_entity { s32 holding_cpu; s32 selected_cpu; s32 runnable_cpu; /* cpu @p is runnable on, -1 if not */ + u32 ops_qseq; /* protected by rq lock */ struct task_struct *kf_tasks[2]; /* see SCX_CALL_OP_TASK() */ struct list_head runnable_node; /* rq->scx.runnable_list */ diff --git a/kernel/sched/ext/ext.c b/kernel/sched/ext/ext.c index 5209510d6a5e..ac8fe70a9fb6 100644 --- a/kernel/sched/ext/ext.c +++ b/kernel/sched/ext/ext.c @@ -2057,8 +2057,14 @@ void scx_do_enqueue_task(struct rq *rq, struct task_struct *p, u64 enq_flags, if (unlikely(!SCX_HAS_OP(sch, enqueue))) goto global; - /* DSQ bypass didn't trigger, enqueue on the BPF scheduler */ - qseq = rq->scx.ops_qseq++ << SCX_OPSS_QSEQ_SHIFT; + /* + * DSQ bypass didn't trigger, enqueue on the BPF scheduler. Wrap the + * per-task qseq counter where the QSEQ field wraps and skip 0, which is + * what scx_bpf_dsq_insert() records for a task in NONE or DISPATCHING. + */ + p->scx.ops_qseq = ((p->scx.ops_qseq + 1) & + (SCX_OPSS_QSEQ_MASK >> SCX_OPSS_QSEQ_SHIFT)) ?: 1; + qseq = (unsigned long)p->scx.ops_qseq << SCX_OPSS_QSEQ_SHIFT; WARN_ON_ONCE(atomic_long_read(&p->scx.ops_state) != SCX_OPSS_NONE); atomic_long_set(&p->scx.ops_state, SCX_OPSS_QUEUEING | qseq); @@ -6969,9 +6975,9 @@ static void scx_dump_cpu(struct scx_sched *sch, struct seq_buf *s, seq_buf_init(&ns, buf, avail); dump_newline(&ns); - scx_dump_line(&ns, "CPU %-4d: nr_run=%u flags=0x%x cpu_rel=%d ops_qseq=%lu ksync=%lu", + scx_dump_line(&ns, "CPU %-4d: nr_run=%u flags=0x%x cpu_rel=%d ksync=%lu", cpu, rq->scx.nr_running, rq->scx.flags, rq->scx.cpu_released, - rq->scx.ops_qseq, rq->scx.kick_sync); + rq->scx.kick_sync); scx_rescue_dump(&ns, rq); scx_dump_line(&ns, " curr=%s[%d] class=%ps", rq->curr->comm, rq->curr->pid, rq->curr->sched_class); diff --git a/kernel/sched/ext/internal.h b/kernel/sched/ext/internal.h index 1df8f583b0ec..5b37faa532d6 100644 --- a/kernel/sched/ext/internal.h +++ b/kernel/sched/ext/internal.h @@ -1998,6 +1998,11 @@ enum scx_ops_state { * dequeue/requeue, the dispatcher can tell whether it still has a claim * on the task being dispatched. * + * QSEQ is generated from the per-task p->scx.ops_qseq counter so that + * it doesn't repeat across QUEUED instances of the same task even if + * the task moves between rqs. 0 is never used as a valid QSEQ since + * NONE and DISPATCHING map to this value. + * * As some 32bit archs can't do 64bit store_release/load_acquire, * p->scx.ops_state is atomic_long_t which leaves 30 bits for QSEQ on * 32bit machines. The dispatch race window QSEQ protects is very narrow diff --git a/kernel/sched/sched.h b/kernel/sched/sched.h index 742cce9da4e9..9ed0cb605c33 100644 --- a/kernel/sched/sched.h +++ b/kernel/sched/sched.h @@ -813,7 +813,6 @@ struct scx_rq { #endif struct list_head runnable_list; /* runnable tasks on this rq */ struct list_head ddsp_deferred_locals; /* deferred ddsps from enq */ - unsigned long ops_qseq; /* both stashed across the activate_task() in move_remote_task_to_local_dsq() */ u64 remote_activate_enq_flags; struct scx_sched *remote_activate_sch; From b52ef450139f5632d7e53e6fd5f5ec46c7029dac Mon Sep 17 00:00:00 2001 From: Fuad Tabba Date: Wed, 30 Sep 2026 13:12:12 +0100 Subject: [PATCH 147/367] arm64/boot: Don't set PMUv3p9 FGT2 bits without PMUv3 On a CPU with FGT2 and an IMPLEMENTATION DEFINED PMU (PMUVer 0b1111), __init_el2_fgt2 sets the PMUv3p9 bits in HDFGRTR2_EL2 and HDFGWTR2_EL2, which are RES0 there. The macro only checks that PMUVer is at least PMUv3p9, and 0b1111 passes. Exclude 0b1111 before the compare, as __init_el2_debug does. Fixes: 858c7bfcb35e1 ("arm64/boot: Enable EL2 requirements for FEAT_PMUv3p9") Signed-off-by: Fuad Tabba Reviewed-by: Bradley Morgan Reviewed-by: Anshuman Khandual Signed-off-by: Will Deacon --- arch/arm64/include/asm/el2_setup.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/arch/arm64/include/asm/el2_setup.h b/arch/arm64/include/asm/el2_setup.h index 87560d8b254e..1da277baacf7 100644 --- a/arch/arm64/include/asm/el2_setup.h +++ b/arch/arm64/include/asm/el2_setup.h @@ -421,8 +421,9 @@ mov x2, xzr mrs x1, id_aa64dfr0_el1 ubfx x1, x1, #ID_AA64DFR0_EL1_PMUVer_SHIFT, #4 - cmp x1, #ID_AA64DFR0_EL1_PMUVer_V3P9 - b.lt .Lskip_pmuv3p9_\@ + cmp x1, #ID_AA64DFR0_EL1_PMUVer_IMP_DEF + ccmp x1, #ID_AA64DFR0_EL1_PMUVer_V3P9, #8, ne + b.lt .Lskip_pmuv3p9_\@ // Skip if < PMUv3p9 or IMP_DEF orr x0, x0, #HDFGRTR2_EL2_nPMICNTR_EL0 orr x0, x0, #HDFGRTR2_EL2_nPMICFILTR_EL0 From 528b383dc35d1e5caf64ddf5d35f983ae543b0d0 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Fri, 2 Oct 2026 15:52:32 +0200 Subject: [PATCH 148/367] namespace: unhash a dentry before detaching the mounts on it unlink(), rmdir() and rename() remove the entry from the filesystem, call detach_mounts() on the entry with the inode locked and then call d_delete() once the inode is unlocked. The thing is that between detach_mounts() and d_delete() the dentry is still hashed and positive. But after detach_mounts() nothing covers the dentry anymore. A lookup that finds this dentry in the dcache can uncover what the mounts hid. That's a problem when unlinking files or directories that are mountpoints in other mount namespaces. Everybody who had the underlying entry covered can race the detach_mount() call until d_delete() has run. The race window isn't all that small. It encompasses namespace_unlock() with a full synchronize_rcu_expedited() grace period and the inode_unlock() of the entry. In my experiments three walkers that kept trying read an overmounted file in 11938 of the 16140 unlinks that removed its mountpoint from a bind mount of the filesystem within a minute. Fun fact, d_invalidate() has the same ordering problem but gets it right. It unhashes the dentry first and detaches the mounts afterwards. Let's do the same in __detach_mounts(): - A lookup that hasn't found the dentry yet misses it in the dcache and waits for the directory lock that the caller holds until the name is gone for good. - A lockless lookup that found it already fails the mount_lock check while the dentry still counts as a mountpoint and the d_seq check once it doesn't, and retries. This fixes the lockless path. We still need to fix the reference count lookup in a follow-up patch. So d_drop() the dentry. The dentry stays positive and held but can't be found anymore. Then proceed with the detach and unlink. Reproducer: The reads were counted with the program below, three walkers for 60 s in a VM with 4 CPUs. The race window is stretched with the debug patch pasted here. fs.detach_race_walk_us sleeps in lookup_fast() once it found a mountpoint dentry and before its mounts are crossed. fs.detach_race_unlink_us sleeps in vfs_unlink() between detach_mounts() and d_delete(). // SPDX-License-Identifier: GPL-2.0 /* * unlink_covered: unlink a file that is a mountpoint in a detached copy of * its mount, against lookups that open it through that copy. * * A is a tmpfs with the file f1 (content SECRET_F) and the plain file * MARK_A. A' is an open_tree(OPEN_TREE_CLONE) copy of A with MARK_A bound * on A'/f1, held through an O_PATH fd on its root once the tree fd is * closed. Walkers open f1 through that fd while the driver unlinks A/f1, * where nothing is mounted on it. A walker may read MARK_A or get ENOENT. * A read of SECRET_F is a hit: the name was found after its mount was * gone. The lockless walks use openat2(RESOLVE_CACHED). * * usage: unlink_covered [-t seconds] [-w walkers] */ #ifndef _GNU_SOURCE #define _GNU_SOURCE #endif #include #include #include #include #include #include #include #include #include #include #include #include #ifndef __NR_open_tree #define __NR_open_tree 428 #endif #ifndef __NR_move_mount #define __NR_move_mount 429 #endif #ifndef __NR_openat2 #define __NR_openat2 437 #endif #ifndef OPEN_TREE_CLONE #define OPEN_TREE_CLONE 1 #endif #ifndef OPEN_TREE_CLOEXEC #define OPEN_TREE_CLOEXEC O_CLOEXEC #endif #ifndef MOVE_MOUNT_F_EMPTY_PATH #define MOVE_MOUNT_F_EMPTY_PATH 0x00000004 #endif #define WORK "/tmp/uc" #define ADIR WORK "/A" static int duration = 60, nwalkers = 3; static atomic_int stop, writer_waiting; static pthread_rwlock_t cur_lock = PTHREAD_RWLOCK_INITIALIZER; static int cur_fd = -1; /* the root of A', -1 while there is none */ static atomic_long n_unlink, n_walk, n_mark, n_enoent, n_other, n_secret, n_secret_cached; static void die(const char *what) { fprintf(stderr, "FATAL %s: %s\n", what, strerror(errno)); exit(2); } static void put_file(int dfd, const char *name, const char *content) { int fd = openat(dfd, name, O_CREAT | O_WRONLY | O_TRUNC | O_CLOEXEC, 0644); if (fd < 0 || write(fd, content, strlen(content)) < 0) die(name); close(fd); } /* "plain/../" @n times, then f1: a longer walk that checks nothing on its way */ static char *longpath(int n) { char *p = malloc(n * 9 + 3), *q = p; for (int i = 0; i < n; i++, q += 9) memcpy(q, "plain/../", 9); strcpy(q, "f1"); return p; } static void try_read(int dfd, const char *path, int cached) { struct open_how how = { .flags = O_RDONLY | O_CLOEXEC, .resolve = RESOLVE_CACHED }; char buf[32] = ""; long n; int fd; if (cached) fd = syscall(__NR_openat2, dfd, path, &how, sizeof(how)); else fd = openat(dfd, path, O_RDONLY | O_CLOEXEC); atomic_fetch_add(&n_walk, 1); if (fd < 0) { if (errno == ENOENT) atomic_fetch_add(&n_enoent, 1); else if (!cached || errno != EAGAIN) atomic_fetch_add(&n_other, 1); return; } n = read(fd, buf, sizeof(buf) - 1); close(fd); if (n >= 6 && !strncmp(buf, "SECRET", 6)) { if (cached) atomic_fetch_add(&n_secret_cached, 1); if (!atomic_fetch_add(&n_secret, 1)) printf("HIT: read \"%s\" through %s (%s walk)\n", buf, path, cached ? "lockless" : "any"); } else { atomic_fetch_add(&n_mark, 1); } } static void *walker(void *arg) { unsigned int r = (long)arg * 2654435761u; while (!atomic_load(&stop)) { char *p_long, *p_short; int dfd; while (atomic_load(&writer_waiting) && !atomic_load(&stop)) usleep(20); pthread_rwlock_rdlock(&cur_lock); dfd = cur_fd; if (dfd < 0) { pthread_rwlock_unlock(&cur_lock); usleep(100); continue; } r = r * 1103515245u + 12345u; p_long = longpath(1 + (r >> 8) % 400); p_short = longpath(0); try_read(dfd, p_long, 0); try_read(dfd, p_short, 0); try_read(dfd, p_long, 1); pthread_rwlock_unlock(&cur_lock); free(p_long); free(p_short); } return NULL; } /* hand the walkers a new A' (or none), close the old one */ static void publish(int fd) { int old; atomic_fetch_add(&writer_waiting, 1); pthread_rwlock_wrlock(&cur_lock); old = cur_fd; cur_fd = fd; pthread_rwlock_unlock(&cur_lock); atomic_fetch_sub(&writer_waiting, 1); if (old >= 0) close(old); } static void *driver(void *arg __attribute__((unused))) { int a; if (mkdir(ADIR, 0755) && errno != EEXIST) die("mkdir A"); if (mount("A", ADIR, "tmpfs", 0, "size=4M")) die("mount A"); a = open(ADIR, O_PATH | O_DIRECTORY | O_CLOEXEC); if (a < 0 || mkdirat(a, "plain", 0755)) die("A/plain"); put_file(a, "MARK_A", "MARK_A"); while (!atomic_load(&stop)) { int t, m, fd_a; put_file(a, "f1", "SECRET_F"); t = syscall(__NR_open_tree, a, "", OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC | AT_EMPTY_PATH); if (t < 0) die("open_tree A"); m = syscall(__NR_open_tree, a, "MARK_A", OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC); if (m < 0) die("open_tree MARK_A"); if (syscall(__NR_move_mount, m, "", t, "f1", MOVE_MOUNT_F_EMPTY_PATH)) die("move_mount"); close(m); fd_a = openat(t, ".", O_PATH | O_DIRECTORY | O_CLOEXEC); if (fd_a < 0) die("open A'"); publish(fd_a); usleep(200 + rand() % 1000); close(t); /* A' is unmounted, fd_a holds it */ usleep(200 + rand() % 1000); if (unlinkat(a, "f1", 0)) /* through A, a plain file there */ die("unlink f1"); atomic_fetch_add(&n_unlink, 1); usleep(rand() % 300); publish(-1); } close(a); umount2(ADIR, MNT_DETACH); return NULL; } int main(int argc, char **argv) { pthread_t d, *w; int c, i; setvbuf(stdout, NULL, _IOLBF, 0); while ((c = getopt(argc, argv, "t:w:")) != -1) { switch (c) { case 't': duration = atoi(optarg); break; case 'w': nwalkers = atoi(optarg); break; default: fprintf(stderr, "usage: unlink_covered [-t seconds] [-w walkers]\n"); return 2; } } if (unshare(CLONE_NEWNS) || mount(NULL, "/", NULL, MS_REC | MS_PRIVATE, NULL)) die("unshare"); if (mkdir(WORK, 0755) && errno != EEXIST) die("mkdir"); w = calloc(nwalkers, sizeof(*w)); for (i = 0; i < nwalkers; i++) if (pthread_create(&w[i], NULL, walker, (void *)(long)i)) die("pthread_create"); if (pthread_create(&d, NULL, driver, NULL)) die("pthread_create"); sleep(duration); atomic_store(&stop, 1); pthread_join(d, NULL); for (i = 0; i < nwalkers; i++) pthread_join(w[i], NULL); printf("unlink_covered: %d s, %d walkers: unlinks %ld walks %ld mark %ld enoent %ld other %ld SECRET %ld (lockless %ld)\n", duration, nwalkers, atomic_load(&n_unlink), atomic_load(&n_walk), atomic_load(&n_mark), atomic_load(&n_enoent), atomic_load(&n_other), atomic_load(&n_secret), atomic_load(&n_secret_cached)); return atomic_load(&n_secret) ? 1 : 0; } diff --git a/fs/namei.c b/fs/namei.c --- a/fs/namei.c +++ b/fs/namei.c @@ -35,6 +35,7 @@ #include #include #include +#include #include #include #include @@ -1205,9 +1206,26 @@ static int sysctl_protected_symlinks __read_mostly; static int sysctl_protected_hardlinks __read_mostly; static int sysctl_protected_fifos __read_mostly; static int sysctl_protected_regular __read_mostly; +/* debug: widen the two windows of the detach_mounts() race */ +static int sysctl_detach_race_walk_us __read_mostly; +static int sysctl_detach_race_unlink_us __read_mostly; #ifdef CONFIG_SYSCTL static const struct ctl_table namei_sysctls[] = { + { + .procname = "detach_race_walk_us", + .data = &sysctl_detach_race_walk_us, + .maxlen = sizeof(int), + .mode = 0644, + .proc_handler = proc_dointvec, + }, + { + .procname = "detach_race_unlink_us", + .data = &sysctl_detach_race_unlink_us, + .maxlen = sizeof(int), + .mode = 0644, + .proc_handler = proc_dointvec, + }, { .procname = "protected_symlinks", .data = &sysctl_protected_symlinks, @@ -1878,6 +1896,10 @@ static struct dentry *lookup_fast(struct nameidata *nd) dentry = __d_lookup(parent, &nd->last); if (unlikely(!dentry)) return NULL; + /* debug: between finding a mountpoint and crossing its mounts */ + if (unlikely(sysctl_detach_race_walk_us) && d_mountpoint(dentry)) + usleep_range(sysctl_detach_race_walk_us, + sysctl_detach_race_walk_us + 10); status = d_revalidate(nd->inode, &nd->last, dentry, nd->flags); } if (unlikely(status <= 0)) { @@ -5693,6 +5715,10 @@ int vfs_unlink(struct mnt_idmap *idmap, struct inode *dir, if (!error) { dont_mount(dentry); detach_mounts(dentry); + /* debug: the mounts are gone, d_delete() is still to come */ + if (unlikely(sysctl_detach_race_unlink_us)) + usleep_range(sysctl_detach_race_unlink_us, + sysctl_detach_race_unlink_us + 10); } } } Fixes: 8ed936b5671b ("vfs: Lazily remove mounts on unlinked files and directories.") Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20261002-work-mount-fixes-4-v1-1-dd44b89d44ce@kernel.org Signed-off-by: Christian Brauner (Amutable) --- fs/namespace.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/fs/namespace.c b/fs/namespace.c index fcf42f192aae..e576a5d6eff0 100644 --- a/fs/namespace.c +++ b/fs/namespace.c @@ -1996,6 +1996,9 @@ static int do_umount(struct mount *mnt, int flags) * detach_mounts allows lazily unmounting those mounts instead of * leaking them. * + * The dentry is unhashed before the mounts go so that no lookup finds + * what they covered. The caller removes it for good afterwards. + * * The caller may hold dentry->d_inode->i_rwsem. */ void __detach_mounts(struct dentry *dentry) @@ -2009,6 +2012,8 @@ void __detach_mounts(struct dentry *dentry) if (!lookup_mountpoint(dentry, &mp)) return; + /* the name goes first, what covered it goes second */ + d_drop(dentry); event++; while (mp.node.next) { mnt = hlist_entry(mp.node.next, struct mount, mnt_mp_list); From 8189ab688f5a2c745dc90b63374da02f213d6994 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Fri, 2 Oct 2026 15:52:33 +0200 Subject: [PATCH 149/367] namei: don't reveal overmounted entries in refwalk In rcuwalk the dentry is validated before it is accepted. step_into() step_into() rechecks d_seq and __follow_mount_rcu() rechecks mount_lock. An entry that gets unlinked in between causes the lookup to retry and miss. A refwalk doesn't do this. lookup_fast() takes a reference on the hashed dentry and simply accepts it. So an unlink that happens after the reference was taken isn't seen by refwalk. That's fine for a simple file. We just happened to open it before it was unlinked, no problem. For a mountpoint and specifically a locked mountpoint it very much isn't. The unlink detaches all mounts and then removes the name. Any refwalk that hasn't traversed the mounts yet simply reveals the underlying entry. It's a very narrow window but it can be hit: reads of a covered file in 60 s, 3 walkers, ~15000 unlinks no widening 27 that step + 200 us 3741 See the appended patch for a more reliable reproducer. So check the entry after step_into(). unlink(), rmdir() and rename() mark the dentry with dont_mount() before they detach the mounts and remove the dentry. So a dentry that is marked with DCACHE_CANT_MOUNT and is unhashed by the time its mounts were looked at is a name that was unlinked under the refwalk. DCACHE_CANT_MOUNT is read after the mounts were looked up and it is set before they are detached by detach_mounts(). A refwalk that missed the mounts will see DCACHE_CANT_MOUNT. Plain d_unlinked() is fine. A rename takes the dentry off its hash chain but ___d_drop() leaves d_hash.pprev set. So only __d_drop() and a rename over the dentry unhash it. The only move that flips IS_ROOT splices in a disconnected alias. That doesn't have DCACHE_CANT_MOUNT set. Add the to step_into_slowpath(). ".." and LOOKUP_DOWN may legitimately land on an unhashed directory. A refwalk that crossed onto a mount has path.mnt different from nd->path.mnt. A dentry that a filesystem dropped on its own (d_invalidate(), d_drop()) doesn't have the flag set and is treated as before. // SPDX-License-Identifier: GPL-2.0 /* * unlink_covered: unlink a file that is a mountpoint in a detached copy of * its mount, against walkers that open it through that copy. * * A is a tmpfs with the file f1 (content SECRET_F) and the plain file * MARK_A. A' is an open_tree(OPEN_TREE_CLONE) copy of A with MARK_A bound * on A'/f1, held through an O_PATH fd on its root once the tree fd is * closed. Walkers open f1 through that fd while the driver unlinks A/f1, * where nothing is mounted on it. A walker may read MARK_A or get ENOENT. * A read of SECRET_F is a hit: the name was found after its mount was * gone. The lockless walks use openat2(RESOLVE_CACHED). * * usage: unlink_covered [-t seconds] [-w walkers] */ #ifndef _GNU_SOURCE #define _GNU_SOURCE #endif #include #include #include #include #include #include #include #include #include #include #include #include #ifndef __NR_open_tree #define __NR_open_tree 428 #endif #ifndef __NR_move_mount #define __NR_move_mount 429 #endif #ifndef __NR_openat2 #define __NR_openat2 437 #endif #ifndef OPEN_TREE_CLONE #define OPEN_TREE_CLONE 1 #endif #ifndef OPEN_TREE_CLOEXEC #define OPEN_TREE_CLOEXEC O_CLOEXEC #endif #ifndef MOVE_MOUNT_F_EMPTY_PATH #define MOVE_MOUNT_F_EMPTY_PATH 0x00000004 #endif #define WORK "/tmp/uc" #define ADIR WORK "/A" static int duration = 60, nwalkers = 3; static atomic_int stop, writer_waiting; static pthread_rwlock_t cur_lock = PTHREAD_RWLOCK_INITIALIZER; static int cur_fd = -1; /* the root of A', -1 while there is none */ static atomic_long n_unlink, n_walk, n_mark, n_enoent, n_other, n_secret, n_secret_cached; static void die(const char *what) { fprintf(stderr, "FATAL %s: %s\n", what, strerror(errno)); exit(2); } static void put_file(int dfd, const char *name, const char *content) { int fd = openat(dfd, name, O_CREAT | O_WRONLY | O_TRUNC | O_CLOEXEC, 0644); if (fd < 0 || write(fd, content, strlen(content)) < 0) die(name); close(fd); } /* "plain/../" @n times, then f1: a longer walk that checks nothing on its way */ static char *longpath(int n) { char *p = malloc(n * 9 + 3), *q = p; for (int i = 0; i < n; i++, q += 9) memcpy(q, "plain/../", 9); strcpy(q, "f1"); return p; } static void try_read(int dfd, const char *path, int cached) { struct open_how how = { .flags = O_RDONLY | O_CLOEXEC, .resolve = RESOLVE_CACHED }; char buf[32] = ""; long n; int fd; if (cached) fd = syscall(__NR_openat2, dfd, path, &how, sizeof(how)); else fd = openat(dfd, path, O_RDONLY | O_CLOEXEC); atomic_fetch_add(&n_walk, 1); if (fd < 0) { if (errno == ENOENT) atomic_fetch_add(&n_enoent, 1); else if (!cached || errno != EAGAIN) atomic_fetch_add(&n_other, 1); return; } n = read(fd, buf, sizeof(buf) - 1); close(fd); if (n >= 6 && !strncmp(buf, "SECRET", 6)) { if (cached) atomic_fetch_add(&n_secret_cached, 1); if (!atomic_fetch_add(&n_secret, 1)) printf("HIT: read \"%s\" through %s (%s walk)\n", buf, path, cached ? "lockless" : "any"); } else { atomic_fetch_add(&n_mark, 1); } } static void *walker(void *arg) { unsigned int r = (long)arg * 2654435761u; while (!atomic_load(&stop)) { char *p_long, *p_short; int dfd; while (atomic_load(&writer_waiting) && !atomic_load(&stop)) usleep(20); pthread_rwlock_rdlock(&cur_lock); dfd = cur_fd; if (dfd < 0) { pthread_rwlock_unlock(&cur_lock); usleep(100); continue; } r = r * 1103515245u + 12345u; p_long = longpath(1 + (r >> 8) % 400); p_short = longpath(0); try_read(dfd, p_long, 0); try_read(dfd, p_short, 0); try_read(dfd, p_long, 1); pthread_rwlock_unlock(&cur_lock); free(p_long); free(p_short); } return NULL; } /* hand the walkers a new A' (or none), close the old one */ static void publish(int fd) { int old; atomic_fetch_add(&writer_waiting, 1); pthread_rwlock_wrlock(&cur_lock); old = cur_fd; cur_fd = fd; pthread_rwlock_unlock(&cur_lock); atomic_fetch_sub(&writer_waiting, 1); if (old >= 0) close(old); } static void *driver(void *arg __attribute__((unused))) { int a; if (mkdir(ADIR, 0755) && errno != EEXIST) die("mkdir A"); if (mount("A", ADIR, "tmpfs", 0, "size=4M")) die("mount A"); a = open(ADIR, O_PATH | O_DIRECTORY | O_CLOEXEC); if (a < 0 || mkdirat(a, "plain", 0755)) die("A/plain"); put_file(a, "MARK_A", "MARK_A"); while (!atomic_load(&stop)) { int t, m, fd_a; put_file(a, "f1", "SECRET_F"); t = syscall(__NR_open_tree, a, "", OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC | AT_EMPTY_PATH); if (t < 0) die("open_tree A"); m = syscall(__NR_open_tree, a, "MARK_A", OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC); if (m < 0) die("open_tree MARK_A"); if (syscall(__NR_move_mount, m, "", t, "f1", MOVE_MOUNT_F_EMPTY_PATH)) die("move_mount"); close(m); fd_a = openat(t, ".", O_PATH | O_DIRECTORY | O_CLOEXEC); if (fd_a < 0) die("open A'"); publish(fd_a); usleep(200 + rand() % 1000); close(t); /* A' is unmounted, fd_a holds it */ usleep(200 + rand() % 1000); if (unlinkat(a, "f1", 0)) /* through A, a plain file there */ die("unlink f1"); atomic_fetch_add(&n_unlink, 1); usleep(rand() % 300); publish(-1); } close(a); umount2(ADIR, MNT_DETACH); return NULL; } int main(int argc, char **argv) { pthread_t d, *w; int c, i; setvbuf(stdout, NULL, _IOLBF, 0); while ((c = getopt(argc, argv, "t:w:")) != -1) { switch (c) { case 't': duration = atoi(optarg); break; case 'w': nwalkers = atoi(optarg); break; default: fprintf(stderr, "usage: unlink_covered [-t seconds] [-w walkers]\n"); return 2; } } if (unshare(CLONE_NEWNS) || mount(NULL, "/", NULL, MS_REC | MS_PRIVATE, NULL)) die("unshare"); if (mkdir(WORK, 0755) && errno != EEXIST) die("mkdir"); w = calloc(nwalkers, sizeof(*w)); for (i = 0; i < nwalkers; i++) if (pthread_create(&w[i], NULL, walker, (void *)(long)i)) die("pthread_create"); if (pthread_create(&d, NULL, driver, NULL)) die("pthread_create"); sleep(duration); atomic_store(&stop, 1); pthread_join(d, NULL); for (i = 0; i < nwalkers; i++) pthread_join(w[i], NULL); printf("unlink_covered: %d s, %d walkers: unlinks %ld walks %ld mark %ld enoent %ld other %ld SECRET %ld (lockless %ld)\n", duration, nwalkers, atomic_load(&n_unlink), atomic_load(&n_walk), atomic_load(&n_mark), atomic_load(&n_enoent), atomic_load(&n_other), atomic_load(&n_secret), atomic_load(&n_secret_cached)); return atomic_load(&n_secret) ? 1 : 0; } diff --git a/fs/namei.c b/fs/namei.c --- a/fs/namei.c +++ b/fs/namei.c @@ -35,6 +35,7 @@ #include #include #include +#include #include #include #include @@ -1205,9 +1206,26 @@ static int sysctl_protected_symlinks __read_mostly; static int sysctl_protected_hardlinks __read_mostly; static int sysctl_protected_fifos __read_mostly; static int sysctl_protected_regular __read_mostly; +/* debug: widen the two windows of the detach_mounts() race */ +static int sysctl_detach_race_walk_us __read_mostly; +static int sysctl_detach_race_unlink_us __read_mostly; #ifdef CONFIG_SYSCTL static const struct ctl_table namei_sysctls[] = { + { + .procname = "detach_race_walk_us", + .data = &sysctl_detach_race_walk_us, + .maxlen = sizeof(int), + .mode = 0644, + .proc_handler = proc_dointvec, + }, + { + .procname = "detach_race_unlink_us", + .data = &sysctl_detach_race_unlink_us, + .maxlen = sizeof(int), + .mode = 0644, + .proc_handler = proc_dointvec, + }, { .procname = "protected_symlinks", .data = &sysctl_protected_symlinks, @@ -1878,6 +1896,10 @@ static struct dentry *lookup_fast(struct nameidata *nd) dentry = __d_lookup(parent, &nd->last); if (unlikely(!dentry)) return NULL; + /* debug: between finding a mountpoint and crossing its mounts */ + if (unlikely(sysctl_detach_race_walk_us) && d_mountpoint(dentry)) + usleep_range(sysctl_detach_race_walk_us, + sysctl_detach_race_walk_us + 10); status = d_revalidate(nd->inode, &nd->last, dentry, nd->flags); } if (unlikely(status <= 0)) { @@ -5693,6 +5715,10 @@ int vfs_unlink(struct mnt_idmap *idmap, struct inode *dir, if (!error) { dont_mount(dentry); detach_mounts(dentry); + /* debug: the mounts are gone, d_delete() is still to come */ + if (unlikely(sysctl_detach_race_unlink_us)) + usleep_range(sysctl_detach_race_unlink_us, + sysctl_detach_race_unlink_us + 10); } } } Fixes: 8ed936b5671b ("vfs: Lazily remove mounts on unlinked files and directories.") Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20261002-work-mount-fixes-4-v1-2-dd44b89d44ce@kernel.org Signed-off-by: Christian Brauner (Amutable) --- fs/namei.c | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/fs/namei.c b/fs/namei.c index 20a6534ea3ef..c87c14ee1a25 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -2086,6 +2086,33 @@ static noinline const char *pick_link(struct nameidata *nd, struct path *link, return NULL; } +/* + * Be careful in case the dentry is unlinked or renamed. Any mounts + * stacked on top of it are going away. We need to make sure that we + * don't reveal the underyling dentry during refwalk. In rcuwalk we + * catch this via d_seq and another lookup for the name. Give the same + * guarantee in refwalk. + */ +static bool unlink_may_reveal(struct nameidata *nd, int flags, + struct path *path, struct dentry *dentry) +{ + /* ".." and LOOKUP_DOWN may land on an unhashed directory */ + if (flags & WALK_NOFOLLOW) + return false; + if (nd->flags & LOOKUP_REVAL) + return false; + /* We crossed onto a mount and the name led us here while it still existed */ + if (path->mnt != nd->path.mnt) + return false; + /* only a name on its way out is flagged */ + if (likely(!cant_mount(dentry))) + return false; + if (!d_unlinked(dentry)) + return false; + dput(no_free_ptr(path->dentry)); + return true; +} + /* * Do we need to follow links? We _really_ want to be able * to do this check without having to look at inode->i_op, @@ -2115,6 +2142,8 @@ static noinline const char *step_into_slowpath(struct nameidata *nd, int flags, if (unlikely(!inode)) return ERR_PTR(-ENOENT); } else { + if (unlikely(unlink_may_reveal(nd, flags, &path, dentry))) + return ERR_PTR(-ESTALE); dput(nd->path.dentry); if (nd->path.mnt != path.mnt) mntput(nd->path.mnt); From b0d1022b9bdb1e98aa7fb3244fdd50a58c61766c Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Fri, 2 Oct 2026 15:52:34 +0200 Subject: [PATCH 150/367] fcntl: refuse F_SET_RW_HINT on an immutable inode An immutable inode is never written to so write hints are pointless. Refuse the hint on an immutable inode the way setattr(), fallocate() and etxattr() refuse their changes. Fixes: c75b1d9421f8 ("fs: add fcntl() interface for setting/getting write life time hints") Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20261002-work-mount-fixes-4-v1-3-dd44b89d44ce@kernel.org Signed-off-by: Christian Brauner (Amutable) --- fs/fcntl.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/fs/fcntl.c b/fs/fcntl.c index c158f082f1da..9b71bc9d71d1 100644 --- a/fs/fcntl.c +++ b/fs/fcntl.c @@ -372,6 +372,9 @@ static long fcntl_set_rw_hint(struct file *file, unsigned long arg) u64 __user *argp = (u64 __user *)arg; u64 hint; + /* nothing is ever written to it */ + if (IS_IMMUTABLE(inode)) + return -EPERM; if (!inode_owner_or_capable(file_mnt_idmap(file), inode)) return -EPERM; From 66cda7ec9abec7ec3b81678c48deab3450d038a6 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Fri, 2 Oct 2026 15:52:35 +0200 Subject: [PATCH 151/367] selftests/filesystems: check that an immutable inode takes no write hint Check that an immutable inode takes no write hint. Link: https://patch.msgid.link/20261002-work-mount-fixes-4-v1-4-dd44b89d44ce@kernel.org Signed-off-by: Christian Brauner (Amutable) --- .../testing/selftests/filesystems/.gitignore | 1 + tools/testing/selftests/filesystems/Makefile | 2 +- .../selftests/filesystems/rw_hint_test.c | 129 ++++++++++++++++++ 3 files changed, 131 insertions(+), 1 deletion(-) create mode 100644 tools/testing/selftests/filesystems/rw_hint_test.c diff --git a/tools/testing/selftests/filesystems/.gitignore b/tools/testing/selftests/filesystems/.gitignore index 9eb185fb2f9d..62f7b1c46649 100644 --- a/tools/testing/selftests/filesystems/.gitignore +++ b/tools/testing/selftests/filesystems/.gitignore @@ -7,3 +7,4 @@ anon_inode_test kernfs_test idmapped_tmpfile ustat_test +rw_hint_test diff --git a/tools/testing/selftests/filesystems/Makefile b/tools/testing/selftests/filesystems/Makefile index 03be337c1f35..93e2cc9123b0 100644 --- a/tools/testing/selftests/filesystems/Makefile +++ b/tools/testing/selftests/filesystems/Makefile @@ -1,7 +1,7 @@ # SPDX-License-Identifier: GPL-2.0 CFLAGS += $(KHDR_INCLUDES) -TEST_GEN_PROGS := devpts_pts file_stressor anon_inode_test kernfs_test fclog ustat_test +TEST_GEN_PROGS := devpts_pts file_stressor anon_inode_test kernfs_test fclog ustat_test rw_hint_test TEST_GEN_PROGS += idmapped_tmpfile TEST_GEN_PROGS_EXTENDED := dnotify_test diff --git a/tools/testing/selftests/filesystems/rw_hint_test.c b/tools/testing/selftests/filesystems/rw_hint_test.c new file mode 100644 index 000000000000..d1930f82f63b --- /dev/null +++ b/tools/testing/selftests/filesystems/rw_hint_test.c @@ -0,0 +1,129 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * F_SET_RW_HINT is refused on an immutable inode. Nothing is ever written + * to it and it may be shared with everybody, like a namespace file or the + * root of an empty mount namespace. + */ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../kselftest_harness.h" + +/* and don't mix with the libc headers */ +#ifndef FS_IOC_GETFLAGS +#define FS_IOC_GETFLAGS _IOR('f', 1, long) +#define FS_IOC_SETFLAGS _IOW('f', 2, long) +#endif +#ifndef FS_IMMUTABLE_FL +#define FS_IMMUTABLE_FL 0x00000010 +#endif +#ifndef F_LINUX_SPECIFIC_BASE +#define F_LINUX_SPECIFIC_BASE 1024 +#endif +#ifndef F_GET_RW_HINT +#define F_GET_RW_HINT (F_LINUX_SPECIFIC_BASE + 11) +#define F_SET_RW_HINT (F_LINUX_SPECIFIC_BASE + 12) +#endif +#ifndef RWH_WRITE_LIFE_SHORT +#define RWH_WRITE_LIFE_SHORT 2 +#endif +#ifndef UNSHARE_EMPTY_MNTNS +#define UNSHARE_EMPTY_MNTNS 0x00100000 +#endif + +static int set_hint(int fd, uint64_t hint) +{ + return fcntl(fd, F_SET_RW_HINT, &hint); +} + +static long get_hint(int fd) +{ + uint64_t hint; + + if (fcntl(fd, F_GET_RW_HINT, &hint)) + return -1; + return hint; +} + +TEST(immutable_file) +{ + char path[] = "/tmp/rw_hint.XXXXXX"; + int fd, flags; + + if (geteuid()) + SKIP(return, "test requires root"); + + fd = mkstemp(path); + ASSERT_GE(fd, 0); + unlink(path); + ASSERT_EQ(set_hint(fd, RWH_WRITE_LIFE_SHORT), 0); + EXPECT_EQ(get_hint(fd), RWH_WRITE_LIFE_SHORT); + + if (ioctl(fd, FS_IOC_GETFLAGS, &flags)) { + close(fd); + SKIP(return, "no file attributes on this filesystem"); + } + flags |= FS_IMMUTABLE_FL; + ASSERT_EQ(ioctl(fd, FS_IOC_SETFLAGS, &flags), 0); + EXPECT_EQ(set_hint(fd, RWH_WRITE_LIFE_SHORT), -1); + EXPECT_EQ(errno, EPERM); + flags &= ~FS_IMMUTABLE_FL; + ASSERT_EQ(ioctl(fd, FS_IOC_SETFLAGS, &flags), 0); + EXPECT_EQ(set_hint(fd, RWH_WRITE_LIFE_SHORT), 0); + close(fd); +} + +TEST(namespace_file) +{ + int fd; + + if (geteuid()) + SKIP(return, "test requires root"); + + fd = open("/proc/self/ns/mnt", O_RDONLY | O_CLOEXEC); + ASSERT_GE(fd, 0); + EXPECT_EQ(set_hint(fd, RWH_WRITE_LIFE_SHORT), -1); + EXPECT_EQ(errno, EPERM); + close(fd); +} + +TEST(empty_mntns_root) +{ + int status; + pid_t pid; + + if (geteuid()) + SKIP(return, "test requires root"); + + pid = fork(); + ASSERT_GE(pid, 0); + if (pid == 0) { + int fd; + + if (unshare(UNSHARE_EMPTY_MNTNS)) + _exit(errno == EINVAL ? 100 : 1); + fd = open("/", O_RDONLY | O_DIRECTORY | O_CLOEXEC); + if (fd < 0) + _exit(2); + if (set_hint(fd, RWH_WRITE_LIFE_SHORT) == 0) + _exit(3); + _exit(errno == EPERM ? 0 : 4); + } + ASSERT_EQ(waitpid(pid, &status, 0), pid); + ASSERT_TRUE(WIFEXITED(status)); + if (WEXITSTATUS(status) == 100) + SKIP(return, "UNSHARE_EMPTY_MNTNS not supported"); + EXPECT_EQ(WEXITSTATUS(status), 0); +} + +TEST_HARNESS_MAIN From ba985ce08471c555af2a2fbe217c1bc2e72a6d8c Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Fri, 2 Oct 2026 15:52:36 +0200 Subject: [PATCH 152/367] namespace: refuse an automount below a mount that is in no namespace It's possible to add autmounts even when the parent mount isn't in the mount namespace of the caller. The only requirement we have is that the parent mount namespace must not be NULL, i.e., unmounted. Problem is that clone_private_mount() has MNT_NS_INTERNAL which makes that trivially true. So that passes the test and attach_recursive_mnt() accepts that as a mount point and funny enough, count_mounts() dereferences MNT_NS_INTERNAL. The problem is it is an error pointer... So we can reach this in userspace via fanotify. A filesystem mark on the lower filesystem of an overlay reports paths on the layer clone and reading the event hands out a descriptor on it. For example with debugfs as the lower layer it goes kaboom: openat(evfd, "tracing", O_DIRECTORY) Oops: general protection fault KASAN: null-ptr-deref in range [0x1d0-0x1d7] RIP: 0010:count_mounts+0x35/0x200 attach_recursive_mnt finish_automount And since that sleeping beauty happens under namespace_sem held for writing every mount operation on the system blocks from then on. Congrats. Use is_mounted() instead which rejects unmounted and internal mounts alike. The open fails with EINVAL just as it did before clone_private_mount() used MNT_NS_INTERNAL. Fixes: df820f8de4e4 ("ovl: make private mounts longterm") Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20261002-work-mount-fixes-4-v1-5-dd44b89d44ce@kernel.org Signed-off-by: Christian Brauner (Amutable) --- fs/namespace.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/namespace.c b/fs/namespace.c index e576a5d6eff0..60b57572fc64 100644 --- a/fs/namespace.c +++ b/fs/namespace.c @@ -3806,7 +3806,7 @@ static int do_add_mount(struct mount *newmnt, const struct pinned_mountpoint *mp if (!(mnt_flags & MNT_SHRINKABLE)) return -EINVAL; /* ... and for those we'd better have mountpoint still alive */ - if (!parent->mnt_ns) + if (!is_mounted(&parent->mnt)) return -EINVAL; } From 33aadc7c72055fca93cb5a3691d836ebf8b31c9b Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Fri, 2 Oct 2026 15:52:37 +0200 Subject: [PATCH 153/367] namespace: handle mount locking for automounts correctly If mounts are propagated across user namespaces, attach_recursive_mnt() locks every copy of the source mount to protect overmounts from vanishing and revealing the underlying files or directories. The user namespace is taken from the caller's mount namespaces since this is where the mounts end up. Except, that's not always true. Automounts may legitimately get popped in by tasks located in a different mount and user namespace during path lookup. Then check doesn't make sense at that point. The copy in the namespace of the parent - which may be the host's - is now locked and the host cannot change the flags of its own mounts anymore. Here's the reproducer: - host has debugfs mounted nosuid,nodev,noexec and shared - tracefs automount below it is not yet active - hand a child process a directory descriptor on that mount - child process enters auser and mount namespace - child process' namespace now holds a locked copy of the host's debugfs mount that receives propagation from it - child process stats "tracing/." through the descriptor - lookup runs on the host's mount so the automount lands below the host's mount - propagation puts a copy below the child's copy - both try to clear the flags on the mount they got, with a bind remount: host, on its own automount: MS_REMOUNT|MS_BIND = EPERM child, on the copy in its namespace: MS_REMOUNT|MS_BIND = 0 So the lock landed on the host's mount instead of the child's copy. Congrats. So we need to compare with the owner of the namespace the mount actually gets mounted on. For all regular cases that is the caller's mount namespace and so nothing changes. Detached trees in anonymous mount namespaces by be handed over via SCM_RIGHTS or inherited in other ways on purpose so the attaching task's mount namespace is authoritative, not the creator of the detached tree. Fixes: 132c94e31b8b ("vfs: Carefully propogate mounts across user namespaces") Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20261002-work-mount-fixes-4-v1-6-dd44b89d44ce@kernel.org Signed-off-by: Christian Brauner (Amutable) --- fs/namespace.c | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/fs/namespace.c b/fs/namespace.c index 60b57572fc64..27bf8665ed58 100644 --- a/fs/namespace.c +++ b/fs/namespace.c @@ -2604,11 +2604,11 @@ enum mnt_tree_flags_t { static int attach_recursive_mnt(struct mount *source_mnt, const struct pinned_mountpoint *dest) { - struct user_namespace *user_ns = current->nsproxy->mnt_ns->user_ns; struct mount *dest_mnt = dest->parent; struct mountpoint *dest_mp = dest->mp; HLIST_HEAD(tree_list); struct mnt_namespace *ns = dest_mnt->mnt_ns; + struct user_namespace *user_ns = ns->user_ns; struct pinned_mountpoint root = {}; struct mountpoint *shorter = NULL; struct mount *child, *p; @@ -2617,6 +2617,22 @@ static int attach_recursive_mnt(struct mount *source_mnt, int err = 0; bool moving = mnt_has_parent(source_mnt); + /* + * A caller in an unprivileged mount namespaces may trigger an + * automount and propagate locked mounts into privileged mount + * namespaces. Take ownership from the target mount namespace. + * It's equivalent for everything but the automount case. + * + * Detached trees in anonymous mount namespaces by be handed + * over via SCM_RIGHTS or inherited in other ways on purpose + * the attaching task's mount namespace is authoritative, not + * the creator of the detached tree. + */ + if (is_anon_ns(ns)) + user_ns = current->nsproxy->mnt_ns->user_ns; + else + user_ns = ns->user_ns; + /* * Preallocate a mountpoint in case the new mounts need to be * mounted beneath mounts on the same mountpoint. From caf16f9b4aa66e5092f95e98cda54f2bb7482ed2 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Fri, 2 Oct 2026 15:52:38 +0200 Subject: [PATCH 154/367] nullfs: don't update the access time There's no point in updating access times of the nullfs instance. Raise S_IMMUTABLE. Fixes: 9d4e752a24f7 ("namespace: allow creating empty mount namespaces") Cc: stable@vger.kernel.org # v7.1+ Link: https://patch.msgid.link/20261002-work-mount-fixes-4-v1-7-dd44b89d44ce@kernel.org Signed-off-by: Christian Brauner (Amutable) --- fs/nullfs.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fs/nullfs.c b/fs/nullfs.c index e06352c7b2cc..40aa228bd81a 100644 --- a/fs/nullfs.c +++ b/fs/nullfs.c @@ -32,8 +32,8 @@ static int nullfs_fs_fill_super(struct super_block *s, struct fs_context *fc) make_empty_dir_inode(inode); simple_inode_init_ts(inode); inode->i_ino = 1; - /* ... and immutable. */ - inode->i_flags |= S_IMMUTABLE; + /* ... and immutable, reading it leaves no trace either. */ + inode->i_flags |= S_IMMUTABLE | S_NOATIME; s->s_root = d_make_root(inode); if (!s->s_root) From f78ddcc872248c7aa1d7e904928441a0be4c6d5d Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Fri, 2 Oct 2026 15:52:39 +0200 Subject: [PATCH 155/367] namespace: never expire a locked mount Locked mounts are special. They protect the underlying files and directories from being revealed. do_umount() refuses to unmount locked mounts but shrink_submounts() doesn't. A shrinkable mount can become locked once the owner of a user namespace puts it beneath a locked mount with MOVE_MOUNT_BENEATH. The locked property now moves to the mount at the bottom making it possible to unmount the top mount. So umount() of an unlocked ancestor now expires the bottom mount first and the covered directory is revealed: move_mount(c -> x/hidden, MOVE_MOUNT_BENEATH) = 0 the cover after the lock moved: umount2(x/hidden) = 0 the holder of the lock: umount2(x/hidden) = EINVAL the root of the copy, busy: umount2(x) = EBUSY reads x/hidden/secret: "covered-by-root" So leave a locked mount alone as it dies together with its parent. A plain umount() of an unlocked mount with a locked one below it is EBUSY from now on. It's the same for any other locked child. A lazy umount still takes the whole tree. mark_mounts_for_expiry() never sees a locked mount. lock_mnt_tree() leaves a mount on an expiry list alone and nothing else puts a locked one on such a list. Add an assert for this. Fixes: 5ff9d8a65ce8 ("vfs: Lock in place mounts from more privileged users") Fixes: c62a4766937e ("move_mount: transfer MNT_LOCKED") Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20261002-work-mount-fixes-4-v1-8-dd44b89d44ce@kernel.org Signed-off-by: Christian Brauner (Amutable) --- fs/namespace.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/fs/namespace.c b/fs/namespace.c index 27bf8665ed58..e74e63466c24 100644 --- a/fs/namespace.c +++ b/fs/namespace.c @@ -4032,6 +4032,8 @@ void mark_mounts_for_expiry(struct list_head *mounts) list_for_each_entry_safe(mnt, next, mounts, mnt_expire) { if (!is_mounted(&mnt->mnt)) continue; + /* lock_mnt_tree() leaves expirable mounts alone */ + VFS_WARN_ON_ONCE(IS_MNT_LOCKED(mnt)); if (!xchg(&mnt->mnt_expiry_mark, 1) || propagate_mount_busy(mnt, 1)) continue; @@ -4058,7 +4060,8 @@ EXPORT_SYMBOL_GPL(mark_mounts_for_expiry); */ static bool shrink_submount(struct mount *mnt) { - if (propagate_mount_busy(mnt, 1)) + /* not the kernel's to remove either */ + if (IS_MNT_LOCKED(mnt) || propagate_mount_busy(mnt, 1)) return false; touch_mnt_namespace(mnt->mnt_ns); umount_tree(mnt, UMOUNT_PROPAGATE|UMOUNT_SYNC); From d74d66aa708ccf2bc0b1e265a83fed0f2308d5c4 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Fri, 2 Oct 2026 15:52:40 +0200 Subject: [PATCH 156/367] namespace: keep the lock on a mount that a propagated copy is moved beneath attach_recursive_mnt() transfers MNT_LOCKED from the top mount to the mount that is moved beneath it with MOVE_MOUNT_BENEATH. This allows the owner of a user namespace to replace its locked /proc or its root. The mount beneath takes on the job of covering the underlying mountpoint allowing the top mount to be unmounted. Consider two mount namespaces: (H) The host H has a shared mount P with a secret in P/d (Z) Z is a user namespace made from M. Its copy of P receives propagation from the host's P and its copy of M's cover on P/d is locked Z's owner must not get to see P/d. Now (H) mounts X on P/d. This propagates into (Z). The copy of X lands beneath (Z)'s locked cover. The locked property is transfered from (Z)'s cover to the copy of X propagated beneath it. The cover is now unlocked. Now (H) unmounts X again. The copy of X in (Z) gets unmounted and the covering mount is left unlocked on top of P/d. (Z) can now unmount it: Z: umount2(P/d) = EINVAL /* the cover is locked */ H: mount X on P/d, umount X /* both propagate into Z /* Z: umount2(P/d) = 0 /* the cover is now unlocked */ Z: read P/d/secret = "covered-by-root" /* secret revealed */ So only transfer the locked property to the mount beneath for mounts the caller has placed. A propagated copy that lands beneath a locked mount is locked as well so that the mount at the bottom of the stack carries a lock the way every check expects. The mount on top of it remains locked to ensure that it keeps covering even if the propagated mount is unmounted again. Fixes: c62a4766937e ("move_mount: transfer MNT_LOCKED") Cc: stable@vger.kernel.org # v7.1+ Link: https://patch.msgid.link/20261002-work-mount-fixes-4-v1-9-dd44b89d44ce@kernel.org Signed-off-by: Christian Brauner (Amutable) --- fs/namespace.c | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/fs/namespace.c b/fs/namespace.c index e74e63466c24..bb0183ec2aaf 100644 --- a/fs/namespace.c +++ b/fs/namespace.c @@ -2712,15 +2712,17 @@ static int attach_recursive_mnt(struct mount *source_mnt, /* * If @q was locked it was meant to hide * whatever was under it. Let @child take over - * that job and lock it, then we can unlock @q. - * That'll allow another namespace to shed @q - * and reveal @child. Clearly, that mounter - * consented to this by not severing the mount - * relationship. Otherwise, what's the point. + * that job and lock it. If @child is the mount + * the caller placed we can then unlock @q: + * nothing another namespace does removes it + * again. A propagated copy goes away when the + * mounter of the original unmounts it, so @q + * keeps its lock. */ if (IS_MNT_LOCKED(q)) { child->mnt.mnt_flags |= MNT_LOCKED; - q->mnt.mnt_flags &= ~MNT_LOCKED; + if (child == source_mnt) + q->mnt.mnt_flags &= ~MNT_LOCKED; } mnt_change_mountpoint(r, mp, q); } From 921c14fd00edb1e6c1e4248a669d65e391b89b7e Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Fri, 2 Oct 2026 15:52:41 +0200 Subject: [PATCH 157/367] selftests/filesystems: check that a lock lands on the right mount and stays Check MNT_LOCKED: - A child in a user namespace of its own triggers the tracefs automount through a directory descriptor on the host's debugfs mount. The copy in the child's namespace has to be locked and the host's mount unlocked. - A child moves a bind of that automount beneath a locked covering mount, unmounts the covering mount and asks for the umount of an unlocked ancestor. This must not expire the mount which holds the lock now. - The host mounts and unmounts on a directory that a locked mount covers in a user namespace further down the propagation chain. That cover has to be locked afterwards as it was before. Link: https://patch.msgid.link/20261002-work-mount-fixes-4-v1-10-dd44b89d44ce@kernel.org Signed-off-by: Christian Brauner (Amutable) --- .../filesystems/umount_propagation/Makefile | 2 +- .../umount_propagation/locked_mount_test.c | 432 ++++++++++++++++++ 2 files changed, 433 insertions(+), 1 deletion(-) create mode 100644 tools/testing/selftests/filesystems/umount_propagation/locked_mount_test.c diff --git a/tools/testing/selftests/filesystems/umount_propagation/Makefile b/tools/testing/selftests/filesystems/umount_propagation/Makefile index eb85612abf8d..31f783a92b23 100644 --- a/tools/testing/selftests/filesystems/umount_propagation/Makefile +++ b/tools/testing/selftests/filesystems/umount_propagation/Makefile @@ -1,5 +1,5 @@ # SPDX-License-Identifier: GPL-2.0 -TEST_GEN_PROGS := umount_propagation_test shrink_submounts_test +TEST_GEN_PROGS := umount_propagation_test shrink_submounts_test locked_mount_test CFLAGS += -Wall -O2 -g $(KHDR_INCLUDES) diff --git a/tools/testing/selftests/filesystems/umount_propagation/locked_mount_test.c b/tools/testing/selftests/filesystems/umount_propagation/locked_mount_test.c new file mode 100644 index 000000000000..0799b2ba545d --- /dev/null +++ b/tools/testing/selftests/filesystems/umount_propagation/locked_mount_test.c @@ -0,0 +1,432 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * MNT_LOCKED keeps the owner of a user namespace from revealing what a + * mount covers. The lock has to be set on the copy in that namespace and + * only there, it has to survive the expiry of a mount placed beneath the + * locked one, and it has to survive the propagated umount of such a copy. + */ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../../kselftest_harness.h" + +#ifndef MOVE_MOUNT_F_EMPTY_PATH +#define MOVE_MOUNT_F_EMPTY_PATH 0x00000004 +#endif +#ifndef MOVE_MOUNT_BENEATH +#define MOVE_MOUNT_BENEATH 0x00000200 +#endif + +#define DIR_LEN 64 +#define PATH_LEN 192 +#define FLAGS (MS_NOSUID | MS_NODEV | MS_NOEXEC) + +/* exit codes of the children, each test says what they mean */ +enum { + CHILD_OK, + CHILD_SETUP, + CHILD_STEP1, + CHILD_STEP2, + CHILD_STEP3, + CHILD_STEP4, +}; + +static int write_file(const char *path, const char *s) +{ + ssize_t n = -1; + int fd; + + fd = open(path, O_WRONLY | O_CLOEXEC); + if (fd >= 0) { + n = write(fd, s, strlen(s)); + close(fd); + } + return n == (ssize_t)strlen(s) ? 0 : -1; +} + +static int create_file(const char *path, const char *s) +{ + ssize_t n = -1; + int fd; + + fd = open(path, O_WRONLY | O_CREAT | O_EXCL | O_CLOEXEC, 0644); + if (fd >= 0) { + n = write(fd, s, strlen(s)); + close(fd); + } + return n == (ssize_t)strlen(s) ? 0 : -1; +} + +/* Root in a new user namespace with a copy of the mount namespace. */ +static int enter_userns(void) +{ + uid_t uid = getuid(); + gid_t gid = getgid(); + char map[32]; + + prctl(PR_SET_DUMPABLE, 1); + if (unshare(CLONE_NEWUSER | CLONE_NEWNS)) + return -1; + if (write_file("/proc/self/setgroups", "deny") && errno != ENOENT) + return -1; + snprintf(map, sizeof(map), "0 %d 1", uid); + if (write_file("/proc/self/uid_map", map)) + return -1; + snprintf(map, sizeof(map), "0 %d 1", gid); + if (write_file("/proc/self/gid_map", map)) + return -1; + return setgid(0) || setuid(0) ? -1 : 0; +} + +static int wait_child(pid_t pid) +{ + int status; + + if (waitpid(pid, &status, 0) != pid || !WIFEXITED(status)) + return -1; + return WEXITSTATUS(status); +} + +static int wait_byte(int fd) +{ + char c; + + return read(fd, &c, 1) == 1 ? 0 : -1; +} + +static int send_byte(int fd) +{ + return write(fd, "x", 1) == 1 ? 0 : -1; +} + +/* A read of @path fails: the file is covered. */ +static bool covered(const char *path) +{ + int fd = open(path, O_RDONLY | O_CLOEXEC); + + if (fd >= 0) + close(fd); + return fd < 0; +} + +FIXTURE(locked_mount) { + char base[DIR_LEN]; + bool tracing; /* debugfs with the tracefs automount is there */ +}; + +FIXTURE_SETUP(locked_mount) +{ + char p[PATH_LEN]; + struct stat st; + + if (geteuid()) + SKIP(return, "test requires root"); + + snprintf(self->base, sizeof(self->base), "/tmp/locked_mount.XXXXXX"); + ASSERT_NE(mkdtemp(self->base), NULL); + ASSERT_EQ(unshare(CLONE_NEWNS), 0); + ASSERT_EQ(mount("", "/", NULL, MS_REC | MS_PRIVATE, NULL), 0); + ASSERT_EQ(mount("tmpfs", self->base, "tmpfs", 0, "mode=0755"), 0); + + /* an automount that a user can name: the tracefs below debugfs */ + snprintf(p, sizeof(p), "%s/dbg", self->base); + ASSERT_EQ(mkdir(p, 0755), 0); + self->tracing = !mount("debugfs", p, "debugfs", FLAGS, NULL); + if (self->tracing) { + /* the cases trigger it themselves */ + snprintf(p, sizeof(p), "%s/dbg/tracing", self->base); + self->tracing = !fstatat(AT_FDCWD, p, &st, AT_NO_AUTOMOUNT) && + S_ISDIR(st.st_mode); + } +} + +FIXTURE_TEARDOWN(locked_mount) +{ + umount2(self->base, MNT_DETACH); + rmdir(self->base); +} + +/* + * The child keeps a directory descriptor on the host's debugfs mount, moves + * to a user namespace of its own and triggers the automount through the + * descriptor. The mount goes below the host's mount and propagates into the + * child's copy. The child's copy has to be locked, the host's not. + */ +static int automount_child(const char *base, int dfd, int to_host, int from_host) +{ + char p[PATH_LEN]; + struct stat st; + + if (enter_userns()) + return CHILD_SETUP; + if (fstatat(dfd, "tracing/.", &st, 0)) + return CHILD_STEP1; + if (send_byte(to_host) || wait_byte(from_host)) + return CHILD_SETUP; + /* the flags of the copy are locked: EPERM */ + snprintf(p, sizeof(p), "%s/dbg/tracing", base); + if (!mount(NULL, p, NULL, MS_REMOUNT | MS_BIND, NULL) || errno != EPERM) + return CHILD_STEP2; + return CHILD_OK; +} + +TEST_F(locked_mount, automount_locked_in_the_triggering_namespace) +{ + int to_host[2], from_host[2], dfd, ret; + char p[PATH_LEN]; + pid_t pid; + + if (!self->tracing) + SKIP(return, "test requires debugfs with the tracefs automount"); + + snprintf(p, sizeof(p), "%s/dbg", self->base); + ASSERT_EQ(mount(NULL, p, NULL, MS_SHARED, NULL), 0); + dfd = open(p, O_RDONLY | O_DIRECTORY | O_CLOEXEC); + ASSERT_GE(dfd, 0); + ASSERT_EQ(pipe(to_host), 0); + ASSERT_EQ(pipe(from_host), 0); + + pid = fork(); + ASSERT_GE(pid, 0); + if (pid == 0) + _exit(automount_child(self->base, dfd, to_host[1], from_host[0])); + close(dfd); + ASSERT_EQ(wait_byte(to_host[0]), 0); + + /* the host's own mount isn't locked: the flags can go */ + snprintf(p, sizeof(p), "%s/dbg/tracing", self->base); + EXPECT_EQ(mount(NULL, p, NULL, MS_REMOUNT | MS_BIND, NULL), 0); + + ASSERT_EQ(send_byte(from_host[1]), 0); + ret = wait_child(pid); + TH_LOG("child exit code %d", ret); + EXPECT_EQ(ret, CHILD_OK); +} + +/* + * A copy of a tree with a locked cover. The child puts a shrinkable mount + * beneath the cover, which hands the lock down, unmounts the cover and then + * asks for the umount of an unlocked ancestor. That expires the shrinkable + * mount on a kernel that doesn't look at the lock, and the covered + * directory is bare. + */ +static int expiry_child(const char *base) +{ + char srv[PATH_LEN], shr[PATH_LEN], x[PATH_LEN], c[PATH_LEN]; + char hidden[PATH_LEN], secret[PATH_LEN]; + + snprintf(srv, sizeof(srv), "%s/srv", base); + snprintf(shr, sizeof(shr), "%s/shr", base); + snprintf(x, sizeof(x), "%s/x", base); + snprintf(c, sizeof(c), "%s/c", base); + snprintf(hidden, sizeof(hidden), "%s/x/hidden", base); + snprintf(secret, sizeof(secret), "%s/x/hidden/secret", base); + + if (enter_userns()) + return CHILD_SETUP; + if (mount(srv, x, NULL, MS_BIND | MS_REC, NULL) || + mount(shr, c, NULL, MS_BIND, NULL)) + return CHILD_SETUP; + /* the cover is locked */ + if (!umount2(hidden, 0) || errno != EINVAL) + return CHILD_STEP1; + if (syscall(__NR_move_mount, AT_FDCWD, c, AT_FDCWD, hidden, MOVE_MOUNT_BENEATH)) + return CHILD_STEP2; + /* the lock moved down, the cover may go */ + if (umount2(hidden, 0)) + return CHILD_STEP3; + /* the holder of the lock may not, in any way */ + if (!umount2(hidden, 0) || errno != EINVAL) + return CHILD_STEP4; + if (chdir(x)) + return CHILD_SETUP; + umount2(x, 0); + return covered(secret) ? CHILD_OK : CHILD_STEP4; +} + +TEST_F(locked_mount, expiry_leaves_a_locked_mount_alone) +{ + char p[PATH_LEN], q[PATH_LEN]; + pid_t pid; + int ret; + + if (!self->tracing) + SKIP(return, "test requires debugfs with the tracefs automount"); + + snprintf(p, sizeof(p), "%s/dbg/tracing", self->base); + snprintf(q, sizeof(q), "%s/shr", self->base); + ASSERT_EQ(mkdir(q, 0755), 0); + /* a bind of an automount is shrinkable as well */ + ASSERT_EQ(mount(p, q, NULL, MS_BIND, NULL), 0); + snprintf(p, sizeof(p), "%s/srv", self->base); + ASSERT_EQ(mkdir(p, 0755), 0); + ASSERT_EQ(mount("tmpfs", p, "tmpfs", 0, "mode=0755"), 0); + snprintf(p, sizeof(p), "%s/srv/hidden", self->base); + ASSERT_EQ(mkdir(p, 0755), 0); + snprintf(p, sizeof(p), "%s/srv/hidden/secret", self->base); + ASSERT_EQ(create_file(p, "covered-by-root\n"), 0); + snprintf(p, sizeof(p), "%s/srv/hidden", self->base); + ASSERT_EQ(mount("tmpfs", p, "tmpfs", 0, "mode=0755"), 0); + snprintf(p, sizeof(p), "%s/x", self->base); + ASSERT_EQ(mkdir(p, 0755), 0); + snprintf(p, sizeof(p), "%s/c", self->base); + ASSERT_EQ(mkdir(p, 0755), 0); + + pid = fork(); + ASSERT_GE(pid, 0); + if (pid == 0) + _exit(expiry_child(self->base)); + ret = wait_child(pid); + TH_LOG("child exit code %d", ret); + EXPECT_EQ(ret, CHILD_OK); +} + +/* + * The copy of the mount tree that a user namespace gets at its creation has + * the automounts in it locked unless they are on an expiry list. A bind of + * such a tree inside the namespace copies the lock to the automount but not + * to the root of the bind, so the child may ask for the umount of that root. + * That must not expire the locked automount below it: the root is busy, the + * umount fails and the automount has to be there afterwards. + */ +static int copied_tree_child(const char *base) +{ + char dbg[PATH_LEN], x[PATH_LEN], tracing[PATH_LEN]; + struct stat root, st; + + snprintf(dbg, sizeof(dbg), "%s/dbg", base); + snprintf(x, sizeof(x), "%s/x", base); + snprintf(tracing, sizeof(tracing), "%s/x/tracing", base); + + if (enter_userns()) + return CHILD_SETUP; + if (mount(dbg, x, NULL, MS_BIND | MS_REC, NULL)) + return CHILD_SETUP; + /* the copy of the automount carries the lock */ + if (!umount2(tracing, 0) || errno != EINVAL) + return CHILD_STEP1; + /* the root of the bind doesn't; keep it busy */ + if (chdir(x)) + return CHILD_SETUP; + if (!umount2(x, 0) || errno != EBUSY) + return CHILD_STEP2; + /* the automount below it must not have gone */ + if (stat(x, &root) || fstatat(AT_FDCWD, tracing, &st, AT_NO_AUTOMOUNT)) + return CHILD_SETUP; + return st.st_dev != root.st_dev ? CHILD_OK : CHILD_STEP3; +} + +TEST_F(locked_mount, umount_of_a_bind_leaves_a_locked_automount_alone) +{ + char p[PATH_LEN]; + struct stat st; + pid_t pid; + int ret; + + if (!self->tracing) + SKIP(return, "test requires debugfs with the tracefs automount"); + + /* the copy the child gets has to contain the automount */ + snprintf(p, sizeof(p), "%s/dbg/tracing/.", self->base); + ASSERT_EQ(stat(p, &st), 0); + snprintf(p, sizeof(p), "%s/x", self->base); + ASSERT_EQ(mkdir(p, 0755), 0); + + pid = fork(); + ASSERT_GE(pid, 0); + if (pid == 0) + _exit(copied_tree_child(self->base)); + ret = wait_child(pid); + TH_LOG("child exit code %d", ret); + EXPECT_EQ(ret, CHILD_OK); +} + +/* + * Z is a user namespace with a copy of a tree in which P/d is covered by a + * locked mount. The host mounts X on P/d, which propagates beneath Z's + * cover, and unmounts it again. Z's cover has to be locked afterwards as + * it was before. + */ +static int propagation_child(const char *base, int to_host, int from_host) +{ + char d[PATH_LEN], secret[PATH_LEN]; + + snprintf(d, sizeof(d), "%s/P/d", base); + snprintf(secret, sizeof(secret), "%s/P/d/secret", base); + + if (enter_userns()) + return CHILD_SETUP; + /* the cover is locked */ + if (!umount2(d, 0) || errno != EINVAL) + return CHILD_STEP1; + if (send_byte(to_host) || wait_byte(from_host)) + return CHILD_SETUP; + /* X came and went beneath it: still locked */ + if (!umount2(d, 0) || errno != EINVAL) + return CHILD_STEP2; + return covered(secret) ? CHILD_OK : CHILD_STEP3; +} + +TEST_F(locked_mount, propagated_copy_keeps_the_cover_locked) +{ + int to_host[2], from_host[2], ret; + char p[PATH_LEN]; + pid_t pid; + + snprintf(p, sizeof(p), "%s/P", self->base); + ASSERT_EQ(mkdir(p, 0755), 0); + ASSERT_EQ(mount("tmpfs", p, "tmpfs", 0, "mode=0755"), 0); + ASSERT_EQ(mount(NULL, p, NULL, MS_SHARED, NULL), 0); + snprintf(p, sizeof(p), "%s/P/d", self->base); + ASSERT_EQ(mkdir(p, 0755), 0); + snprintf(p, sizeof(p), "%s/P/d/secret", self->base); + ASSERT_EQ(create_file(p, "covered-by-root\n"), 0); + ASSERT_EQ(pipe(to_host), 0); + ASSERT_EQ(pipe(from_host), 0); + + pid = fork(); + ASSERT_GE(pid, 0); + if (pid == 0) { + /* M: a manager's namespace that covers P/d for Z */ + pid_t z; + + if (unshare(CLONE_NEWNS)) + _exit(CHILD_SETUP); + snprintf(p, sizeof(p), "%s/P", self->base); + if (mount(NULL, p, NULL, MS_SLAVE, NULL)) + _exit(CHILD_SETUP); + snprintf(p, sizeof(p), "%s/P/d", self->base); + if (mount("tmpfs", p, "tmpfs", 0, "mode=0755")) + _exit(CHILD_SETUP); + z = fork(); + if (z < 0) + _exit(CHILD_SETUP); + if (z == 0) + _exit(propagation_child(self->base, to_host[1], from_host[0])); + _exit(wait_child(z)); + } + ASSERT_EQ(wait_byte(to_host[0]), 0); + + /* the host mounts on P/d and unmounts again; both propagate */ + snprintf(p, sizeof(p), "%s/P/d", self->base); + ASSERT_EQ(mount("tmpfs", p, "tmpfs", 0, "mode=0755"), 0); + ASSERT_EQ(umount2(p, 0), 0); + + ASSERT_EQ(send_byte(from_host[1]), 0); + ret = wait_child(pid); + TH_LOG("child exit code %d", ret); + EXPECT_EQ(ret, CHILD_OK); +} + +TEST_HARNESS_MAIN From 2244ca5662d7a5fd24d4aac0ab68458089efcabd Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Fri, 2 Oct 2026 15:52:42 +0200 Subject: [PATCH 158/367] selftests/filesystems: check the atime of the empty mount namespace root No access time updates for immutable nullfs. Link: https://patch.msgid.link/20261002-work-mount-fixes-4-v1-11-dd44b89d44ce@kernel.org Signed-off-by: Christian Brauner (Amutable) --- .../filesystems/empty_mntns/.gitignore | 1 + .../filesystems/empty_mntns/Makefile | 2 +- .../empty_mntns/nullfs_atime_test.c | 129 ++++++++++++++++++ 3 files changed, 131 insertions(+), 1 deletion(-) create mode 100644 tools/testing/selftests/filesystems/empty_mntns/nullfs_atime_test.c diff --git a/tools/testing/selftests/filesystems/empty_mntns/.gitignore b/tools/testing/selftests/filesystems/empty_mntns/.gitignore index 32125b3eaa80..8ea166068d6b 100644 --- a/tools/testing/selftests/filesystems/empty_mntns/.gitignore +++ b/tools/testing/selftests/filesystems/empty_mntns/.gitignore @@ -3,3 +3,4 @@ clone3_empty_mntns_test empty_mntns_test overmount_chroot_test internal_sb_reconfigure_test +nullfs_atime_test diff --git a/tools/testing/selftests/filesystems/empty_mntns/Makefile b/tools/testing/selftests/filesystems/empty_mntns/Makefile index b64818b962ca..4b2ea6acec1c 100644 --- a/tools/testing/selftests/filesystems/empty_mntns/Makefile +++ b/tools/testing/selftests/filesystems/empty_mntns/Makefile @@ -4,7 +4,7 @@ CFLAGS += -Wall -O2 -g $(KHDR_INCLUDES) $(TOOLS_INCLUDES) LDLIBS += -lcap TEST_GEN_PROGS := empty_mntns_test overmount_chroot_test clone3_empty_mntns_test -TEST_GEN_PROGS += internal_sb_reconfigure_test +TEST_GEN_PROGS += internal_sb_reconfigure_test nullfs_atime_test include ../../lib.mk diff --git a/tools/testing/selftests/filesystems/empty_mntns/nullfs_atime_test.c b/tools/testing/selftests/filesystems/empty_mntns/nullfs_atime_test.c new file mode 100644 index 000000000000..51e34f3c4f54 --- /dev/null +++ b/tools/testing/selftests/filesystems/empty_mntns/nullfs_atime_test.c @@ -0,0 +1,129 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * The root of every empty mount namespace is the same nullfs inode. A read + * of it by one user must not change the access time another user sees. + */ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../../kselftest_harness.h" + +#ifndef UNSHARE_EMPTY_MNTNS +#define UNSHARE_EMPTY_MNTNS 0x00100000 +#endif + +enum { + CHILD_OK, + CHILD_UNSUPPORTED, + CHILD_SETUP, + CHILD_CHANGED, +}; + +static int wait_byte(int fd) +{ + char c; + + return read(fd, &c, 1) == 1 ? 0 : -1; +} + +static int send_byte(int fd) +{ + return write(fd, "x", 1) == 1 ? 0 : -1; +} + +static int empty_mntns(void) +{ + if (!unshare(UNSHARE_EMPTY_MNTNS)) + return 0; + return errno == EINVAL ? CHILD_UNSUPPORTED : CHILD_SETUP; +} + +/* the watcher: stats its root before and after the reader read its own */ +static int watcher(int to_reader, int from_reader) +{ + struct stat before, after; + int ret; + + ret = empty_mntns(); + if (ret) + return ret; + if (stat("/", &before)) + return CHILD_SETUP; + if (send_byte(to_reader) || wait_byte(from_reader)) + return CHILD_SETUP; + if (stat("/", &after)) + return CHILD_SETUP; + if (before.st_atim.tv_sec != after.st_atim.tv_sec || + before.st_atim.tv_nsec != after.st_atim.tv_nsec) + return CHILD_CHANGED; + return CHILD_OK; +} + +/* the reader: lists its own root, which is the same inode */ +static int reader(int to_watcher, int from_watcher) +{ + struct dirent *de; + DIR *d; + int ret; + + ret = empty_mntns(); + if (ret) + return ret; + if (wait_byte(from_watcher)) + return CHILD_SETUP; + d = opendir("/"); + if (!d) + return CHILD_SETUP; + while ((de = readdir(d))) + ; + closedir(d); + return send_byte(to_watcher) ? CHILD_SETUP : CHILD_OK; +} + +static int wait_child(pid_t pid) +{ + int status; + + if (waitpid(pid, &status, 0) != pid || !WIFEXITED(status)) + return -1; + return WEXITSTATUS(status); +} + +TEST(empty_mntns_root_atime) +{ + int to_reader[2], to_watcher[2], w, r; + pid_t watcher_pid, reader_pid; + + if (geteuid()) + SKIP(return, "test requires root"); + ASSERT_EQ(pipe(to_reader), 0); + ASSERT_EQ(pipe(to_watcher), 0); + + watcher_pid = fork(); + ASSERT_GE(watcher_pid, 0); + if (watcher_pid == 0) + _exit(watcher(to_reader[1], to_watcher[0])); + reader_pid = fork(); + ASSERT_GE(reader_pid, 0); + if (reader_pid == 0) + _exit(reader(to_watcher[1], to_reader[0])); + + r = wait_child(reader_pid); + w = wait_child(watcher_pid); + if (r == CHILD_UNSUPPORTED || w == CHILD_UNSUPPORTED) + SKIP(return, "UNSHARE_EMPTY_MNTNS not supported"); + EXPECT_EQ(r, CHILD_OK); + EXPECT_EQ(w, CHILD_OK) + TH_LOG("the access time of the root changed while this namespace did nothing"); +} + +TEST_HARNESS_MAIN From 39796254f4458a532dcef8197ec84b7c80ad2b18 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Fri, 2 Oct 2026 15:52:43 +0200 Subject: [PATCH 159/367] selftests/filesystems: check that an automount below an overlay layer is refused Make sure that we don't automount on top of internal things. Link: https://patch.msgid.link/20261002-work-mount-fixes-4-v1-12-dd44b89d44ce@kernel.org Signed-off-by: Christian Brauner (Amutable) --- .../filesystems/overlayfs/.gitignore | 1 + .../selftests/filesystems/overlayfs/Makefile | 1 + .../overlayfs/automount_in_layer.c | 174 ++++++++++++++++++ 3 files changed, 176 insertions(+) create mode 100644 tools/testing/selftests/filesystems/overlayfs/automount_in_layer.c diff --git a/tools/testing/selftests/filesystems/overlayfs/.gitignore b/tools/testing/selftests/filesystems/overlayfs/.gitignore index 077f7a128168..b343cc430051 100644 --- a/tools/testing/selftests/filesystems/overlayfs/.gitignore +++ b/tools/testing/selftests/filesystems/overlayfs/.gitignore @@ -2,3 +2,4 @@ dev_in_maps set_layers_via_fds idmapped_mounts +automount_in_layer diff --git a/tools/testing/selftests/filesystems/overlayfs/Makefile b/tools/testing/selftests/filesystems/overlayfs/Makefile index b3185f684add..382a59bda5e7 100644 --- a/tools/testing/selftests/filesystems/overlayfs/Makefile +++ b/tools/testing/selftests/filesystems/overlayfs/Makefile @@ -9,6 +9,7 @@ LOCAL_HDRS += ../wrappers.h log.h TEST_GEN_PROGS := dev_in_maps TEST_GEN_PROGS += set_layers_via_fds TEST_GEN_PROGS += idmapped_mounts +TEST_GEN_PROGS += automount_in_layer include ../../lib.mk diff --git a/tools/testing/selftests/filesystems/overlayfs/automount_in_layer.c b/tools/testing/selftests/filesystems/overlayfs/automount_in_layer.c new file mode 100644 index 000000000000..0476c4582bdf --- /dev/null +++ b/tools/testing/selftests/filesystems/overlayfs/automount_in_layer.c @@ -0,0 +1,174 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * A layer of an overlay is a private clone of the mount it was given and + * belongs to no mount namespace. fanotify hands out descriptors on it. An + * automount triggered through one has no namespace to go into: the open has + * to fail, not oops with namespace_sem held. + */ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../../kselftest_harness.h" + +#define DIR_LEN 64 +#define PATH_LEN 192 + +static bool have_fs(const char *name) +{ + char line[128]; + bool found = false; + FILE *f; + + f = fopen("/proc/filesystems", "re"); + if (!f) + return false; + while (fgets(line, sizeof(line), f)) { + char *nl = strchr(line, '\n'); + char *tab = strchr(line, '\t'); + + if (nl) + *nl = 0; + if (tab && !strcmp(tab + 1, name)) + found = true; + } + fclose(f); + return found; +} + +static int mnt_id_of(int fd) +{ + char path[64], buf[4096], *p; + ssize_t n; + int info; + + snprintf(path, sizeof(path), "/proc/self/fdinfo/%d", fd); + info = open(path, O_RDONLY | O_CLOEXEC); + if (info < 0) + return -1; + n = read(info, buf, sizeof(buf) - 1); + close(info); + if (n <= 0) + return -1; + buf[n] = 0; + p = strstr(buf, "mnt_id:"); + return p ? atoi(p + strlen("mnt_id:")) : -1; +} + +static bool on_debugfs(int fd) +{ + struct statfs sf; + + return !fstatfs(fd, &sf) && sf.f_type == DEBUGFS_MAGIC; +} + +FIXTURE(layer) { + char base[DIR_LEN]; + int fan; + int evfd; /* on the layer clone of the lower debugfs */ +}; + +FIXTURE_SETUP(layer) +{ + char lower[PATH_LEN], other[PATH_LEN], ovl[PATH_LEN], opts[2 * PATH_LEN + 16]; + struct fanotify_event_metadata *ev; + struct pollfd pfd; + char buf[4096]; + int lfd, lower_id; + ssize_t n; + DIR *d; + + self->fan = -1; + self->evfd = -1; + if (geteuid()) + SKIP(return, "test requires root"); + if (!have_fs("debugfs") || !have_fs("tracefs")) + SKIP(return, "test requires debugfs with the tracefs automount"); + if (!have_fs("overlay")) + SKIP(return, "test requires overlayfs"); + + snprintf(self->base, sizeof(self->base), "/tmp/layer.XXXXXX"); + ASSERT_NE(mkdtemp(self->base), NULL); + ASSERT_EQ(unshare(CLONE_NEWNS), 0); + ASSERT_EQ(mount("", "/", NULL, MS_REC | MS_PRIVATE, NULL), 0); + ASSERT_EQ(mount("tmpfs", self->base, "tmpfs", 0, "mode=0755"), 0); + snprintf(lower, sizeof(lower), "%s/lower", self->base); + snprintf(other, sizeof(other), "%s/other", self->base); + snprintf(ovl, sizeof(ovl), "%s/ovl", self->base); + ASSERT_EQ(mkdir(lower, 0755), 0); + ASSERT_EQ(mkdir(other, 0755), 0); + ASSERT_EQ(mkdir(ovl, 0755), 0); + ASSERT_EQ(mount("debugfs", lower, "debugfs", 0, NULL), 0); + snprintf(opts, sizeof(opts), "lowerdir=%s:%s", lower, other); + ASSERT_EQ(mount("overlay", ovl, "overlay", MS_RDONLY, opts), 0); + + self->fan = fanotify_init(FAN_CLASS_NOTIF | FAN_NONBLOCK | FAN_CLOEXEC, + O_RDONLY | O_CLOEXEC); + ASSERT_GE(self->fan, 0); + ASSERT_EQ(fanotify_mark(self->fan, FAN_MARK_ADD | FAN_MARK_FILESYSTEM, + FAN_OPEN | FAN_ONDIR, AT_FDCWD, lower), 0); + lfd = open(lower, O_RDONLY | O_DIRECTORY | O_CLOEXEC); + ASSERT_GE(lfd, 0); + lower_id = mnt_id_of(lfd); + close(lfd); + + /* the overlay opens its lower directory through the layer clone */ + d = opendir(ovl); + ASSERT_NE(d, NULL); + closedir(d); + pfd.fd = self->fan; + pfd.events = POLLIN; + ASSERT_EQ(poll(&pfd, 1, 5000), 1); + n = read(self->fan, buf, sizeof(buf)); + ASSERT_GT(n, 0); + for (ev = (void *)buf; FAN_EVENT_OK(ev, n); ev = FAN_EVENT_NEXT(ev, n)) { + if (ev->fd < 0) + continue; + if (self->evfd < 0 && on_debugfs(ev->fd) && + mnt_id_of(ev->fd) != lower_id) + self->evfd = ev->fd; + else + close(ev->fd); + } + ASSERT_GE(self->evfd, 0) + TH_LOG("no event on the layer clone"); +} + +FIXTURE_TEARDOWN(layer) +{ + if (self->evfd >= 0) + close(self->evfd); + if (self->fan >= 0) + close(self->fan); + umount2(self->base, MNT_DETACH); + rmdir(self->base); +} + +TEST_F(layer, automount_below_the_clone_is_refused) +{ + struct stat st; + int fd; + + /* the automount point is there */ + ASSERT_EQ(fstatat(self->evfd, "tracing", &st, AT_NO_AUTOMOUNT), 0); + /* the clone is in no namespace, so the automount has nowhere to go */ + fd = openat(self->evfd, "tracing", O_RDONLY | O_DIRECTORY | O_CLOEXEC); + EXPECT_LT(fd, 0); + EXPECT_EQ(errno, EINVAL); + if (fd >= 0) + close(fd); +} + +TEST_HARNESS_MAIN From 67b69b8a6ea730b1ee8fe682bd22b9b7fd5d9820 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Fri, 2 Oct 2026 15:52:44 +0200 Subject: [PATCH 160/367] fhandle: decide the subtree check under mount_lock may_decode_fh() lets a caller who is privileged over the mount namespace of @root decode handles below @root->dentry as long as the mount is mounted and no locked child covers something below it. The three parts are read one after the other without a lock: is_mounted() and capable_wrt_mount() read ->mnt_ns and has_locked_children() walks ->mnt_mounts under a mount_lock of its own. Today the gaps are harmless. A lazy umount in between clears ->mnt_ns, but the locked children stay attached to their unmounted parent, so the walk still finds them and the decode is refused either way. The following patches detach every unmounted mount from its parent. Then an umount between is_mounted() and the walk makes the walk come back empty and the caller decodes into what a locked child covered. So take mount_lock once and answer all three questions under it. is_mounted() is stable there, umount_tree() clears ->mnt_ns on the write side, and a mounted mount still has its locked children on its list. ns_capable() under the spinlock is fine, generic_permission() calls it in RCU walk already. has_locked_children() loses its locking wrapper, its other callers hold namespace_sem or mount_lock anyway. Link: https://patch.msgid.link/20261002-work-mount-fixes-4-v1-13-dd44b89d44ce@kernel.org Signed-off-by: Christian Brauner (Amutable) --- fs/fhandle.c | 20 +++++++++++++++++--- fs/namespace.c | 14 ++++---------- 2 files changed, 21 insertions(+), 13 deletions(-) diff --git a/fs/fhandle.c b/fs/fhandle.c index f8829231e3d7..d22f2e065677 100644 --- a/fs/fhandle.c +++ b/fs/fhandle.c @@ -298,6 +298,22 @@ static bool capable_wrt_mount(struct mount *mount) return mnt_ns && ns_capable(mnt_ns->user_ns, CAP_SYS_ADMIN); } +/* + * Does the caller have an unobstructed way to everything below @root? Only + * if the mount is mounted, the caller is privileged over its mount namespace + * and no locked child covers something below @root->dentry. One answer from + * under mount_lock: an umount in between clears ->mnt_ns and takes the + * children off the mount, the locked ones too. + */ +static bool subtree_unobstructed(const struct path *root) +{ + struct mount *mnt = real_mount(root->mnt); + + guard(mount_locked_reader)(); + return is_mounted(root->mnt) && capable_wrt_mount(mnt) && + !has_locked_children(mnt, root->dentry); +} + static inline int may_decode_fh(struct handle_to_path_ctx *ctx, unsigned int o_flags) { @@ -332,9 +348,7 @@ static inline int may_decode_fh(struct handle_to_path_ctx *ctx, if (ns_capable(root->mnt->mnt_sb->s_user_ns, CAP_SYS_ADMIN)) ctx->flags = HANDLE_CHECK_PERMS; - else if (is_mounted(root->mnt) && - capable_wrt_mount(real_mount(root->mnt)) && - !has_locked_children(real_mount(root->mnt), root->dentry)) + else if (subtree_unobstructed(root)) ctx->flags = HANDLE_CHECK_PERMS | HANDLE_CHECK_SUBTREE; else return -EPERM; diff --git a/fs/namespace.c b/fs/namespace.c index bb0183ec2aaf..e116894c8667 100644 --- a/fs/namespace.c +++ b/fs/namespace.c @@ -2353,7 +2353,7 @@ void dissolve_on_fput(struct vfsmount *mnt) } /* locks: namespace_shared && pinned(mnt) || mount_locked_reader */ -static bool __has_locked_children(struct mount *mnt, struct dentry *dentry) +bool has_locked_children(struct mount *mnt, struct dentry *dentry) { struct mount *child; @@ -2367,12 +2367,6 @@ static bool __has_locked_children(struct mount *mnt, struct dentry *dentry) return false; } -bool has_locked_children(struct mount *mnt, struct dentry *dentry) -{ - guard(mount_locked_reader)(); - return __has_locked_children(mnt, dentry); -} - /* locks: namespace_shared && pinned(mnt) || mount_locked_reader */ static bool __has_children(struct mount *mnt, struct dentry *dentry) { @@ -2441,7 +2435,7 @@ struct vfsmount *clone_private_mount(const struct path *path) if (!ns_capable(old_mnt->mnt_ns->user_ns, CAP_SYS_ADMIN)) return ERR_PTR(-EPERM); - if (__has_locked_children(old_mnt, path->dentry)) + if (has_locked_children(old_mnt, path->dentry)) return ERR_PTR(-EINVAL); new_mnt = clone_mnt(old_mnt, path->dentry, CL_PRIVATE); @@ -3056,7 +3050,7 @@ static struct mount *__do_loopback(const struct path *old_path, if (recurse && !old->mnt_ns) return ERR_PTR(-EINVAL); - if (!recurse && __has_locked_children(old, old_path->dentry)) + if (!recurse && has_locked_children(old, old_path->dentry)) return ERR_PTR(-EINVAL); if (recurse) @@ -3544,7 +3538,7 @@ static int do_set_group(const struct path *from_path, const struct path *to_path return -EINVAL; /* From mount should not have locked children in place of To's root */ - if (__has_locked_children(from, to->mnt.mnt_root)) + if (has_locked_children(from, to->mnt.mnt_root)) return -EINVAL; /* Setting sharing groups is only allowed on private mounts */ From 4209cd7f546b55733d4d221109423600b025575a Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Fri, 2 Oct 2026 15:52:45 +0200 Subject: [PATCH 161/367] namespace: keep the private nullfs instance in knullfs The private nullfs instance that kernel threads are confined to is only ever reachable through init_task's root and pwd. The following patches point mounts at it as well, so keep it in a global. No functional changes. Link: https://patch.msgid.link/20261002-work-mount-fixes-4-v1-14-dd44b89d44ce@kernel.org Signed-off-by: Christian Brauner (Amutable) --- fs/mount.h | 1 + fs/namespace.c | 11 ++++++----- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/fs/mount.h b/fs/mount.h index 2223fb141499..4e68e5cbc254 100644 --- a/fs/mount.h +++ b/fs/mount.h @@ -6,6 +6,7 @@ #include extern struct file_system_type nullfs_fs_type; +extern struct vfsmount *knullfs; extern struct list_head notify_list; struct mnt_namespace { diff --git a/fs/namespace.c b/fs/namespace.c index e116894c8667..d1e83cda57e6 100644 --- a/fs/namespace.c +++ b/fs/namespace.c @@ -80,6 +80,7 @@ static u64 mnt_id_ctr = MNT_UNIQUE_ID_OFFSET; static struct hlist_head *mount_hashtable __ro_after_init; static struct hlist_head *mountpoint_hashtable __ro_after_init; static struct kmem_cache *mnt_cache __ro_after_init; +struct vfsmount *knullfs __ro_after_init; /* private nullfs instance */ static DECLARE_RWSEM(namespace_sem); static HLIST_HEAD(unmounted); /* protected by namespace_sem */ static LIST_HEAD(ex_mountpoints); /* protected by namespace_sem */ @@ -6335,7 +6336,7 @@ static void __init init_mount_tree(void) * * (1) nullfs with mount id 1 * (2) mutable rootfs with mount id 2 - * (3) private nullfs for kthreads (SB_KERNMOUNT) + * (3) private nullfs for kthreads (SB_KERNMOUNT), kept in knullfs * * with (2) mounted on top of (1). The init_task's root and pwd * are pointed at (3) so all kthreads start isolated in nullfs. @@ -6370,11 +6371,11 @@ static void __init init_mount_tree(void) init_mnt_ns.nr_mounts++; } - nullfs_mnt = kern_mount(&nullfs_fs_type); - if (IS_ERR(nullfs_mnt)) + knullfs = kern_mount(&nullfs_fs_type); + if (IS_ERR(knullfs)) panic("VFS: Failed to create private nullfs instance"); - root.mnt = nullfs_mnt; - root.dentry = nullfs_mnt->mnt_root; + root.mnt = knullfs; + root.dentry = knullfs->mnt_root; init_task.nsproxy->mnt_ns = &init_mnt_ns; get_mnt_ns(&init_mnt_ns); From e22e69c2ad8800bacab488d286ca3c5b5e68fcd9 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Fri, 2 Oct 2026 15:52:46 +0200 Subject: [PATCH 162/367] namespace: nothing is mounted on or written through knullfs Mark the root of knullfs with dont_mount(). Nothing is ever mounted on the root of a kernel thread and the following patches make that root reachable from userspace, so say it on the dentry where it doesn't depend on the mount being in no namespace. Let do_lock_mount() refuse such a target before it takes the inode lock and namespace_sem. The flag is sticky so the check needs no lock. The one under the locks stays for a mountpoint that is being removed. Make the mount read-only as well. Its one inode is immutable so nothing could be changed through it anyway, but MNT_READONLY makes that visible the usual way: EROFS instead of EPERM and ST_RDONLY in statvfs(). Link: https://patch.msgid.link/20261002-work-mount-fixes-4-v1-15-dd44b89d44ce@kernel.org Signed-off-by: Christian Brauner (Amutable) --- fs/namespace.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/fs/namespace.c b/fs/namespace.c index d1e83cda57e6..e1b0ade95b0d 100644 --- a/fs/namespace.c +++ b/fs/namespace.c @@ -2814,6 +2814,11 @@ static void do_lock_mount(const struct path *path, scoped_guard(mount_locked_reader) { m = where_to_mount(path, &dentry, beneath); + /* sticky, so it takes no locks to refuse it */ + if (unlikely(cant_mount(dentry))) { + res->parent = ERR_PTR(-ENOENT); + return; + } if (&m->mnt != path->mnt) { mntget(&m->mnt); dget(dentry); @@ -6374,6 +6379,10 @@ static void __init init_mount_tree(void) knullfs = kern_mount(&nullfs_fs_type); if (IS_ERR(knullfs)) panic("VFS: Failed to create private nullfs instance"); + /* nothing is ever mounted on the root of a kernel thread */ + dont_mount(knullfs->mnt_root); + /* and nothing is ever written through it */ + knullfs->mnt_flags |= MNT_READONLY; root.mnt = knullfs; root.dentry = knullfs->mnt_root; From c9ec8607b4d1ce4e862f5b6ba2f0eb727387988a Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Fri, 2 Oct 2026 15:52:47 +0200 Subject: [PATCH 163/367] fsnotify: let a filesystem refuse marks on its objects Don't let nullfs be watched. fanotify refuses mount and filesystem marks on SB_NOUSER superblocks but inode marks of inotify, fanotify and dnotify go through. The one inode of knullfs is the root of every kernel thread and the following patches make it reachable from userspace as the directory that stands in for an unmounted mount. A watch placed through one such directory would report the opens through all the others, across users. Add FS_DISALLOW_NOTIFY next to FS_DISALLOW_NOTIFY_PERM, refuse a mark on any object of such a filesystem in fsnotify_add_mark_list() where every backend ends up and set it for nullfs. There's nothing to watch on a permanently empty and immutable filesystem. Link: https://patch.msgid.link/20261002-work-mount-fixes-4-v1-16-dd44b89d44ce@kernel.org Signed-off-by: Christian Brauner (Amutable) --- fs/notify/mark.c | 4 ++++ fs/nullfs.c | 1 + include/linux/fs.h | 1 + 3 files changed, 6 insertions(+) diff --git a/fs/notify/mark.c b/fs/notify/mark.c index b2640d836a71..d17628580a57 100644 --- a/fs/notify/mark.c +++ b/fs/notify/mark.c @@ -903,6 +903,10 @@ static int fsnotify_add_mark_list(struct fsnotify_mark *mark, void *obj, if (WARN_ON(!fsnotify_valid_obj_type(obj_type))) return -EINVAL; + /* the filesystem doesn't want its objects watched */ + if (sb && (sb->s_type->fs_flags & FS_DISALLOW_NOTIFY)) + return -EINVAL; + /* * Attach the sb info before attaching a connector to any object on sb. * The sb info will remain attached as long as sb lives. diff --git a/fs/nullfs.c b/fs/nullfs.c index 40aa228bd81a..55a04f2d7761 100644 --- a/fs/nullfs.c +++ b/fs/nullfs.c @@ -61,6 +61,7 @@ static int nullfs_init_fs_context(struct fs_context *fc) struct file_system_type nullfs_fs_type = { .name = "nullfs", + .fs_flags = FS_DISALLOW_NOTIFY, .init_fs_context = nullfs_init_fs_context, .kill_sb = kill_anon_super, }; diff --git a/include/linux/fs.h b/include/linux/fs.h index f9d1e05e8ae6..784fa20217c4 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h @@ -2296,6 +2296,7 @@ struct file_system_type { #define FS_POWER_FREEZE 256 /* Always freeze on suspend/hibernate */ #define FS_USERNS_MOUNT_RESTRICTED 512 /* Restrict mount in userns if not already visible */ #define FS_USERNS_DELEGATABLE 1024 /* Can be mounted inside userns from outside */ +#define FS_DISALLOW_NOTIFY 2048 /* No fsnotify marks on its objects */ #define FS_RENAME_DOES_D_MOVE 32768 /* FS will handle d_move() during rename() internally. */ int (*init_fs_context)(struct fs_context *); const struct fs_parameter_spec *parameters; From 31a1fb90624d7e275db775a29a0e00a7f8b3df63 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Fri, 2 Oct 2026 15:52:48 +0200 Subject: [PATCH 164/367] nullfs: refuse file locks Refuse flock() and POSIX locks on nullfs. Its one inode is the root of every kernel thread and the following patches make it the directory that stands in for an unmounted mount, so a lock taken through one such directory would block the locks of every other holder and F_GETLK would tell them the pid of the holder. Give the directory file operations of its own: what libfs gives an empty directory plus ->lock and ->flock that fail with ENOLCK, the way a filesystem without lock support does. Link: https://patch.msgid.link/20261002-work-mount-fixes-4-v1-17-dd44b89d44ce@kernel.org Signed-off-by: Christian Brauner (Amutable) --- fs/nullfs.c | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/fs/nullfs.c b/fs/nullfs.c index 55a04f2d7761..d90c71f5eece 100644 --- a/fs/nullfs.c +++ b/fs/nullfs.c @@ -10,6 +10,34 @@ static const struct super_operations nullfs_super_operations = { .statfs = simple_statfs, }; +static loff_t nullfs_dir_llseek(struct file *file, loff_t offset, int whence) +{ + /* an empty directory has two entries . and .. at offsets 0 and 1 */ + return generic_file_llseek_size(file, offset, whence, 2, 2); +} + +static int nullfs_dir_readdir(struct file *file, struct dir_context *ctx) +{ + dir_emit_dots(file, ctx); + return 0; +} + +/* the one inode of nullfs is shared by every holder, so no locks on it */ +static int nullfs_nolock(struct file *file, int cmd, struct file_lock *fl) +{ + return -ENOLCK; +} + +/* what libfs gives an empty directory, plus the refusal of file locks */ +static const struct file_operations nullfs_dir_operations = { + .llseek = nullfs_dir_llseek, + .read = generic_read_dir, + .iterate_shared = nullfs_dir_readdir, + .fsync = noop_fsync, + .lock = nullfs_nolock, + .flock = nullfs_nolock, +}; + static int nullfs_fs_fill_super(struct super_block *s, struct fs_context *fc) { struct inode *inode; @@ -30,6 +58,7 @@ static int nullfs_fs_fill_super(struct super_block *s, struct fs_context *fc) /* nullfs is permanently empty... */ make_empty_dir_inode(inode); + inode->i_fop = &nullfs_dir_operations; simple_inode_init_ts(inode); inode->i_ino = 1; /* ... and immutable, reading it leaves no trace either. */ From e08f57a7dcb83818c058e415988c23beb6c5a47f Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Fri, 2 Oct 2026 15:52:50 +0200 Subject: [PATCH 165/367] readdir: take no inode lock on an immutable directory iterate_dir() takes the directory's i_rwsem shared and holds it across ->iterate_shared(). For a directory that never has an entry and is never removed the lock keeps nothing still, it only orders every reader and every writer of that inode behind each other. For the directory of a nullfs instance that matters. The instance of the initial mount namespace is the root of every empty mount namespace and the private instance is the root of every kernel thread, so one inode is shared across users who have nothing else in common. And a reader can hold the lock for as long as it likes: back the getdents() buffer with a mapping of a file on a FUSE mount of your own, let the copy of "." and ".." fault and let the server wait. Queue an exclusive taker behind it, a mkdir() in that directory goes through start_dirop() before the read-only mount is reported, and from then on every lookup that misses the dcache in that directory, every create and every mount on it waits until the server answers. One user of an empty mount namespace stalls all the others. Add FOP_IMMUTABLE for the file operations of a directory that never changes and is never removed and let iterate_dir() skip the lock for it. The flag never changes for a file, ->f_pos is protected by f_pos_lock since directories are FMODE_ATOMIC_POS, IS_DEADDIR can't be set on such a directory and neither touch_atime() nor fsnotify take i_rwsem. Set it on the nullfs directory. The placeholder directories of libfs never have an entry either but their owners remove them, so they keep the lock. Fixes: 9d4e752a24f7 ("namespace: allow creating empty mount namespaces") Cc: stable@vger.kernel.org # v7.1+ Link: https://patch.msgid.link/20261002-work-mount-fixes-4-v1-19-dd44b89d44ce@kernel.org Signed-off-by: Christian Brauner (Amutable) --- fs/nullfs.c | 1 + fs/readdir.c | 13 +++++++++---- include/linux/fs.h | 2 ++ 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/fs/nullfs.c b/fs/nullfs.c index d90c71f5eece..ca0dbe3dc259 100644 --- a/fs/nullfs.c +++ b/fs/nullfs.c @@ -36,6 +36,7 @@ static const struct file_operations nullfs_dir_operations = { .fsync = noop_fsync, .lock = nullfs_nolock, .flock = nullfs_nolock, + .fop_flags = FOP_IMMUTABLE, }; static int nullfs_fs_fill_super(struct super_block *s, struct fs_context *fc) diff --git a/fs/readdir.c b/fs/readdir.c index 76bb1ae3a450..f2288841337a 100644 --- a/fs/readdir.c +++ b/fs/readdir.c @@ -87,6 +87,8 @@ EXPORT_SYMBOL(wrap_directory_iterator); int iterate_dir(struct file *file, struct dir_context *ctx) { struct inode *inode = file_inode(file); + /* never an entry, never removed: nothing for the lock to keep still */ + bool locked = !(file->f_op->fop_flags & FOP_IMMUTABLE); int res = -ENOTDIR; if (!file->f_op->iterate_shared) @@ -100,9 +102,11 @@ int iterate_dir(struct file *file, struct dir_context *ctx) if (res) goto out; - res = down_read_killable(&inode->i_rwsem); - if (res) - goto out; + if (locked) { + res = down_read_killable(&inode->i_rwsem); + if (res) + goto out; + } res = -ENOENT; if (!IS_DEADDIR(inode)) { @@ -112,7 +116,8 @@ int iterate_dir(struct file *file, struct dir_context *ctx) fsnotify_access(file); file_accessed(file); } - inode_unlock_shared(inode); + if (locked) + inode_unlock_shared(inode); out: return res; } diff --git a/include/linux/fs.h b/include/linux/fs.h index 784fa20217c4..deb411e86661 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h @@ -1978,6 +1978,8 @@ struct file_operations { #define FOP_ASYNC_LOCK ((__force fop_flags_t)(1 << 6)) /* File system supports uncached read/write buffered IO */ #define FOP_DONTCACHE ((__force fop_flags_t)(1 << 7)) +/* Never changes and is never removed, readdir of a directory takes no lock */ +#define FOP_IMMUTABLE ((__force fop_flags_t)(1 << 8)) /* Wrap a directory iterator that needs exclusive inode access */ int wrap_directory_iterator(struct file *, struct dir_context *, From fa15a241f0a862904d673d2514baf9a26c0893e8 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Fri, 2 Oct 2026 15:52:51 +0200 Subject: [PATCH 166/367] selftests/filesystems: add a helper that holds a readdir in a page fault A getdents64() whose buffer is a page registered with userfaultfd sits in handle_userfault() with whatever iterate_dir() took before it copied the entries. Add readdir_hold.h for tests that want to know what that blocks: it opens the userfaultfd before the test enters a user namespace, the fault happens in the kernel and needs CAP_SYS_PTRACE in the initial one, starts the readdir in a thread, waits until that thread is stuck, queues a create behind it and waits for it to settle, then probes a lookup with a watchdog and says whether it came back. The page is released afterwards so that everything drains on a kernel that still holds the lock across the copy. The two users follow. Link: https://patch.msgid.link/20261002-work-mount-fixes-4-v1-20-dd44b89d44ce@kernel.org Signed-off-by: Christian Brauner (Amutable) --- .../selftests/filesystems/readdir_hold.h | 224 ++++++++++++++++++ 1 file changed, 224 insertions(+) create mode 100644 tools/testing/selftests/filesystems/readdir_hold.h diff --git a/tools/testing/selftests/filesystems/readdir_hold.h b/tools/testing/selftests/filesystems/readdir_hold.h new file mode 100644 index 000000000000..57eee1bef470 --- /dev/null +++ b/tools/testing/selftests/filesystems/readdir_hold.h @@ -0,0 +1,224 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Hold a readdir of a directory in the page fault of its buffer and see + * whether a create and a lookup in that directory wait for it. For a + * directory that is permanently empty they must not. + */ +#ifndef __SELFTESTS_READDIR_HOLD_H +#define __SELFTESTS_READDIR_HOLD_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define HOLD_FAULT_MS 5000 /* for the reader to reach the fault */ +#define HOLD_QUEUE_MS 2000 /* for the create to queue up behind it */ +#define HOLD_LOOKUP_MS 5000 /* for the lookup to come back */ + +struct readdir_hold { + int uffd; + int taskdir; /* /proc/self/task, from before any namespace change */ + long page_size; + char *page; /* faults until released */ + int dfd; + pid_t creator_tid; + int created; + int done[2]; /* the finder writes a byte when it is back */ +}; + +/* + * The fault happens in the kernel, so the userfaultfd needs CAP_SYS_PTRACE + * in the initial user namespace or vm.unprivileged_userfaultfd. Call this + * before entering a user namespace. + */ +static inline int readdir_hold_init(struct readdir_hold *h) +{ + struct uffdio_api api = { .api = UFFD_API }; + + h->page = MAP_FAILED; + h->page_size = sysconf(_SC_PAGESIZE); + h->taskdir = open("/proc/self/task", O_RDONLY | O_DIRECTORY | O_CLOEXEC); + h->uffd = syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK); + if (h->uffd < 0 || h->taskdir < 0 || ioctl(h->uffd, UFFDIO_API, &api)) { + if (h->uffd >= 0) + close(h->uffd); + if (h->taskdir >= 0) + close(h->taskdir); + h->uffd = h->taskdir = -1; + return -1; + } + return 0; +} + +static inline void readdir_hold_destroy(struct readdir_hold *h) +{ + if (h->uffd >= 0) + close(h->uffd); + if (h->taskdir >= 0) + close(h->taskdir); + h->uffd = h->taskdir = -1; +} + +static inline void *readdir_hold_reader(void *arg) +{ + struct readdir_hold *h = arg; + + /* the first byte written to the buffer faults until released */ + syscall(__NR_getdents64, h->dfd, h->page, h->page_size); + return NULL; +} + +static inline void *readdir_hold_creator(void *arg) +{ + struct readdir_hold *h = arg; + + h->creator_tid = syscall(__NR_gettid); + /* takes the directory lock exclusive before it fails */ + mkdirat(h->dfd, "x", 0755); + __atomic_store_n(&h->created, 1, __ATOMIC_RELEASE); + return NULL; +} + +static inline void *readdir_hold_finder(void *arg) +{ + struct readdir_hold *h = arg; + int fd; + + /* a lookup that misses the dcache takes the lock shared */ + fd = openat(h->dfd, "no_such_name", O_RDONLY | O_CLOEXEC); + if (fd >= 0) + close(fd); + if (write(h->done[1], "x", 1) != 1) + perror("readdir_hold: finder"); + return NULL; +} + +/* the creator is back, or waits in the kernel for the lock */ +static inline bool readdir_hold_creator_settled(struct readdir_hold *h) +{ + char path[32], buf[256], *p; + ssize_t n; + int fd; + + if (__atomic_load_n(&h->created, __ATOMIC_ACQUIRE)) + return true; + if (!h->creator_tid) + return false; + snprintf(path, sizeof(path), "%d/stat", h->creator_tid); + fd = openat(h->taskdir, path, O_RDONLY | O_CLOEXEC); + if (fd < 0) + return false; + n = read(fd, buf, sizeof(buf) - 1); + close(fd); + if (n <= 0) + return false; + buf[n] = 0; + /* "pid (comm) state ..." */ + p = strrchr(buf, ')'); + return p && p[1] == ' ' && p[2] == 'D'; +} + +static inline bool readdir_hold_faulted(struct readdir_hold *h) +{ + struct pollfd pfd = { .fd = h->uffd, .events = POLLIN }; + struct uffd_msg msg; + + if (poll(&pfd, 1, HOLD_FAULT_MS) != 1) + return false; + if (read(h->uffd, &msg, sizeof(msg)) != sizeof(msg)) + return false; + return msg.event == UFFD_EVENT_PAGEFAULT; +} + +/* let the reader go on */ +static inline void readdir_hold_release(struct readdir_hold *h) +{ + struct uffdio_copy cp = { + .dst = (unsigned long)h->page, + .len = h->page_size, + }; + void *zero; + + zero = calloc(1, h->page_size); + if (!zero) + return; + cp.src = (unsigned long)zero; + if (ioctl(h->uffd, UFFDIO_COPY, &cp) && errno != EEXIST) + perror("readdir_hold: UFFDIO_COPY"); + free(zero); +} + +/* + * A readdir of @dfd that sticks in the fault of its buffer, then a create + * and a lookup in @dfd. Whether the lookup came back while the readdir was + * still stuck goes to @stalled. Returns -1 when that could not be found + * out. + */ +static inline int readdir_hold_check(struct readdir_hold *h, int dfd, + bool *stalled) +{ + pthread_t reader, creator, finder; + struct uffdio_register reg = {}; + struct pollfd pfd; + int ret = -1, i; + + h->dfd = dfd; + h->created = 0; + h->creator_tid = 0; + h->page = mmap(NULL, h->page_size, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if (h->page == MAP_FAILED) + return -1; + reg.range.start = (unsigned long)h->page; + reg.range.len = h->page_size; + reg.mode = UFFDIO_REGISTER_MODE_MISSING; + if (ioctl(h->uffd, UFFDIO_REGISTER, ®) || pipe2(h->done, O_CLOEXEC)) + goto out_page; + + if (pthread_create(&reader, NULL, readdir_hold_reader, h)) + goto out_pipe; + if (!readdir_hold_faulted(h)) + goto out_reader; + if (pthread_create(&creator, NULL, readdir_hold_creator, h)) + goto out_reader; + for (i = 0; i < HOLD_QUEUE_MS / 10 && !readdir_hold_creator_settled(h); i++) + usleep(10000); + if (pthread_create(&finder, NULL, readdir_hold_finder, h)) + goto out_creator; + + pfd.fd = h->done[0]; + pfd.events = POLLIN; + *stalled = poll(&pfd, 1, HOLD_LOOKUP_MS) != 1; + ret = 0; + + readdir_hold_release(h); + pthread_join(finder, NULL); +out_creator: + if (ret) + readdir_hold_release(h); + pthread_join(creator, NULL); +out_reader: + if (ret) + readdir_hold_release(h); + pthread_join(reader, NULL); +out_pipe: + close(h->done[0]); + close(h->done[1]); +out_page: + munmap(h->page, h->page_size); + h->page = MAP_FAILED; + return ret; +} + +#endif /* __SELFTESTS_READDIR_HOLD_H */ From 098bf3be6cecb9b297f5a5865e89d36d16edf19e Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Fri, 2 Oct 2026 15:52:52 +0200 Subject: [PATCH 167/367] selftests/filesystems: check that reading the root of an empty mount namespace stalls nobody A readdir of the root of an empty mount namespace stuck in the page fault of its buffer must stall neither a create nor a lookup in that directory. The test needs userfaultfd and skips without it. Link: https://patch.msgid.link/20261002-work-mount-fixes-4-v1-21-dd44b89d44ce@kernel.org Signed-off-by: Christian Brauner (Amutable) --- .../filesystems/empty_mntns/.gitignore | 1 + .../filesystems/empty_mntns/Makefile | 5 ++- .../empty_mntns/root_readdir_test.c | 45 +++++++++++++++++++ 3 files changed, 50 insertions(+), 1 deletion(-) create mode 100644 tools/testing/selftests/filesystems/empty_mntns/root_readdir_test.c diff --git a/tools/testing/selftests/filesystems/empty_mntns/.gitignore b/tools/testing/selftests/filesystems/empty_mntns/.gitignore index 8ea166068d6b..27bcbcaeb1d1 100644 --- a/tools/testing/selftests/filesystems/empty_mntns/.gitignore +++ b/tools/testing/selftests/filesystems/empty_mntns/.gitignore @@ -4,3 +4,4 @@ empty_mntns_test overmount_chroot_test internal_sb_reconfigure_test nullfs_atime_test +root_readdir_test diff --git a/tools/testing/selftests/filesystems/empty_mntns/Makefile b/tools/testing/selftests/filesystems/empty_mntns/Makefile index 4b2ea6acec1c..22af2164c54e 100644 --- a/tools/testing/selftests/filesystems/empty_mntns/Makefile +++ b/tools/testing/selftests/filesystems/empty_mntns/Makefile @@ -4,7 +4,9 @@ CFLAGS += -Wall -O2 -g $(KHDR_INCLUDES) $(TOOLS_INCLUDES) LDLIBS += -lcap TEST_GEN_PROGS := empty_mntns_test overmount_chroot_test clone3_empty_mntns_test -TEST_GEN_PROGS += internal_sb_reconfigure_test nullfs_atime_test +TEST_GEN_PROGS += internal_sb_reconfigure_test nullfs_atime_test root_readdir_test + +LOCAL_HDRS += ../readdir_hold.h include ../../lib.mk @@ -12,3 +14,4 @@ $(OUTPUT)/empty_mntns_test: ../utils.c $(OUTPUT)/overmount_chroot_test: ../utils.c $(OUTPUT)/clone3_empty_mntns_test: ../utils.c $(OUTPUT)/internal_sb_reconfigure_test: ../utils.c +$(OUTPUT)/root_readdir_test: LDLIBS += -pthread diff --git a/tools/testing/selftests/filesystems/empty_mntns/root_readdir_test.c b/tools/testing/selftests/filesystems/empty_mntns/root_readdir_test.c new file mode 100644 index 000000000000..0ff544247027 --- /dev/null +++ b/tools/testing/selftests/filesystems/empty_mntns/root_readdir_test.c @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Reading the root of an empty mount namespace holds nothing that anybody + * else waits for: the directory never has an entry, so a readdir stuck in + * the page fault of its buffer stalls neither a create nor a lookup in it. + */ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include + +#include "../../kselftest_harness.h" +#include "../readdir_hold.h" +#include "empty_mntns.h" + +TEST(readdir_blocks_nobody) +{ + struct readdir_hold hold; + bool stalled; + int dfd; + + if (geteuid()) + SKIP(return, "test requires root"); + if (readdir_hold_init(&hold)) + SKIP(return, "test requires userfaultfd"); + if (unshare(UNSHARE_EMPTY_MNTNS)) { + readdir_hold_destroy(&hold); + if (errno == EINVAL) + SKIP(return, "UNSHARE_EMPTY_MNTNS not supported"); + ASSERT_TRUE(false) + TH_LOG("unshare(UNSHARE_EMPTY_MNTNS): %m"); + } + dfd = open("/", O_RDONLY | O_DIRECTORY | O_CLOEXEC); + ASSERT_GE(dfd, 0); + ASSERT_EQ(readdir_hold_check(&hold, dfd, &stalled), 0); + /* the lookup came back while the readdir was stuck in its fault */ + EXPECT_FALSE(stalled); + close(dfd); + readdir_hold_destroy(&hold); +} + +TEST_HARNESS_MAIN From e2151c29cec73f6cc70abf1c7cbc7b9b202489f5 Mon Sep 17 00:00:00 2001 From: Alexey Charkov Date: Wed, 30 Sep 2026 20:13:03 +0400 Subject: [PATCH 168/367] regulator: of: fill in supply names in of_regulator_bulk_get_all() of_regulator_bulk_get_all() returns an array it allocated itself, and fills in only the consumer of each entry. Every other way of getting a bulk array has the supply name set, because the caller provides it, and the core expects it to be there: regulator_bulk_enable() prints it when a supply fails to enable, so a caller that hands such an array to it dereferences uninitialised memory on that path. Copy each name into the array's own allocation, right behind the entries, so that it shares the array's lifetime and callers still have nothing extra to free. That also retires the fixed 64 byte stack buffer the names were assembled in, which is_supply_name() never bounded the copy against. Cc: stable@vger.kernel.org Reported-by: Sashiko Closes: https://sashiko.dev/#/patchset/20260928-b4-rk3576-reboot-mode-v1-0-65486b03bd41@flipper.net?part=3 Fixes: 27b9ecc7a9ba ("regulator: Add of_regulator_bulk_get_all") Signed-off-by: Alexey Charkov Link: https://patch.msgid.link/20260930-regulator-get-all-v2-1-ca82070d22f2@flipper.net Signed-off-by: Mark Brown --- drivers/regulator/of_regulator.c | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/drivers/regulator/of_regulator.c b/drivers/regulator/of_regulator.c index c0cc6cc0afd8..785b7a11dfc6 100644 --- a/drivers/regulator/of_regulator.c +++ b/drivers/regulator/of_regulator.c @@ -935,15 +935,15 @@ static int is_supply_name(const char *name) int of_regulator_bulk_get_all(struct device *dev, struct device_node *np, struct regulator_bulk_data **consumers) { - int num_consumers = 0; + int num_consumers = 0, names_len = 0; struct regulator *tmp; struct regulator_bulk_data *_consumers = NULL; struct property *prop; + char *names; int i, n = 0, ret; - char name[64]; /* - * first pass: get numbers of xxx-supply + * first pass: get numbers of xxx-supply and the room their names take * second pass: fill consumers */ restart: @@ -953,16 +953,19 @@ int of_regulator_bulk_get_all(struct device *dev, struct device_node *np, continue; if (!_consumers) { num_consumers++; + names_len += i + 1; continue; } else { - memcpy(name, prop->name, i); - name[i] = '\0'; - tmp = regulator_get(dev, name); + memcpy(names, prop->name, i); + names[i] = '\0'; + tmp = regulator_get(dev, names); if (IS_ERR(tmp)) { ret = PTR_ERR(tmp); goto error; } + _consumers[n].supply = names; _consumers[n].consumer = tmp; + names += i + 1; n++; continue; } @@ -973,9 +976,16 @@ int of_regulator_bulk_get_all(struct device *dev, struct device_node *np, } if (num_consumers == 0) return 0; - _consumers = kmalloc_objs(struct regulator_bulk_data, num_consumers); + /* + * The supply names are kept in the same allocation as the array, so + * that they share its lifetime and the caller has nothing extra to + * free. + */ + _consumers = kzalloc(size_add(size_mul(num_consumers, sizeof(*_consumers)), + names_len), GFP_KERNEL); if (!_consumers) return -ENOMEM; + names = (char *)(_consumers + num_consumers); goto restart; error: From b35ff7bd7dd56915a511eec56c8763ee8f9f7e9f Mon Sep 17 00:00:00 2001 From: Alexey Charkov Date: Wed, 30 Sep 2026 20:13:04 +0400 Subject: [PATCH 169/367] regulator: of: state who owns the array from of_regulator_bulk_get_all() of_regulator_bulk_get_all() allocates the consumer array and hands it to the caller, but never said so. Its own error path frees the array, while regulator_bulk_free() only puts the regulators, so both callers release the regulators and leak the array. Spell the contract out in the kerneldoc. Signed-off-by: Alexey Charkov Link: https://patch.msgid.link/20260930-regulator-get-all-v2-2-ca82070d22f2@flipper.net Signed-off-by: Mark Brown --- drivers/regulator/of_regulator.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/regulator/of_regulator.c b/drivers/regulator/of_regulator.c index 785b7a11dfc6..09e26febfc83 100644 --- a/drivers/regulator/of_regulator.c +++ b/drivers/regulator/of_regulator.c @@ -929,6 +929,11 @@ static int is_supply_name(const char *name) * before returning to the caller, and @consumers will not be * changed. * + * On success the array is allocated here and handed to the caller, which + * owns it from then on: release the regulators with regulator_bulk_free() + * and free the array itself with kfree(). The supply names live in the + * same allocation, so they are gone once the array is freed. + * * Return: Number of regulators on success, or a negative error number * on failure. */ From 25bf14f817168079f731975b8998fc2af380f29e Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Sat, 12 Sep 2026 11:54:40 +0200 Subject: [PATCH 170/367] keys: finalize persistent keyring timeout after link attempt When no keyring exists for the requested UID, KEYCTL_GET_PERSISTENT creates and registers one before linking it to the requested destination. The configured timeout is set only after the destination link succeeds. A destination restricted with KEYCTL_RESTRICT_KEYRING makes that link fail with -EPERM. With persistent_keyring_expiry set to 60 seconds, /proc/keys still reports the registered keyring's expiry as "perm". The failed call therefore leaves a quota-exempt keyring in the namespace's hidden register, where it may remain until namespace teardown. Rename the write-locked helper to key_get_or_create_persistent() and return the key reference through a result parameter. Return 0 when it creates a keyring and 1 when the retry finds an existing one. Return a negative error on failure. Another caller may create the keyring between the initial read-locked lookup and the retry under the write lock. Set the timeout after permission checking and linking. Do this on success, or on failure if the call allocated the keyring. This leaves a new keyring collectible after failure without starting its timeout while linking is still in progress. Cc: stable@vger.kernel.org # v5.10+ Fixes: f36f8c75ae2e ("KEYS: Add per-user_namespace registers for persistent per-UID kerberos caches") Assisted-by: LLM Signed-off-by: Karl Mehltretter Link: https://lore.kernel.org/r/20260912095440.79864-1-kmehltretter@gmail.com Reviewed-by: Jarkko Sakkinen Signed-off-by: Jarkko Sakkinen --- security/keys/persistent.c | 55 ++++++++++++++++++++++---------------- 1 file changed, 32 insertions(+), 23 deletions(-) diff --git a/security/keys/persistent.c b/security/keys/persistent.c index 97af230aa4b2..bdab99539326 100644 --- a/security/keys/persistent.c +++ b/security/keys/persistent.c @@ -33,25 +33,31 @@ static int key_create_persistent_register(struct user_namespace *ns) } /* - * Create the persistent keyring for the specified user. + * Get or create the persistent keyring for the specified user. * * Called with the namespace's sem locked for writing. + * + * Returns a boolean indicating whether the keyring already existed, + * or a negative error. On success, *persistent_ref holds a reference + * to the keyring. */ -static key_ref_t key_create_persistent(struct user_namespace *ns, kuid_t uid, - struct keyring_index_key *index_key) +static int key_get_or_create_persistent(struct user_namespace *ns, kuid_t uid, + struct keyring_index_key *index_key, + key_ref_t *persistent_ref) { struct key *persistent; - key_ref_t reg_ref, persistent_ref; + key_ref_t reg_ref; if (!ns->persistent_keyring_register) { - long err = key_create_persistent_register(ns); + int err = key_create_persistent_register(ns); + if (err < 0) - return ERR_PTR(err); + return err; } else { reg_ref = make_key_ref(ns->persistent_keyring_register, true); - persistent_ref = find_key_to_update(reg_ref, index_key); - if (persistent_ref) - return persistent_ref; + *persistent_ref = find_key_to_update(reg_ref, index_key); + if (*persistent_ref) + return 1; } persistent = keyring_alloc(index_key->description, @@ -61,9 +67,10 @@ static key_ref_t key_create_persistent(struct user_namespace *ns, kuid_t uid, KEY_ALLOC_NOT_IN_QUOTA, NULL, ns->persistent_keyring_register); if (IS_ERR(persistent)) - return ERR_CAST(persistent); + return PTR_ERR(persistent); - return make_key_ref(persistent, true); + *persistent_ref = make_key_ref(persistent, true); + return 0; } /* @@ -78,6 +85,7 @@ static long key_get_persistent(struct user_namespace *ns, kuid_t uid, key_ref_t reg_ref, persistent_ref; char buf[32]; long ret; + bool created = false; /* Look in the register if it exists */ memset(&index_key, 0, sizeof(index_key)); @@ -100,23 +108,24 @@ static long key_get_persistent(struct user_namespace *ns, kuid_t uid, * also need to create the register. */ down_write(&ns->keyring_sem); - persistent_ref = key_create_persistent(ns, uid, &index_key); + ret = key_get_or_create_persistent(ns, uid, &index_key, + &persistent_ref); up_write(&ns->keyring_sem); - if (!IS_ERR(persistent_ref)) - goto found; - - return PTR_ERR(persistent_ref); + if (ret < 0) + return ret; + created = ret == 0; found: + persistent = key_ref_to_ptr(persistent_ref); ret = key_task_permission(persistent_ref, current_cred(), KEY_NEED_LINK); - if (ret == 0) { - persistent = key_ref_to_ptr(persistent_ref); + if (ret == 0) ret = key_link(key_ref_to_ptr(dest_ref), persistent); - if (ret == 0) { - key_set_timeout(persistent, persistent_keyring_expiry); - ret = persistent->serial; - } - } + + if (ret == 0 || created) + key_set_timeout(persistent, persistent_keyring_expiry); + + if (ret == 0) + ret = persistent->serial; key_ref_put(persistent_ref); return ret; From dd3ea3fcba7c75493760cdbccd35f029597e8d5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=84=B1=EB=B3=91=EC=B0=AC?= Date: Wed, 30 Sep 2026 13:18:01 +0900 Subject: [PATCH 171/367] KEYS: Fix add_key() race with keyring restriction MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit __key_create_or_update() snapshots keyring->restrict_link before taking the destination keyring's semaphore. keyring_restrict() installs a restriction while holding that semaphore. This allows a writer to observe no restriction, wait for the keyring owner to install a reject-all restriction and return successfully, and then link a key using the stale NULL snapshot. The writer only needs write permission on the destination keyring. Move the restrict_link read after __key_link_lock() and __key_link_begin(). The read and the subsequent restriction check are then serialized with restriction installation by keyring->sem. The race was reproduced on v7.2.8 in 19 executions where restriction installation returned before the link completed. All 19 linked the key despite the reject-all restriction. With this change, 312 executions reached the same ordering and every add_key() call failed with -EPERM. The issue was found by manual concurrency analysis assisted by AI-based analysis and independently verified with a QEMU reproducer and kernel instrumentation. Fixes: 5ac7eace2d00 ("KEYS: Add a facility to restrict new links into a keyring") Cc: stable@vger.kernel.org Signed-off-by: 성병찬 Reviewed-by: Jarkko Sakkinen Link: https://lore.kernel.org/r/20260930041802.6114-1-tjdqudcks0424@naver.com Signed-off-by: Jarkko Sakkinen --- security/keys/key.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/security/keys/key.c b/security/keys/key.c index b34a64d81d47..a438c4508595 100644 --- a/security/keys/key.c +++ b/security/keys/key.c @@ -840,9 +840,6 @@ static key_ref_t __key_create_or_update(key_ref_t keyring_ref, key_check(keyring); - if (!(flags & KEY_ALLOC_BYPASS_RESTRICTION)) - restrict_link = keyring->restrict_link; - key_ref = ERR_PTR(-ENOTDIR); if (keyring->type != &key_type_keyring) goto error_put_type; @@ -880,6 +877,9 @@ static key_ref_t __key_create_or_update(key_ref_t keyring_ref, goto error_link_end; } + if (!(flags & KEY_ALLOC_BYPASS_RESTRICTION)) + restrict_link = keyring->restrict_link; + if (restrict_link && restrict_link->check) { ret = restrict_link->check(keyring, index_key.type, &prep.payload, restrict_link->key); From 94b6df050433875f493fb8298b58d7b0dc32eb9c Mon Sep 17 00:00:00 2001 From: Haibo Chen Date: Mon, 28 Sep 2026 19:53:11 +0800 Subject: [PATCH 172/367] spi: spi-nxp-fspi: exit stop mode before waiting for DLL lock For 8D-8D-8D (octal DTR) mode the FlexSPI samples read data using MCR0[RXCLKSRC] = 3 (flash-memory-provided read strobe on the DQS pad), which requires the DLL to be locked. Per the i.MX95 Reference Manual (FlexSPI chapter, "DLL configuration for sampling"), when the serial root clock is above 100 MHz the DLL is enabled (DLLxCR[DLLEN] = 1, OVRDEN = 0) and software must then wait for STS2[xREFLOCK] and STS2[xSLVLOCK] to be asserted before starting a read or write. This DLL lock is relative to the serial root clock. While the module is in stop mode (MCR0[MDIS] = 1) the AHB and serial clocks are gated off internally, so the serial root clock the DLL locks against is not running. nxp_fspi_dll_calibration() both programs DLLxCR and then polls STS2 for lock. When it runs entirely in stop mode the STS2 REF/SLV lock bits never assert (observed as "DLL lock failed" with STS2 = 0). The MCR0 bit fields (RXCLKSRC) and the serial root clock rate still have to be reconfigured in stop mode, so keep entering stop mode around that. Only move the exit from stop mode (clearing MCR0[MDIS]) ahead of the DLL calibration/override step, so that the serial root clock is running when the DLL is calibrated and STS2 can report lock. Fixes: b4bde5cfff8e ("spi: spi-nxp-fspi: enter stop mode before reconfiguring MCR0 and DLL") Signed-off-by: Haibo Chen Link: https://patch.msgid.link/20260928-fspi-fix-v1-1-a0f9288cd5c8@nxp.com Signed-off-by: Mark Brown --- drivers/spi/spi-nxp-fspi.c | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/drivers/spi/spi-nxp-fspi.c b/drivers/spi/spi-nxp-fspi.c index 67d991dd249d..66cb58f95fdb 100644 --- a/drivers/spi/spi-nxp-fspi.c +++ b/drivers/spi/spi-nxp-fspi.c @@ -898,10 +898,10 @@ static int nxp_fspi_select_mem(struct nxp_fspi *f, struct spi_device *spi, dev_dbg(f->dev, "Target device [CS:%x] selected\n", spi_get_chipselect(spi, 0)); /* - * Per the FlexSPI reference manual (initialization sequence), MCR0 and - * the DLL control registers should be configured while the module is in - * stop mode (MCR0[MDIS] = 1). Enter stop mode before reconfiguring the - * RX sample clock source and the DLL, then exit stop mode afterwards. + * Per the FlexSPI reference manual (initialization sequence), MCR0 + * should be configured while the module is in stop mode (MCR0[MDIS] = 1). + * Enter stop mode before reconfiguring the RX sample clock source, then + * exit stop mode afterwards. */ reg = fspi_readl(f, f->iobase + FSPI_MCR0); fspi_writel(f, reg | FSPI_MCR0_MDIS, f->iobase + FSPI_MCR0); @@ -936,6 +936,16 @@ static int nxp_fspi_select_mem(struct nxp_fspi *f, struct spi_device *spi, if (ret) return ret; + /* + * Exit stop mode before running the DLL calibration. The DLL + * reference clock is derived from the module clock domain, which is + * gated while MCR0[MDIS] = 1. Calibrating the DLL in stop mode leaves + * the STS2 lock bits stuck at 0 (the DLL never runs), so clear MDIS + * first and let the module and reference clock run. + */ + reg = fspi_readl(f, f->iobase + FSPI_MCR0); + fspi_writel(f, reg & ~FSPI_MCR0_MDIS, f->iobase + FSPI_MCR0); + /* * If clock rate > 100MHz, then switch from DLL override mode to * DLL calibration mode. @@ -945,10 +955,6 @@ static int nxp_fspi_select_mem(struct nxp_fspi *f, struct spi_device *spi, else nxp_fspi_dll_override(f); - /* Exit stop mode now that MCR0 and the DLL have been reconfigured. */ - reg = fspi_readl(f, f->iobase + FSPI_MCR0); - fspi_writel(f, reg & ~FSPI_MCR0_MDIS, f->iobase + FSPI_MCR0); - f->pre_op_rate = op->max_freq; f->selected = spi_get_chipselect(spi, 0); From fd99864b0ac936e870c7ae28354c5cd3dad5efd0 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Thu, 1 Oct 2026 17:14:44 +0200 Subject: [PATCH 173/367] spi: cadence-qspi: Fix status polling with octal DTR chips Most SPI NOR chips today use 0x05 as ReaD Status Register (RDSR) opcode. After almost every transaction, the core will wait for the said transation to be digested by the chip by polling its status register. This mechanism has been automated by certain chips, such as the Cadence QSPI controller. Unfortunately, the feature has been disabled for various (relevant) reasons. Disabling this feature has been done by setting WRITE_COMPLETION_CTRL_REG.DISABLE_POLLING_FLD, keeping the rest of the register intact. In this same register, there is also a field named WRITE_COMPLETION_CTRL_REG.OPCODE_FLD, which by default also contains 0x05, as primary RDSR opcode. As a result, both the SPI NOR and SPI NAND cores, primary users through spi-mem of the spi_mem_ops interface, do send their own RDSR. In this driver, such command goes through one of the fifth possible data path specifically crafted for short register-like commands, named STIG. In STIG mode, the opcode of the operation is written to OSPI_FLASH_CMD_CTRL_REG. This means that the opcode field of STIG configuration and the opcode field of the automatic polling configuration contain the same value. Unfortunately, the TRM explains in two different places that this configuration is forbidden, without explaining why it is problematic: $12.4.2.4.11 OSPI Software-Triggered Instruction Generator (STIG) ``` OSPI_FLASH_CMD_CTRL_REG[31-24] CMD_OPCODE_FLD bits should be set different than OSPI_DEV_INSTR_RD_CONFIG_REG[7-0] RD_OPCODE_NON_XIP_FLD and OSPI_DEV_INSTR_WR_CONFIG_REG[7-0] WR_OPCODE_FLD. ``` and $12.4.2.5.2 Configuring the OSPI Controller for Optimal Use ``` When using the OSPI Controller, the opcodes in OSPI_DEV_INSTR_RD_CONFIG_REG[7-0] RD_OPCODE_NON_XIP_FLD, OSPI_DEV_INSTR_WR_CONFIG_REG[7-0] WR_OPCODE_FLD and OSPI_WRITE_COMPLETION_CTRL_REG[7-0] OPCODE_FLD bit fields shall not match the opcode in the OSPI_FLASH_CMD_CTRL_REG[31-24] CMD_OPCODE_FLD bit field. ``` For some reasons, this is only unveiled now when testing in ODTR mode. Plain SDR modes work flawlessly with the same chips. One possible reason for this, is that the internal data path in ODTR mode is very different due to the extra opcode extension/repetition which may be interpreted differently (this is a wild guess). For sure there is an interference, as even the TRM states that there is some magic happening: $12.4.2.4.13 OSPI Command Translation ``` The WREN and the RDSR device instructions are the only ones that are sent by the controller under the hood. ``` I cannot probe the bus when the problem arises, so I don't know exactly what is sent on the bus. Practically speaking, the chip was driving the lines low (reading all 0s), which is not the usual default state when the chip is not driving the lines (all 1s). Other people on the mailing list claimed successfully using ODTR chips with this controller. My take here is that either they were using a variant of the IP without write completion at all (there is a quirk for it), or they were using chips slightly more flexible about their opcode sequency or maybe with different addr/dummy requirements. Clearing the WRITE_COMPLETION_CTRL_REG.OPCODE_FLD while disabling write completion polling entirely seems the best compromise. Signed-off-by: Miquel Raynal Link: https://patch.msgid.link/20261001-winbond-spi-next-cadence-rdsr-fix-v1-1-ec5ff594ba5d@bootlin.com Signed-off-by: Mark Brown --- drivers/spi/spi-cadence-quadspi.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/spi/spi-cadence-quadspi.c b/drivers/spi/spi-cadence-quadspi.c index ecb0be394696..5a5f1a852cbe 100644 --- a/drivers/spi/spi-cadence-quadspi.c +++ b/drivers/spi/spi-cadence-quadspi.c @@ -1056,8 +1056,7 @@ static int cqspi_write_setup(struct cqspi_flash_pdata *f_pdata, * care of polling the status register. */ if (cqspi->wr_completion) { - reg = readl(reg_base + CQSPI_REG_WR_COMPLETION_CTRL); - reg |= CQSPI_REG_WR_DISABLE_AUTO_POLL; + reg = CQSPI_REG_WR_DISABLE_AUTO_POLL; writel(reg, reg_base + CQSPI_REG_WR_COMPLETION_CTRL); /* * DAC mode require auto polling as flash needs to be polled From 5158353121bae4f552cb677ce8c5c3ef52f93a0d Mon Sep 17 00:00:00 2001 From: Zixuan Chai Date: Sun, 27 Sep 2026 01:04:39 +0800 Subject: [PATCH 174/367] net: bridge: avoid recursive multicast port cleanup br_multicast_sg_del_exclude_ports() removes automatically added STAR_EXCL port groups by calling br_multicast_del_pg(). For S,G entries, that deletion calls back into br_multicast_sg_del_exclude_ports(), so a large number of STAR_EXCL ports consumes one kernel stack frame per port and can hit the stack guard page. Keep the complete port-group deletion path for cleanup, but suppress the S,G exclude-port cleanup while that helper is already walking the same entry. Normal deletion callers retain the existing behavior. Fixes: 8266a0491e92 ("net: bridge: mcast: handle port group filter modes") Cc: stable@vger.kernel.org Reported-by: Vega Assisted-by: LLM Signed-off-by: Zixuan Chai Signed-off-by: Ren Wei Signed-off-by: David S. Miller --- net/bridge/br_multicast.c | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/net/bridge/br_multicast.c b/net/bridge/br_multicast.c index 2f9bb30e1a1f..3fbad7b59769 100644 --- a/net/bridge/br_multicast.c +++ b/net/bridge/br_multicast.c @@ -81,6 +81,10 @@ __br_multicast_add_group(struct net_bridge_mcast *brmctx, bool blocked); static void br_multicast_find_del_pg(struct net_bridge *br, struct net_bridge_port_group *pg); +static void __br_multicast_del_pg(struct net_bridge_mdb_entry *mp, + struct net_bridge_port_group *pg, + struct net_bridge_port_group __rcu **pp, + bool sg_del_exclude_ports); static void __br_multicast_stop(struct net_bridge_mcast *brmctx); static int br_mc_disabled_update(struct net_device *dev, bool value, @@ -458,7 +462,7 @@ static void br_multicast_sg_del_exclude_ports(struct net_bridge_mdb_entry *sgmp) for (pp = &sgmp->ports; (p = mlock_dereference(*pp, sgmp->br)) != NULL;) { if (!(p->flags & MDB_PG_FLAGS_PERMANENT)) - br_multicast_del_pg(sgmp, p, pp); + __br_multicast_del_pg(sgmp, p, pp, false); else pp = &p->next; } @@ -799,9 +803,10 @@ static void br_multicast_destroy_port_group(struct net_bridge_mcast_gc *gc) kfree_rcu(pg, rcu); } -void br_multicast_del_pg(struct net_bridge_mdb_entry *mp, - struct net_bridge_port_group *pg, - struct net_bridge_port_group __rcu **pp) +static void __br_multicast_del_pg(struct net_bridge_mdb_entry *mp, + struct net_bridge_port_group *pg, + struct net_bridge_port_group __rcu **pp, + bool sg_del_exclude_ports) { struct net_bridge *br = pg->key.port->br; struct net_bridge_group_src *ent; @@ -820,7 +825,8 @@ void br_multicast_del_pg(struct net_bridge_mdb_entry *mp, if (!br_multicast_is_star_g(&mp->addr)) { rhashtable_remove_fast(&br->sg_port_tbl, &pg->rhnode, br_sg_port_rht_params); - br_multicast_sg_del_exclude_ports(mp); + if (sg_del_exclude_ports) + br_multicast_sg_del_exclude_ports(mp); } else { br_multicast_star_g_handle_mode(pg, MCAST_INCLUDE); } @@ -832,6 +838,13 @@ void br_multicast_del_pg(struct net_bridge_mdb_entry *mp, mod_timer(&mp->timer, jiffies); } +void br_multicast_del_pg(struct net_bridge_mdb_entry *mp, + struct net_bridge_port_group *pg, + struct net_bridge_port_group __rcu **pp) +{ + __br_multicast_del_pg(mp, pg, pp, true); +} + static void br_multicast_find_del_pg(struct net_bridge *br, struct net_bridge_port_group *pg) { From aaaaf87ea99b8766c9a8aa0e71aa42e6bc8a5320 Mon Sep 17 00:00:00 2001 From: "Denis V. Lunev" Date: Tue, 29 Sep 2026 09:25:19 +0200 Subject: [PATCH 175/367] openvswitch: fix soft lockup in the netlink flow dump A production compute node carrying a few thousand datapath flows hit a soft lockup inside a single netlink flow dump and panicked. ovs_flow_cmd_dump() calls ovs_flow_stats_get() for every flow it emits, and that releases stats->lock with spin_unlock_bh() once per CPU that has touched the flow. Every release is a local_bh_enable(), and each one runs the pending softirq backlog in the dumping thread's own context. The skb bounds the flows one callback emits, but not the softirq work it absorbs. On a CPU that carries the box's packet load the backlog refills as fast as it drains, so the dumping thread becomes that CPU's softirq engine. It never sleeps and it has no reschedule point, so under voluntary preemption nothing can take the CPU away from it: neither the ksoftirqd the kernel woke to take the work over, nor the stopper thread the softlockup detector dispatches to refresh its timestamp. Hold BH off across the whole callback instead, the way ctnetlink_dump_table() does, so the nested spin_unlock_bh() stop draining softirqs. The loop already runs under rcu_read_lock() and cannot sleep. What it gives up is preemption under CONFIG_PREEMPT, since a BH-off region is not preemptible outside PREEMPT_RT. The region stays short: the skb caps the flows one callback emits, and empty buckets cost no skb space but are each visited once per dump, as the cursor only moves forward. The table grows on insert and shrinks only on flush, so the walk is bounded by the largest flow count the datapath has held. The softirq backlog the callback used to absorb has no bound at all. Fixes: 63e7959c4b9b ("openvswitch: Per NUMA node flow stats.") Cc: stable@vger.kernel.org Signed-off-by: Denis V. Lunev Reviewed-by: Ilya Maximets Signed-off-by: David S. Miller --- net/openvswitch/datapath.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/net/openvswitch/datapath.c b/net/openvswitch/datapath.c index 631a03136fa1..4fc5d0bebd85 100644 --- a/net/openvswitch/datapath.c +++ b/net/openvswitch/datapath.c @@ -1532,6 +1532,12 @@ static int ovs_flow_cmd_dump(struct sk_buff *skb, struct netlink_callback *cb) return -ENODEV; } + /* + * Not needed for safety. Stops every spin_unlock_bh() in + * ovs_flow_stats_get() from running the softirq backlog. + */ + local_bh_disable(); + ti = rcu_dereference(dp->table.ti); for (;;) { struct sw_flow *flow; @@ -1552,6 +1558,8 @@ static int ovs_flow_cmd_dump(struct sk_buff *skb, struct netlink_callback *cb) cb->args[0] = bucket; cb->args[1] = obj; } + + local_bh_enable(); rcu_read_unlock(); return skb->len; } From e58c3805ed325fe2d4ae2dd467c0486cc860b733 Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Thu, 1 Oct 2026 10:13:49 -0700 Subject: [PATCH 176/367] fsverity: RCU-delay the freeing of struct fsverity_info __fsverity_get_info() calls rhashtable_lookup_fast(), which just uses rcu_read_lock() and doesn't directly synchronize with fsverity_remove_info(). For the same inode this isn't a problem: its fsverity_info is removed only at inode eviction time or upon failure to enable verity, when the inode no longer needs its fsverity_info and it will no longer be accessed via that inode. However, this is broken and can cause a use-after-free for concurrent __fsverity_get_info() for *different* inodes. Those rely on following fsverity_info::rhash_head in the rhashtable under rcu_read_lock() only. They also use fsverity_info::inode to do the key comparison. Fix this by RCU-delaying the freeing of 'struct fsverity_info' after it's been removed from the rhashtable. Fixes: f77f281b6118 ("fsverity: use a hashtable to find the fsverity_info") Cc: stable@vger.kernel.org Reviewed-by: Christoph Hellwig Reviewed-by: Sandeep Dhavale Link: https://patch.msgid.link/20261001171349.81454-1-ebiggers@kernel.org Signed-off-by: Eric Biggers --- fs/verity/fsverity_private.h | 15 ++++++++++++++- fs/verity/open.c | 13 ++++++++++++- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/fs/verity/fsverity_private.h b/fs/verity/fsverity_private.h index 881d46f25e08..6f85f63861aa 100644 --- a/fs/verity/fsverity_private.h +++ b/fs/verity/fsverity_private.h @@ -11,6 +11,7 @@ #define pr_fmt(fmt) "fs-verity: " fmt #include +#include #include /* @@ -76,7 +77,19 @@ struct merkle_tree_params { struct fsverity_info { struct rhash_head rhash_head; struct merkle_tree_params tree_params; - u8 root_hash[FS_VERITY_MAX_DIGEST_SIZE]; + union { + u8 root_hash[FS_VERITY_MAX_DIGEST_SIZE]; + + /* + * Used for final freeing of the struct, in which case root_hash + * is no longer needed. Specifically, the only fields needed + * besides the rcu_head are those needed by fsverity_free_info() + * itself, and those that can be accessed by fsverity_get_info() + * (rhash_head and inode) during the RCU-protected rhashtable + * chain search trying to find another inode's fsverity_info. + */ + struct rcu_head rcu_head; + }; u8 file_digest[FS_VERITY_MAX_DIGEST_SIZE]; struct inode *inode; unsigned long *hash_block_verified; diff --git a/fs/verity/open.c b/fs/verity/open.c index d0c56a7faa3b..52c8cd014921 100644 --- a/fs/verity/open.c +++ b/fs/verity/open.c @@ -399,11 +399,22 @@ void fsverity_free_info(struct fsverity_info *vi) kmem_cache_free(fsverity_info_cachep, vi); } +static void fsverity_free_info_rcu(struct rcu_head *head) +{ + fsverity_free_info(container_of(head, struct fsverity_info, rcu_head)); +} + void fsverity_remove_info(struct fsverity_info *vi) { rhashtable_remove_fast(&fsverity_info_hash, &vi->rhash_head, fsverity_info_hash_params); - fsverity_free_info(vi); + /* + * The freeing must be RCU-delayed because __fsverity_get_info() for a + * *different* inode's fsverity_info in the same rhashtable hash chain + * can still be accessing *this* fsverity_info's rhash_head and inode + * fields as part of the RCU-protected hash chain search. + */ + call_rcu(&vi->rcu_head, fsverity_free_info_rcu); } void fsverity_cleanup_inode(struct inode *inode) From ae8364085574203e7f4ea961921a1c7493bcb6de Mon Sep 17 00:00:00 2001 From: Rahul Pon Date: Wed, 30 Sep 2026 16:46:41 +0530 Subject: [PATCH 177/367] i2c: qcom-geni: release runtime PM reference when set_rate fails geni_i2c_xfer() takes a runtime-PM reference with pm_runtime_get_sync() and then returns directly if the set_rate() callback fails, leaking the reference and keeping the controller resumed for good. Route that error through the existing cleanup path, which drops the reference and resets the transfer state. Found by code review; compile-tested with arm64 defconfig plus ACPI and W=1. Fixes: 10e74f4c5046 ("i2c: qcom-geni: Enable I2C on SA8255p Qualcomm platforms") Assisted-by: LLM Signed-off-by: Rahul Pon Signed-off-by: Andi Shyti Link: https://patch.msgid.link/20260930111641.1134-1-theflyingrahul@gmail.com --- drivers/i2c/busses/i2c-qcom-geni.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/i2c/busses/i2c-qcom-geni.c b/drivers/i2c/busses/i2c-qcom-geni.c index 6a39e21d5ed4..ed6495ef811c 100644 --- a/drivers/i2c/busses/i2c-qcom-geni.c +++ b/drivers/i2c/busses/i2c-qcom-geni.c @@ -1003,13 +1003,14 @@ static int geni_i2c_xfer(struct i2c_adapter *adap, ret = gi2c->dev_data->set_rate(&gi2c->se, gi2c->clk_freq_out); if (ret) - return ret; + goto out_pm; if (gi2c->gpi_mode) ret = geni_i2c_gpi_xfer(gi2c, msgs, num); else ret = geni_i2c_fifo_xfer(gi2c, msgs, num); +out_pm: pm_runtime_put_autosuspend(gi2c->se.dev); gi2c->cur = NULL; gi2c->err = 0; From 58304e139cb3fa640da74bc02101f0926d0607e2 Mon Sep 17 00:00:00 2001 From: Shin'ichiro Kawasaki Date: Wed, 16 Sep 2026 15:56:57 +0900 Subject: [PATCH 178/367] zloop: zero out only the unread tail of short reads zloop_complete_rq() handles a short read from a zone's backing file by zero-filling every bio in the request. This is correct when the entire request is beyond the written part of the zone. When a read request straddles the boundary between written and unwritten data, read_iter() returns a partial byte count and zloop then zeroes the whole request even for the partially read area with valid data. Such an unexpected zero data read was observed with fio test script t/zbd/test-zbd-support and its test case 69, failing with "bad magic header 0". Avoid the unexpected zero data read by zeroing out only the unread tail part. When read data size cmd->ret is smaller than the request size, call the new helper function zloop_fill_zero_rq() that walks through each bio_vec and fills zeros from the specified start offset. Fixes: eb0570c7df23 ("block: new zoned loop block device driver") Reviewed-by: Damien Le Moal Reviewed-by: Christoph Hellwig Signed-off-by: Shin'ichiro Kawasaki Link: https://patch.msgid.link/20260916065657.62724-1-shinichiro.kawasaki@opensource.wdc.com Signed-off-by: Jens Axboe --- drivers/block/zloop.c | 31 ++++++++++++++++++++++++------- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/drivers/block/zloop.c b/drivers/block/zloop.c index f0ca221524db..394dc2408ed7 100644 --- a/drivers/block/zloop.c +++ b/drivers/block/zloop.c @@ -800,6 +800,27 @@ static void zloop_cmd_workfn(struct work_struct *work) current->flags = orig_flags; } +static void zloop_fill_zero_rq(struct request *rq, unsigned int zero_from) +{ + struct req_iterator iter; + struct bio_vec bv; + unsigned int pos = 0; + unsigned int end, off; + + rq_for_each_segment(bv, rq, iter) { + end = pos + bv.bv_len; + if (end > zero_from) { + if (pos < zero_from) + off = zero_from - pos; + else + off = 0; + memzero_page(bv.bv_page, bv.bv_offset + off, + bv.bv_len - off); + } + pos = end; + } +} + static void zloop_complete_rq(struct request *rq) { struct zloop_cmd *cmd = blk_mq_rq_to_pdu(rq); @@ -814,13 +835,9 @@ static void zloop_complete_rq(struct request *rq) pr_err("Zone %u: failed read sector %llu, %llu sectors\n", zone_no, cmd->sector, cmd->nr_sectors); - if (cmd->ret >= 0 && cmd->ret != blk_rq_bytes(rq)) { - /* short read */ - struct bio *bio; - - __rq_for_each_bio(bio, rq) - zero_fill_bio(bio); - } + /* Short read. Zero out the unread tail beyond cmd->ret. */ + if (cmd->ret >= 0 && cmd->ret != blk_rq_bytes(rq)) + zloop_fill_zero_rq(rq, cmd->ret); break; case REQ_OP_WRITE: case REQ_OP_ZONE_APPEND: From 5755f1bd0217ffe3c5cf85467bf6b0680f398265 Mon Sep 17 00:00:00 2001 From: Keith Busch Date: Mon, 5 Oct 2026 04:18:24 -0700 Subject: [PATCH 179/367] Revert "nvme: do not reset controllers in NVME_CTRL_NEW state" This reverts commit c20bba234f3e1dcd81f7a2ec21ea7bf01a4cf35f. The standard transport drivers were okay with the patch, but the Apple driver needs a bit of work before it can handle the state transition requirement. Link: https://lore.kernel.org/linux-nvme/94912d46-381d-4988-800a-6ef3b595233f@cyberchaos.dev/ Reported-by: Yureka Lilian Signed-off-by: Keith Busch --- drivers/nvme/host/core.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/nvme/host/core.c b/drivers/nvme/host/core.c index 5d7dfd7a63d4..08f45905e103 100644 --- a/drivers/nvme/host/core.c +++ b/drivers/nvme/host/core.c @@ -598,6 +598,7 @@ bool nvme_change_ctrl_state(struct nvme_ctrl *ctrl, break; case NVME_CTRL_RESETTING: switch (old_state) { + case NVME_CTRL_NEW: case NVME_CTRL_LIVE: changed = true; atomic_long_inc(&ctrl->nr_reset); From 3e6ff34afc7ec7a6156e58502b5a56771517cf93 Mon Sep 17 00:00:00 2001 From: Filipe Manana Date: Fri, 18 Sep 2026 11:58:00 +0100 Subject: [PATCH 180/367] btrfs: clear BTRFS_ROOT_IN_TRANS_SETUP on early exit from record_root_in_trans() If we exit early because the transaction that last used the root already matches the current transaction, we leave the BTRFS_ROOT_IN_TRANS_SETUP bit set in the root (which we just set right before the exit). While this does not cause any functional issue, it makes callers of btrfs_record_root_in_trans() lock fs_info->reloc_mutex and call record_root_in_trans() for nothing, causing unnecessary lock contention, until one of them clears the bit in record_root_in_trans(). One caller of btrfs_record_root_in_trans() is start_transaction(), used to start new transaction or joining an existing one, which is a hot path. So clear BTRFS_ROOT_IN_TRANS_SETUP on early exit. Assisted-by: LLM Reviewed-by: Boris Burkov Reviewed-by: Qu Wenruo Signed-off-by: Filipe Manana Signed-off-by: David Sterba --- fs/btrfs/transaction.c | 1 + 1 file changed, 1 insertion(+) diff --git a/fs/btrfs/transaction.c b/fs/btrfs/transaction.c index 6802b94ed76f..11b38133f737 100644 --- a/fs/btrfs/transaction.c +++ b/fs/btrfs/transaction.c @@ -432,6 +432,7 @@ static int record_root_in_trans(struct btrfs_trans_handle *trans, spin_lock(&fs_info->fs_roots_radix_lock); if (btrfs_get_root_last_trans(root) == trans->transid && !force) { spin_unlock(&fs_info->fs_roots_radix_lock); + clear_bit(BTRFS_ROOT_IN_TRANS_SETUP, &root->state); return 0; } radix_tree_tag_set(&fs_info->fs_roots_radix, From 1fd742c7479961da6d0bb794ef8ac6b7b9442847 Mon Sep 17 00:00:00 2001 From: Wentao Liang Date: Wed, 16 Sep 2026 17:16:10 +0000 Subject: [PATCH 181/367] btrfs: scrub: fix local_root reference leak in scrub_print_warning_inode() When paths_from_inode() fails, scrub_print_warning_inode() jumps to err without dropping the reference taken by btrfs_get_fs_root(), leaking a reference to the root every time path resolution fails while printing scrub warnings. Every other error and success path of the function drops the reference. Drop the reference on the paths_from_inode() failure path too. Fixes: 558540c17771 ("btrfs scrub: print paths of corrupted files") CC: stable@vger.kernel.org Reviewed-by: Qu Wenruo Signed-off-by: Wentao Liang Reviewed-by: David Sterba Signed-off-by: David Sterba --- fs/btrfs/scrub.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/fs/btrfs/scrub.c b/fs/btrfs/scrub.c index c09d4213ad89..479c0e037015 100644 --- a/fs/btrfs/scrub.c +++ b/fs/btrfs/scrub.c @@ -536,8 +536,10 @@ static int scrub_print_warning_inode(u64 inum, u64 offset, u64 num_bytes, } ret = paths_from_inode(inum, ipath); - if (ret < 0) + if (ret < 0) { + btrfs_put_root(local_root); goto err; + } /* * we deliberately ignore the bit ipath might have been too small to From 80a8dbb30d2b00b9c671b02bd73f700f6d20100f Mon Sep 17 00:00:00 2001 From: Yang Xiuwei Date: Wed, 19 Aug 2026 10:54:32 +0800 Subject: [PATCH 182/367] btrfs: always return -EIOCBQUEUED after btrfs_uring_read_extent_endio If all bios finish before btrfs_encoded_read_regular_fill_pages() returns, it calls btrfs_uring_read_extent_endio() and previously returned the I/O status. A negative errno then made btrfs_uring_read_extent() unlock and free while btrfs_uring_read_finished() did the same again. Return -EIOCBQUEUED so only the deferred path cleans up. Reported-by: Yue Sun Closes: https://lore.kernel.org/linux-btrfs/20260630091609.3414-1-samsun1006219@gmail.com/ Suggested-by: Jens Axboe Fixes: 34310c442e17 ("btrfs: add io_uring command for encoded reads (ENCODED_READ ioctl)") Signed-off-by: Yang Xiuwei Reviewed-by: David Sterba Signed-off-by: David Sterba --- fs/btrfs/inode.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c index e97446fc9e10..6a085a4d5d61 100644 --- a/fs/btrfs/inode.c +++ b/fs/btrfs/inode.c @@ -9623,7 +9623,6 @@ int btrfs_encoded_read_regular_fill_pages(struct btrfs_inode *inode, struct completion sync_reads; unsigned long i = 0; struct btrfs_bio *bbio; - int ret; /* * Fast path for synchronous reads which completes in this call, io_uring @@ -9670,10 +9669,10 @@ int btrfs_encoded_read_regular_fill_pages(struct btrfs_inode *inode, if (uring_ctx) { if (refcount_dec_and_test(&priv->pending_refs)) { - ret = blk_status_to_errno(READ_ONCE(priv->status)); - btrfs_uring_read_extent_endio(uring_ctx, ret); + int error = blk_status_to_errno(READ_ONCE(priv->status)); + + btrfs_uring_read_extent_endio(uring_ctx, error); kfree(priv); - return ret; } return -EIOCBQUEUED; From a8ea92830b3e0e163d4e829bdcb17ca83c0f951c Mon Sep 17 00:00:00 2001 From: Yang Xiuwei Date: Wed, 19 Aug 2026 10:54:33 +0800 Subject: [PATCH 183/367] btrfs: free iov when btrfs_uring_read_extent() fails After btrfs_uring_read_extent(), the caller always jumped to out_acct. That skips kfree(data->iov), which is only correct for -EIOCBQUEUED where the deferred path owns the iov. On failure, fall through to out_free instead. Fixes: 34310c442e17 ("btrfs: add io_uring command for encoded reads (ENCODED_READ ioctl)") Signed-off-by: Yang Xiuwei Reviewed-by: David Sterba Signed-off-by: David Sterba --- fs/btrfs/ioctl.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fs/btrfs/ioctl.c b/fs/btrfs/ioctl.c index 21c4e755f9c5..58eba94a73b8 100644 --- a/fs/btrfs/ioctl.c +++ b/fs/btrfs/ioctl.c @@ -4865,8 +4865,8 @@ static int btrfs_uring_encoded_read(struct io_uring_cmd *cmd, unsigned int issue cached_state, disk_bytenr, disk_io_size, count, data->args.compression, data->iov, cmd); - - goto out_acct; + if (ret == -EIOCBQUEUED) + goto out_acct; } out_free: From b6e8add5d79d1f6f819a054c833bab452bd7365d Mon Sep 17 00:00:00 2001 From: Yang Xiuwei Date: Wed, 19 Aug 2026 10:54:34 +0800 Subject: [PATCH 184/367] btrfs: unlock inode and extent in caller when io_uring read extent fails btrfs_uring_read_extent() runs only after btrfs_encoded_read() has taken the inode shared lock and the extent lock. On failure it used to unlock in out_fail, and a pages-array allocation failure returned -ENOMEM without unlocking at all. Unlock in the caller instead on all failure returns, matching the copy_to_user() error path. The deferred -EIOCBQUEUED path still unlocks in btrfs_uring_read_finished(). Fixes: 34310c442e17 ("btrfs: add io_uring command for encoded reads (ENCODED_READ ioctl)") Suggested-by: Qu Wenruo Reviewed-by: Qu Wenruo Signed-off-by: Yang Xiuwei Reviewed-by: David Sterba Signed-off-by: David Sterba --- fs/btrfs/ioctl.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/fs/btrfs/ioctl.c b/fs/btrfs/ioctl.c index 58eba94a73b8..131373bf1e30 100644 --- a/fs/btrfs/ioctl.c +++ b/fs/btrfs/ioctl.c @@ -4601,7 +4601,7 @@ static void btrfs_uring_read_finished(struct io_tw_req tw_req, io_tw_token_t tw) size_t page_offset; ssize_t ret; - /* The inode lock has already been acquired in btrfs_uring_read_extent. */ + /* The inode lock has already been acquired in btrfs_encoded_read(). */ btrfs_lockdep_inode_acquire(inode, i_rwsem); if (priv->err) { @@ -4667,7 +4667,6 @@ static int btrfs_uring_read_extent(struct kiocb *iocb, struct iov_iter *iter, struct iovec *iov, struct io_uring_cmd *cmd) { struct btrfs_inode *inode = BTRFS_I(file_inode(iocb->ki_filp)); - struct extent_io_tree *io_tree = &inode->io_tree; struct page **pages = NULL; struct btrfs_uring_priv *priv = NULL; unsigned long nr_pages; @@ -4723,8 +4722,6 @@ static int btrfs_uring_read_extent(struct kiocb *iocb, struct iov_iter *iter, return -EIOCBQUEUED; out_fail: - btrfs_unlock_extent(io_tree, start, lockend, &cached_state); - btrfs_inode_unlock(inode, BTRFS_ILOCK_SHARED); kfree(priv); for (int i = 0; i < nr_pages; i++) { if (pages[i]) @@ -4867,6 +4864,8 @@ static int btrfs_uring_encoded_read(struct io_uring_cmd *cmd, unsigned int issue data->iov, cmd); if (ret == -EIOCBQUEUED) goto out_acct; + btrfs_unlock_extent(io_tree, start, lockend, &cached_state); + btrfs_inode_unlock(inode, BTRFS_ILOCK_SHARED); } out_free: From 51562a8cb11628d3a5e105b4c347ec5473e5745f Mon Sep 17 00:00:00 2001 From: Yang Xiuwei Date: Wed, 19 Aug 2026 10:54:35 +0800 Subject: [PATCH 185/367] btrfs: don't stash io_uring encoded data across -EAGAIN Returning -EAGAIN while leaving btrfs_uring_encoded_data in the cmd PDU leaks if the request is cancelled or the ring exits before reissue. io_uring does not free driver PDU allocations on cleanup. Write: io_queue_sqe() always issues with IO_URING_F_NONBLOCK first, so return -EAGAIN before allocating and free data on every exit. Read: free on nowait -EAGAIN too; only -EIOCBQUEUED keeps the allocation for btrfs_uring_read_finished(). Fixes: 34310c442e17 ("btrfs: add io_uring command for encoded reads (ENCODED_READ ioctl)") Fixes: e32dcdb0af9f ("btrfs: add io_uring interface for encoded writes") Signed-off-by: Yang Xiuwei Reviewed-by: David Sterba Signed-off-by: David Sterba --- fs/btrfs/ioctl.c | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/fs/btrfs/ioctl.c b/fs/btrfs/ioctl.c index 131373bf1e30..44d582d8cf3f 100644 --- a/fs/btrfs/ioctl.c +++ b/fs/btrfs/ioctl.c @@ -4834,7 +4834,7 @@ static int btrfs_uring_encoded_read(struct io_uring_cmd *cmd, unsigned int issue ret = btrfs_encoded_read(&kiocb, &data->iter, &data->args, &cached_state, &disk_bytenr, &disk_io_size); if (ret == -EAGAIN) - goto out_acct; + goto out_free; if (ret < 0 && ret != -EIOCBQUEUED) goto out_free; @@ -4876,8 +4876,10 @@ static int btrfs_uring_encoded_read(struct io_uring_cmd *cmd, unsigned int issue add_rchar(current, ret); inc_syscr(current); - if (ret != -EIOCBQUEUED && ret != -EAGAIN) + if (ret != -EIOCBQUEUED) { kfree(data); + bc->data = NULL; + } return ret; } @@ -4906,6 +4908,11 @@ static int btrfs_uring_encoded_write(struct io_uring_cmd *cmd, unsigned int issu goto out_acct; } + if (issue_flags & IO_URING_F_NONBLOCK) { + ret = -EAGAIN; + goto out_acct; + } + if (!data) { data = kzalloc_obj(*data, GFP_NOFS); if (!data) { @@ -4974,11 +4981,6 @@ static int btrfs_uring_encoded_write(struct io_uring_cmd *cmd, unsigned int issu } } - if (issue_flags & IO_URING_F_NONBLOCK) { - ret = -EAGAIN; - goto out_acct; - } - pos = data->args.offset; ret = rw_verify_area(WRITE, file, &pos, data->args.len); if (ret < 0) @@ -5004,8 +5006,8 @@ static int btrfs_uring_encoded_write(struct io_uring_cmd *cmd, unsigned int issu add_wchar(current, ret); inc_syscw(current); - if (ret != -EAGAIN) - kfree(data); + kfree(data); + bc->data = NULL; return ret; } From 858bee1c77857aabcc2327cd32dd8266332de332 Mon Sep 17 00:00:00 2001 From: Filipe Manana Date: Fri, 25 Sep 2026 13:47:21 +0100 Subject: [PATCH 186/367] btrfs: fix xattr replace when multiple xattrs are packed in the same item If we have a btrfs_dir_item item that packs multiple xattrs and then we replace the value of one of them (with the setxattr(2) family of syscalls) with another value of a different size, we end up not having a fully initialized btrfs_dir_item, resulting in a corruption that the tree checker will detect at extent buffer writeback time. This is because in btrfs_setxattr() when we find a btrfs_dir_item with multiple xattrs (due to the crc32c hash of their name being the same) we delete one of the xattr items (btrfs_dir_item) and then insert a new one, but the deletion and insertion results in shifting existing data in the leaf and therefore when the new value of a xattr has a different size, the new btrfs_dir_item is placed in a leaf section that was not initialized and we only copy the value's data and set the value's length in the new btrfs_dir_item, without setting the name, the name's length, the key (which must be all zeroes for xattrs), flags (BTRFS_FT_XATTR) and transaction ID. The following script reproduces the issue: $ cat test.sh #!/bin/bash DEV=/dev/sdi MNT=/mnt/sdi mkfs.btrfs -f $DEV mount $DEV $MNT touch $MNT/testfile # Add two xattrs that, on btrfs, have the same hash (crc32c) for their # name and therefore are packed into the same btrfs_dir_item. setfattr -n user.foobar -v 123 $MNT/testfile setfattr -n user.WvG1c1Td -v qwerty $MNT/testfile # Verify the xattrs are present. echo "xattrs before:" getfattr --absolute-names --dump $MNT/testfile # Now replace the value of the foobar xattr with a significantly larger # value. setfattr -n user.foobar -v abcdefghijklmnopqrstuvwxyz $MNT/testfile # Check the xattrs have the expected values. echo "xattrs after:" getfattr --absolute-names --dump $MNT/testfile umount $MNT Running it: $ ./test.sh (...) xattrs before: # file: /mnt/sdi/testfile user.WvG1c1Td="qwerty" user.foobar="123" xattrs after: # file: /mnt/sdi/testfile user.WvG1c1Td="qwerty" So the "user.foobar" xattr is missing and there was a transaction abort when unmounting the fs with the following traces in dmesg: $ dmesg [869800.159271] BTRFS warning (device sdi): access to eb bytenr 30474240 len 16384 out of range start 16015 len 25964 [869800.159293] ------------[ cut here ]------------ [869800.159296] WARNING: fs/btrfs/extent_io.c:4408 at report_eb_range+0x44/0x60 [btrfs], CPU#8: getfattr/2605179 [869800.168961] Modules linked in: btrfs dm_thin_pool (...) [869800.190527] CPU: 8 UID: 0 PID: 2605179 Comm: getfattr Tainted: G W 7.3.0-rc3-btrfs-next-244+ #1 PREEMPT(full) [869800.193726] Tainted: [W]=WARN [869800.194502] Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS rel-1.16.2-0-gea1b7a073390-prebuilt.qemu.org 04/01/2014 [869800.197576] RIP: 0010:report_eb_range+0x44/0x60 [btrfs] [869800.198985] Code: 48 8b 7b 18 (...) [869800.203531] RSP: 0018:ffffce4541937d58 EFLAGS: 00010246 [869800.204609] RAX: 0000000000000000 RBX: ffff8dde054b8738 RCX: 0000000000000000 [869800.206137] RDX: 0000000000000000 RSI: 0000000000000001 RDI: ffffffffc04c92a0 [869800.207645] RBP: 0000000000003e8f R08: 0000000000000000 R09: 3fffffffffefffff [869800.226175] R10: ffffce4541937a88 R11: 0000000000000003 R12: 000000000000656c [869800.227594] R13: ffff8dde14be800f R14: 000000000000656c R15: 0000000000000069 [869800.229097] FS: 00007f59023bb780(0000) GS:ffff8de5788ed000(0000) knlGS:0000000000000000 [869800.231137] CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 [869800.232321] CR2: 0000558dc45e6a78 CR3: 0000000765b16004 CR4: 0000000000370ef0 [869800.233783] Call Trace: [869800.234330] [869800.234791] read_extent_buffer+0x4d/0x100 [btrfs] [869800.235898] btrfs_listxattr+0x199/0x240 [btrfs] [869800.236912] vfs_listxattr+0x51/0xa0 [869800.237683] listxattr+0x7e/0x100 [869800.238398] path_listxattrat+0x9e/0x190 [869800.239117] do_syscall_64+0x89/0x470 [869800.239877] entry_SYSCALL_64_after_hwframe+0x76/0x7e [869800.240914] RIP: 0033:0x7f59024cdcb7 [869800.241686] Code: f0 ff ff 73 (...) [869800.245353] RSP: 002b:00007ffd056dcdb8 EFLAGS: 00000246 ORIG_RAX: 00000000000000c2 [869800.246892] RAX: ffffffffffffffda RBX: 00007ffd056df2e2 RCX: 00007f59024cdcb7 [869800.248853] RDX: 0000000000006600 RSI: 0000558dc45e0470 RDI: 00007ffd056df2e2 [869800.250514] RBP: 00007ffd056df2e2 R08: 0000000000006600 R09: 0000000000006600 [869800.252295] R10: 0000000000000004 R11: 0000000000000246 R12: 00000000ffffff9c [869800.254105] R13: 0000558dc45e0470 R14: 0000000000006600 R15: 0000000000000000 [869800.255931] [869800.256529] ---[ end trace 0000000000000000 ]--- [869800.260071] page: refcount:2 mapcount:0 mapping:000000007ccfc77f index:0x1d10 pfn:0x608c69 [869800.260075] memcg:ffff8dde00344d40 [869800.260076] aops:btree_aops [btrfs] ino:1 [869800.260151] flags: 0x17fffc00000402a(uptodate|lru|private|writeback|node=0|zone=2|lastcpupid=0x1ffff) [869800.260154] raw: 017fffc00000402a fffff4adc773d4c8 fffff4adc48f3d88 ffff8de36504ba30 [869800.260155] raw: 0000000000001d10 ffff8dde054b8738 00000002ffffffff ffff8dde00344d40 [869800.260156] page dumped because: eb page dump [869800.260157] BTRFS critical (device sdi): corrupt leaf: root=5 block=30474240 slot=5 ino=257, invalid location key type, have 46, expect 132 or 1 [869800.260161] BTRFS info (device sdi): leaf 30474240 gen 9 total ptrs 6 free space 15629 owner 5 [869800.260163] BTRFS info (device sdi): refs 3 lock_owner 0 current 2550294 [869800.260164] item 0 key (256 INODE_ITEM 0) itemoff 16123 itemsize 160 [869800.260165] inode generation 3 transid 0 size 0 nbytes 16384 [869800.260166] block group 0 mode 40755 links 1 uid 0 gid 0 [869800.260167] rdev 0 sequence 0 flags 0x0 [869800.260168] atime 1790340637.0 [869800.260169] ctime 1790340637.0 [869800.260169] mtime 1790340637.0 [869800.260170] otime 1790340637.0 [869800.260170] item 1 key (256 INODE_REF 256) itemoff 16111 itemsize 12 [869800.260172] index 0 name_len 2 [869800.260172] item 2 key (256 DIR_ITEM 982728850) itemoff 16073 itemsize 38 [869800.260173] location key (257 1 0) type 1 [869800.260174] transid 9 data_len 0 name_len 8 [869800.260175] item 3 key (257 INODE_ITEM 0) itemoff 15913 itemsize 160 [869800.260176] inode generation 9 transid 9 size 0 nbytes 0 [869800.260177] block group 0 mode 100664 links 1 uid 0 gid 0 [869800.260177] rdev 0 sequence 0 flags 0x0 [869800.260178] atime 1790340638.38778502 [869800.260179] ctime 1790340638.38778502 [869800.260179] mtime 1790340638.38778502 [869800.260180] otime 1790340638.38778502 [869800.260180] item 4 key (257 INODE_REF 256) itemoff 15895 itemsize 18 [869800.260181] index 2 name_len 8 [869800.260182] item 5 key (257 XATTR_ITEM 751495445) itemoff 15779 itemsize 116 [869800.260183] location key (0 0 0) type 8 [869800.266052] transid 9 data_len 6 name_len 13 [869800.266053] location key (8243121639454149888 46 7229457603934778967) type 0 [869800.266055] transid 113 data_len 26 name_len 0 [869800.266056] location key (8608196880778817904 120 162425) type 9 [869800.266057] transid 8391162079612502016 data_len 26982 name_len 25964 [869800.266058] BTRFS error (device sdi): block=30474240 write time tree block corruption detected [869800.266091] ------------[ cut here ]------------ [869800.266092] WARNING: fs/btrfs/disk-io.c:336 at btree_csum_one_bio+0x20b/0x220 [btrfs], CPU#7: kworker/u50:7/2550294 [869800.268392] Modules linked in: btrfs dm_thin_pool (...) [869800.365851] CPU: 7 UID: 0 PID: 2550294 Comm: kworker/u50:7 Tainted: G W 7.3.0-rc3-btrfs-next-244+ #1 PREEMPT(full) [869800.368937] Tainted: [W]=WARN [869800.369834] Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS rel-1.16.2-0-gea1b7a073390-prebuilt.qemu.org 04/01/2014 [869800.372778] Workqueue: writeback wb_workfn (flush-btrfs-3821) [869800.374314] RIP: 0010:btree_csum_one_bio+0x20b/0x220 [btrfs] [869800.375915] Code: 89 44 24 04 (...) [869800.380639] RSP: 0018:ffffce4548e3f7d0 EFLAGS: 00010246 [869800.382008] RAX: 0000000000000000 RBX: ffff8dde054b8738 RCX: 0000000000000000 [869800.383850] RDX: 0000000000000000 RSI: 0000000000000001 RDI: ffff8de091c2ddc0 [869800.385710] RBP: ffff8dde196a2000 R08: 0000000000000000 R09: 3fffffffffefffff [869800.387388] R10: ffffce4548e3f500 R11: 0000000000000003 R12: ffffce4548e3f7d8 [869800.388973] R13: ffff8dde196a2000 R14: ffff8de36504b750 R15: ffff8dde4c497b00 [869800.390414] FS: 0000000000000000(0000) GS:ffff8de5788ad000(0000) knlGS:0000000000000000 [869800.392002] CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 [869800.393160] CR2: 000055cd6e92ad5c CR3: 00000007cb264001 CR4: 0000000000370ef0 [869800.394595] Call Trace: [869800.395113] [869800.395570] btrfs_submit_bbio+0x872/0x890 [btrfs] [869800.397284] write_meta_extent_buffer+0x70/0x80 [btrfs] [869800.398940] btree_writepages+0x141/0x4f0 [btrfs] [869800.400426] ? get_random_u32+0x8a/0xf0 [869800.401417] ? build_slab_freelist+0x47/0x130 [869800.402574] ? preempt_count_add+0x6b/0xa0 [869800.403633] ? _raw_spin_lock_irqsave+0x23/0x50 [869800.404807] ? _raw_spin_unlock_irqrestore+0x22/0x40 [869800.406085] ? alloc_from_new_slab+0x18f/0x330 [869800.407223] do_writepages+0xc6/0x160 [869800.408191] ? refill_objects+0xd8/0x300 [869800.409211] __writeback_single_inode+0x42/0x350 [869800.410408] writeback_sb_inodes+0x231/0x560 [869800.411511] wb_writeback+0x8a/0x300 [869800.412440] wb_workfn+0xbf/0x460 [869800.413291] ? _raw_spin_unlock+0x14/0x30 [869800.414328] ? finish_task_switch.isra.0+0xb9/0x380 [869800.415105] process_one_work+0x1d1/0x3d0 [869800.416633] worker_thread+0x1c4/0x330 [869800.417467] ? __pfx_worker_thread+0x10/0x10 [869800.418452] kthread+0xfc/0x130 [869800.419257] ? __pfx_kthread+0x10/0x10 [869800.420089] ret_from_fork+0x1f7/0x2c0 [869800.420863] ? __pfx_kthread+0x10/0x10 [869800.421654] ret_from_fork_asm+0x1a/0x30 [869800.422484] [869800.422953] ---[ end trace 0000000000000000 ]--- [869800.424005] BTRFS error (device sdi state A): Transaction 9 aborted (-EIO) [869800.424010] BTRFS: error (device sdi state A) in __btrfs_run_delayed_items:1162: errno=-5 IO failure [869800.424011] BTRFS info (device sdi state EA): forced readonly [869800.424013] BTRFS warning (device sdi state EA): Skipping commit of aborted transaction. [869800.424014] BTRFS: error (device sdi state EA) in cleanup_transaction:2076: errno=-5 IO failure Fix this by always setting all fields in the new btrfs_dir_item when we replace an existing xattr. Fixes: 5f5bc6b1e2d5 ("Btrfs: make xattr replace operations atomic") Reviewed-by: Qu Wenruo Signed-off-by: Filipe Manana Signed-off-by: David Sterba --- fs/btrfs/xattr.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/fs/btrfs/xattr.c b/fs/btrfs/xattr.c index ab55d10bd71f..e8762e12f3e1 100644 --- a/fs/btrfs/xattr.c +++ b/fs/btrfs/xattr.c @@ -161,6 +161,7 @@ int btrfs_setxattr(struct btrfs_trans_handle *trans, struct inode *inode, const u16 old_data_len = btrfs_dir_data_len(leaf, di); const u32 item_size = btrfs_item_size(leaf, slot); const u32 data_size = sizeof(*di) + name_len + size; + unsigned long name_ptr; unsigned long data_ptr; char *ptr; @@ -189,8 +190,16 @@ int btrfs_setxattr(struct btrfs_trans_handle *trans, struct inode *inode, ptr = btrfs_item_ptr(leaf, slot, char); ptr += btrfs_item_size(leaf, slot) - data_size; di = (struct btrfs_dir_item *)ptr; + memzero_extent_buffer(leaf, (unsigned long)ptr + + offsetof(struct btrfs_dir_item, location), + sizeof(struct btrfs_disk_key)); + btrfs_set_dir_flags(leaf, di, BTRFS_FT_XATTR); + btrfs_set_dir_transid(leaf, di, trans->transid); + btrfs_set_dir_name_len(leaf, di, name_len); btrfs_set_dir_data_len(leaf, di, size); + name_ptr = (unsigned long)(di + 1); data_ptr = ((unsigned long)(di + 1)) + name_len; + write_extent_buffer(leaf, name, name_ptr, name_len); write_extent_buffer(leaf, value, data_ptr, size); } else { /* From 41c8899d59898f6fa51ceebd2b6e55257191a8ee Mon Sep 17 00:00:00 2001 From: Filipe Manana Date: Fri, 25 Sep 2026 17:12:45 +0100 Subject: [PATCH 187/367] btrfs: fix lost error return value in btrfs_listxattr() If the input buffer does not have enough space to store the current xattr, we set 'iter_ret' to -ERANGE and then do "break", but that only exits the while loop over the xattrs in the current btrfs_dir_item, and then we continue the btrfs_for_each_slot() iteration, which overwrites the value of 'iter_ret' causing us to lose the error return value and proceed as if the buffer has enough space. Fix this by returning -ERANGE directly (the path is automatically freed) instead of breaking from the while loop. Fixes: 184b3d190087 ("btrfs: use btrfs_for_each_slot in btrfs_listxattr") Assisted-by: LLM (found the bug) Reviewed-by: Qu Wenruo Signed-off-by: Filipe Manana Signed-off-by: David Sterba --- fs/btrfs/xattr.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/fs/btrfs/xattr.c b/fs/btrfs/xattr.c index e8762e12f3e1..cb001a211f55 100644 --- a/fs/btrfs/xattr.c +++ b/fs/btrfs/xattr.c @@ -329,10 +329,8 @@ ssize_t btrfs_listxattr(struct dentry *dentry, char *buffer, size_t size) if (!size) goto next; - if (!buffer || (name_len + 1) > size_left) { - iter_ret = -ERANGE; - break; - } + if (!buffer || (name_len + 1) > size_left) + return -ERANGE; read_extent_buffer(leaf, buffer, name_ptr, name_len); buffer[name_len] = '\0'; From d0cb7a7b2a490c381ad97f53155d02a748b60dea Mon Sep 17 00:00:00 2001 From: Michael Garland Date: Mon, 5 Oct 2026 01:10:32 -0600 Subject: [PATCH 188/367] ASoC: amd: yc: Add DMI quirk for Acer Aspire Go 15 (AG15-21P) The Acer Aspire Go 15 (AG15-21P, board MDC Herbag2_MDU) has two PDM DMICs connected to the ACP. The BIOS does not set the AcpDmicConnected _DSD property, so the acp6x machine driver never registers the DMIC card. The existing Herbag_MDU entry does not match this board name, so add a DMI quirk for it. Tested on BIOS V1.11: the acp6x DMIC capture device is registered and records audio on both channels. Cc: stable@vger.kernel.org Signed-off-by: Michael Garland Link: https://patch.msgid.link/20261005-acp6x-herbag2-quirk-v1-1-a1f2683d758f@gmail.com Signed-off-by: Mark Brown --- sound/soc/amd/yc/acp6x-mach.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/sound/soc/amd/yc/acp6x-mach.c b/sound/soc/amd/yc/acp6x-mach.c index e798bf6a9cd0..1be8504571ad 100644 --- a/sound/soc/amd/yc/acp6x-mach.c +++ b/sound/soc/amd/yc/acp6x-mach.c @@ -787,6 +787,13 @@ static const struct dmi_system_id yc_acp_quirk_table[] = { DMI_MATCH(DMI_BOARD_NAME, "Herbag_MDU"), } }, + { + .driver_data = &acp6x_card, + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "MDC"), + DMI_MATCH(DMI_BOARD_NAME, "Herbag2_MDU"), + } + }, { .driver_data = &acp6x_card, .matches = { From 381a0925c556a8d91a80a9b9e3e86651d60ffee3 Mon Sep 17 00:00:00 2001 From: Chengfeng Ye Date: Mon, 5 Oct 2026 00:24:42 +0800 Subject: [PATCH 189/367] Bluetooth: hci_sock: Serialize dead-device detachment Rebinding an HCI socket after its controller is unregistered can race monitor control replay and crash the kernel. hci_sock_bind() clears hci_pi(sk)->hdev and drops the device reference under only the socket lock, whereas send_monitor_control_replay() holds hci_sk_list.lock for reading. Replay can observe a non-NULL hdev, then bind can clear it and drop the last reference before create_monitor_ctrl_open() reads hdev->id. This can cause a NULL pointer dereference if the pointer is reloaded, or a use-after-free if the old pointer is retained. The kernel reported: KASAN: null-ptr-deref in range [0x0000000000000068-0x000000000000006f] RIP: 0010:create_monitor_ctrl_open+0x579/0x800 Call Trace: hci_sock_bind+0xd96/0x1190 __sys_bind+0x166/0x200 __x64_sys_bind+0x6d/0xb0 do_syscall_64+0xdd/0x4a0 entry_SYSCALL_64_after_hwframe+0x77/0x7f The same missing serialization lets hci_send_to_sock() select a socket for the old device and queue its frame after bind has detached the socket or installed a new binding. Take hci_sk_list.lock for writing when clearing the device pointer and resetting the socket state. This waits for existing replay and delivery readers to finish before detachment and prevents later readers from using the old binding. Keep hci_dev_put() outside the critical section because the final device release can sleep. Fixes: e04480920d1e ("Bluetooth: defer cleanup of resources in hci_unregister_dev()") Cc: stable@vger.kernel.org Assisted-by: GPT-6 Astra Signed-off-by: Chengfeng Ye Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/hci_sock.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/net/bluetooth/hci_sock.c b/net/bluetooth/hci_sock.c index 6d56c77741e1..81068b585764 100644 --- a/net/bluetooth/hci_sock.c +++ b/net/bluetooth/hci_sock.c @@ -1223,8 +1223,10 @@ static int hci_sock_bind(struct socket *sock, struct sockaddr_unsized *addr, */ hdev = hci_pi(sk)->hdev; if (hdev && hci_dev_test_flag(hdev, HCI_UNREGISTER)) { + write_lock(&hci_sk_list.lock); hci_pi(sk)->hdev = NULL; sk->sk_state = BT_OPEN; + write_unlock(&hci_sk_list.lock); hci_dev_put(hdev); } hdev = NULL; From 9af12272061d206af8bc3e40215cb694421ab73a Mon Sep 17 00:00:00 2001 From: Chengfeng Ye Date: Mon, 5 Oct 2026 00:26:12 +0800 Subject: [PATCH 190/367] Bluetooth: hci_sync: Fix command skb lifetime during scan setup During PA sync scan setup, hci_le_set_ext_scan_param_sync() gets the address used for the PA_LINK lookup from hci_sent_cmd_data(). The returned pointer borrows storage from sent_cmd or req_skb, without holding a reference to either skb. The scan worker runs on req_workqueue while hci_cmd_work() runs on the separate device workqueue. After the scan worker loads sent_cmd, the command worker can free and replace it before hci_sent_cmd_data() dereferences the skb. The command payload can also be freed between returning from the helper and comparing the address. The connection lookup's RCU critical section does not protect the command skb. KASAN reported: BUG: KASAN: slab-use-after-free in hci_sent_cmd_data+0x27c/0x2f0 Workqueue: hci0 hci_cmd_sync_work Call Trace: hci_sent_cmd_data+0x27c/0x2f0 hci_le_set_scan_param_sync+0x43a/0x850 hci_passive_scan_sync+0xb09/0x1460 hci_update_passive_scan_sync+0x47c/0x6e0 hci_le_pa_create_sync+0x200/0xa70 hci_cmd_sync_work+0x13c/0x290 Allocated by task 95: skb_clone+0x13f/0x340 hci_cmd_work+0x2a9/0x7e0 Freed by task 95: kmem_cache_free+0xba/0x3a0 hci_cmd_work+0x29c/0x7e0 Use hdev->lock to serialize the address snapshot with sent_cmd replacement. Protect req_skb replacement and completion cleanup with the same lock because the helper falls back to that skb. Copy the address before releasing the lock and use the copy for the existing RCU-protected connection lookup. Release the mutex before sending or waiting for commands, preserving the lookup and completion ordering. Fixes: 22cbf4f84c00 ("Bluetooth: hci_sync: Use QoS to determine which PHY to scan") Cc: stable@vger.kernel.org Assisted-by: GPT-6 Astra Signed-off-by: Chengfeng Ye Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/hci_core.c | 4 ++++ net/bluetooth/hci_sync.c | 10 +++++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/net/bluetooth/hci_core.c b/net/bluetooth/hci_core.c index 2076689eb302..74d9865e08c2 100644 --- a/net/bluetooth/hci_core.c +++ b/net/bluetooth/hci_core.c @@ -4115,9 +4115,11 @@ static int hci_send_cmd_sync(struct hci_dev *hdev, struct sk_buff *skb) bt_dev_dbg(hdev, "skb %p", skb); + hci_dev_lock(hdev); kfree_skb(hdev->sent_cmd); hdev->sent_cmd = skb_clone(skb, GFP_KERNEL); + hci_dev_unlock(hdev); if (!hdev->sent_cmd) { skb_queue_head(&hdev->cmd_q, skb); queue_work(hdev->workqueue, &hdev->cmd_work); @@ -4138,8 +4140,10 @@ static int hci_send_cmd_sync(struct hci_dev *hdev, struct sk_buff *skb) if (READ_ONCE(hdev->req_status) == HCI_REQ_PEND && !hci_dev_test_and_set_flag(hdev, HCI_CMD_PENDING)) { + hci_dev_lock(hdev); kfree_skb(hdev->req_skb); hdev->req_skb = skb_get(hdev->sent_cmd); + hci_dev_unlock(hdev); } return err; diff --git a/net/bluetooth/hci_sync.c b/net/bluetooth/hci_sync.c index c01c8b58d9e8..a92a81846e9d 100644 --- a/net/bluetooth/hci_sync.c +++ b/net/bluetooth/hci_sync.c @@ -32,8 +32,10 @@ static void hci_cmd_sync_complete(struct hci_dev *hdev, u8 result, u16 opcode, WRITE_ONCE(hdev->req_status, HCI_REQ_DONE); /* Free the request command so it is not used as response */ + hci_dev_lock(hdev); kfree_skb(hdev->req_skb); hdev->req_skb = NULL; + hci_dev_unlock(hdev); if (skb) { struct sock *sk = hci_skb_sk(skb); @@ -3064,15 +3066,21 @@ static int hci_le_set_ext_scan_param_sync(struct hci_dev *hdev, u8 type, */ if (hci_dev_test_flag(hdev, HCI_PA_SYNC)) { struct hci_cp_le_add_to_accept_list *sent; + bdaddr_t bdaddr; + hci_dev_lock(hdev); sent = hci_sent_cmd_data(hdev, HCI_OP_LE_ADD_TO_ACCEPT_LIST); + if (sent) + bacpy(&bdaddr, &sent->bdaddr); + hci_dev_unlock(hdev); + if (sent) { struct hci_conn *conn; rcu_read_lock(); conn = hci_conn_hash_lookup_ba(hdev, PA_LINK, - &sent->bdaddr); + &bdaddr); if (conn) { struct bt_iso_qos *qos = &conn->iso_qos; From 555ff78fa719f50ff4cdbdb40aa1acc6e8e327d3 Mon Sep 17 00:00:00 2001 From: Chengfeng Ye Date: Mon, 5 Oct 2026 00:25:48 +0800 Subject: [PATCH 191/367] Bluetooth: ISO: Reject concurrent BIS listener setup iso_sock_listen() drops the socket lock before calling iso_listen_bis() to preserve the hdev->lock ordering. Two concurrent listen() calls on the same broadcast socket can therefore both observe BT_BOUND. Caller A can attach connA and release the locks, then caller B can acquire them and attach connB before A publishes BT_LISTEN. __iso_chan_add() checks whether connB already has a socket, but not whether the socket already has a connection. The second attachment overwrites iso_pi(sk)->conn, leaking the connA reference and leaving connA->sk intact. Socket teardown follows connB, so later events on connA can cause a stale socket access. Recheck BT_BOUND and require iso_pi(sk)->conn to be NULL after taking both the HCI device and socket locks in iso_listen_bis(). The connection check also covers the interval between attachment and publication of BT_LISTEN. Reject a competing setup with -EBADFD through the existing unlock path before allocating another connection. A recorded KASAN report from an instrumented kernel shows: BUG: KASAN: slab-use-after-free in iso_sock_hold+0x119/0x1f0 Call Trace: iso_sock_hold+0x119/0x1f0 iso_conn_del+0xe4/0x270 hci_disconn_complete_evt+0x351/0x8c0 hci_event_packet+0x71b/0xb20 hci_rx_work+0x293/0x730 Allocated by task 93: sk_prot_alloc+0x113/0x220 sk_alloc+0x2b/0x6d0 bt_sock_alloc+0x29/0x370 iso_sock_alloc.constprop.0+0x19/0x300 iso_sock_create+0x94/0x100 Freed by task 93: kfree+0x121/0x3c0 __sk_destruct+0x42b/0x540 iso_sock_release+0x29d/0x340 __sock_release+0xa1/0x260 sock_close+0x10/0x20 Fixes: 168e28305b87 ("Bluetooth: iso: Fix circular lock in iso_listen_bis") Cc: stable@vger.kernel.org Assisted-by: GPT-6 Astra Signed-off-by: Chengfeng Ye Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/iso.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/net/bluetooth/iso.c b/net/bluetooth/iso.c index 7657c2a0abbf..2e70c0df9ef3 100644 --- a/net/bluetooth/iso.c +++ b/net/bluetooth/iso.c @@ -1340,6 +1340,11 @@ static int iso_listen_bis(struct sock *sk) hci_dev_lock(hdev); lock_sock(sk); + if (sk->sk_state != BT_BOUND || iso_pi(sk)->conn) { + err = -EBADFD; + goto unlock; + } + /* Fail if user set invalid QoS */ if (iso_pi(sk)->qos_user_set && !check_bcast_qos(&iso_pi(sk)->qos)) { iso_pi(sk)->qos = default_qos; From e32d80293a0e1a160d5e5101daa8c82148c07510 Mon Sep 17 00:00:00 2001 From: Chengfeng Ye Date: Mon, 5 Oct 2026 15:50:31 +0800 Subject: [PATCH 192/367] Bluetooth: ISO: Serialize concurrent connect calls iso_sock_connect() checks the socket state before taking the socket lock and drops the lock again before setting up a connection. Two callers can both pass the admission check while the socket is open or bound. Caller A can copy its destination for route selection, then caller B can replace the socket destination before A binds or connects the CIS. A then uses B's destination with the route selected for its own request. B can also proceed after A attaches a connection and sets BT_CONNECT. For deferred BIS setup, B can bind a second BIS and overwrite iso_pi(sk)->conn, leaving the first connection's reference and conn->sk back-pointer stranded. Concurrent CIS connects can likewise replace the socket's connection. Later teardown only detaches the current connection, so a callback on the old connection can race with socket release and access a freed socket. KASAN reported: BUG: KASAN: slab-use-after-free in iso_sock_hold+0xf7/0x1b0 Call Trace: iso_sock_hold+0xf7/0x1b0 iso_conn_del+0x7b/0x1d0 iso_connect_cfm+0x186/0x16a0 hci_conn_failed+0x154/0x280 hci_abort_conn_sync+0x3dc/0x7d0 hci_cmd_sync_work+0x173/0x300 Allocated by task 121: sk_alloc+0x2b/0x6d0 bt_sock_alloc+0x29/0x370 iso_sock_alloc.constprop.0+0x19/0x300 iso_sock_create+0x94/0x100 Freed by task 121: kfree+0x121/0x3c0 __sk_destruct+0x42b/0x540 iso_sock_release+0x29d/0x340 __sock_release+0xa1/0x260 Check admission under the socket lock and mark the connect operation in progress before publishing its destination. Keep that flag set across route lookup and connection setup, rejecting another connect with -EBADFD before it can change the destination or attach a connection. Clear the flag after either helper returns, including on failure, so a failed setup can be retried. A separate flag is needed because the socket lock must be released before acquiring the HCI device lock, while BT_CONNECT requires an attached connection. Keep the existing state transitions and the deferred CIS completion through iso_sock_recvmsg(). Reject an existing socket attachment in __iso_chan_add() before taking a new reference or publishing either pointer, so every caller preserves the connection association. Keep the same-socket, same-connection success case for deferred CIS setup. Reject an attached socket before BIS setup so a reusable BIS is not claimed before the attachment is rejected. In CIS setup, reject a different HCI connection before iso_conn_add() and drop the per-attempt HCI hold directly. An unowned iso_conn may still carry another reference after detachment, so its destruction cannot be relied on to release that hold. iso_listen_bis() creates a fresh PA HCI/ISO pair under the device lock, so the temporary iso_conn_put() frees the ISO candidate and drops its HCI hold on rejection. iso_conn_ready() uses a freshly allocated, unpublished child with no connection, making the socket-side rejection unreachable. Fixes: ccf74f2390d6 ("Bluetooth: Add BTPROTO_ISO socket type") Cc: stable@vger.kernel.org Suggested-by: Pauli Virtanen Assisted-by: GPT-6 Astra Signed-off-by: Chengfeng Ye Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/iso.c | 47 +++++++++++++++++++++++++++++++++++---------- 1 file changed, 37 insertions(+), 10 deletions(-) diff --git a/net/bluetooth/iso.c b/net/bluetooth/iso.c index 2e70c0df9ef3..d6ac5b1f49bc 100644 --- a/net/bluetooth/iso.c +++ b/net/bluetooth/iso.c @@ -61,6 +61,7 @@ enum { BT_SK_BIG_SYNC, BT_SK_PA_SYNC, BT_SK_KILLED, + BT_SK_CONNECTING, }; struct iso_pinfo { @@ -350,6 +351,9 @@ static int __iso_chan_add(struct iso_conn *conn, struct sock *sk, return -EBUSY; } + if (iso_pi(sk)->conn) + return -EISCONN; + if (!conn->hcon) { BT_ERR("conn->hcon missing"); return -EIO; @@ -410,6 +414,11 @@ static int iso_connect_bis(struct sock *sk) hci_dev_lock(hdev); lock_sock(sk); + if (iso_pi(sk)->conn) { + err = -EISCONN; + goto unlock; + } + if (!bis_capable(hdev)) { err = -EOPNOTSUPP; goto unlock; @@ -562,6 +571,13 @@ static int iso_connect_cis(struct sock *sk) lockdep_assert_held(&hcon->hdev->lock); + /* The socket lock keeps the current attachment and its hcon stable. */ + if (iso_pi(sk)->conn && iso_pi(sk)->conn->hcon != hcon) { + hci_conn_drop(hcon); + err = -EISCONN; + goto unlock; + } + conn = iso_conn_add(hcon); if (!conn) { hci_conn_drop(hcon); @@ -1269,17 +1285,26 @@ static int iso_sock_connect(struct socket *sock, struct sockaddr_unsized *addr, addr->sa_family != AF_BLUETOOTH) return -EINVAL; - if (sk->sk_state != BT_OPEN && sk->sk_state != BT_BOUND) - return -EBADFD; + lock_sock(sk); - if (sk->sk_type != SOCK_SEQPACKET) - return -EINVAL; + if ((sk->sk_state != BT_OPEN && sk->sk_state != BT_BOUND) || + test_bit(BT_SK_CONNECTING, &iso_pi(sk)->flags)) { + err = -EBADFD; + goto done; + } + + if (sk->sk_type != SOCK_SEQPACKET) { + err = -EINVAL; + goto done; + } /* Check if the address type is of LE type */ - if (!bdaddr_type_is_le(sa->iso_bdaddr_type)) - return -EINVAL; + if (!bdaddr_type_is_le(sa->iso_bdaddr_type)) { + err = -EINVAL; + goto done; + } - lock_sock(sk); + set_bit(BT_SK_CONNECTING, &iso_pi(sk)->flags); bacpy(&iso_pi(sk)->dst, &sa->iso_bdaddr); iso_pi(sk)->dst_type = sa->iso_bdaddr_type; @@ -1291,16 +1316,18 @@ static int iso_sock_connect(struct socket *sock, struct sockaddr_unsized *addr, else err = iso_connect_bis(sk); - if (err) - return err; - lock_sock(sk); + clear_bit(BT_SK_CONNECTING, &iso_pi(sk)->flags); + if (err) + goto done; + if (!test_bit(BT_SK_DEFER_SETUP, &bt_sk(sk)->flags)) { err = bt_sock_wait_state(sk, BT_CONNECTED, sock_sndtimeo(sk, flags & O_NONBLOCK)); } +done: release_sock(sk); return err; } From 87db7795cbaaf636ae22ae031f4906db7bb77803 Mon Sep 17 00:00:00 2001 From: "Borislav Petkov (AMD)" Date: Fri, 2 Oct 2026 14:16:17 -0700 Subject: [PATCH 193/367] x86/CPU/AMD: Fix AMD TLBI Erratum #1718 The processor may fail to invalidate a TLB entry while executing an INVLPGB/TLBSYNC instruction pair. Detect the erratum and apply a software workaround when a microcode fix is not present. Fixes: 82378c6c2f43 ("x86/mm: Use INVLPGB for kernel TLB flushes") Signed-off-by: Borislav Petkov (AMD) Reviewed-by: Rik van Riel Acked-by: Lorenzo Stoakes (ARM) Tested-by: Lorenzo Stoakes (ARM) Tested-by: Rik van Riel Cc: stable@kernel.org Link: https://patch.msgid.link/20261002211617.1001617-1-bp@kernel.org --- arch/x86/include/asm/cpufeatures.h | 3 +++ arch/x86/include/asm/tlb.h | 6 ++++++ arch/x86/kernel/cpu/amd.c | 4 ++++ 3 files changed, 13 insertions(+) diff --git a/arch/x86/include/asm/cpufeatures.h b/arch/x86/include/asm/cpufeatures.h index f70ee74b5f92..fe84cf4e97d3 100644 --- a/arch/x86/include/asm/cpufeatures.h +++ b/arch/x86/include/asm/cpufeatures.h @@ -477,6 +477,8 @@ #define X86_FEATURE_NO_NESTED_DATA_BP (20*32+ 0) /* No Nested Data Breakpoints */ #define X86_FEATURE_WRMSR_XX_BASE_NS (20*32+ 1) /* WRMSR to {FS,GS,KERNEL_GS}_BASE is non-serializing */ #define X86_FEATURE_LFENCE_RDTSC (20*32+ 2) /* LFENCE always serializing / synchronizes RDTSC */ + +#define X86_FEATURE_TLBSYNC_SFW_NO (20*32+ 4) /* TLBI Erratum #1718 fixed in microcode */ #define X86_FEATURE_VERW_CLEAR (20*32+ 5) /* The memory form of VERW mitigates TSA */ #define X86_FEATURE_NULL_SEL_CLR_BASE (20*32+ 6) /* Null Selector Clears Base */ @@ -587,4 +589,5 @@ #define X86_BUG_TSA X86_BUG( 1*32+ 9) /* "tsa" CPU is affected by Transient Scheduler Attacks */ #define X86_BUG_VMSCAPE X86_BUG( 1*32+10) /* "vmscape" CPU is affected by VMSCAPE attacks from guests */ #define X86_BUG_SEAMRET_INVD_VMCS X86_BUG( 1*32+11) /* "seamret_invd_vmcs" SEAMRET from P-SEAMLDR clears the current VMCS */ +#define X86_BUG_TLBI X86_BUG( 1*32+12) /* "tlbi" AMD TLBI Erratum #1718 */ #endif /* _ASM_X86_CPUFEATURES_H */ diff --git a/arch/x86/include/asm/tlb.h b/arch/x86/include/asm/tlb.h index 866ea78ba156..eba925c85f52 100644 --- a/arch/x86/include/asm/tlb.h +++ b/arch/x86/include/asm/tlb.h @@ -110,6 +110,12 @@ static inline void __tlbsync(void) /* TLBSYNC: supported in binutils >= 0.36. */ asm volatile(".byte 0x0f, 0x01, 0xff" ::: "memory"); + + if (!static_cpu_has_bug(X86_BUG_TLBI)) + return; + + /* TLBSYNC: supported in binutils >= 0.36. */ + asm volatile(".byte 0x0f, 0x01, 0xff" ::: "memory"); } #else /* Some compilers (I'm looking at you clang!) simply can't do DCE */ diff --git a/arch/x86/kernel/cpu/amd.c b/arch/x86/kernel/cpu/amd.c index 54e14ed276b5..40463bad1078 100644 --- a/arch/x86/kernel/cpu/amd.c +++ b/arch/x86/kernel/cpu/amd.c @@ -1049,6 +1049,10 @@ static void init_amd_zen5(struct cpuinfo_x86 *c) msr_clear_bit(MSR_AMD64_CPUID_FN_7, 18); pr_emerg_once("RDSEED32 is broken. Disabling the corresponding CPUID bit.\n"); } + + if (cpu_has(c, X86_FEATURE_INVLPGB) && + !cpu_has(c, X86_FEATURE_TLBSYNC_SFW_NO)) + set_cpu_bug(c, X86_BUG_TLBI); } static void init_amd(struct cpuinfo_x86 *c) From 7484bc43ba6b8e189b482db5b75e7842890a82b1 Mon Sep 17 00:00:00 2001 From: Vishal Badole Date: Fri, 2 Oct 2026 17:02:03 +0530 Subject: [PATCH 194/367] EDAC/amd64: Mask UMC chip select to the four implemented selects For DRAM ECC errors, the chip select value can be found in the MCA_SYND[ErrorInformation] field. ErrorInformation[2:0] was first documented in Zen1. The 3 bit field implied that up to 8 chip selects may be used. However, all Zen-based systems have only ever had up to 4 chip selects. So only bits [1:0] would contain a valid chip select value. Future systems are redefining the field to ErrorInformation[1:0]. This matches the existing 'up to 4 chip selects' implementations. And it frees up the extra bit for other decoding. Change the current chip select mask from 0x7 to 0x3 to prepare for the decode change. Fixes: 713ad54675fd ("EDAC, amd64: Define and register UMC error decode function") Suggested-by: Yazen Ghannam Signed-off-by: Vishal Badole Signed-off-by: Borislav Petkov (AMD) Reviewed-by: Yazen Ghannam Cc: Link: https://patch.msgid.link/20261002113204.3358815-2-Vishal.Badole@amd.com --- drivers/edac/amd64_edac.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/edac/amd64_edac.c b/drivers/edac/amd64_edac.c index 475235c402e8..0c0d11c72925 100644 --- a/drivers/edac/amd64_edac.c +++ b/drivers/edac/amd64_edac.c @@ -2796,13 +2796,13 @@ static inline void decode_bus_error(int node_id, struct mce *m) * the instance_id. For example, instance_id=0xYXXXXX where Y is the channel * number. * - * For DRAM ECC errors, the Chip Select number is given in bits [2:0] of + * For DRAM ECC errors, the Chip Select number is given in bits [1:0] of * the MCA_SYND[ErrorInformation] field. */ static void umc_get_err_info(struct mce *m, struct err_info *err) { err->channel = (m->ipid & GENMASK(31, 0)) >> 20; - err->csrow = m->synd & 0x7; + err->csrow = m->synd & 0x3; } static void decode_umc_error(int node_id, struct mce *m) From e7ed652b1312ffcb9ea2153946615856b5097c78 Mon Sep 17 00:00:00 2001 From: Alan Previn Date: Tue, 29 Sep 2026 12:20:37 -0700 Subject: [PATCH 195/367] drm/xe/mmio: Fix xe_mmio_wait32() to honor delay/sleep maximums Check for overflow in udelay use in __xe_mmio_wait32 and avoid growing delays becoming intollerably large by capping the in-loop wait time. v7: - Change assisted-by to "LLM" (Rodrigo). - Move the max-wait macro to outside the function (Rodrigo). v6: - Fix commit message to remove items dropped in v5. Fix the use of MAX_UDELAY_MS for microsecond unit. (Shasiko review). v5: - Simplify: don't introduce additional redesign and use min_t with a backoff to cap wait. (Rodrigo). v4: - Simplify using poll_timeout_us and poll_timeout_us_atomic and update all callers to never use a param for atomic (Jani) v3: - Use div64_s64 kernel API for 32-bit vs 64-bit division. v2: - Fixed checkpatch failure. - Added helper for 64-bit DIV_ROUND_UP on 32-bit CPU (Shasiko review). Fixed bug max range in usleep_range(Shasiko review) Fixes: 5c09bd6ccd41 ("drm/xe/mmio: Move xe_mmio_wait32() to xe_mmio.c") Signed-off-by: Alan Previn Reviewed-by: Rodrigo Vivi Assisted-by: LLM Link: https://patch.msgid.link/20260929192037.2233429-2-alan.previn.teres.alexis@intel.com Signed-off-by: Rodrigo Vivi (cherry picked from commit 70dab9ee7d620e6018a64798aed065d14e9baed1) Signed-off-by: Rodrigo Vivi --- drivers/gpu/drm/xe/xe_mmio.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/xe/xe_mmio.c b/drivers/gpu/drm/xe/xe_mmio.c index 7fa18dfcb5a2..455a07d79cd9 100644 --- a/drivers/gpu/drm/xe/xe_mmio.c +++ b/drivers/gpu/drm/xe/xe_mmio.c @@ -320,6 +320,7 @@ u64 xe_mmio_read64_2x32(struct xe_mmio *mmio, struct xe_reg reg) return (u64)udw << 32 | ldw; } +#define __XE_MMIO_WAIT_MAX_BACKOFF_100MS (100 * USEC_PER_MSEC) static int __xe_mmio_wait32(struct xe_mmio *mmio, struct xe_reg reg, u32 mask, u32 val, u32 timeout_us, u32 *out_val, bool atomic, bool expect_match) { @@ -350,10 +351,10 @@ static int __xe_mmio_wait32(struct xe_mmio *mmio, struct xe_reg reg, u32 mask, u wait = ktime_us_delta(end, cur); if (atomic) - udelay(wait); + udelay(min_t(s64, wait, (MAX_UDELAY_MS * 1000))); else usleep_range(wait, wait << 1); - wait <<= 1; + wait = min_t(s64, wait << 1, __XE_MMIO_WAIT_MAX_BACKOFF_100MS); } if (ret != 0) { From f7fc61e66041b1f8300cad87005ba84604bfefd9 Mon Sep 17 00:00:00 2001 From: Tvrtko Ursulin Date: Fri, 25 Sep 2026 09:00:16 +0100 Subject: [PATCH 196/367] drm/xe: Do not emit Wa_16010904313 twice During development of commit e8372edec948 ("drm/xe/xelp: Implement Wa_16010904313") one of the interim patch versions contained an incorrect rebase on top of some upstream changes, which caused Wa_16010904313 to be emitted both from the indirect context and from the workaround batch buffer. As this workaround needs to be emitted from the indirect context for rcs and ccs, but from the workaround batch buffer for bcs, vcs and vecs, we need to pass the location as an input parameter to setup_timestamp_wa() so it can decide whether to emit itself or not. Signed-off-by: Tvrtko Ursulin Fixes: e8372edec948 ("drm/xe/xelp: Implement Wa_16010904313") Cc: Matt Roper Cc: Rodrigo Vivi Reviewed-by: Matt Roper Link: https://patch.msgid.link/20260925080016.64802-1-tvrtko.ursulin@igalia.com Signed-off-by: Rodrigo Vivi [Rodrigo: small adjustments on commit message for check-patch] (cherry picked from commit 38631a7bce195b88814b93bf2b6d3e48c827fef2) Signed-off-by: Rodrigo Vivi --- drivers/gpu/drm/xe/xe_lrc.c | 38 ++++++++++++++++++++++++------------- 1 file changed, 25 insertions(+), 13 deletions(-) diff --git a/drivers/gpu/drm/xe/xe_lrc.c b/drivers/gpu/drm/xe/xe_lrc.c index 35b4e8289b5f..e4388a960490 100644 --- a/drivers/gpu/drm/xe/xe_lrc.c +++ b/drivers/gpu/drm/xe/xe_lrc.c @@ -1099,8 +1099,7 @@ static void xe_lrc_finish(struct xe_lrc *lrc) #define CONTEXT_ACTIVE XE_LRC_CTX_TIMESTAMP_ACTIVE static ssize_t setup_utilization_wa(struct xe_lrc *lrc, struct xe_hw_engine *hwe, - u32 *batch, - size_t max_len) + u32 *batch, size_t max_len, bool indirect) { u32 *cmd = batch; @@ -1131,19 +1130,25 @@ static ssize_t setup_utilization_wa(struct xe_lrc *lrc, } static ssize_t setup_timestamp_wa(struct xe_lrc *lrc, struct xe_hw_engine *hwe, - u32 *batch, size_t max_len) + u32 *batch, size_t max_len, bool indirect) { const u32 ts_addr = __xe_lrc_ctx_timestamp_ggtt_addr(lrc); u32 *cmd = batch; - if (!XE_GT_WA(lrc->gt, 16010904313) || - !(hwe->class == XE_ENGINE_CLASS_RENDER || - hwe->class == XE_ENGINE_CLASS_COMPUTE || - hwe->class == XE_ENGINE_CLASS_COPY || + if (!XE_GT_WA(lrc->gt, 16010904313)) + return 0; + + if (!indirect && + !(hwe->class == XE_ENGINE_CLASS_COPY || hwe->class == XE_ENGINE_CLASS_VIDEO_DECODE || hwe->class == XE_ENGINE_CLASS_VIDEO_ENHANCE)) return 0; + if (indirect && + !(hwe->class == XE_ENGINE_CLASS_RENDER || + hwe->class == XE_ENGINE_CLASS_COMPUTE)) + return 0; + if (xe_gt_WARN_ON(lrc->gt, max_len < 12)) return -ENOSPC; @@ -1169,7 +1174,8 @@ static ssize_t setup_timestamp_wa(struct xe_lrc *lrc, struct xe_hw_engine *hwe, static ssize_t setup_configfs_post_ctx_restore_bb(struct xe_lrc *lrc, struct xe_hw_engine *hwe, - u32 *batch, size_t max_len) + u32 *batch, size_t max_len, + bool indirect) { struct xe_device *xe = gt_to_xe(lrc->gt); const u32 *user_batch; @@ -1198,7 +1204,8 @@ static ssize_t setup_configfs_post_ctx_restore_bb(struct xe_lrc *lrc, static ssize_t setup_configfs_mid_ctx_restore_bb(struct xe_lrc *lrc, struct xe_hw_engine *hwe, - u32 *batch, size_t max_len) + u32 *batch, size_t max_len, + bool indirect) { struct xe_device *xe = gt_to_xe(lrc->gt); const u32 *user_batch; @@ -1227,7 +1234,8 @@ static ssize_t setup_configfs_mid_ctx_restore_bb(struct xe_lrc *lrc, static ssize_t setup_invalidate_state_cache_wa(struct xe_lrc *lrc, struct xe_hw_engine *hwe, - u32 *batch, size_t max_len) + u32 *batch, size_t max_len, + bool indirect) { u32 *cmd = batch; @@ -1247,7 +1255,8 @@ static ssize_t setup_invalidate_state_cache_wa(struct xe_lrc *lrc, static ssize_t setup_invalidate_auxccs_wa(struct xe_lrc *lrc, struct xe_hw_engine *hwe, - u32 *batch, size_t max_len) + u32 *batch, size_t max_len, + bool indirect) { struct xe_gt *gt = lrc->gt; u32 *(*emit)(struct xe_gt *gt, u32 *cmd) = @@ -1264,7 +1273,7 @@ static ssize_t setup_invalidate_auxccs_wa(struct xe_lrc *lrc, struct bo_setup { ssize_t (*setup)(struct xe_lrc *lrc, struct xe_hw_engine *hwe, - u32 *batch, size_t max_size); + u32 *batch, size_t max_size, bool indirect); }; struct bo_setup_state { @@ -1273,6 +1282,7 @@ struct bo_setup_state { struct xe_hw_engine *hwe; size_t max_size; size_t reserve_dw; + bool indirect; unsigned int offset; const struct bo_setup *funcs; unsigned int num_funcs; @@ -1298,7 +1308,8 @@ static int setup_bo(struct bo_setup_state *state) for (size_t i = 0; i < state->num_funcs; i++) { ssize_t len = state->funcs[i].setup(state->lrc, state->hwe, - state->ptr, remain); + state->ptr, remain, + state->indirect); remain -= len; @@ -1404,6 +1415,7 @@ setup_indirect_ctx(struct xe_lrc *lrc, struct xe_hw_engine *hwe) struct bo_setup_state state = { .lrc = lrc, .hwe = hwe, + .indirect = true, .max_size = (63 * 64) /* max 63 cachelines */, .buffer = NULL, .offset = __xe_lrc_indirect_ctx_offset(lrc), From 83d5acca1f4f6132038ea88c72c5844f62e42d44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Hellstr=C3=B6m?= Date: Fri, 2 Oct 2026 12:08:29 +0200 Subject: [PATCH 197/367] drm/xe: Fix shrinker accounting double-subtraction on nested external pins MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit xe_bo_pin_external() and xe_bo_unpin_external() support nested pins, since the underlying ttm_bo_pin()/ttm_bo_unpin() maintain a pin_count refcount rather than a boolean. However, the shrinker accounting calls xe_ttm_tt_account_subtract() and xe_ttm_tt_account_add() are invoked unconditionally on every pin_external()/unpin_external() call, instead of only on the transition into and out of the pinned state. An external BO (dma-buf) can be pinned more than once while already pinned, for example when it has multiple attachments/importers, each independently calling xe_bo_pin_external(). Each such nested pin subtracts the BO's pages from the shrinker's accounting again, even though the BO's pages were already removed from consideration by the first pin. Symmetrically, each nested unpin adds them back again before the BO is actually unpinned. This desynchronizes the shrinker's page and object counts from reality, and since they are declared as signed long but interpreted as unsigned long in xe_shrinker_count(), the underflow can turn into an enormous shrinkable/purgeable page count, causing the shrinker to be invoked excessively under memory pressure. Guard the accounting calls with the same pin-count transition checks already used to guard the pinned_link list maintenance, so accounting is only updated on the outermost pin and the final unpin. While centralizing this logic into xe_bo_account_pin()/ xe_bo_account_unpin(), also close a second, related accounting gap affecting imported dma-bufs. For an imported bo's ttm_tt, xe's ttm_tt_populate hook (xe_ttm_tt_populate()) early-returns without calling xe_ttm_tt_account_add(), since TTM_TT_FLAG_EXTERNAL is set without TTM_TT_FLAG_EXTERNAL_MAPPABLE. However, TTM core's ttm_tt_populate() wrapper unconditionally marks the tt as populated on driver-hook success, so ttm_tt_is_populated() still reports true for it. xe_bo_account_pin()/xe_bo_account_unpin() trusted ttm_tt_is_populated() as a proxy for "these pages are accounted for by the shrinker", and would subtract/add pages for such a tt that were never added in the first place, again underflowing the shrinker's counts. This is reachable today via xe_bo_pin_external(), and would have become newly reachable via the fb-pin path once this patch routes __xe_pin_fb_vma() through xe_bo_pin_account() below, so fix it here rather than in a later patch, to avoid a commit in this series that introduces the regression before fixing it. Skip the accounting for imported bos, using the same EXTERNAL && !EXTERNAL_MAPPABLE test xe_ttm_tt_populate()/xe_ttm_tt_unpopulate() use to decide whether to act, via a new xe_ttm_bo_is_imported() helper (hoisted up from its only prior use site). v2: - Fix the critical issue caught by Sashiko AI review: the pin-count transition check introduced here was still bypassed by the fb-pin path (__xe_pin_fb_vma()/__xe_unpin_fb_vma()), which could leave a stale pinned_link entry and a permanent shrinker accounting leak when the fb-unpin performed the true last unpin. Addressed in a separate follow-up patch in this series rather than folded in here, since it is a distinct bug with its own Fixes: tag. - Drop the unneeded local "last_unpin" variable in xe_bo_unpin_external(), restoring the original inline bo->ttm.pin_count == 1 check; it was a no-op rename not used by any of the new accounting helpers, and was going to be deleted again by the follow-up patch anyway. v3: - Exclude imported dma-bufs from the pin/unpin shrinker accounting, fixing another underflow caught by Sashiko AI review. - Add a short comment on the backup-object pin site noting it's already populated/accounted at pin time, addressing (as a false-positive clarification, no code fix needed) a separate Medium-severity accounting-leak concern also caught by Sashiko AI review. Fixes: 00c8efc3180f ("drm/xe: Add a shrinker for xe bos") Cc: Thomas Hellström Cc: Matthew Brost Cc: Rodrigo Vivi Cc: intel-xe@lists.freedesktop.org Cc: # v6.15+ Reviewed-by: Matthew Auld Reviewed-by: Himal Prasad Ghimiray Assisted-by: LLM Signed-off-by: Thomas Hellström Link: https://patch.msgid.link/20261002100830.12297-2-thomas.hellstrom@linux.intel.com (cherry picked from commit 6a117a2bb7ca657e3a19f83f4c06265d38dee06c) Signed-off-by: Rodrigo Vivi --- drivers/gpu/drm/xe/display/xe_fb_pin.c | 6 +- drivers/gpu/drm/xe/xe_bo.c | 116 +++++++++++++++++++------ drivers/gpu/drm/xe/xe_bo.h | 2 + 3 files changed, 95 insertions(+), 29 deletions(-) diff --git a/drivers/gpu/drm/xe/display/xe_fb_pin.c b/drivers/gpu/drm/xe/display/xe_fb_pin.c index 73469ea5f333..f1dbdc802df8 100644 --- a/drivers/gpu/drm/xe/display/xe_fb_pin.c +++ b/drivers/gpu/drm/xe/display/xe_fb_pin.c @@ -356,7 +356,7 @@ static struct i915_vma *__xe_pin_fb_vma(struct drm_gem_object *obj, bool is_dpt, drm_exec_retry_on_contention(&exec); xe_validation_retry_on_oom(&ctx, &ret); if (!ret) - ttm_bo_pin(&bo->ttm); + xe_bo_pin_account(bo); } if (ret) goto err; @@ -373,7 +373,7 @@ static struct i915_vma *__xe_pin_fb_vma(struct drm_gem_object *obj, bool is_dpt, err_unpin: ttm_bo_reserve(&bo->ttm, false, false, NULL); - ttm_bo_unpin(&bo->ttm); + xe_bo_unpin_account(bo); ttm_bo_unreserve(&bo->ttm); err: kfree(vma); @@ -393,7 +393,7 @@ static void __xe_unpin_fb_vma(struct i915_vma *vma) xe_ggtt_node_remove(vma->node, false); ttm_bo_reserve(&vma->bo->ttm, false, false, NULL); - ttm_bo_unpin(&vma->bo->ttm); + xe_bo_unpin_account(vma->bo); ttm_bo_unreserve(&vma->bo->ttm); kfree(vma); } diff --git a/drivers/gpu/drm/xe/xe_bo.c b/drivers/gpu/drm/xe/xe_bo.c index b4921a627ff3..f85d5f9b5fce 100644 --- a/drivers/gpu/drm/xe/xe_bo.c +++ b/drivers/gpu/drm/xe/xe_bo.c @@ -455,6 +455,85 @@ static void xe_ttm_tt_account_subtract(struct xe_device *xe, struct ttm_tt *tt) xe_shrinker_mod_pages(xe->mem.shrinker, -(long)tt->num_pages, 0); } +static bool xe_ttm_bo_is_imported(struct ttm_buffer_object *tbo) +{ + dma_resv_assert_held(tbo->base.resv); + + return tbo->ttm && + (tbo->ttm->page_flags & (TTM_TT_FLAG_EXTERNAL | TTM_TT_FLAG_EXTERNAL_MAPPABLE)) == + TTM_TT_FLAG_EXTERNAL; +} + +/* + * Account @bo's pages as pinned for the shrinker. Removes @bo's pages + * from the shrinker's shrinkable / purgeable counts on the transition + * from unpinned to pinned. Must be called with @bo's dma-resv held, + * after &ttm_buffer_object.pin_count has been incremented by + * ttm_bo_pin(). Safe to call unconditionally regardless of which pin + * path (kernel, external, framebuffer, backup object, ...) is pinning + * @bo, since it only acts on the true 0->1 pin_count transition. + * + * Imported bos (xe_ttm_bo_is_imported()) are excluded: for those, + * xe_ttm_tt_populate() never actually populates the tt or calls + * xe_ttm_tt_account_add(), but TTM core's ttm_tt_populate() still + * unconditionally marks the tt as populated on driver-hook success. + * Relying on ttm_tt_is_populated() for these would subtract pages that + * were never added, underflowing the shrinker's counts. + */ +static void xe_bo_account_pin(struct xe_bo *bo) +{ + struct xe_device *xe = xe_bo_device(bo); + + if (bo->ttm.pin_count == 1 && bo->ttm.ttm && ttm_tt_is_populated(bo->ttm.ttm) && + !xe_ttm_bo_is_imported(&bo->ttm)) + xe_ttm_tt_account_subtract(xe, bo->ttm.ttm); +} + +/* + * Counterpart to xe_bo_account_pin(). Must be called with @bo's dma-resv + * held, before &ttm_buffer_object.pin_count is decremented by + * ttm_bo_unpin(), so that the check against the true 1->0 transition sees + * the pin count that is about to be released. See xe_bo_account_pin() for + * why imported bos are excluded. + */ +static void xe_bo_account_unpin(struct xe_bo *bo) +{ + struct xe_device *xe = xe_bo_device(bo); + + if (bo->ttm.pin_count == 1 && bo->ttm.ttm && ttm_tt_is_populated(bo->ttm.ttm) && + !xe_ttm_bo_is_imported(&bo->ttm)) + xe_ttm_tt_account_add(xe, bo->ttm.ttm); +} + +/** + * xe_bo_pin_account() - Pin a bo and account its pages for the shrinker + * @bo: The buffer object to pin. + * + * Pins @bo via ttm_bo_pin() and updates the shrinker's shrinkable / + * purgeable page accounting to match. Must be called with @bo's dma-resv + * held. Safe to call regardless of which pin path (kernel, external, + * framebuffer, backup object, ...) is pinning @bo. + */ +void xe_bo_pin_account(struct xe_bo *bo) +{ + ttm_bo_pin(&bo->ttm); + xe_bo_account_pin(bo); +} + +/** + * xe_bo_unpin_account() - Unpin a bo and account its pages for the shrinker + * @bo: The buffer object to unpin. + * + * Counterpart to xe_bo_pin_account(). Updates the shrinker's shrinkable / + * purgeable page accounting to match, then unpins @bo via ttm_bo_unpin(). + * Must be called with @bo's dma-resv held. + */ +void xe_bo_unpin_account(struct xe_bo *bo) +{ + xe_bo_account_unpin(bo); + ttm_bo_unpin(&bo->ttm); +} + static void update_global_total_pages(struct ttm_device *ttm_dev, long num_pages) { @@ -1389,7 +1468,8 @@ int xe_bo_notifier_prepare_pinned(struct xe_bo *bo) } backup->parent_obj = xe_bo_get(bo); /* Released by bo_destroy */ - ttm_bo_pin(&backup->ttm); + /* Note: XE_BO_FLAG_SYSTEM resolves to XE_PL_TT so already populated. */ + xe_bo_pin_account(backup); bo->backup_obj = backup; } @@ -1409,7 +1489,7 @@ int xe_bo_notifier_unprepare_pinned(struct xe_bo *bo) { xe_bo_lock(bo, false); if (bo->backup_obj) { - ttm_bo_unpin(&bo->backup_obj->ttm); + xe_bo_unpin_account(bo->backup_obj); xe_bo_put(bo->backup_obj); bo->backup_obj = NULL; } @@ -1620,7 +1700,7 @@ int xe_bo_restore_pinned(struct xe_bo *bo) xe_bo_vunmap(backup); if (!bo->backup_obj) { if (xe_bo_is_pinned(backup)) - ttm_bo_unpin(&backup->ttm); + xe_bo_unpin_account(backup); xe_bo_put(backup); } out_unlock_bo: @@ -2012,15 +2092,6 @@ static vm_fault_t xe_err_to_fault_t(int err) return VM_FAULT_SIGBUS; } -static bool xe_ttm_bo_is_imported(struct ttm_buffer_object *tbo) -{ - dma_resv_assert_held(tbo->base.resv); - - return tbo->ttm && - (tbo->ttm->page_flags & (TTM_TT_FLAG_EXTERNAL | TTM_TT_FLAG_EXTERNAL_MAPPABLE)) == - TTM_TT_FLAG_EXTERNAL; -} - static vm_fault_t xe_bo_cpu_fault_fastpath(struct vm_fault *vmf, struct xe_device *xe, struct xe_bo *bo, bool needs_rpm) { @@ -3141,12 +3212,13 @@ uint64_t vram_region_gpu_offset(struct ttm_resource *res) int xe_bo_pin_external(struct xe_bo *bo, bool in_place, struct drm_exec *exec) { struct xe_device *xe = xe_bo_device(bo); + bool first_pin = !xe_bo_is_pinned(bo); int err; xe_assert(xe, !bo->vm); xe_assert(xe, xe_bo_is_user(bo)); - if (!xe_bo_is_pinned(bo)) { + if (first_pin) { if (!in_place) { err = xe_bo_validate(bo, NULL, false, exec); if (err) @@ -3158,9 +3230,7 @@ int xe_bo_pin_external(struct xe_bo *bo, bool in_place, struct drm_exec *exec) spin_unlock(&xe->pinned.lock); } - ttm_bo_pin(&bo->ttm); - if (bo->ttm.ttm && ttm_tt_is_populated(bo->ttm.ttm)) - xe_ttm_tt_account_subtract(xe, bo->ttm.ttm); + xe_bo_pin_account(bo); /* * FIXME: If we always use the reserve / unreserve functions for locking @@ -3216,9 +3286,7 @@ int xe_bo_pin(struct xe_bo *bo, struct drm_exec *exec) spin_unlock(&xe->pinned.lock); } - ttm_bo_pin(&bo->ttm); - if (bo->ttm.ttm && ttm_tt_is_populated(bo->ttm.ttm)) - xe_ttm_tt_account_subtract(xe, bo->ttm.ttm); + xe_bo_pin_account(bo); /* * FIXME: If we always use the reserve / unreserve functions for locking @@ -3252,9 +3320,7 @@ void xe_bo_unpin_external(struct xe_bo *bo) list_del_init(&bo->pinned_link); spin_unlock(&xe->pinned.lock); - ttm_bo_unpin(&bo->ttm); - if (bo->ttm.ttm && ttm_tt_is_populated(bo->ttm.ttm)) - xe_ttm_tt_account_add(xe, bo->ttm.ttm); + xe_bo_unpin_account(bo); /* * FIXME: If we always use the reserve / unreserve functions for locking @@ -3279,14 +3345,12 @@ void xe_bo_unpin(struct xe_bo *bo) if (bo->backup_obj) { if (xe_bo_is_pinned(bo->backup_obj)) - ttm_bo_unpin(&bo->backup_obj->ttm); + xe_bo_unpin_account(bo->backup_obj); xe_bo_put(bo->backup_obj); bo->backup_obj = NULL; } } - ttm_bo_unpin(&bo->ttm); - if (bo->ttm.ttm && ttm_tt_is_populated(bo->ttm.ttm)) - xe_ttm_tt_account_add(xe, bo->ttm.ttm); + xe_bo_unpin_account(bo); } /** diff --git a/drivers/gpu/drm/xe/xe_bo.h b/drivers/gpu/drm/xe/xe_bo.h index 152bfcffe399..53a701d3d242 100644 --- a/drivers/gpu/drm/xe/xe_bo.h +++ b/drivers/gpu/drm/xe/xe_bo.h @@ -229,6 +229,8 @@ int xe_bo_pin_external(struct xe_bo *bo, bool in_place, struct drm_exec *exec); int xe_bo_pin(struct xe_bo *bo, struct drm_exec *exec); void xe_bo_unpin_external(struct xe_bo *bo); void xe_bo_unpin(struct xe_bo *bo); +void xe_bo_pin_account(struct xe_bo *bo); +void xe_bo_unpin_account(struct xe_bo *bo); int xe_bo_validate(struct xe_bo *bo, struct xe_vm *vm, bool allow_res_evict, struct drm_exec *exec); From cf238c067eaae1cbfb00738e29219b9568d2313e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Hellstr=C3=B6m?= Date: Fri, 2 Oct 2026 12:08:30 +0200 Subject: [PATCH 198/367] drm/xe: Fix stale pinned_link entry when fb-pin performs the final unpin MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit xe_bo_pin_external() and xe_bo_unpin_external() maintain the bo's pinned_link list membership in xe->pinned.late.external, based on whether the current call is the outermost pin or the final unpin. However, the same external bo's pin_count can also be raised and lowered directly by __xe_pin_fb_vma()/__xe_unpin_fb_vma(), which pin the bo as a display scanout buffer without going through xe_bo_pin_external()/xe_bo_unpin_external() at all, and have no notion of, or ownership over, pinned_link. If a bo is pinned both externally (e.g. dma-buf export) and as an fb, and the external unpin happens first, xe_bo_unpin_external() correctly observes pin_count > 1 and leaves the bo on pinned_link. When the fb unpin later performs the true last unpin (pin_count 1 -> 0), it never touches pinned_link, leaving the bo linked on xe->pinned.late.external indefinitely. Once the bo is subsequently freed, this stale list entry points into freed memory, corrupting the list and risking a use-after-free the next time the list is walked or spliced. The backup object pin/unpin sites in xe_bo_notifier_prepare_pinned()/ xe_bo_notifier_unprepare_pinned() have the same bypass characteristic, though they never add their bo to a pinned list, so are not affected by this particular list-corruption issue. Move the pinned_link removal into xe_bo_account_unpin(), which already runs on every unpin path (kernel, external, framebuffer, backup object) right before the true 1 -> 0 pin_count transition. Since list_del_init() only operates on the node itself, this removal is list-agnostic and safe to perform regardless of which list (external or kernel_bo_present) the bo happens to be linked on, or which code path is performing the final unpin. Drop the now-redundant explicit list_del_init() calls in xe_bo_unpin_external() and xe_bo_unpin(). v2: - New patch v3: - Rebased on changes to previous patch. Fixes: 44e694958b95 ("drm/xe/display: Implement display support") Cc: Maarten Lankhorst Cc: Rodrigo Vivi Cc: intel-xe@lists.freedesktop.org Cc: # v6.8+ Reviewed-by: Matthew Auld Reviewed-by: Himal Prasad Ghimiray Assisted-by: LLM Signed-off-by: Thomas Hellström Link: https://patch.msgid.link/20261002100830.12297-3-thomas.hellstrom@linux.intel.com (cherry picked from commit ca58d36a1f93af586ee93b340c9f9fd81312cc01) Signed-off-by: Rodrigo Vivi --- drivers/gpu/drm/xe/xe_bo.c | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/drivers/gpu/drm/xe/xe_bo.c b/drivers/gpu/drm/xe/xe_bo.c index f85d5f9b5fce..7b95ce0445a9 100644 --- a/drivers/gpu/drm/xe/xe_bo.c +++ b/drivers/gpu/drm/xe/xe_bo.c @@ -495,12 +495,27 @@ static void xe_bo_account_pin(struct xe_bo *bo) * ttm_bo_unpin(), so that the check against the true 1->0 transition sees * the pin count that is about to be released. See xe_bo_account_pin() for * why imported bos are excluded. + * + * On the true last unpin, also removes @bo from whichever pinned-bo list + * (external or kernel_bo_present) it may currently be linked on, since a + * bo's final unpin can happen through a pin path (e.g. framebuffer, + * backup object) that has no notion of, or ownership over, that list. + * This is safe and list-agnostic: list_del_init() only needs the node + * itself, not knowledge of which list it is threaded through, and is a + * no-op if @bo is not linked. */ static void xe_bo_account_unpin(struct xe_bo *bo) { struct xe_device *xe = xe_bo_device(bo); + bool last_unpin = bo->ttm.pin_count == 1; - if (bo->ttm.pin_count == 1 && bo->ttm.ttm && ttm_tt_is_populated(bo->ttm.ttm) && + if (last_unpin && !list_empty(&bo->pinned_link)) { + spin_lock(&xe->pinned.lock); + list_del_init(&bo->pinned_link); + spin_unlock(&xe->pinned.lock); + } + + if (last_unpin && bo->ttm.ttm && ttm_tt_is_populated(bo->ttm.ttm) && !xe_ttm_bo_is_imported(&bo->ttm)) xe_ttm_tt_account_add(xe, bo->ttm.ttm); } @@ -3315,11 +3330,6 @@ void xe_bo_unpin_external(struct xe_bo *bo) xe_assert(xe, xe_bo_is_pinned(bo)); xe_assert(xe, xe_bo_is_user(bo)); - spin_lock(&xe->pinned.lock); - if (bo->ttm.pin_count == 1 && !list_empty(&bo->pinned_link)) - list_del_init(&bo->pinned_link); - spin_unlock(&xe->pinned.lock); - xe_bo_unpin_account(bo); /* @@ -3338,10 +3348,7 @@ void xe_bo_unpin(struct xe_bo *bo) xe_assert(xe, xe_bo_is_pinned(bo)); if (mem_type_is_vram(place->mem_type) || bo->flags & XE_BO_FLAG_GGTT) { - spin_lock(&xe->pinned.lock); xe_assert(xe, !list_empty(&bo->pinned_link)); - list_del_init(&bo->pinned_link); - spin_unlock(&xe->pinned.lock); if (bo->backup_obj) { if (xe_bo_is_pinned(bo->backup_obj)) From d333c8bca6dc3c5e978f6698d39dd799f3b0dc8d Mon Sep 17 00:00:00 2001 From: Jakub Kicinski Date: Wed, 30 Sep 2026 18:25:59 -0700 Subject: [PATCH 199/367] net: make dev_xdp_sb_prog_count() see programs attached through a link ethtool refuses to enable tcp-data-split on a device running a single-buffer XDP program (which can't deal with the payload landing in a separate buffer). dev_xdp_sb_prog_count() only looks at xdp_state[].prog though, ignoring bpf_link integration. Get the prog pointer from dev_xdp_prog(), which will consult both. bpf_xdp_link_update() needs a bit of a touch-up, too, since ethtool calls dev_xdp_sb_prog_count() with just the instance lock (for ops-locked devices) - we now have to make sure the link updates happen under that lock. Spotted by AI while reviewing the XDP propagation series, verified and tested on netdevsim with a C test along these lines: link = bpf_program__attach_xdp(prog, ifindex); system("ethtool -G eth0 tcp-data-split on") Fixes: 197258f0ef68 ("net: ethtool: add hds_config member in ethtool_netdev_state") Acked-by: Stanislav Fomichev Link: https://patch.msgid.link/20261001012559.297751-1-kuba@kernel.org Signed-off-by: Jakub Kicinski --- net/core/dev.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/net/core/dev.c b/net/core/dev.c index 18dc88990510..e76762e29360 100644 --- a/net/core/dev.c +++ b/net/core/dev.c @@ -10364,13 +10364,15 @@ EXPORT_SYMBOL_GPL(dev_xdp_prog_count); u8 dev_xdp_sb_prog_count(struct net_device *dev) { + struct bpf_prog *prog; u8 count = 0; int i; - for (i = 0; i < __MAX_XDP_MODE; i++) - if (dev->xdp_state[i].prog && - !dev->xdp_state[i].prog->aux->xdp_has_frags) + for (i = 0; i < __MAX_XDP_MODE; i++) { + prog = dev_xdp_prog(dev, i); + if (prog && !prog->aux->xdp_has_frags) count++; + } return count; } @@ -10750,11 +10752,12 @@ static int bpf_xdp_link_update(struct bpf_link *link, struct bpf_prog *new_prog, bpf_op = dev_xdp_bpf_op(xdp_link->dev, mode); err = dev_xdp_install(xdp_link->dev, mode, bpf_op, NULL, xdp_link->flags, new_prog); + if (!err) + old_prog = xchg(&link->prog, new_prog); netdev_unlock_ops(xdp_link->dev); if (err) goto out_unlock; - old_prog = xchg(&link->prog, new_prog); bpf_prog_put(old_prog); out_unlock: From fe4114221364899ab33c3f9574da6b108d3fa79e Mon Sep 17 00:00:00 2001 From: Or Har-Toov Date: Wed, 30 Sep 2026 20:04:06 +0300 Subject: [PATCH 200/367] net/mlx5: Lag, split aggregate speed into oper and max helpers mlx5_lag_sum_devices_speed computes the LAG aggregate by summing oper speeds across all ports. This has two bugs. First, it relies on the assumption that a port whose carrier is down will report an oper speed of zero and therefore not contribute to the sum. This assumption does not always hold: when the link partner disconnects the port transitions to DOWN state but firmware may still report a non-zero oper speed, causing the aggregate to include a port that is not actively carrying traffic. Second, in active-backup mode only one port transmits at a time, so the aggregate should reflect a single port speed rather than the sum of all ports. Fix this by splitting mlx5_lag_sum_devices_speed into two helpers. mlx5_lag_get_devices_oper_speed reflects the speed currently available: it queries the vport state of each port and skips any port that is not UP, rather than relying on oper speed being zero. mlx5_lag_get_devices_max_speed is state-independent and returns the maximum achievable speed used as a fallback when speed is 0; for active-backup it takes the maximum single-port speed instead of the sum. The bugs were discovered during integration and reproduced on a back-to-back ConnectX-8 setup. The first bug (stale oper speed when link goes down) was observed: after the link partner disconnected, the aggregate TX speed reported to vports remained full speed because firmware still returned the last oper speed, causing vports to be rate-limited to the old aggregate speed. The second bug (active-backup summing all port speeds instead of the active port's speed) was also triggered: in active-backup mode the computed aggregate was double the expected value since both port speeds were summed even though only one port was active. Fixes: 28ea6036dad2 ("net/mlx5: Handle port and vport speed change events in MPESW") Signed-off-by: Or Har-Toov Reviewed-by: Shay Drori Reviewed-by: Mark Bloch Signed-off-by: Tariq Toukan Link: https://patch.msgid.link/20260930170406.148548-1-tariqt@nvidia.com Signed-off-by: Jakub Kicinski --- .../net/ethernet/mellanox/mlx5/core/lag/lag.c | 79 +++++++++++++------ 1 file changed, 57 insertions(+), 22 deletions(-) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/lag/lag.c b/drivers/net/ethernet/mellanox/mlx5/core/lag/lag.c index 3b34bec559e0..4e173b08cb37 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/lag/lag.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/lag/lag.c @@ -1432,30 +1432,40 @@ static bool mlx5_lag_should_disable_lag(struct mlx5_lag *ldev, bool do_bond) } #ifdef CONFIG_MLX5_ESWITCH -static int -mlx5_lag_sum_devices_speed(struct mlx5_lag *ldev, u32 *sum_speed, - int (*get_speed)(struct mlx5_core_dev *, u32 *)) +static int mlx5_lag_get_devices_oper_speed(struct mlx5_lag *ldev, + u32 *sum_speed) { - struct mlx5_core_dev *pf_mdev; - struct lag_func *pf; int pf_idx; - u32 speed; - int ret; *sum_speed = 0; mlx5_ldev_for_each(pf_idx, 0, ldev) { + u8 opmod = MLX5_VPORT_STATE_OP_MOD_VNIC_VPORT; + struct mlx5_core_dev *pf_mdev; + struct lag_func *pf; + u32 speed; + u8 state; + int ret; + pf = mlx5_lag_pf(ldev, pf_idx); if (!pf) continue; pf_mdev = pf->dev; if (!pf_mdev) continue; + ret = mlx5_query_vport_max_tx_speed(pf_mdev, opmod, 0, 0, + &speed, &state); + if (ret) { + mlx5_core_dbg(pf_mdev, "State query failed (err=%d)\n", + ret); + return ret; + } + if (state != VPORT_STATE_UP) + continue; - ret = get_speed(pf_mdev, &speed); + ret = mlx5_port_oper_linkspeed(pf_mdev, &speed); if (ret) { mlx5_core_dbg(pf_mdev, - "Failed to get device speed using %ps. Device %s speed is not available (err=%d)\n", - get_speed, dev_name(pf_mdev->device), + "Failed to get oper speed (err=%d)\n", ret); return ret; } @@ -1466,17 +1476,42 @@ mlx5_lag_sum_devices_speed(struct mlx5_lag *ldev, u32 *sum_speed, return 0; } -static int mlx5_lag_sum_devices_max_speed(struct mlx5_lag *ldev, u32 *max_speed) +static int mlx5_lag_get_devices_max_speed(struct mlx5_lag *ldev, u32 *max_speed) { - return mlx5_lag_sum_devices_speed(ldev, max_speed, - mlx5_port_max_linkspeed); -} + bool take_max; + int pf_idx; -static int mlx5_lag_sum_devices_oper_speed(struct mlx5_lag *ldev, - u32 *oper_speed) -{ - return mlx5_lag_sum_devices_speed(ldev, oper_speed, - mlx5_port_oper_linkspeed); + take_max = ldev->tracker.tx_type == NETDEV_LAG_TX_TYPE_ACTIVEBACKUP; + if (ldev->mode == MLX5_LAG_MODE_MPESW) + take_max = false; + + *max_speed = 0; + mlx5_ldev_for_each(pf_idx, 0, ldev) { + struct mlx5_core_dev *pf_mdev; + struct lag_func *pf; + u32 speed; + int ret; + + pf = mlx5_lag_pf(ldev, pf_idx); + if (!pf) + continue; + pf_mdev = pf->dev; + if (!pf_mdev) + continue; + + ret = mlx5_port_max_linkspeed(pf_mdev, &speed); + if (ret) { + mlx5_core_dbg(pf_mdev, + "Failed to get max speed (err=%d)\n", + ret); + return ret; + } + + *max_speed = take_max ? + max(*max_speed, speed) : *max_speed + speed; + } + + return 0; } static void mlx5_lag_modify_device_vports_speed(struct mlx5_core_dev *mdev, @@ -1525,7 +1560,7 @@ void mlx5_lag_set_vports_agg_speed(struct mlx5_lag *ldev) int pf_idx; if (ldev->mode == MLX5_LAG_MODE_MPESW) { - if (mlx5_lag_sum_devices_oper_speed(ldev, &speed)) + if (mlx5_lag_get_devices_oper_speed(ldev, &speed)) return; } else { speed = ldev->tracker.bond_speed_mbps; @@ -1533,8 +1568,8 @@ void mlx5_lag_set_vports_agg_speed(struct mlx5_lag *ldev) return; } - /* If speed is not set, use the sum of max speeds of all PFs */ - if (!speed && mlx5_lag_sum_devices_max_speed(ldev, &speed)) + /* If speed is not set, fall back to the max achievable speed */ + if (!speed && mlx5_lag_get_devices_max_speed(ldev, &speed)) return; speed = speed / MLX5_MAX_TX_SPEED_UNIT; From e3f33b0a1d89d738e2b913dab519226eaf76e15b Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Thu, 1 Oct 2026 22:12:53 +0000 Subject: [PATCH 201/367] ipv4: free inet_opt and ireq_opt after an RCU grace period tcp_v4_syn_recv_sock() transfers ownership of ireq->ireq_opt to the child socket (newinet->inet_opt) without copying it. Another cpu can concurrently process a retransmitted SYN for the same request socket, and send a SYNACK from tcp_check_req(). tcp_v4_send_synack() and inet_csk_route_req() read ireq->ireq_opt under rcu_read_lock() only, and ip_build_and_send_pkt() and ip_options_build() then read opt->optlen twice. Note that the SYNACK timer itself is not an issue: it holds a reference on its request socket, and inet_csk_reqsk_queue_drop() calls timer_delete_sync() before the child can be freed. Since commit 079096f103fa ("tcp/dccp: install syn_recv requests into ehash table"), request sockets are processed without holding the listener lock, so nothing prevents the child socket from being freed while the SYNACK is still being built. TCP child sockets do not have SOCK_RCU_FREE, and inet_sock_destruct() frees inet_opt with a plain kfree(), leading to a use-after-free in ip_options_build(). A similar issue exists with request socket migration (net.ipv4.tcp_migrate_req=1, or a BPF_SK_REUSEPORT_SELECT_OR_MIGRATE program). reqsk_timer_handler() clones the request socket with inet_reqsk_clone(), so that the old request socket and its clone share the same ireq_opt, then reqsk_migrate_reset() clears the pointer in the old one. Another cpu holding a reference on the old request socket can still be using these options (sending a SYNACK, or creating a child in tcp_v4_syn_recv_sock()) when the clone is freed, and tcp_v4_reqsk_destructor() also uses a plain kfree(). Readers already use RCU, and other paths replacing inet_opt (do_ip_setsockopt(), cipso_v4_sock_setattr()...) already use kfree_rcu(). Use kfree_rcu() in inet_sock_destruct() and tcp_v4_reqsk_destructor() as well. IPv6 is not affected by the first issue, because tcp_v6_syn_recv_sock() duplicates the options. tcp_v6_reqsk_destructor() has the same migration issue with ipv6_opt, which is only set by CALIPSO. This will be addressed in a separate patch. Fixes: 079096f103fa ("tcp/dccp: install syn_recv requests into ehash table") Fixes: c905dee62232 ("tcp: Migrate TCP_NEW_SYN_RECV requests at retransmitting SYN+ACKs.") Reported-by: Xinyang Ge Signed-off-by: Eric Dumazet Reviewed-by: Kuniyuki Iwashima Link: https://patch.msgid.link/20261001221253.2964024-1-edumazet@kernel.org Signed-off-by: Jakub Kicinski --- net/ipv4/af_inet.c | 2 +- net/ipv4/tcp_ipv4.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/net/ipv4/af_inet.c b/net/ipv4/af_inet.c index 4ce38c99fef9..14ce01092fda 100644 --- a/net/ipv4/af_inet.c +++ b/net/ipv4/af_inet.c @@ -161,7 +161,7 @@ void inet_sock_destruct(struct sock *sk) WARN_ON_ONCE(sk->sk_wmem_queued); WARN_ON_ONCE(sk->sk_forward_alloc); - kfree(rcu_dereference_protected(inet->inet_opt, 1)); + kfree_rcu(rcu_dereference_protected(inet->inet_opt, 1), rcu); dst_release(rcu_dereference_protected(sk->sk_dst_cache, 1)); dst_release(rcu_dereference_protected(sk->sk_rx_dst, 1)); psp_sk_assoc_free(sk); diff --git a/net/ipv4/tcp_ipv4.c b/net/ipv4/tcp_ipv4.c index 04dbb2babbcd..bebc5a8d1ab6 100644 --- a/net/ipv4/tcp_ipv4.c +++ b/net/ipv4/tcp_ipv4.c @@ -1209,7 +1209,7 @@ static int tcp_v4_send_synack(const struct sock *sk, struct dst_entry *dst, */ static void tcp_v4_reqsk_destructor(struct request_sock *req) { - kfree(rcu_dereference_protected(inet_rsk(req)->ireq_opt, 1)); + kfree_rcu(rcu_dereference_protected(inet_rsk(req)->ireq_opt, 1), rcu); } #ifdef CONFIG_TCP_MD5SIG From 11705541e7d421706a54f7f6ead8a3316c91c2bc Mon Sep 17 00:00:00 2001 From: Ovidiu Panait Date: Mon, 28 Sep 2026 20:34:37 +0000 Subject: [PATCH 202/367] net: stmmac: Remove VLAN perfect matching dead code stmmac_vlan_update() falls back to "perfect matching" when the VLAN hash filter is unavailable (!priv->dma_cap.vlhash). This fallback has been unreachable in normal operation since its introduction in commit c7ab0b8088d7 ("net: stmmac: Fallback to VLAN Perfect filtering if HASH is not available") because the NETIF_F_HW_VLAN_{CTAG,STAG}_FILTER features are advertised only when priv->dma_cap.vlhash is true. The fallback is also duplicating the code in vlan_add_hw_rx_fltr(), which is always available since stmmac_get_num_vlan() returns at least 1. Therefore, remove it. Signed-off-by: Ovidiu Panait Reviewed-by: Maxime Chevallier Link: https://patch.msgid.link/20260928203441.34876-2-ovidiu.panait.rb@renesas.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/stmicro/stmmac/hwif.h | 2 +- .../net/ethernet/stmicro/stmmac/stmmac_main.c | 13 +----- .../net/ethernet/stmicro/stmmac/stmmac_vlan.c | 41 +------------------ 3 files changed, 4 insertions(+), 52 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/hwif.h b/drivers/net/ethernet/stmicro/stmmac/hwif.h index 857f7562c6c6..df2126d71c2f 100644 --- a/drivers/net/ethernet/stmicro/stmmac/hwif.h +++ b/drivers/net/ethernet/stmicro/stmmac/hwif.h @@ -633,7 +633,7 @@ struct stmmac_est_ops { struct stmmac_vlan_ops { /* VLAN */ void (*update_vlan_hash)(struct mac_device_info *hw, u32 hash, - u16 perfect_match, bool is_double); + bool is_double); void (*enable_vlan)(struct mac_device_info *hw, u32 type); void (*rx_hw_vlan)(struct mac_device_info *hw, struct dma_desc *rx_desc, struct sk_buff *skb); diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index 9741f97fa37a..a92940473ccd 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -6895,29 +6895,18 @@ static u32 stmmac_vid_crc32_le(__le16 vid_le) static int stmmac_vlan_update(struct stmmac_priv *priv, bool is_double) { u32 crc, hash = 0; - u16 pmatch = 0; - int count = 0; u16 vid = 0; for_each_set_bit(vid, priv->active_vlans, VLAN_N_VID) { __le16 vid_le = cpu_to_le16(vid); crc = bitrev32(~stmmac_vid_crc32_le(vid_le)) >> 28; hash |= (1 << crc); - count++; - } - - if (!priv->dma_cap.vlhash) { - if (count > 2) /* VID = 0 always passes filter */ - return -EOPNOTSUPP; - - pmatch = vid; - hash = 0; } if (!netif_running(priv->dev)) return 0; - return stmmac_update_vlan_hash(priv, priv->hw, hash, pmatch, is_double); + return stmmac_update_vlan_hash(priv, priv->hw, hash, is_double); } /* FIXME: This may need RXC to be running, but it may be called with BH diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.c index e24efe3bfedb..983a90cb9767 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.c @@ -162,7 +162,7 @@ static void vlan_restore_hw_rx_fltr(struct net_device *dev, } static void vlan_update_hash(struct mac_device_info *hw, u32 hash, - u16 perfect_match, bool is_double) + bool is_double) { void __iomem *ioaddr = hw->pcsr; u32 value; @@ -184,20 +184,6 @@ static void vlan_update_hash(struct mac_device_info *hw, u32 hash, } writel(value, ioaddr + VLAN_TAG); - } else if (perfect_match) { - u32 value = VLAN_ETV; - - if (is_double) { - value |= VLAN_EDVLP; - value |= VLAN_ESVL; - value |= VLAN_DOVLTC; - } else { - value &= ~VLAN_EDVLP; - value &= ~VLAN_ESVL; - value &= ~VLAN_DOVLTC; - } - - writel(value | perfect_match, ioaddr + VLAN_TAG); } else { value &= ~(VLAN_VTHM | VLAN_ETV); value &= ~(VLAN_EDVLP | VLAN_ESVL); @@ -251,7 +237,7 @@ static void vlan_set_hw_mode(struct mac_device_info *hw) } static void dwxgmac2_update_vlan_hash(struct mac_device_info *hw, u32 hash, - u16 perfect_match, bool is_double) + bool is_double) { void __iomem *ioaddr = hw->pcsr; @@ -279,29 +265,6 @@ static void dwxgmac2_update_vlan_hash(struct mac_device_info *hw, u32 hash, value &= ~VLAN_VID; writel(value, ioaddr + VLAN_TAG); - } else if (perfect_match) { - u32 value = readl(ioaddr + XGMAC_PACKET_FILTER); - - value |= XGMAC_FILTER_VTFE; - - writel(value, ioaddr + XGMAC_PACKET_FILTER); - - value = readl(ioaddr + VLAN_TAG); - - value &= ~VLAN_VTHM; - value |= VLAN_ETV; - if (is_double) { - value |= VLAN_EDVLP; - value |= VLAN_ESVL; - value |= VLAN_DOVLTC; - } else { - value &= ~VLAN_EDVLP; - value &= ~VLAN_ESVL; - value &= ~VLAN_DOVLTC; - } - - value &= ~VLAN_VID; - writel(value | perfect_match, ioaddr + VLAN_TAG); } else { u32 value = readl(ioaddr + XGMAC_PACKET_FILTER); From 69aa9e76677c97ba992df03a3c74d839470e7363 Mon Sep 17 00:00:00 2001 From: Ovidiu Panait Date: Mon, 28 Sep 2026 20:34:38 +0000 Subject: [PATCH 203/367] net: stmmac: Stop toggling the EDVLP bit Currently, the double VLAN EDVLP bit is toggled whenever an 802.1ad VLAN is registered. This bit enables processing of the inner VLAN tag, which is completely unrelated to S-Tag VLAN handling. Move EDVLP handling into vlan_set_hw_mode() instead, and keep it always enabled, so that COE can work for packets with an inner VLAN header. Add a dedicated callback for dwxlgmac2, as it doesn't implement the set_hw_vlan_mode callback, like the other cores. Suggested-by: Joseph Steel Signed-off-by: Ovidiu Panait Link: https://patch.msgid.link/20260928203441.34876-3-ovidiu.panait.rb@renesas.com Signed-off-by: Jakub Kicinski --- .../net/ethernet/stmicro/stmmac/stmmac_vlan.c | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.c index 983a90cb9767..200b34588c7f 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.c @@ -174,19 +174,16 @@ static void vlan_update_hash(struct mac_device_info *hw, u32 hash, if (hash) { value |= VLAN_VTHM | VLAN_ETV; if (is_double) { - value |= VLAN_EDVLP; value |= VLAN_ESVL; value |= VLAN_DOVLTC; } else { - value &= ~VLAN_EDVLP; value &= ~VLAN_ESVL; value &= ~VLAN_DOVLTC; } writel(value, ioaddr + VLAN_TAG); } else { - value &= ~(VLAN_VTHM | VLAN_ETV); - value &= ~(VLAN_EDVLP | VLAN_ESVL); + value &= ~(VLAN_VTHM | VLAN_ETV | VLAN_ESVL); value &= ~VLAN_DOVLTC; value &= ~VLAN_VID; @@ -222,6 +219,7 @@ static void vlan_set_hw_mode(struct mac_device_info *hw) void __iomem *ioaddr = hw->pcsr; u32 value = readl(ioaddr + VLAN_TAG); + value |= VLAN_EDVLP; value &= ~VLAN_TAG_CTRL_EVLS_MASK; if (hw->hw_vlan_en) @@ -254,11 +252,9 @@ static void dwxgmac2_update_vlan_hash(struct mac_device_info *hw, u32 hash, value |= VLAN_VTHM | VLAN_ETV; if (is_double) { - value |= VLAN_EDVLP; value |= VLAN_ESVL; value |= VLAN_DOVLTC; } else { - value &= ~VLAN_EDVLP; value &= ~VLAN_ESVL; value &= ~VLAN_DOVLTC; } @@ -274,8 +270,7 @@ static void dwxgmac2_update_vlan_hash(struct mac_device_info *hw, u32 hash, value = readl(ioaddr + VLAN_TAG); - value &= ~(VLAN_VTHM | VLAN_ETV); - value &= ~(VLAN_EDVLP | VLAN_ESVL); + value &= ~(VLAN_VTHM | VLAN_ETV | VLAN_ESVL); value &= ~VLAN_DOVLTC; value &= ~VLAN_VID; @@ -283,6 +278,14 @@ static void dwxgmac2_update_vlan_hash(struct mac_device_info *hw, u32 hash, } } +static void dwxlgmac2_set_hw_vlan_mode(struct mac_device_info *hw) +{ + void __iomem *ioaddr = hw->pcsr; + u32 value = readl(ioaddr + VLAN_TAG); + + writel(value | VLAN_EDVLP, ioaddr + VLAN_TAG); +} + const struct stmmac_vlan_ops dwmac_vlan_ops = { .update_vlan_hash = vlan_update_hash, .enable_vlan = vlan_enable, @@ -296,6 +299,7 @@ const struct stmmac_vlan_ops dwmac_vlan_ops = { const struct stmmac_vlan_ops dwxlgmac2_vlan_ops = { .update_vlan_hash = dwxgmac2_update_vlan_hash, .enable_vlan = vlan_enable, + .set_hw_vlan_mode = dwxlgmac2_set_hw_vlan_mode, }; const struct stmmac_vlan_ops dwxgmac210_vlan_ops = { From 354b07bc1dcaa94b40a5904159c1a53b33d7cf59 Mon Sep 17 00:00:00 2001 From: Ovidiu Panait Date: Mon, 28 Sep 2026 20:34:39 +0000 Subject: [PATCH 204/367] net: stmmac: Rename double VLAN references to svlan The ESVL and DOVLTC bits control S-VLAN tag processing and have nothing to do with the double VLAN feature, which only provides a way to process an additional inner VLAN tag. However, the driver code that handles them always refers to "double VLAN", which is unrelated and makes the implementation confusing. The driver does not use any of the inner VLAN tag features, and the networking core does not support offloads for the inner tag anyway. To reduce the confusion regarding S-Tag vs double VLAN handling, rename double -> svlan. No functional change intended. Suggested-by: Joseph Steel Signed-off-by: Ovidiu Panait Reviewed-by: Maxime Chevallier Link: https://patch.msgid.link/20260928203441.34876-4-ovidiu.panait.rb@renesas.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/stmicro/stmmac/hwif.h | 2 +- drivers/net/ethernet/stmicro/stmmac/stmmac.h | 2 +- .../net/ethernet/stmicro/stmmac/stmmac_main.c | 34 +++++++++---------- .../stmicro/stmmac/stmmac_selftests.c | 30 ++++++++-------- .../net/ethernet/stmicro/stmmac/stmmac_vlan.c | 8 ++--- 5 files changed, 38 insertions(+), 38 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/hwif.h b/drivers/net/ethernet/stmicro/stmmac/hwif.h index df2126d71c2f..5e2654c91b41 100644 --- a/drivers/net/ethernet/stmicro/stmmac/hwif.h +++ b/drivers/net/ethernet/stmicro/stmmac/hwif.h @@ -633,7 +633,7 @@ struct stmmac_est_ops { struct stmmac_vlan_ops { /* VLAN */ void (*update_vlan_hash)(struct mac_device_info *hw, u32 hash, - bool is_double); + bool is_svlan); void (*enable_vlan)(struct mac_device_info *hw, u32 type); void (*rx_hw_vlan)(struct mac_device_info *hw, struct dma_desc *rx_desc, struct sk_buff *skb); diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac.h b/drivers/net/ethernet/stmicro/stmmac/stmmac.h index 7582fca63741..d2d387f45c10 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac.h +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac.h @@ -344,7 +344,7 @@ struct stmmac_priv { void __iomem *ptpaddr; void __iomem *estaddr; unsigned long active_vlans[BITS_TO_LONGS(VLAN_N_VID)]; - unsigned int num_double_vlans; + unsigned int num_svlans; int sfty_irq; struct stmmac_msi *msi; diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index a92940473ccd..26248e042f77 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -6892,7 +6892,7 @@ static u32 stmmac_vid_crc32_le(__le16 vid_le) return crc; } -static int stmmac_vlan_update(struct stmmac_priv *priv, bool is_double) +static int stmmac_vlan_update(struct stmmac_priv *priv, bool is_svlan) { u32 crc, hash = 0; u16 vid = 0; @@ -6906,7 +6906,7 @@ static int stmmac_vlan_update(struct stmmac_priv *priv, bool is_double) if (!netif_running(priv->dev)) return 0; - return stmmac_update_vlan_hash(priv, priv->hw, hash, is_double); + return stmmac_update_vlan_hash(priv, priv->hw, hash, is_svlan); } /* FIXME: This may need RXC to be running, but it may be called with BH @@ -6915,8 +6915,8 @@ static int stmmac_vlan_update(struct stmmac_priv *priv, bool is_double) static int stmmac_vlan_rx_add_vid(struct net_device *ndev, __be16 proto, u16 vid) { struct stmmac_priv *priv = netdev_priv(ndev); - unsigned int num_double_vlans; - bool is_double = false; + unsigned int num_svlans; + bool is_svlan = false; int ret; ret = pm_runtime_resume_and_get(priv->device); @@ -6924,11 +6924,11 @@ static int stmmac_vlan_rx_add_vid(struct net_device *ndev, __be16 proto, u16 vid return ret; if (be16_to_cpu(proto) == ETH_P_8021AD) - is_double = true; + is_svlan = true; set_bit(vid, priv->active_vlans); - num_double_vlans = priv->num_double_vlans + is_double; - ret = stmmac_vlan_update(priv, num_double_vlans); + num_svlans = priv->num_svlans + is_svlan; + ret = stmmac_vlan_update(priv, num_svlans); if (ret) { clear_bit(vid, priv->active_vlans); goto err_pm_put; @@ -6938,12 +6938,12 @@ static int stmmac_vlan_rx_add_vid(struct net_device *ndev, __be16 proto, u16 vid ret = stmmac_add_hw_vlan_rx_fltr(priv, ndev, priv->hw, proto, vid); if (ret) { clear_bit(vid, priv->active_vlans); - stmmac_vlan_update(priv, priv->num_double_vlans); + stmmac_vlan_update(priv, priv->num_svlans); goto err_pm_put; } } - priv->num_double_vlans = num_double_vlans; + priv->num_svlans = num_svlans; err_pm_put: pm_runtime_put(priv->device); @@ -6957,8 +6957,8 @@ static int stmmac_vlan_rx_add_vid(struct net_device *ndev, __be16 proto, u16 vid static int stmmac_vlan_rx_kill_vid(struct net_device *ndev, __be16 proto, u16 vid) { struct stmmac_priv *priv = netdev_priv(ndev); - unsigned int num_double_vlans; - bool is_double = false; + unsigned int num_svlans; + bool is_svlan = false; int ret; ret = pm_runtime_resume_and_get(priv->device); @@ -6966,11 +6966,11 @@ static int stmmac_vlan_rx_kill_vid(struct net_device *ndev, __be16 proto, u16 vi return ret; if (be16_to_cpu(proto) == ETH_P_8021AD) - is_double = true; + is_svlan = true; clear_bit(vid, priv->active_vlans); - num_double_vlans = priv->num_double_vlans - is_double; - ret = stmmac_vlan_update(priv, num_double_vlans); + num_svlans = priv->num_svlans - is_svlan; + ret = stmmac_vlan_update(priv, num_svlans); if (ret) { set_bit(vid, priv->active_vlans); goto del_vlan_error; @@ -6980,12 +6980,12 @@ static int stmmac_vlan_rx_kill_vid(struct net_device *ndev, __be16 proto, u16 vi ret = stmmac_del_hw_vlan_rx_fltr(priv, ndev, priv->hw, proto, vid); if (ret) { set_bit(vid, priv->active_vlans); - stmmac_vlan_update(priv, priv->num_double_vlans); + stmmac_vlan_update(priv, priv->num_svlans); goto del_vlan_error; } } - priv->num_double_vlans = num_double_vlans; + priv->num_svlans = num_svlans; del_vlan_error: pm_runtime_put(priv->device); @@ -7001,7 +7001,7 @@ static void stmmac_vlan_restore(struct stmmac_priv *priv) if (priv->hw->num_vlan) stmmac_restore_hw_vlan_rx_fltr(priv, priv->dev, priv->hw); - stmmac_vlan_update(priv, priv->num_double_vlans); + stmmac_vlan_update(priv, priv->num_svlans); } static int stmmac_bpf(struct net_device *dev, struct netdev_bpf *bpf) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_selftests.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_selftests.c index c25dc9f89270..c485217ba880 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_selftests.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_selftests.c @@ -243,7 +243,7 @@ struct stmmac_test_priv { int (*func)(struct sk_buff *skb, struct net_device *ndev, struct packet_type *pt, struct net_device *orig_ndev); bool capture_all; - int double_vlan; + int svlan; int vlan_id; int ok; }; @@ -285,7 +285,7 @@ static int stmmac_test_loopback_validate(struct sk_buff *skb, } ihdr = ip_hdr(skb); - if (tpriv->double_vlan) + if (tpriv->svlan) ihdr = (struct iphdr *)(skb_network_header(skb) + 4); if (tpriv->packet->tcp) { @@ -936,7 +936,7 @@ static int stmmac_test_vlan_validate(struct sk_buff *skb, struct iphdr *ihdr; u16 proto; - proto = tpriv->double_vlan ? ETH_P_8021AD : ETH_P_8021Q; + proto = tpriv->svlan ? ETH_P_8021AD : ETH_P_8021Q; skb = skb_unshare(skb, GFP_ATOMIC); if (!skb) @@ -963,7 +963,7 @@ static int stmmac_test_vlan_validate(struct sk_buff *skb, } ihdr = ip_hdr(skb); - if (tpriv->double_vlan) + if (tpriv->svlan) ihdr = (struct iphdr *)(skb_network_header(skb) + 4); if (ihdr->protocol != IPPROTO_UDP) goto out; @@ -1080,7 +1080,7 @@ static int stmmac_test_vlanfilt_perfect(struct stmmac_priv *priv) return ret; } -static int __stmmac_test_dvlanfilt(struct stmmac_priv *priv) +static int __stmmac_test_svlanfilt(struct stmmac_priv *priv) { struct stmmac_packet_attrs attr = { }; struct stmmac_test_priv *tpriv; @@ -1092,7 +1092,7 @@ static int __stmmac_test_dvlanfilt(struct stmmac_priv *priv) return -ENOMEM; tpriv->ok = false; - tpriv->double_vlan = true; + tpriv->svlan = true; init_completion(&tpriv->comp); tpriv->pt.type = htons(ETH_P_8021Q); @@ -1155,15 +1155,15 @@ static int __stmmac_test_dvlanfilt(struct stmmac_priv *priv) return ret; } -static int stmmac_test_dvlanfilt(struct stmmac_priv *priv) +static int stmmac_test_svlanfilt(struct stmmac_priv *priv) { if (!priv->dma_cap.vlhash) return -EOPNOTSUPP; - return __stmmac_test_dvlanfilt(priv); + return __stmmac_test_svlanfilt(priv); } -static int stmmac_test_dvlanfilt_perfect(struct stmmac_priv *priv) +static int stmmac_test_svlanfilt_perfect(struct stmmac_priv *priv) { int ret, prev_cap = priv->dma_cap.vlhash; @@ -1171,7 +1171,7 @@ static int stmmac_test_dvlanfilt_perfect(struct stmmac_priv *priv) return -EOPNOTSUPP; priv->dma_cap.vlhash = 0; - ret = __stmmac_test_dvlanfilt(priv); + ret = __stmmac_test_svlanfilt(priv); priv->dma_cap.vlhash = prev_cap; return ret; @@ -1372,7 +1372,7 @@ static int stmmac_test_vlanoff_common(struct stmmac_priv *priv, bool svlan) proto = svlan ? ETH_P_8021AD : ETH_P_8021Q; tpriv->ok = false; - tpriv->double_vlan = svlan; + tpriv->svlan = svlan; init_completion(&tpriv->comp); tpriv->pt.type = svlan ? htons(ETH_P_8021Q) : htons(ETH_P_IP); @@ -1960,11 +1960,11 @@ static const struct stmmac_test { .name = "VLAN Filtering (perf) ", .fn = stmmac_test_vlanfilt_perfect, }, { - .name = "Double VLAN Filter ", - .fn = stmmac_test_dvlanfilt, + .name = "SVLAN Filtering ", + .fn = stmmac_test_svlanfilt, }, { - .name = "Double VLAN Filter (perf) ", - .fn = stmmac_test_dvlanfilt_perfect, + .name = "SVLAN Filtering (perf) ", + .fn = stmmac_test_svlanfilt_perfect, }, { .name = "Flexible RX Parser ", .fn = stmmac_test_rxp, diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.c index 200b34588c7f..fb9aad748cb3 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.c @@ -162,7 +162,7 @@ static void vlan_restore_hw_rx_fltr(struct net_device *dev, } static void vlan_update_hash(struct mac_device_info *hw, u32 hash, - bool is_double) + bool is_svlan) { void __iomem *ioaddr = hw->pcsr; u32 value; @@ -173,7 +173,7 @@ static void vlan_update_hash(struct mac_device_info *hw, u32 hash, if (hash) { value |= VLAN_VTHM | VLAN_ETV; - if (is_double) { + if (is_svlan) { value |= VLAN_ESVL; value |= VLAN_DOVLTC; } else { @@ -235,7 +235,7 @@ static void vlan_set_hw_mode(struct mac_device_info *hw) } static void dwxgmac2_update_vlan_hash(struct mac_device_info *hw, u32 hash, - bool is_double) + bool is_svlan) { void __iomem *ioaddr = hw->pcsr; @@ -251,7 +251,7 @@ static void dwxgmac2_update_vlan_hash(struct mac_device_info *hw, u32 hash, value = readl(ioaddr + VLAN_TAG); value |= VLAN_VTHM | VLAN_ETV; - if (is_double) { + if (is_svlan) { value |= VLAN_ESVL; value |= VLAN_DOVLTC; } else { From c63ce7b9c7e7b68267899e4a0fc14b35d2c1dfcc Mon Sep 17 00:00:00 2001 From: Ovidiu Panait Date: Mon, 28 Sep 2026 20:34:40 +0000 Subject: [PATCH 205/367] net: stmmac: Do not advertise S-VLAN stripping when it is disabled C-VLAN and S-VLAN tag stripping are both controlled by the EVLS bit, so disabling rx-vlan-offload also disables S-VLAN tag stripping. However, rx-vlan-stag-hw-parse keeps being advertised as enabled: root@rzv2h-evk:~# ethtool -K end1 rx-vlan-offload off root@rzv2h-evk:~# ethtool -k end1 | grep -i vlan rx-vlan-offload: off tx-vlan-offload: off [fixed] rx-vlan-filter: on [fixed] vlan-challenged: off [fixed] tx-vlan-stag-hw-insert: off [fixed] rx-vlan-stag-hw-parse: on [fixed] rx-vlan-stag-filter: on [fixed] Fix this inconsistency by making NETIF_F_HW_VLAN_STAG_RX follow NETIF_F_HW_VLAN_CTAG_RX. Fixes: 750011e239a5 ("net: stmmac: Add support for HW-accelerated VLAN stripping") Signed-off-by: Ovidiu Panait Reviewed-by: Maxime Chevallier Link: https://patch.msgid.link/20260928203441.34876-5-ovidiu.panait.rb@renesas.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index 26248e042f77..13e11df7f4c8 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -6268,6 +6268,13 @@ static netdev_features_t stmmac_fix_features(struct net_device *dev, if (priv->plat->bugged_jumbo && (dev->mtu > ETH_DATA_LEN)) features &= ~NETIF_F_CSUM_MASK; + if (dwmac_is_xmac(priv->plat->core_type)) { + if (features & NETIF_F_HW_VLAN_CTAG_RX) + features |= NETIF_F_HW_VLAN_STAG_RX; + else + features &= ~NETIF_F_HW_VLAN_STAG_RX; + } + return features; } From da17990421d79f1bfb4142f9d767fd33daecc3d8 Mon Sep 17 00:00:00 2001 From: Ovidiu Panait Date: Mon, 28 Sep 2026 20:34:41 +0000 Subject: [PATCH 206/367] net: stmmac: Disable S-Tag processing on dwmac4 Currently, hardware VLAN stripping is broken for 802.1ad tags. vlan_rx_hw() hardcodes ETH_P_8021Q when putting the hardware tag into the skb, rather than using the actual protocol from the packet. Because of this, packets that contain a 802.1ad outer tag are incorrectly passed up the stack as having an 802.1Q tag. This causes QinQ ping between two hosts to fail. vlan_rx_hw() is shared by dwxgmac2 and dwmac4: on dwxgmac2 the tag type is available in the RDES3 write-back descriptor (the ET_LT field), so the outer tag type can be determined based on that info. However, dwmac4 doesn't seem to provide the tag type. The Length/Type field in RDES3 only indicates whether the packet is single or double-tagged, not which tag type was stripped. Since dwmac4 cannot report the stripped tag type, it cannot support hardware S-Tag stripping correctly. Therefore, restrict the NETIF_F_HW_VLAN_STAG_RX and NETIF_F_HW_VLAN_STAG_FILTER advertisement to dwxgmac2 only. With this, 802.1ad tags are left in place and handled by the software VLAN path. Fixes: 750011e239a5 ("net: stmmac: Add support for HW-accelerated VLAN stripping") Signed-off-by: Ovidiu Panait Reviewed-by: Maxime Chevallier Link: https://patch.msgid.link/20260928203441.34876-6-ovidiu.panait.rb@renesas.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index 13e11df7f4c8..77d2bffb8dc1 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -6268,7 +6268,7 @@ static netdev_features_t stmmac_fix_features(struct net_device *dev, if (priv->plat->bugged_jumbo && (dev->mtu > ETH_DATA_LEN)) features &= ~NETIF_F_CSUM_MASK; - if (dwmac_is_xmac(priv->plat->core_type)) { + if (priv->plat->core_type == DWMAC_CORE_XGMAC) { if (features & NETIF_F_HW_VLAN_CTAG_RX) features |= NETIF_F_HW_VLAN_STAG_RX; else @@ -8055,14 +8055,18 @@ static int __stmmac_dvr_probe(struct device *device, ndev->watchdog_timeo = msecs_to_jiffies(watchdog); #ifdef STMMAC_VLAN_TAG_USED /* Both mac100 and gmac support receive VLAN tag detection */ - ndev->features |= NETIF_F_HW_VLAN_CTAG_RX | NETIF_F_HW_VLAN_STAG_RX; + ndev->features |= NETIF_F_HW_VLAN_CTAG_RX; + if (priv->plat->core_type == DWMAC_CORE_XGMAC) + ndev->features |= NETIF_F_HW_VLAN_STAG_RX; + if (dwmac_is_xmac(priv->plat->core_type)) { ndev->hw_features |= NETIF_F_HW_VLAN_CTAG_RX; priv->hw->hw_vlan_en = true; } if (priv->dma_cap.vlhash) { ndev->features |= NETIF_F_HW_VLAN_CTAG_FILTER; - ndev->features |= NETIF_F_HW_VLAN_STAG_FILTER; + if (priv->plat->core_type == DWMAC_CORE_XGMAC) + ndev->features |= NETIF_F_HW_VLAN_STAG_FILTER; } if (priv->dma_cap.vlins) ndev->features |= NETIF_F_HW_VLAN_CTAG_TX; From 73670ddb91a4d22e1deb3efd962242de1ec48e90 Mon Sep 17 00:00:00 2001 From: Joshua Washington Date: Wed, 30 Sep 2026 14:47:33 -0700 Subject: [PATCH 207/367] gve: fix XSK buffer leak when rings are stopped GVE does not free XSK buffers when resetting ring state as a part of stopping queues. This causes all XSK buffers which are posted to the NIC to be leaked. Free XSK buffers attached to an allocated buf_state when stopping rings. This issue was hit in production. Fixes: c1fffc5d66a7 ("gve: implement DQO RX datapath and control path for AF_XDP zero-copy") Cc: stable@vger.kernel.org Reviewed-by: Tim Hostetler Reviewed-by: Jordan Rhee Signed-off-by: Joshua Washington Link: https://patch.msgid.link/20260930214735.3288780-2-joshwash@google.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/google/gve/gve_rx_dqo.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/net/ethernet/google/gve/gve_rx_dqo.c b/drivers/net/ethernet/google/gve/gve_rx_dqo.c index 5cf242b28557..a2c4a08ce68d 100644 --- a/drivers/net/ethernet/google/gve/gve_rx_dqo.c +++ b/drivers/net/ethernet/google/gve/gve_rx_dqo.c @@ -113,6 +113,12 @@ static void gve_rx_reset_ring_dqo(struct gve_priv *priv, int idx) gve_free_to_page_pool(rx, bs, false); else gve_free_qpl_page_dqo(bs); + + if (gve_buf_state_is_allocated(rx, bs) && + bs->xsk_buff) { + xsk_buff_free(bs->xsk_buff); + bs->xsk_buff = NULL; + } } } From 9fed6a8ab8a5bacba2c709cb19d2dd6d2fa9112e Mon Sep 17 00:00:00 2001 From: Joshua Washington Date: Wed, 30 Sep 2026 14:47:34 -0700 Subject: [PATCH 208/367] gve: fix XSK buffer leak on error descriptor When the error bit is set in the RX completion descriptor, the buf_state and its attached buffer should be freed. In the case of AF_XDP ZC, the XSK buffer was not freed, leading to a leak. This issue was caught by LLM. Fixes: c1fffc5d66a7 ("gve: implement DQO RX datapath and control path for AF_XDP zero-copy") Cc: stable@vger.kernel.org Reviewed-by: Jordan Rhee Reviewed-by: Tim Hostetler Signed-off-by: Joshua Washington Link: https://patch.msgid.link/20260930214735.3288780-3-joshwash@google.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/google/gve/gve_rx_dqo.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/net/ethernet/google/gve/gve_rx_dqo.c b/drivers/net/ethernet/google/gve/gve_rx_dqo.c index a2c4a08ce68d..82ed5e48b00a 100644 --- a/drivers/net/ethernet/google/gve/gve_rx_dqo.c +++ b/drivers/net/ethernet/google/gve/gve_rx_dqo.c @@ -842,7 +842,12 @@ static int gve_rx_dqo(struct napi_struct *napi, struct gve_rx_ring *rx, } if (unlikely(compl_desc->rx_error)) { - gve_free_buffer(rx, buf_state); + if (buf_state->xsk_buff) { + xsk_buff_free(buf_state->xsk_buff); + gve_free_buf_state(rx, buf_state); + } else { + gve_free_buffer(rx, buf_state); + } return -EINVAL; } From 0cb6939a4dfefa822007b7a71986e06bc4f61f54 Mon Sep 17 00:00:00 2001 From: Joshua Washington Date: Wed, 30 Sep 2026 14:47:35 -0700 Subject: [PATCH 209/367] gve: fix napi_disable deadlock when attempting to disable XSK pools When disabling XSK pools, GVE calls the unlocked versions of napi_disable and napi_enable. However, the netdev lock has already been acquired before ndo_bpf is called because GVE supports queue management ops. Calling the unlocked versions of napi_disable/enable results in a deadlock when attempting to disable XSK pools, as the thread attempts to re-acquire a lock it already holds. Update the NAPI calls to use the locked versions. This issue was found in production. Fixes: 606048cbd834 ("net: designate XSK pool pointers in queues as "ops protected"") Cc: stable@vger.kernel.org Reviewed-by: Harshitha Ramamurthy Reviewed-by: Jordan Rhee Signed-off-by: Joshua Washington Link: https://patch.msgid.link/20260930214735.3288780-4-joshwash@google.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/google/gve/gve_main.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/net/ethernet/google/gve/gve_main.c b/drivers/net/ethernet/google/gve/gve_main.c index 9cc343a16271..a6c18e2431cc 100644 --- a/drivers/net/ethernet/google/gve/gve_main.c +++ b/drivers/net/ethernet/google/gve/gve_main.c @@ -1708,17 +1708,17 @@ static int gve_xsk_pool_disable(struct net_device *dev, } napi_rx = &priv->ntfy_blocks[priv->rx[qid].ntfy_id].napi; - napi_disable(napi_rx); /* make sure current rx poll is done */ + napi_disable_locked(napi_rx); /* make sure current rx poll is done */ tx_qid = gve_xdp_tx_queue_id(priv, qid); napi_tx = &priv->ntfy_blocks[priv->tx[tx_qid].ntfy_id].napi; - napi_disable(napi_tx); /* make sure current tx poll is done */ + napi_disable_locked(napi_tx); /* make sure current tx poll is done */ gve_unreg_xsk_pool(priv, qid); smp_mb(); /* Make sure it is visible to the workers on datapath */ - napi_enable(napi_rx); - napi_enable(napi_tx); + napi_enable_locked(napi_rx); + napi_enable_locked(napi_tx); if (gve_is_gqi(priv)) { if (gve_rx_work_pending(&priv->rx[qid])) napi_schedule(napi_rx); From 7e170da2edec9fb0717543571cc14b0aabee2b46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leto=20Liu=20=28=E5=88=98=E6=B6=9B=29?= Date: Wed, 30 Sep 2026 08:58:48 +0200 Subject: [PATCH 210/367] net: airoha: Add retry mechanism to airoha_qdma_set_trtcm_param() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CPU accesses QDMA via the bus. When multiple modules are using the bus simultaneously, CPU access to QDMA may encounter bus timeouts and fails, resulting in QDMA configuration failures and potentially causing packet transmission issues. In order to mitigate the issue, introduce a retry mechanism to airoha_qdma_set_trtcm_param routine in order to ensure the configuration is correctly applied to the hardware. It's enough to try a second time for the TRTCM config to be actually applied to make sure we are not in the middle of bucket handling as it does operate on fixed time slot internally. Fixes: ef1ca9271313b ("net: airoha: Add sched HTB offload support") Signed-off-by: Leto Liu (刘涛) [ improve commit description, drop memory block, apply better loop logic] Signed-off-by: Christian Marangi Link: https://patch.msgid.link/20260930065855.46171-1-ansuelsmth@gmail.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/airoha/airoha_eth.c | 32 +++++++++++++++++++----- drivers/net/ethernet/airoha/airoha_eth.h | 2 ++ 2 files changed, 28 insertions(+), 6 deletions(-) diff --git a/drivers/net/ethernet/airoha/airoha_eth.c b/drivers/net/ethernet/airoha/airoha_eth.c index 64619e9a704d..6ca57875b6d6 100644 --- a/drivers/net/ethernet/airoha/airoha_eth.c +++ b/drivers/net/ethernet/airoha/airoha_eth.c @@ -2800,14 +2800,34 @@ static int airoha_qdma_set_trtcm_param(struct airoha_qdma *qdma, int channel, FIELD_PREP(TRTCM_METER_GROUP_MASK, group) | FIELD_PREP(TRTCM_PARAM_INDEX_MASK, idx) | FIELD_PREP(TRTCM_PARAM_RATE_TYPE_MASK, mode); + int i; - airoha_qdma_wr(qdma, REG_TRTCM_DATA_LOW(addr), val); - airoha_qdma_wr(qdma, REG_TRTCM_CFG_PARAM(addr), config); + /* + * HW might be in the middle of bucket handling and might + * require an additional try to actually apply the new TRTCM + * config. + */ + for (i = 0; i < TRTCM_CONFIG_RETRY_MAX; i++) { + u32 data = 0; - return read_poll_timeout(airoha_qdma_rr, val, - val & TRTCM_PARAM_RW_DONE_MASK, - USEC_PER_MSEC, 10 * USEC_PER_MSEC, true, - qdma, REG_TRTCM_CFG_PARAM(addr)); + airoha_qdma_wr(qdma, REG_TRTCM_DATA_LOW(addr), val); + airoha_qdma_wr(qdma, REG_TRTCM_CFG_PARAM(addr), config); + + if (read_poll_timeout(airoha_qdma_rr, data, + data & TRTCM_PARAM_RW_DONE_MASK, + USEC_PER_MSEC, 10 * USEC_PER_MSEC, + true, qdma, REG_TRTCM_CFG_PARAM(addr))) + continue; + + if (airoha_qdma_get_trtcm_param(qdma, channel, addr, param, + mode, &data, NULL)) + continue; + + if (data == val) + return 0; + } + + return -EBUSY; } static int airoha_qdma_set_trtcm_config(struct airoha_qdma *qdma, int channel, diff --git a/drivers/net/ethernet/airoha/airoha_eth.h b/drivers/net/ethernet/airoha/airoha_eth.h index 8277c1c87bb3..338fde03a149 100644 --- a/drivers/net/ethernet/airoha/airoha_eth.h +++ b/drivers/net/ethernet/airoha/airoha_eth.h @@ -171,6 +171,8 @@ enum trtcm_param { #define TRTCM_TOKEN_RATE_MASK GENMASK(23, 6) #define TRTCM_TOKEN_RATE_FRACTION_MASK GENMASK(5, 0) +#define TRTCM_CONFIG_RETRY_MAX 2 + enum airoha_dma_map_type { AIROHA_DMA_UNMAPPED, AIROHA_DMA_MAP_SINGLE, From edba6017c19f97ecc718fad6b50e0908392247ac Mon Sep 17 00:00:00 2001 From: Zongmin Zhou Date: Mon, 5 Oct 2026 19:02:37 -0600 Subject: [PATCH 211/367] riscv: hwprobe: use _BITULL() rather than BIT() in MIPS vendor uapi header BIT() is a kernel-internal macro that is not available to userspace, but the MIPS vendor extension uapi header uses it without defining or including it. Any userspace program that includes this header and uses RISCV_HWPROBE_VENDOR_EXT_XMIPSEXECTL fails to build. Use _BITULL(0) from linux/const.h instead, which keeps the value at 1, so there is no ABI change. Fixes: bb4b0f8a1bcb ("riscv: hwprobe: Add MIPS vendor extension probing") Cc: stable@vger.kernel.org Signed-off-by: Zongmin Zhou Reviewed-by: Jesse Taube Link: https://patch.msgid.link/20260918073650.42586-1-min_halo@163.com Signed-off-by: Paul Walmsley --- arch/riscv/include/uapi/asm/vendor/mips.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/arch/riscv/include/uapi/asm/vendor/mips.h b/arch/riscv/include/uapi/asm/vendor/mips.h index e65ab268b265..a31c23afe74f 100644 --- a/arch/riscv/include/uapi/asm/vendor/mips.h +++ b/arch/riscv/include/uapi/asm/vendor/mips.h @@ -1,3 +1,5 @@ /* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ -#define RISCV_HWPROBE_VENDOR_EXT_XMIPSEXECTL BIT(0) +#include + +#define RISCV_HWPROBE_VENDOR_EXT_XMIPSEXECTL _BITULL(0) From 756724133bd3b89e097c92110221b44cafe0d06a Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Mon, 5 Oct 2026 19:02:37 -0600 Subject: [PATCH 212/367] riscv: limit sifive errata to CONFIG_64BIT There are two errata for this vendor, and they both individually depend on CONFIG_64BIT already. However, an 32-bit allmodconfig produces this warning from clang for a condition that can never be true. arch/riscv/errata/sifive/errata.c:29:14: error: result of comparison of constant 9223372036854775815 with expression of type 'unsigned long' is always true [-Werror,-Wtautological-constant-out-of-range-compare] 29 | if (arch_id != 0x8000000000000007 || | ~~~~~~~ ^ ~~~~~~~~~~~~~~~~~~ arch/riscv/errata/sifive/errata.c:42:14: error: result of comparison of constant 9223372036854775815 with expression of type 'unsigned long' is always true [-Werror,-Wtautological-constant-out-of-range-compare] 42 | if (arch_id != 0x8000000000000007 && arch_id != 0x1) | ~~~~~~~ ^ ~~~~~~~~~~~~~~~~~~ Signed-off-by: Arnd Bergmann Link: https://patch.msgid.link/20260916083752.47310-1-arnd@kernel.org Signed-off-by: Paul Walmsley --- arch/riscv/Kconfig.errata | 2 +- arch/riscv/Kconfig.socs | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/arch/riscv/Kconfig.errata b/arch/riscv/Kconfig.errata index 3c945d086c7d..38ade0ea4e9c 100644 --- a/arch/riscv/Kconfig.errata +++ b/arch/riscv/Kconfig.errata @@ -46,7 +46,7 @@ config ERRATA_MIPS_P8700_PAUSE_OPCODE config ERRATA_SIFIVE bool "SiFive errata" - depends on RISCV_ALTERNATIVE + depends on RISCV_ALTERNATIVE && 64BIT help All SiFive errata Kconfig depend on this Kconfig. Disabling this Kconfig will disable all SiFive errata. Please say "Y" diff --git a/arch/riscv/Kconfig.socs b/arch/riscv/Kconfig.socs index 429e07589306..cecbce0c4983 100644 --- a/arch/riscv/Kconfig.socs +++ b/arch/riscv/Kconfig.socs @@ -34,6 +34,7 @@ config ARCH_RENESAS config ARCH_SIFIVE bool "SiFive SoCs" select ERRATA_SIFIVE + depends on 64BIT help This enables support for SiFive SoC platform hardware. From 572b25f3a6453d8e7fc28cc0cefe41ed15be166c Mon Sep 17 00:00:00 2001 From: Troy Mitchell Date: Mon, 5 Oct 2026 19:02:37 -0600 Subject: [PATCH 213/367] riscv: vector: Fix data pointer constraints in context save/restore The standard vector save/restore asm advances datap but declares it as input-only. An inlined caller reusing the original pointer may therefore use the advanced address instead. Declare datap as read-write so the compiler can preserve the original pointer when needed. Fixes: 03c3fcd9941a ("riscv: Introduce struct/helpers to save/restore per-task Vector state") Signed-off-by: Troy Mitchell Reviewed-by: GUO Ren (XuanTie) Reviewed-by: Aurelien Jarno Reviewed-by: Andy Chiu Link: https://patch.msgid.link/20260908-riscv-vector-asm-fix-v1-1-147f314efb2b@linux.dev Signed-off-by: Paul Walmsley --- arch/riscv/include/asm/vector.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/arch/riscv/include/asm/vector.h b/arch/riscv/include/asm/vector.h index fffe72a77208..c7fd6d50a7a4 100644 --- a/arch/riscv/include/asm/vector.h +++ b/arch/riscv/include/asm/vector.h @@ -230,7 +230,7 @@ static inline void __riscv_v_vstate_save(struct __riscv_v_ext_state *save_to, "add %1, %1, %0\n\t" "vse8.v v24, (%1)\n\t" ".option pop\n\t" - : "=&r" (vl) : "r" (datap) : "memory"); + : "=&r" (vl), "+r" (datap) : : "memory"); } riscv_v_disable(); } @@ -266,7 +266,7 @@ static inline void __riscv_v_vstate_restore(struct __riscv_v_ext_state *restore_ "add %1, %1, %0\n\t" "vle8.v v24, (%1)\n\t" ".option pop\n\t" - : "=&r" (vl) : "r" (datap) : "memory"); + : "=&r" (vl), "+r" (datap) : : "memory"); } __vstate_csr_restore(restore_from); riscv_v_disable(); From 55b1276ef9c1c27e8a2b2c5e8ef0515b5ad0332c Mon Sep 17 00:00:00 2001 From: Aurelien Jarno Date: Mon, 5 Oct 2026 19:02:37 -0600 Subject: [PATCH 214/367] riscv: vector: Fix output operands in context save The __vstate_csr_save() inline assembly declares vcsr as an output operand while the assembly only writes vstart, vtype, and vl. Remove it as it is later saved by the C code. Fixes: d863910eabaf ("riscv: vector: Support xtheadvector save/restore") Signed-off-by: Aurelien Jarno Link: https://patch.msgid.link/20260911194218.3379486-1-aurelien@aurel32.net Signed-off-by: Paul Walmsley --- arch/riscv/include/asm/vector.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/arch/riscv/include/asm/vector.h b/arch/riscv/include/asm/vector.h index c7fd6d50a7a4..1f8dcae3af51 100644 --- a/arch/riscv/include/asm/vector.h +++ b/arch/riscv/include/asm/vector.h @@ -137,8 +137,8 @@ static __always_inline void __vstate_csr_save(struct __riscv_v_ext_state *dest) "csrr %0, " __stringify(CSR_VSTART) "\n\t" "csrr %1, " __stringify(CSR_VTYPE) "\n\t" "csrr %2, " __stringify(CSR_VL) "\n\t" - : "=r" (dest->vstart), "=r" (dest->vtype), "=r" (dest->vl), - "=r" (dest->vcsr) : :); + : "=r" (dest->vstart), "=r" (dest->vtype), "=r" (dest->vl) + : :); if (has_xtheadvector()) { unsigned long status; From 148ab873d8194cb44f29474da3b4428fa2e100d1 Mon Sep 17 00:00:00 2001 From: Yong-Xuan Wang Date: Mon, 5 Oct 2026 19:02:38 -0600 Subject: [PATCH 215/367] RISC-V: errata: Add SiFive MAL-9092 workaround SiFive cores with MAL-9092 require fence.rw instructions around HLV/HLVX/HSV hypervisor instructions to ensure proper memory ordering when accessing guest memory. Note that HSTATUS.HU is cleared during KVM initialization, restricting hypervisor instructions to HS-mode only. If future changes enable HSTATUS.HU for U-mode access, additional errata considerations will be needed. Signed-off-by: Yong-Xuan Wang Reviewed-by: Samuel Holland Link: https://patch.msgid.link/20260817-sifive-errata-v1-1-6a80f1a2743d@sifive.com Signed-off-by: Paul Walmsley --- arch/riscv/Kconfig.errata | 14 ++++++++++++++ arch/riscv/errata/sifive/errata.c | 20 ++++++++++++++++++++ arch/riscv/include/asm/errata_list_vendors.h | 3 ++- arch/riscv/include/asm/insn-def.h | 16 +++++++++++++--- 4 files changed, 49 insertions(+), 4 deletions(-) diff --git a/arch/riscv/Kconfig.errata b/arch/riscv/Kconfig.errata index 38ade0ea4e9c..a6aac8774f25 100644 --- a/arch/riscv/Kconfig.errata +++ b/arch/riscv/Kconfig.errata @@ -96,6 +96,20 @@ config ERRATA_STARFIVE_JH7100 Say "Y" if you want to support the BeagleV Starlight and/or StarFive VisionFive V1 boards. +config ERRATA_SIFIVE_MAL_9092 + bool "Apply SiFive HLV/HLVX/HSV fence errata" + depends on ERRATA_SIFIVE && 64BIT + default y + help + This will apply the SiFive MAL-9092 errata to add fence.rw + instructions around all HLV/HLVX/HSV instructions. + + Note: HSTATUS.HU is cleared during KVM initialization. If future + changes enable HSTATUS.HU to allow hypervisor instructions in + U-mode, additional errata handling may be required. + + If you don't know what to do here, say "Y". + config ERRATA_THEAD bool "T-HEAD errata" depends on RISCV_ALTERNATIVE diff --git a/arch/riscv/errata/sifive/errata.c b/arch/riscv/errata/sifive/errata.c index df80c9614df1..1a5d223ebf35 100644 --- a/arch/riscv/errata/sifive/errata.c +++ b/arch/riscv/errata/sifive/errata.c @@ -51,6 +51,22 @@ static bool errata_cip_1200_check_func(unsigned long arch_id, unsigned long imp return true; } +static bool errata_mal_9092_check_func(unsigned long arch_id, unsigned long impid) +{ + /* + * Affected cores: + * Architecture ID: 0x8000000000000109 + * Implementation ID: 0x19251031, 0x19251217, 0x19260320 + */ + + if (arch_id != 0x8000000000000109) + return false; + if (impid != 0x19251031 && impid != 0x19251217 && impid != 0x19260320) + return false; + + return true; +} + static struct errata_info_t errata_list[ERRATA_SIFIVE_NUMBER] = { { .name = "cip-453", @@ -60,6 +76,10 @@ static struct errata_info_t errata_list[ERRATA_SIFIVE_NUMBER] = { .name = "cip-1200", .check_func = errata_cip_1200_check_func }, + { + .name = "mal-9092", + .check_func = errata_mal_9092_check_func + }, }; static u32 __init_or_module sifive_errata_probe(unsigned long archid, diff --git a/arch/riscv/include/asm/errata_list_vendors.h b/arch/riscv/include/asm/errata_list_vendors.h index ec7eba373437..c62a82f60468 100644 --- a/arch/riscv/include/asm/errata_list_vendors.h +++ b/arch/riscv/include/asm/errata_list_vendors.h @@ -11,7 +11,8 @@ #ifdef CONFIG_ERRATA_SIFIVE #define ERRATA_SIFIVE_CIP_453 0 #define ERRATA_SIFIVE_CIP_1200 1 -#define ERRATA_SIFIVE_NUMBER 2 +#define ERRATA_SIFIVE_MAL_9092 2 +#define ERRATA_SIFIVE_NUMBER 3 #endif #ifdef CONFIG_ERRATA_THEAD diff --git a/arch/riscv/include/asm/insn-def.h b/arch/riscv/include/asm/insn-def.h index 7c6daf116756..8fcc848c196e 100644 --- a/arch/riscv/include/asm/insn-def.h +++ b/arch/riscv/include/asm/insn-def.h @@ -192,18 +192,28 @@ INSN_R(OPCODE_SYSTEM, FUNC3(0), FUNC7(49), \ __RD(0), RS1(gaddr), RS2(vmid)) +#define ALT_SIFIVE_MAL_9092_FENCE \ +ALTERNATIVE("nop", "fence rw, rw", SIFIVE_VENDOR_ID, \ + ERRATA_SIFIVE_MAL_9092, CONFIG_ERRATA_SIFIVE_MAL_9092) + #define HLVX_HU(dest, addr) \ + ALT_SIFIVE_MAL_9092_FENCE "\n" \ INSN_R(OPCODE_SYSTEM, FUNC3(4), FUNC7(50), \ - RD(dest), RS1(addr), __RS2(3)) + RD(dest), RS1(addr), __RS2(3)) "\n" \ + ALT_SIFIVE_MAL_9092_FENCE #define HLV_W(dest, addr) \ + ALT_SIFIVE_MAL_9092_FENCE "\n" \ INSN_R(OPCODE_SYSTEM, FUNC3(4), FUNC7(52), \ - RD(dest), RS1(addr), __RS2(0)) + RD(dest), RS1(addr), __RS2(0)) "\n" \ + ALT_SIFIVE_MAL_9092_FENCE #ifdef CONFIG_64BIT #define HLV_D(dest, addr) \ + ALT_SIFIVE_MAL_9092_FENCE "\n" \ INSN_R(OPCODE_SYSTEM, FUNC3(4), FUNC7(54), \ - RD(dest), RS1(addr), __RS2(0)) + RD(dest), RS1(addr), __RS2(0)) "\n" \ + ALT_SIFIVE_MAL_9092_FENCE #else #define HLV_D(dest, addr) \ __ASM_STR(.error "hlv.d requires 64-bit support") From 6694612317e5225086e39ca1ba97ada2b1942e48 Mon Sep 17 00:00:00 2001 From: Yong-Xuan Wang Date: Mon, 5 Oct 2026 19:02:38 -0600 Subject: [PATCH 216/367] RISC-V: Clear HSTATUS.HU on CPU initialization The RISC-V privileged specification does not mandate HSTATUS reset values, leaving the HU bit potentially set after hardware reset. When HU=1, hypervisor instructions (HLV/HLVX/HSV) can execute in U-mode to access guest memory, which may cause unintended behavior if not explicitly controlled. Clear HSTATUS.HU during CPU initialization to ensure hypervisor instructions are only available in HS-mode, preventing unexpected guest memory access from U-mode code. Signed-off-by: Yong-Xuan Wang Reviewed-by: Samuel Holland Link: https://patch.msgid.link/20260924-hstatus_hu-v2-1-7e970f5f1d8d@sifive.com Cc: stable@vger.kernel.org [pjw@kernel.org: capitalized RISCV_ISA_EXT_h to fix build; added stable cc] Signed-off-by: Paul Walmsley --- arch/riscv/include/asm/cpufeature.h | 2 ++ arch/riscv/kernel/cpufeature.c | 12 ++++++++++++ arch/riscv/kernel/setup.c | 2 ++ arch/riscv/kernel/smpboot.c | 2 ++ arch/riscv/kernel/suspend.c | 2 ++ 5 files changed, 20 insertions(+) diff --git a/arch/riscv/include/asm/cpufeature.h b/arch/riscv/include/asm/cpufeature.h index 739fcc84bf7b..5efa72823475 100644 --- a/arch/riscv/include/asm/cpufeature.h +++ b/arch/riscv/include/asm/cpufeature.h @@ -40,6 +40,8 @@ extern u32 thead_vlenb_of; void __init riscv_user_isa_enable(void); +void riscv_clear_hypervisor_csr(void); + #define _RISCV_ISA_EXT_DATA(_name, _id, _subset_exts, _subset_exts_size, _validate) { \ .name = #_name, \ .property = #_name, \ diff --git a/arch/riscv/kernel/cpufeature.c b/arch/riscv/kernel/cpufeature.c index 61d21f714830..7329792978cc 100644 --- a/arch/riscv/kernel/cpufeature.c +++ b/arch/riscv/kernel/cpufeature.c @@ -1233,6 +1233,18 @@ void __init riscv_user_isa_enable(void) pr_warn("Zicbop disabled as it is unavailable on some harts\n"); } +void riscv_clear_hypervisor_csr(void) +{ + if (!riscv_has_extension_unlikely(RISCV_ISA_EXT_H)) + return; + + /* + * Clear HSTATUS.HU to restrict hypervisor instructions to HS-mode. + * This prevents user-mode from executing HLV/HSV instructions. + */ + csr_clear(CSR_HSTATUS, HSTATUS_HU); +} + #ifdef CONFIG_RISCV_ALTERNATIVE /* * Alternative patch sites consider 48 bits when determining when to patch diff --git a/arch/riscv/kernel/setup.c b/arch/riscv/kernel/setup.c index a32344bb220d..d79f99b86033 100644 --- a/arch/riscv/kernel/setup.c +++ b/arch/riscv/kernel/setup.c @@ -366,6 +366,8 @@ void __init setup_arch(char **cmdline_p) if (!IS_ENABLED(CONFIG_RISCV_ISA_ZBB) || !riscv_isa_extension_available(NULL, ZBB)) static_branch_disable(&efficient_ffs_key); + + riscv_clear_hypervisor_csr(); } bool arch_cpu_is_hotpluggable(int cpu) diff --git a/arch/riscv/kernel/smpboot.c b/arch/riscv/kernel/smpboot.c index f6ef57930b50..a9de2dae804c 100644 --- a/arch/riscv/kernel/smpboot.c +++ b/arch/riscv/kernel/smpboot.c @@ -244,6 +244,8 @@ asmlinkage __visible void smp_callin(void) numa_add_cpu(curr_cpuid); + riscv_clear_hypervisor_csr(); + pr_debug("CPU%u: Booted secondary hartid %lu\n", curr_cpuid, cpuid_to_hartid_map(curr_cpuid)); diff --git a/arch/riscv/kernel/suspend.c b/arch/riscv/kernel/suspend.c index 3efbf7874f3b..db220966f782 100644 --- a/arch/riscv/kernel/suspend.c +++ b/arch/riscv/kernel/suspend.c @@ -43,6 +43,8 @@ void suspend_save_csrs(struct suspend_context *context) void suspend_restore_csrs(struct suspend_context *context) { + riscv_clear_hypervisor_csr(); + csr_write(CSR_SCRATCH, 0); if (riscv_has_extension_unlikely(RISCV_ISA_EXT_XLINUXENVCFG)) csr_write(CSR_ENVCFG, context->envcfg); From a510e1ae8c9030750c2edf7b0bc9b186cd8c7a06 Mon Sep 17 00:00:00 2001 From: Wei-Jie Hung Date: Mon, 5 Oct 2026 19:02:38 -0600 Subject: [PATCH 217/367] riscv: patch: fix handling of bpf-jit execmem addresses Commit 8718e5a3090b ("riscv: patch: skip fixmap mapping when kernel text is already writable") gated the core_kernel_text() branch of patch_map() on CONFIG_STRICT_KERNEL_RWX, and explicitly left the vmalloc branch unchanged. That branch has a separate problem. That branch only creates a temporary writable fixmap alias when CONFIG_STRICT_MODULE_RWX is enabled, and otherwise assumes the target is directly writable and returns the address unchanged. That assumption no longer holds: bpf_prog_pack_alloc calls set_memory_rox() on every pack unconditionally in alloc_new_pack(), independently of any CONFIG_STRICT_*_RWX option, so BPF text in the vmalloc area is read-only regardless of what CONFIG_STRICT_MODULE_RWX says. With CONFIG_STRICT_MODULE_RWX=n, patch_map() returns the read-only address directly, the subsequent copy_to_kernel_nofault() takes a page fault and returns -EFAULT. bpf_arch_text_copy() turns that into -EINVAL, which propagates through bpf_jit_binary_pack_finalize() to the WARN_ON() in bpf_int_jit_compile() and leaves the program un-JITed. Note that BPF cannot be fixed the way kprobes was in commit bdc46e507b59 ("riscv: mm: make EXECMEM_KPROBES writable without CONFIG_STRICT_MODULE_RWX"). EXECMEM_BPF is already PAGE_KERNEL, i.e. writable at allocation time; the read-only mapping is established afterwards by generic code in alloc_new_pack(), which arch code cannot influence. The only place this can be handled is patch_map(). This is the same problem that was fixed on arm64 by commit b1480ed230ac ("arm64: patching: fix handling of execmem addresses"), and the fix is the same: CONFIG_EXECMEM is what actually tracks whether the vmalloc area can hold executable memory that needs a temporary alias to be written. CONFIG_BPF_JIT, CONFIG_KPROBES and CONFIG_MODULES all select it. Note that this is not limited to CONFIG_MODULES=n. Unlike arm64, riscv selects CONFIG_ARCH_OPTIONAL_KERNEL_RWX, so CONFIG_STRICT_MODULE_RWX can be disabled with CONFIG_MODULES=y as well, and the bug is reachable in that configuration too. The !CONFIG_MMU build is unaffected: it has its own __patch_insn_set() and __patch_insn_write() implementations and never calls patch_map(). Fixes: 2c9e5d4a0082 ("bpf: remove CONFIG_BPF_JIT dependency on CONFIG_MODULES of") Signed-off-by: Wei-Jie Hung Reviewed-by: Pu Lehui Tested-by: Pu Lehui Link: https://patch.msgid.link/20260914115607.350097-1-imbigking12@gmail.com Cc: stable@vger.kernel.org Signed-off-by: Paul Walmsley --- arch/riscv/kernel/patch.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/riscv/kernel/patch.c b/arch/riscv/kernel/patch.c index 2239c28981bc..2b5adf01c550 100644 --- a/arch/riscv/kernel/patch.c +++ b/arch/riscv/kernel/patch.c @@ -48,7 +48,7 @@ static __always_inline void *patch_map(void *addr, const unsigned int fixmap) if (!IS_ENABLED(CONFIG_STRICT_KERNEL_RWX)) return addr; phys = __pa_symbol(addr); - } else if (IS_ENABLED(CONFIG_STRICT_MODULE_RWX)) { + } else if (IS_ENABLED(CONFIG_EXECMEM)) { struct page *page = vmalloc_to_page(addr); BUG_ON(!page); From 282f4a8aa95c5a01b96e55eb5da297560e5f2986 Mon Sep 17 00:00:00 2001 From: Shao Mingyin Date: Mon, 5 Oct 2026 19:02:38 -0600 Subject: [PATCH 218/367] riscv: lib: Fix ZBB strnlen wrap-around regression on huge counts The aligned scan boundary is derived from the last valid byte, (s + count - 1). When count is huge (e.g. SIZE_MAX, which FORTIFY strcat/strlcat pass when the destination size is not known at compile time), s + count wraps around and the boundary lands before s, so the ZBB path returns a bogus length. The original implementation (5ba15d419fab) had the same wrap-around in its (s + count) & ~7 boundary computation; after 5d588c684833 the wrapped boundary is caught by the pre-loop guard "bgeu t0, t4, 2f", which then always exits for aligned strings of 8 or more characters and strnlen() returns 8 instead of the real length. This silently truncates strings built by fortified strcat: the dm sysfs name attribute shows "live-bas" instead of "live-base", the truncated name pollutes the udev database, and blivet/anaconda (as well as LVM/dm-crypt/multipath userspace) break on RISC-V systems. Detect the wrap-around and saturate the boundary to the top of the address space, making the scan equivalent to strlen(). The saturation clamps the increment to ~s, so it stays branchless and wrap-free: s + min(count - 1, ~s) == saturating_add(s, count - 1) Normal counts are unaffected. Fixes: 5ba15d419fab ("riscv: lib: add strnlen() implementation") Cc: stable@vger.kernel.org Suggested-by: David Laight Suggested-by: Qingfang Deng Signed-off-by: Shao Mingyin Acked-by: Michael Neuling Tested-by: Troy Mitchell Reviewed-by: Aurelien Jarno Link: https://patch.msgid.link/20260915152656708z04s4oSYY2BGj34F36RZa@zte.com.cn Signed-off-by: Paul Walmsley --- arch/riscv/lib/strnlen.S | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/arch/riscv/lib/strnlen.S b/arch/riscv/lib/strnlen.S index a8911605c248..5b3bbf0a3098 100644 --- a/arch/riscv/lib/strnlen.S +++ b/arch/riscv/lib/strnlen.S @@ -87,9 +87,25 @@ strnlen_zbb: * Aligned boundary. Use the address of the last valid byte * (s + count - 1) to avoid loading a word past the count * boundary in the loop below. count == 0 is handled above. + * + * Saturate the boundary when s + count would wrap around (very + * large counts, e.g. SIZE_MAX passed by FORTIFY strcat/strlcat + * with a destination whose size is unknown at compile time). + * Without this, the wrapped boundary lands before s and the + * pre-loop guard below always exits, returning a truncated + * length. + * + * Clamping the increment to ~s (== SIZE_MAX - s) keeps the + * computation branchless and wrap-free: + * + * s + min(count - 1, ~s) == saturating_add(s, count - 1) + * + * Saturating makes the scan equivalent to strlen(). */ - add t4, a0, a1 - addi t4, t4, -1 + addi t4, a1, -1 /* count - 1 */ + not t1, a0 /* SIZE_MAX - s */ + minu t4, t4, t1 /* clamp, so s + t4 can never wrap */ + add t4, a0, t4 /* saturated s + count - 1 */ andi t4, t4, -SZREG /* Get the first word. */ From d5a007b9b457c915ab1a53227e8939e4018aa97a Mon Sep 17 00:00:00 2001 From: Jakub Kicinski Date: Mon, 5 Oct 2026 18:19:20 -0700 Subject: [PATCH 219/367] Revert "net: stmmac: propagate PTP addend and system time programming errors" This reverts commit 232d49dd4b40a666283de9e722899f088ed581b2. Anirudh reports a regression from this change. It is not appropriate for net this late in the release cycle in the first place, so let's revert and revisit in net-next. Reported-by: Anirudh Srinivasan Link: https://lore.kernel.org/CAEev2e8+fr1tJnSErD+2TtLDw5fGJbQOMcM5eefMnLyFUR+m7w@mail.gmail.com Fixes: 232d49dd4b40 ("net: stmmac: propagate PTP addend and system time programming errors") Signed-off-by: Jakub Kicinski --- .../net/ethernet/stmicro/stmmac/stmmac_main.c | 106 +++++------------- .../net/ethernet/stmicro/stmmac/stmmac_ptp.c | 10 +- 2 files changed, 32 insertions(+), 84 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index 77d2bffb8dc1..b837e8e27a35 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -601,64 +601,31 @@ static void stmmac_get_rx_hwtstamp(struct stmmac_priv *priv, struct dma_desc *p, } } -static void stmmac_restore_subsecond_increment(struct stmmac_priv *priv, - u32 default_addend) +static void stmmac_update_subsecond_increment(struct stmmac_priv *priv) { bool xmac = dwmac_is_xmac(priv->plat->core_type); u32 sec_inc = 0; - - stmmac_config_addend(priv, priv->ptpaddr, default_addend); - stmmac_config_hw_tstamping(priv, priv->ptpaddr, priv->systime_flags); - stmmac_config_sub_second_increment(priv, priv->ptpaddr, - priv->plat->clk_ptp_rate, - xmac, &sec_inc); - priv->default_addend = default_addend; - priv->sub_second_inc = sec_inc; -} - -static int stmmac_update_subsecond_increment(struct stmmac_priv *priv, - u32 systime_flags) -{ - bool xmac = dwmac_is_xmac(priv->plat->core_type); - u32 sec_inc = 0, val; u64 temp = 0; - int ret; - stmmac_config_hw_tstamping(priv, priv->ptpaddr, systime_flags); + stmmac_config_hw_tstamping(priv, priv->ptpaddr, priv->systime_flags); /* program Sub Second Increment reg */ stmmac_config_sub_second_increment(priv, priv->ptpaddr, priv->plat->clk_ptp_rate, xmac, &sec_inc); - if (!sec_inc) { - ret = -EINVAL; - goto error; - } + temp = div_u64(1000000000ULL, sec_inc); + + /* Store sub second increment for later use */ + priv->sub_second_inc = sec_inc; /* calculate default added value: * formula is : * addend = (2^32)/freq_div_ratio; * where, freq_div_ratio = 1e9ns/sec_inc */ - temp = div_u64(1000000000ULL, sec_inc); temp = (u64)(temp << 32); - val = div_u64(temp, priv->plat->clk_ptp_rate); - - ret = stmmac_config_addend(priv, priv->ptpaddr, val); - if (ret) - goto error; - - priv->sub_second_inc = sec_inc; - priv->default_addend = val; - - return 0; -error: - /* Restore previous configuration */ - stmmac_config_hw_tstamping(priv, priv->ptpaddr, priv->systime_flags); - stmmac_config_sub_second_increment(priv, priv->ptpaddr, - priv->plat->clk_ptp_rate, xmac, - NULL); - return ret; + priv->default_addend = div_u64(temp, priv->plat->clk_ptp_rate); + stmmac_config_addend(priv, priv->ptpaddr, priv->default_addend); } /** @@ -887,42 +854,35 @@ static int stmmac_hwtstamp_get(struct net_device *dev, /** * stmmac_init_tstamp_counter - init hardware timestamping counter * @priv: driver private structure + * @systime_flags: timestamping flags * Description: * Initialize hardware counter for packet timestamping. * This is valid as long as the interface is open and not suspended. * Will be rerun after resuming from suspend, case in which the timestamping * flags updated by stmmac_hwtstamp_set() also need to be restored. */ -static int stmmac_init_tstamp_counter(struct stmmac_priv *priv) +static int stmmac_init_tstamp_counter(struct stmmac_priv *priv, + u32 systime_flags) { - u32 default_addend = priv->default_addend; struct timespec64 now; - int ret; if (!priv->plat->clk_ptp_rate) { netdev_err(priv->dev, "Invalid PTP clock rate"); return -EINVAL; } - ret = stmmac_update_subsecond_increment(priv, priv->systime_flags); - if (ret) - return ret; + stmmac_config_hw_tstamping(priv, priv->ptpaddr, systime_flags); + priv->systime_flags = systime_flags; + + stmmac_update_subsecond_increment(priv); /* initialize system time */ ktime_get_real_ts64(&now); /* lower 32 bits of tv_sec are safe until y2106 */ - ret = stmmac_init_systime(priv, priv->ptpaddr, (u32)now.tv_sec, - now.tv_nsec); - if (ret) - goto error; + stmmac_init_systime(priv, priv->ptpaddr, (u32)now.tv_sec, now.tv_nsec); return 0; -error: - /* Restore previous configuration */ - stmmac_restore_subsecond_increment(priv, default_addend); - - return ret; } /** @@ -945,14 +905,8 @@ static int stmmac_init_timestamping(struct stmmac_priv *priv) return -EOPNOTSUPP; } - /* Reset hw ts configuration */ - memset(&priv->tstamp_config, 0, sizeof(priv->tstamp_config)); - priv->systime_flags = STMMAC_HWTS_ACTIVE | PTP_TCR_TSCFUPDT; - priv->tsfupdt_coarse = false; - priv->hwts_tx_en = 0; - priv->hwts_rx_en = 0; - - ret = stmmac_init_tstamp_counter(priv); + ret = stmmac_init_tstamp_counter(priv, STMMAC_HWTS_ACTIVE | + PTP_TCR_TSCFUPDT); if (ret) { netdev_warn(priv->dev, "PTP init failed\n"); return ret; @@ -973,6 +927,10 @@ static int stmmac_init_timestamping(struct stmmac_priv *priv) netdev_info(priv->dev, "IEEE 1588-2008 Advanced Timestamp supported\n"); + memset(&priv->tstamp_config, 0, sizeof(priv->tstamp_config)); + priv->hwts_tx_en = 0; + priv->hwts_rx_en = 0; + if (priv->plat->flags & STMMAC_FLAG_HWTSTAMP_CORRECT_LATENCY) stmmac_hwtstamp_correct_latency(priv, priv); @@ -7749,26 +7707,18 @@ static int stmmac_dl_ts_coarse_set(struct devlink *dl, u32 id, { struct stmmac_devlink_priv *dl_priv = devlink_priv(dl); struct stmmac_priv *priv = dl_priv->stmmac_priv; - u32 systime_flags = priv->systime_flags; - int ret; - if (ctx->val.vbool) - systime_flags &= ~PTP_TCR_TSCFUPDT; + priv->tsfupdt_coarse = ctx->val.vbool; + + if (priv->tsfupdt_coarse) + priv->systime_flags &= ~PTP_TCR_TSCFUPDT; else - systime_flags |= PTP_TCR_TSCFUPDT; + priv->systime_flags |= PTP_TCR_TSCFUPDT; /* In Coarse mode, we can use a smaller subsecond increment, let's * reconfigure the systime, subsecond increment and addend. */ - ret = stmmac_update_subsecond_increment(priv, systime_flags); - if (ret) { - NL_SET_ERR_MSG_MOD(extack, - "failed to reconfigure PTP adjustment"); - return ret; - } - - priv->tsfupdt_coarse = ctx->val.vbool; - priv->systime_flags = systime_flags; + stmmac_update_subsecond_increment(priv); return 0; } diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_ptp.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_ptp.c index 493c5d81a36b..3bfcc9760dce 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_ptp.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_ptp.c @@ -28,15 +28,14 @@ static int stmmac_adjust_freq(struct ptp_clock_info *ptp, long scaled_ppm) container_of(ptp, struct stmmac_priv, ptp_clock_ops); unsigned long flags; u32 addend; - int ret; addend = adjust_by_scaled_ppm(priv->default_addend, scaled_ppm); write_lock_irqsave(&priv->ptp_lock, flags); - ret = stmmac_config_addend(priv, priv->ptpaddr, addend); + stmmac_config_addend(priv, priv->ptpaddr, addend); write_unlock_irqrestore(&priv->ptp_lock, flags); - return ret; + return 0; } /** @@ -154,13 +153,12 @@ static int stmmac_set_time(struct ptp_clock_info *ptp, struct stmmac_priv *priv = container_of(ptp, struct stmmac_priv, ptp_clock_ops); unsigned long flags; - int ret; write_lock_irqsave(&priv->ptp_lock, flags); - ret = stmmac_init_systime(priv, priv->ptpaddr, ts->tv_sec, ts->tv_nsec); + stmmac_init_systime(priv, priv->ptpaddr, ts->tv_sec, ts->tv_nsec); write_unlock_irqrestore(&priv->ptp_lock, flags); - return ret; + return 0; } static int stmmac_enable(struct ptp_clock_info *ptp, From 78e04810542da416d3bead707b60cec773f1524d Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Sun, 20 Sep 2026 11:49:53 +0800 Subject: [PATCH 220/367] scsi: ufs: mediatek: Handle mPHY power-on failures The MediaTek helper enables VA09 before powering on the mPHY, but ignores the PHY result. A failure can leave VA09 enabled and publish mphy_powered_on as true, so a later power-on attempt is skipped. Check phy_power_on(), attempt to unwind VA09 on failure, and leave the state flag unchanged. Propagate the helper result from host initialization as well as its existing resume caller. Keep the original PHY error if the supply rollback also fails, and report that rollback separately. The issue was found by our static-analysis tool. Fixes: 561e3a8726b2 ("scsi: ufs-mediatek: Fix unbalanced clock on/off") Fixes: cf137b3ea49a ("scsi: ufs-mediatek: Support VA09 regulator operations") Assisted-by: gpt 5 Signed-off-by: Pengpeng Hou Reviewed-by: Peter Wang Reviewed-by: Stanley Jhu Link: https://patch.msgid.link/20260920034953.19535-1-hppiscas@163.com Signed-off-by: Martin K. Petersen (Oracle) --- drivers/ufs/host/ufs-mediatek.c | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/drivers/ufs/host/ufs-mediatek.c b/drivers/ufs/host/ufs-mediatek.c index 814c1b7343b9..12a6d4138e44 100644 --- a/drivers/ufs/host/ufs-mediatek.c +++ b/drivers/ufs/host/ufs-mediatek.c @@ -509,6 +509,7 @@ static int ufs_mtk_mphy_power_on(struct ufs_hba *hba, bool on) struct ufs_mtk_host *host = ufshcd_get_variant(hba); struct phy *mphy = host->mphy; struct arm_smccc_res res; + int err; int ret = 0; if (!mphy || !(on ^ host->mphy_powered_on)) @@ -523,7 +524,18 @@ static int ufs_mtk_mphy_power_on(struct ufs_hba *hba, bool on) usleep_range(200, 210); ufs_mtk_va09_pwr_ctrl(res, 1); } - phy_power_on(mphy); + ret = phy_power_on(mphy); + if (ret) { + if (ufs_mtk_is_va09_supported(hba)) { + ufs_mtk_va09_pwr_ctrl(res, 0); + err = regulator_disable(host->reg_va09); + if (err) + dev_err(hba->dev, + "failed to unwind va09: %d\n", err); + } + dev_err(hba->dev, "failed to enable mphy: %d\n", ret); + return ret; + } } else { phy_power_off(mphy); if (ufs_mtk_is_va09_supported(hba)) { @@ -1295,7 +1307,9 @@ static int ufs_mtk_init(struct ufs_hba *hba) * * Enable phy clocks specifically here. */ - ufs_mtk_mphy_power_on(hba, true); + err = ufs_mtk_mphy_power_on(hba, true); + if (err) + goto out_variant_clear; if (ufs_mtk_is_rtff_mtcmos(hba)) { /* First Restore here, to avoid backup unexpected value */ From 27c0f718d3b3c99e6d315308e95d987d0382a8a8 Mon Sep 17 00:00:00 2001 From: Ahmed Abdelhaleem Ahmed Date: Tue, 22 Sep 2026 15:46:46 +0000 Subject: [PATCH 221/367] scsi: ch: Do not keep references to data transfer element devices ch_readconfig() looks up the scsi_device of every data transfer element whose SCSI id the changer reports in READ ELEMENT STATUS, and stores it in ch->dt[]. scsi_device_lookup() takes a reference, and nothing ever drops it: ch_destroy() frees the array with kfree(). ch->dt[] is read nowhere else - it only supplies the vendor, model and revision printed in the same loop. Once such a drive is removed, its scsi_device can never be released. It stays on the host's device list at its address, so a new device there is refused by anything that walks the list - target_core_pscsi reports "scsi_device_get() failed for H:C:T:L" - and the low-level driver's module can no longer be unloaded. Only a reboot recovers. It shows with any changer that reports its drives' ids; with the mhvtl virtual library (IBM 3573-TL personality), each create and remove of a library with four drives leaves four references behind, counted by the module's use count in lsmod. With ch not bound the count is unchanged, and with this patch applied it is unchanged too. Drop the reference as soon as the name has been printed, and remove the now unused dt[] array. That also removes the array leaked when ch_probe() fails after ch_readconfig(). The same leak was reported with an RFC patch in 2022, which was not merged. Fixes: daa6eda65a53 ("[SCSI] add scsi changer driver") Cc: stable@vger.kernel.org Link: https://lore.kernel.org/linux-scsi/20220719075442.6215-1-yanghao_ht@163.com/ Signed-off-by: Ahmed Abdelhaleem Ahmed Reviewed-by: Laurence Oberman Fixes: daa6eda65a53 ("[SCSI] add scsi changer driver") Link: https://patch.msgid.link/20260929-ch-dt-leak-v2-1-ddda8d635dca@gmail.com Signed-off-by: Martin K. Petersen (Oracle) --- drivers/scsi/ch.c | 29 +++++++++-------------------- 1 file changed, 9 insertions(+), 20 deletions(-) diff --git a/drivers/scsi/ch.c b/drivers/scsi/ch.c index 4010fdbf813c..c36539c83ed1 100644 --- a/drivers/scsi/ch.c +++ b/drivers/scsi/ch.c @@ -112,7 +112,6 @@ typedef struct { int minor; char name[8]; struct scsi_device *device; - struct scsi_device **dt; /* ptrs to data transfer elements */ u_int firsts[CH_TYPES]; u_int counts[CH_TYPES]; u_int voltags; @@ -363,15 +362,10 @@ ch_readconfig(scsi_changer *ch) vendor_labels[i]); } - /* look up the devices of the data transfer elements */ - ch->dt = kzalloc_objs(*ch->dt, ch->counts[CHET_DT]); - - if (!ch->dt) { - kfree(buffer); - return -ENOMEM; - } - + /* report the devices of the data transfer elements */ for (elem = 0; elem < ch->counts[CHET_DT]; elem++) { + struct scsi_device *sdev; + id = -1; lun = 0; if (elem < CH_DT_MAX && -1 != dt_id[elem]) { @@ -387,10 +381,8 @@ ch_readconfig(scsi_changer *ch) VPRINTK(KERN_INFO, "dt 0x%x: ",elem+ch->firsts[CHET_DT]); if (data[6] & 0x80) { VPRINTK(KERN_CONT, "not this SCSI bus\n"); - ch->dt[elem] = NULL; } else if (0 == (data[6] & 0x30)) { VPRINTK(KERN_CONT, "ID/LUN unknown\n"); - ch->dt[elem] = NULL; } else { id = ch->device->id; lun = 0; @@ -400,18 +392,16 @@ ch_readconfig(scsi_changer *ch) } if (-1 != id) { VPRINTK(KERN_CONT, "ID %i, LUN %i, ",id,lun); - ch->dt[elem] = - scsi_device_lookup(ch->device->host, - ch->device->channel, - id,lun); - if (!ch->dt[elem]) { + sdev = scsi_device_lookup(ch->device->host, + ch->device->channel, + id, lun); + if (!sdev) { /* should not happen */ VPRINTK(KERN_CONT, "Huh? device not found!\n"); } else { VPRINTK(KERN_CONT, "name: %8.8s %16.16s %4.4s\n", - ch->dt[elem]->vendor, - ch->dt[elem]->model, - ch->dt[elem]->rev); + sdev->vendor, sdev->model, sdev->rev); + scsi_device_put(sdev); } } } @@ -575,7 +565,6 @@ static void ch_destroy(struct kref *ref) scsi_changer *ch = container_of(ref, scsi_changer, ref); ch->device = NULL; - kfree(ch->dt); kfree(ch); } From 375f3a5691dd46b5d05b90a8968872ec8b2248c5 Mon Sep 17 00:00:00 2001 From: Stanley Jhu Date: Mon, 28 Sep 2026 11:58:15 +0800 Subject: [PATCH 222/367] scsi: ufs: core: Avoid unsafe MMIO reads in ufshcd_mcq_compl_all_cqes_lock() During MCQ host reset, ufshcd_host_reset_and_restore() stops the host controller via ufshcd_hba_stop() (HCE = 0) before calling ufshcd_complete_requests(hba, true) -> ufshcd_mcq_compl_pending_transfer(hba, true) -> ufshcd_mcq_force_compl_one() -> ufshcd_mcq_compl_all_cqes_lock(). Because ufshcd_mcq_force_compl_one() is its sole caller, ufshcd_mcq_compl_all_cqes_lock() always runs with HCE = 0. Despite the comment above ufshcd_mcq_compl_all_cqes_lock() stating that reading CQTPy may not be safe with the controller disabled, the function still calls ufshcd_mcq_update_cq_tail_slot() at the end of its sweep: 1. Unsafe CQTPy MMIO read: Calling ufshcd_mcq_update_cq_tail_slot() at the end of the sweep reads CQTPy over MMIO while HCE = 0, directly contradicting the function's documented contract (commit 1373df88d535 ("scsi: ufs: core: Add a comment block above ufshcd_mcq_compl_all_cqes_lock()")) that reading CQTPy may not be safe with the controller disabled. 2. Spurious error logs on empty slots: Sweeping all max_entries slots visits empty entries where command_desc_base_addr is 0, causing ufshcd_mcq_process_cqe() to log unguarded dev_err(hba->dev, "Abnormal CQ entry!\n") messages. Fix both issues in ufshcd_mcq_compl_all_cqes_lock(): - Remove the ufshcd_mcq_update_cq_tail_slot() call and the redundant hwq->cq_head_slot = hwq->cq_tail_slot assignment without replacement. The two indices are already equal after the sweep: they are equal when the sweep starts, since ufshcd_mcq_poll_cqe_lock() consumes entries until cq_head_slot reaches cq_tail_slot, and the sweep advances cq_head_slot by exactly one full ring. Both indices are also reinitialized before the queue is reused. - Extract ufshcd_mcq_compl_cqe() and invoke it only on non-empty slots during full-ring sweeps, keeping "Abnormal CQ entry!" logging strictly for unexpected empty entries in ufshcd_mcq_poll_cqe_lock(). Fixes: ab248643d3d6 ("scsi: ufs: core: Add error handling for MCQ mode") Cc: stable@vger.kernel.org Reviewed-by: Peter Wang Signed-off-by: Stanley Jhu Reviewed-by: Bart Van Assche Link: https://patch.msgid.link/20260928035816.1294326-2-stanleyjhu@google.com Signed-off-by: Martin K. Petersen (Oracle) --- drivers/ufs/core/ufs-mcq.c | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/drivers/ufs/core/ufs-mcq.c b/drivers/ufs/core/ufs-mcq.c index 8106d55f4041..df293b66350d 100644 --- a/drivers/ufs/core/ufs-mcq.c +++ b/drivers/ufs/core/ufs-mcq.c @@ -312,20 +312,24 @@ static int ufshcd_mcq_get_tag(struct ufs_hba *hba, struct cq_entry *cqe) UFSHCD_NUM_RESERVED; } +static void ufshcd_mcq_compl_cqe(struct ufs_hba *hba, struct cq_entry *cqe) +{ + int tag = ufshcd_mcq_get_tag(hba, cqe); + + ufshcd_compl_one_cqe(hba, tag, cqe); + /* After processing the CQE, mark it as an empty (invalid) entry. */ + cqe->command_desc_base_addr = 0; +} + static void ufshcd_mcq_process_cqe(struct ufs_hba *hba, struct ufs_hw_queue *hwq) { struct cq_entry *cqe = ufshcd_mcq_cur_cqe(hwq); - if (cqe->command_desc_base_addr) { - int tag = ufshcd_mcq_get_tag(hba, cqe); - - ufshcd_compl_one_cqe(hba, tag, cqe); - /* After processed the cqe, mark it empty (invalid) entry */ - cqe->command_desc_base_addr = 0; - } else { + if (cqe->command_desc_base_addr) + ufshcd_mcq_compl_cqe(hba, cqe); + else dev_err(hba->dev, "Abnormal CQ entry!\n"); - } } /* @@ -333,7 +337,7 @@ static void ufshcd_mcq_process_cqe(struct ufs_hba *hba, * controller disabled (HCE = 0). Reading host controller registers, e.g. the * CQ tail pointer (CQTPy), may not be safe with the host controller disabled. * Hence, iterate over all completion queue entries. This won't result in - * double completions because ufshcd_mcq_process_cqe() clears a CQE after it + * double completions because ufshcd_mcq_compl_cqe() clears a CQE after it * has been processed. */ void ufshcd_mcq_compl_all_cqes_lock(struct ufs_hba *hba, @@ -344,13 +348,13 @@ void ufshcd_mcq_compl_all_cqes_lock(struct ufs_hba *hba, spin_lock_irqsave(&hwq->cq_lock, flags); while (entries > 0) { - ufshcd_mcq_process_cqe(hba, hwq); + struct cq_entry *cqe = ufshcd_mcq_cur_cqe(hwq); + + if (cqe->command_desc_base_addr) + ufshcd_mcq_compl_cqe(hba, cqe); ufshcd_mcq_inc_cq_head_slot(hwq); entries--; } - - ufshcd_mcq_update_cq_tail_slot(hwq); - hwq->cq_head_slot = hwq->cq_tail_slot; spin_unlock_irqrestore(&hwq->cq_lock, flags); } From 469fa055664bfd8717b087a80d59c03055789c17 Mon Sep 17 00:00:00 2001 From: Stanley Jhu Date: Mon, 28 Sep 2026 11:58:16 +0800 Subject: [PATCH 223/367] scsi: ufs: core: Decouple CQ sweep from request iterator in MCQ In MCQ mode, ufshcd_mcq_compl_pending_transfer() uses blk_mq_tagset_busy_iter() to iterate over busy requests during error recovery and host reset. However, both iterator callbacks perform whole-queue operations redundantly for each visited request: - force_compl == true: ufshcd_mcq_force_compl_one() calls ufshcd_mcq_compl_all_cqes_lock() on every busy request, sweeping the entire completion ring (hwq->max_entries slots) once per active request under spin_lock_irqsave even though the first sweep already cleared all completion entries. - force_compl == false: ufshcd_mcq_compl_one() acquires cq_lock and polls CQTPy over MMIO via ufshcd_mcq_poll_cqe_lock() for every busy request without doing any per-request work. Sweep or poll each hardware queue (hba->uhq[i]) once at the start of ufshcd_mcq_compl_pending_transfer(). When force_compl is true, run blk_mq_tagset_busy_iter() afterward to complete residual in-flight requests with DID_REQUEUE, and remove the now-unused ufshcd_mcq_compl_one() callback. Fixes: ab248643d3d6 ("scsi: ufs: core: Add error handling for MCQ mode") Cc: stable@vger.kernel.org Reviewed-by: Bart Van Assche Signed-off-by: Stanley Jhu Reviewed-by: Peter Wang Link: https://patch.msgid.link/20260928035816.1294326-3-stanleyjhu@google.com Signed-off-by: Martin K. Petersen (Oracle) --- drivers/ufs/core/ufshcd.c | 31 ++++++++++++------------------- 1 file changed, 12 insertions(+), 19 deletions(-) diff --git a/drivers/ufs/core/ufshcd.c b/drivers/ufs/core/ufshcd.c index 79b6a2d9b522..ca8329052cbc 100644 --- a/drivers/ufs/core/ufshcd.c +++ b/drivers/ufs/core/ufshcd.c @@ -6044,8 +6044,6 @@ static bool ufshcd_mcq_force_compl_one(struct request *rq, void *priv) if (blk_mq_is_reserved_rq(rq) || !hwq) return true; - ufshcd_mcq_compl_all_cqes_lock(hba, hwq); - /* * For those cmds of which the cqes are not present in the cq, complete * them explicitly. @@ -6061,19 +6059,6 @@ static bool ufshcd_mcq_force_compl_one(struct request *rq, void *priv) return true; } -static bool ufshcd_mcq_compl_one(struct request *rq, void *priv) -{ - struct scsi_device *sdev = rq->q->queuedata; - struct Scsi_Host *shost = sdev->host; - struct ufs_hba *hba = shost_priv(shost); - struct ufs_hw_queue *hwq = ufshcd_mcq_req_to_hwq(hba, rq); - - if (!blk_mq_is_reserved_rq(rq) && hwq) - ufshcd_mcq_poll_cqe_lock(hba, hwq); - - return true; -} - /** * ufshcd_mcq_compl_pending_transfer - MCQ mode function. It is * invoked from the error handler context or ufshcd_host_reset_and_restore() @@ -6088,10 +6073,18 @@ static bool ufshcd_mcq_compl_one(struct request *rq, void *priv) static void ufshcd_mcq_compl_pending_transfer(struct ufs_hba *hba, bool force_compl) { - blk_mq_tagset_busy_iter(&hba->host->tag_set, - force_compl ? ufshcd_mcq_force_compl_one : - ufshcd_mcq_compl_one, - NULL); + int i; + + for (i = 0; i < hba->nr_hw_queues; i++) { + if (force_compl) + ufshcd_mcq_compl_all_cqes_lock(hba, &hba->uhq[i]); + else + ufshcd_mcq_poll_cqe_lock(hba, &hba->uhq[i]); + } + + if (force_compl) + blk_mq_tagset_busy_iter(&hba->host->tag_set, + ufshcd_mcq_force_compl_one, NULL); } /** From 0ec14f8c8c5da1c9c314bce4f0bf53c009fbc998 Mon Sep 17 00:00:00 2001 From: Felix Fietkau Date: Tue, 18 Aug 2026 12:58:23 +0000 Subject: [PATCH 224/367] wifi: mt76: mt7996: take over connection monitoring mt76 runs its own beacon monitor. mt76_beacon_mon_check() counts a miss for each affiliated link of an MLD, and it stops the count while the phy is off channel. mt7996 supplies it through mt76_rx_beacon(), but it does not set CONNECTION_MONITOR. Therefore mac80211 also starts its legacy monitor. The legacy monitor knows one interface, not a set of links. It also counts while the radio serves a different link. After seven beacon intervals it removes an association that still carries traffic on the other links. Set the CONNECTION_MONITOR flag to disable the legacy monitor. Fixes: 4df75606ece5 ("wifi: mt76: add per-link beacon monitoring for MLO") Link: https://patch.msgid.link/20260818125825.395538-3-nbd@nbd.name Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7996/init.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/init.c b/drivers/net/wireless/mediatek/mt76/mt7996/init.c index fb635a092584..2db63c3be9d1 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/init.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/init.c @@ -540,6 +540,7 @@ mt7996_init_wiphy(struct ieee80211_hw *hw, struct mtk_wed_device *wed) ieee80211_hw_set(hw, NO_VIRTUAL_MONITOR); ieee80211_hw_set(hw, SUPPORTS_MULTI_BSSID); ieee80211_hw_set(hw, CHANCTX_STA_CSA); + ieee80211_hw_set(hw, CONNECTION_MONITOR); hw->max_tx_fragments = 4; wiphy->txq_memory_limit = 32 << 20; /* 32 MiB */ From 752770d7a58c821c17909c3ca67f9f14ab146a40 Mon Sep 17 00:00:00 2001 From: Felix Fietkau Date: Tue, 18 Aug 2026 12:58:25 +0000 Subject: [PATCH 225/367] wifi: mt76: account non-AQL frames per peer rather than per link The driver increments wcid->non_aql_packets when a frame goes to the hardware queue. The completion path decrements the counter again. For an MLO peer, the hardware reports the completion on the link that it used for the transmission. This link can differ from the link that the driver queued the frame on. In that case, the increment and the decrement go to different link wcids, and the count drifts upwards until it blocks the station. Keep the count on the wcid of the primary link, like the other peer-wide state. Fixes: 86c051f2c418 ("wifi: mt76: mt7925: enabling MLO when the firmware supports it") Link: https://patch.msgid.link/20260818125825.395538-5-nbd@nbd.name Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt76.h | 7 +++++++ drivers/net/wireless/mediatek/mt76/tx.c | 11 ++++++++--- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt76.h b/drivers/net/wireless/mediatek/mt76/mt76.h index 62b41c8bb7c0..2ade848fe3bb 100644 --- a/drivers/net/wireless/mediatek/mt76/mt76.h +++ b/drivers/net/wireless/mediatek/mt76/mt76.h @@ -1426,6 +1426,13 @@ mtxq_to_txq(struct mt76_txq *mtxq) return container_of(ptr, struct ieee80211_txq, drv_priv); } +/* peer-wide state uses the wcid of the primary link */ +static inline struct mt76_wcid * +mt76_wcid_primary(struct mt76_wcid *wcid) +{ + return wcid->def_wcid ? wcid->def_wcid : wcid; +} + static inline struct ieee80211_sta * wcid_to_sta(struct mt76_wcid *wcid) { diff --git a/drivers/net/wireless/mediatek/mt76/tx.c b/drivers/net/wireless/mediatek/mt76/tx.c index 3707ee19e4ae..b1e894b19cbd 100644 --- a/drivers/net/wireless/mediatek/mt76/tx.c +++ b/drivers/net/wireless/mediatek/mt76/tx.c @@ -234,6 +234,8 @@ mt76_tx_check_non_aql(struct mt76_dev *dev, struct mt76_wcid *wcid, if (!wcid || info->tx_time_est) return; + wcid = mt76_wcid_primary(wcid); + pending = atomic_dec_return(&wcid->non_aql_packets); if (pending < 0) atomic_cmpxchg(&wcid->non_aql_packets, pending, 0); @@ -344,7 +346,8 @@ __mt76_tx_queue_skb(struct mt76_phy *phy, int qid, struct sk_buff *skb, if (!non_aql) return idx; - pending = atomic_inc_return(&wcid->non_aql_packets); + /* the hardware can report the completion on a different link */ + pending = atomic_inc_return(&mt76_wcid_primary(wcid)->non_aql_packets); if (stop && pending >= MT_MAX_NON_AQL_PKT) *stop = true; @@ -506,7 +509,8 @@ mt76_txq_send_burst(struct mt76_phy *phy, struct mt76_queue *q, return 0; } - if (atomic_read(&wcid->non_aql_packets) >= MT_MAX_NON_AQL_PKT) + if (atomic_read(&mt76_wcid_primary(wcid)->non_aql_packets) >= + MT_MAX_NON_AQL_PKT) return 0; skb = mt76_txq_dequeue(phy, mtxq); @@ -603,7 +607,8 @@ mt76_txq_schedule_list(struct mt76_phy *phy, enum mt76_txq_id qid) continue; } - if (atomic_read(&wcid->non_aql_packets) >= MT_MAX_NON_AQL_PKT) + if (atomic_read(&mt76_wcid_primary(wcid)->non_aql_packets) >= + MT_MAX_NON_AQL_PKT) continue; if (dev->queue_ops->tx_cleanup && q->queued + 2 * MT_TXQ_FREE_THR >= q->ndesc) { From 7c2e6b43c3082c2242ecfa5875b2b4b2cd06140f Mon Sep 17 00:00:00 2001 From: Felix Fietkau Date: Tue, 1 Sep 2026 11:22:32 +0200 Subject: [PATCH 226/367] wifi: mt76: use ALTX queue for packets to disassociated stations Running these packets through regular buffering queues could cause them to hang, especially when the packets were for stations that in disassoc state. Fixes: dedf2ec30fe4 ("wifi: mt76: fix queue assignment for deauth packets") Acked-by: Lorenzo Bianconi Link: https://patch.msgid.link/20260901092232.461379-1-nbd@nbd.name Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7915/main.c | 2 ++ drivers/net/wireless/mediatek/mt76/mt7996/main.c | 1 + drivers/net/wireless/mediatek/mt76/tx.c | 3 ++- 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7915/main.c b/drivers/net/wireless/mediatek/mt76/mt7915/main.c index a8286f8becf9..a03d89aabe80 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7915/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7915/main.c @@ -805,6 +805,7 @@ __mt7915_drop_sta(void *ptr, u8 *mac, struct ieee80211_vif *vif) msta = (struct mt7915_sta *)sta->drv_priv; mt7915_mcu_add_sta(data->dev, vif, sta, CONN_STATE_DISCONNECT, false); + msta->wcid.tx_info &= ~MT_WCID_TX_INFO_SET; msta->wcid.sta_disabled = 1; msta->wcid.sta = 0; } @@ -871,6 +872,7 @@ int mt7915_mac_sta_event(struct mt76_dev *mdev, struct ieee80211_vif *vif, mutex_unlock(&dev->mt76.mutex); mt7915_mcu_add_sta(dev, vif, sta, CONN_STATE_DISCONNECT, false); + msta->wcid.tx_info &= ~MT_WCID_TX_INFO_SET; msta->wcid.sta_disabled = 1; msta->wcid.sta = 0; return 0; diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/main.c b/drivers/net/wireless/mediatek/mt76/mt7996/main.c index d17d1dbb2038..dd7a01c02a7c 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/main.c @@ -1433,6 +1433,7 @@ mt7996_mac_sta_event(struct mt7996_dev *dev, struct ieee80211_vif *vif, else if (sta->mlo && links == BIT(link_id)) /* last link */ mt7996_mcu_teardown_mld_sta(dev, link, msta_link); + msta_link->wcid.tx_info &= ~MT_WCID_TX_INFO_SET; msta_link->wcid.sta_disabled = 1; msta_link->wcid.sta = 0; links = links & ~BIT(link_id); diff --git a/drivers/net/wireless/mediatek/mt76/tx.c b/drivers/net/wireless/mediatek/mt76/tx.c index b1e894b19cbd..3c623df2e741 100644 --- a/drivers/net/wireless/mediatek/mt76/tx.c +++ b/drivers/net/wireless/mediatek/mt76/tx.c @@ -680,7 +680,8 @@ mt76_txq_schedule_pending_wcid(struct mt76_phy *phy, struct mt76_wcid *wcid, if ((dev->drv->drv_flags & MT_DRV_HW_MGMT_TXQ) && !(info->flags & IEEE80211_TX_CTL_HW_80211_ENCAP) && !ieee80211_is_data_present(hdr->frame_control) && - (!ieee80211_is_bufferable_mmpdu(skb) || + (!(wcid->tx_info & MT_WCID_TX_INFO_SET) || + !ieee80211_is_bufferable_mmpdu(skb) || ieee80211_is_deauth(hdr->frame_control) || ieee80211_is_disassoc(hdr->frame_control) || head == &wcid->tx_offchannel)) From 8f9e5807671ee97cacf1957f4a09e43f4f1017a9 Mon Sep 17 00:00:00 2001 From: Aaron Ma Date: Wed, 26 Aug 2026 12:36:58 +0800 Subject: [PATCH 227/367] wifi: mt76: mt7925: restore the legacy BSS after MLO teardown Removing the last link of an MLD interface leaves the firmware with the MLD BSS and DEV entry: the removal loop skips the deflink, and the hweight16(mvif->valid_links) fallback reads the stale pre-update valid_links, which is still non-zero. The entry keeps the per-link MAC address, so every subsequent legacy association fails authentication until the interface is removed or the module is reloaded. Remove the deflink BSS and add it back as a legacy BSS on the transition to zero links. Fixes: 69acd6d910b0 ("wifi: mt76: mt7925: add mt7925_change_vif_links") Signed-off-by: Aaron Ma Link: https://patch.msgid.link/20260826043658.2255553-1-aaron.ma@canonical.com Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7925/main.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/main.c b/drivers/net/wireless/mediatek/mt76/mt7925/main.c index 5993b31e1aae..549f9c06b363 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/main.c @@ -2294,6 +2294,18 @@ mt7925_change_vif_links(struct ieee80211_hw *hw, struct ieee80211_vif *vif, mvif->valid_links = new_links; + /* Restore the legacy BSS after disabling MLO */ + if (old_links && !new_links) { + mt792x_mac_link_bss_remove(dev, &mvif->bss_conf, + &mvif->sta.deflink); + err = mt7925_mac_link_bss_add(dev, &vif->bss_conf, + &mvif->sta.deflink); + if (err < 0) { + mt792x_mutex_release(dev); + return err; + } + } + mt792x_mutex_release(dev); return 0; From 9c9e4e4c8b45873e53774ab8207825b37b7565ec Mon Sep 17 00:00:00 2001 From: Tao Gong Date: Sun, 23 Aug 2026 05:37:39 +0000 Subject: [PATCH 228/367] wifi: mt76: mt7996: fix struct mt7996_mcu_wed_rro_ba_delete_event layout Correct the definition of struct mt7996_mcu_wed_rro_ba_delete_event to match the 12-byte event record layout observed from firmware. The event TLV sent by firmware is 12 bytes in length rather than 8 bytes. It contains an additional 4 bytes that appear to be wlan_id and tid. For example, a captured batch deletion of sessions 8, 9, and 10 (TIDs 6, 0, and 1) shows: 00000000: 02 00 0c 00 08 00 01 00 06 00 00 00 02 00 0c 00 00000010: 09 00 01 00 00 00 00 00 02 00 0c 00 0a 00 01 00 00000020: 01 00 00 00 The previous 8-byte structure corrupts the parsing of subsequent TLVs, leading to unhandled delete events and erroneous session deletions. Consequently, clients that create and tear down BA sessions in batches (such as Apple Wi-Fi clients) fail to properly clean up stale sessions, causing HW_RRO to be lost upon subsequent reassociations. Fixes: a5d028d66836 ("wifi: mt76: mt7996: add wed rro delete session garbage collector") Signed-off-by: Tao Gong Link: https://patch.msgid.link/20260823053739.186748-1-gongtao0607@gmail.com Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7996/mcu.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.h b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.h index 74b70fb6da3d..1b272e904100 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.h +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.h @@ -284,6 +284,9 @@ struct mt7996_mcu_wed_rro_ba_delete_event { __le16 len; __le16 session_id; + __le16 wlan_id; + u8 tid; + u8 __rsv1; u8 __rsv2[2]; } __packed; From 916c484d29d82f45c281da2786fcd6a1284430ce Mon Sep 17 00:00:00 2001 From: Nicolas Cavallari Date: Mon, 10 Aug 2026 17:45:10 +0200 Subject: [PATCH 229/367] wifi: mt76: mt7915: disable rx napi when removing device Unloading the mt7915e module with a MT7916 triggers multiples WARN in __netif_napi_del_locked() and in page_pool_disable_direct_recycling() because the driver does not disable the napi before destroying it. This is troublesome since on MT7916 it is required to unload the module and reinsert it with a different enable_6ghz parameter to change the frequency. The system generally becomes unstable after reinserting the module. Fix it by disabling rx napi inside mt7915e itself, even through napi rx is enabled in the mt76 generic code. Disabling it in the mt76 generic code was previously attempted and reverted because napi was disabled too late and rx handlers could access freed structures. Note that the tx side is enabled and disabled by the mt76 generic code. It is disabled in mt7915_stop_hardware() since, unlike mt7915_unregister_device(), is it also called in the probe path if an error happens after rx napi is enabled. Fixes: e57b7901469f ("mt76: add mac80211 driver for MT7915 PCIe-based chipsets") Signed-off-by: Nicolas Cavallari Link: https://patch.msgid.link/20260810154923.32546-1-nicolas.cavallari@green-communications.fr Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7915/init.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/net/wireless/mediatek/mt76/mt7915/init.c b/drivers/net/wireless/mediatek/mt76/mt7915/init.c index ca46a203aa48..c8e389db63a7 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7915/init.c +++ b/drivers/net/wireless/mediatek/mt76/mt7915/init.c @@ -1238,6 +1238,12 @@ static void mt7915_unregister_ext_phy(struct mt7915_dev *dev) static void mt7915_stop_hardware(struct mt7915_dev *dev) { + int i; + + mt76_for_each_q_rx(&dev->mt76, i) { + if (!mt76_queue_is_wed_rro(&dev->mt76.q_rx[i])) + napi_disable(&dev->mt76.napi[i]); + } mt7915_mcu_exit(dev); mt76_connac2_tx_token_put(&dev->mt76); mt7915_dma_cleanup(dev); From 5796b2bf9ff718342773ac760d86eb126c112a4f Mon Sep 17 00:00:00 2001 From: Devin Wittmayer Date: Thu, 10 Sep 2026 19:44:28 -0700 Subject: [PATCH 230/367] wifi: mt76: mt7925: don't put the band auto marker in TGID band_idx starts out as 0xff, which tells the firmware to pick the band on its own. A remain-on-channel grant normally writes a real index over it before anything else looks. Two cases never see that: an MLD station throws the grant away, and an AP has no association to trigger one. The TX descriptor reads the same field into a two-bit slot, so the marker comes out as 3. Every data frame from those two goes out tagged band 3. Beacons are fine, they reach the firmware separately and never build a descriptor. Same numbers on an MT7925 and on a Netgear A9000: stock patched station, one link 0x00000001 0x00000001 station, two links 0x00003001 0x00000001 AP 0x00003013 0x00000013 real band index, STA 0x00001001 0x00001001 real band index, AP 0x00001013 0x00001013 Only the marker cases move. Fixes: 0cb349d742d1 ("wifi: mt76: mt7925: update mt7925_mac_link_bss_add for MLO") Signed-off-by: Devin Wittmayer Link: https://patch.msgid.link/20260911024428.65244-1-lucid_duck@justthetip.ca Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7925/mac.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mac.c b/drivers/net/wireless/mediatek/mt76/mt7925/mac.c index 101f571b027f..3b82c9de26c5 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/mac.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/mac.c @@ -798,7 +798,8 @@ mt7925_mac_write_txwi(struct mt76_dev *dev, __le32 *txwi, val = FIELD_PREP(MT_TXD1_WLAN_IDX, wcid->idx) | FIELD_PREP(MT_TXD1_OWN_MAC, omac_idx); - if (band_idx) + /* 0xff means BAND_AUTO to the firmware, and TGID cannot encode it */ + if (band_idx && band_idx != 0xff) val |= FIELD_PREP(MT_TXD1_TGID, band_idx); txwi[1] = cpu_to_le32(val); From 456de496b53ae73e6e3020fec815dbdc88aa7c07 Mon Sep 17 00:00:00 2001 From: Cristian Papa Date: Thu, 3 Sep 2026 22:07:02 -0300 Subject: [PATCH 231/367] wifi: mt76: mt7603: initialize global station WCID mt7603 uses a driver-private global station WCID for frames that have neither a station nor a vif, including frames injected through a monitor interface. Unlike the core global WCID initialized by mt76_register_device(), this WCID is published without ever being passed to mt76_wcid_init(). Since commit 0335c034e726 ("wifi: mt76: fix race condition related to checking tx queue fill status"), mt76_tx() queues every such frame on the WCID's tx_pending or tx_offchannel queue and links its tx_list. The first injected frame therefore reaches __skb_queue_tail() with an uninitialized sk_buff_head and dereferences a NULL prev pointer. Initialize the WCID before publishing it. The fix was tested on an MT7603 PCIe device. Three wildcard probe requests were injected through a monitor interface. The hardware queue head and tail advanced from 0 to 3 and a second radio captured the third request over the air. No oops or reset occurred. After restoring AP mode, a 150 MB transfer also completed without a reset. Fixes: 0335c034e726 ("wifi: mt76: fix race condition related to checking tx queue fill status") Cc: stable@vger.kernel.org Signed-off-by: Cristian Papa Link: https://patch.msgid.link/20260904010702.16306-1-pcristian292@gmail.com Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7603/init.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/wireless/mediatek/mt76/mt7603/init.c b/drivers/net/wireless/mediatek/mt76/mt7603/init.c index 10f2ec70c792..218ec406d2a8 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7603/init.c +++ b/drivers/net/wireless/mediatek/mt76/mt7603/init.c @@ -230,6 +230,7 @@ mt7603_mac_init(struct mt7603_dev *dev) eth_broadcast_addr(bc_addr); mt7603_wtbl_init(dev, MT7603_WTBL_RESERVED, -1, bc_addr); + mt76_wcid_init(&dev->global_sta.wcid, 0); dev->global_sta.wcid.idx = MT7603_WTBL_RESERVED; rcu_assign_pointer(dev->mt76.wcid[MT7603_WTBL_RESERVED], &dev->global_sta.wcid); From 45f53d98cbaa078907b6a300ff3974dfb5ae8776 Mon Sep 17 00:00:00 2001 From: Junjie Cao Date: Thu, 10 Sep 2026 09:23:00 +0800 Subject: [PATCH 232/367] wifi: mt76: mt792x: treat 0xff ACPI SAR entries as no limit Some ASUS firmware carries 0xff in MTGS power slots. The driver narrows the byte to s8, so the slot reads as -1 in its 0.5 dBm units, wins the min() against the regulatory limit and caps every rate the driver programs. On an ASUS Zenbook 14 UM3406HA (MT7922) all six MTGS tables hold 0xff in every power byte, and txpower_sku shows tmac 3 on every supported OFDM/HT/VHT/HE rate against EEPROM entries of 25-39, under both the FR and the world regulatory domain. Huy Nguyen Dinh Quang saw the same clamp on a Vivobook S 14 M5406WA, with 5 GHz TX at ~40 Mbps, and proposed skipping the geo entry. Skip a geo entry whose power byte is 0xff. The MTDS entry is narrowed the same way on its way into the geo lookup, so give it the same treatment, and clamp the dynamic value plus offset at 127 so a no-limit entry cannot wrap. Fixes: f965333e491e ("mt76: mt7921: introduce ACPI SAR support") Link: https://bugzilla.redhat.com/show_bug.cgi?id=2515420 Reported-by: Huy Nguyen Dinh Quang Closes: https://lore.kernel.org/linux-wireless/20260827134820.2607530-1-huynguyendinhquang@gmail.com/ Signed-off-by: Junjie Cao Tested-by: Cristian Fleischer Link: https://patch.msgid.link/acb947435fc78d5c06039c7bd5609c735468b0ee.1789002236.git.junjie.cao@intel.com Signed-off-by: Felix Fietkau --- .../wireless/mediatek/mt76/mt792x_acpi_sar.c | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt792x_acpi_sar.c b/drivers/net/wireless/mediatek/mt76/mt792x_acpi_sar.c index b468051fbe68..0ffb277f9909 100644 --- a/drivers/net/wireless/mediatek/mt76/mt792x_acpi_sar.c +++ b/drivers/net/wireless/mediatek/mt76/mt792x_acpi_sar.c @@ -279,10 +279,18 @@ mt792x_asar_get_geo_pwr(struct mt792x_phy *phy, if (idx >= max) return dyn_power; - geo_power = (band_pwr + idx)->pwr; - dyn_power += (band_pwr + idx)->offset; + band_pwr += idx; - return min(geo_power, dyn_power); + /* Some OEM tables carry 0xff here; narrowed to s8 that is -1 and + * wins the min() below. Take it as no limit. + */ + if (band_pwr->pwr == 0xff) + return dyn_power; + + geo_power = band_pwr->pwr; + + return min_t(int, geo_power, + min_t(int, dyn_power + band_pwr->offset, 127)); } static s8 @@ -315,7 +323,9 @@ mt792x_asar_range_pwr(struct mt792x_phy *phy, else band = NL80211_BAND_2GHZ; - return mt792x_asar_get_geo_pwr(phy, band, limit[idx]); + /* 0xff: no dynamic limit, see mt792x_asar_get_geo_pwr() */ + return mt792x_asar_get_geo_pwr(phy, band, + limit[idx] == 0xff ? 127 : limit[idx]); } int mt792x_init_acpi_sar_power(struct mt792x_phy *phy, bool set_default) From af97bf5a7e72da9026de184676df8e2fa57e1058 Mon Sep 17 00:00:00 2001 From: Devin Wittmayer Date: Fri, 4 Sep 2026 18:35:51 -0700 Subject: [PATCH 233/367] wifi: mt76: mt792x: pick the SAR table layout from the table itself The SAR power tables are parsed with a version byte from a different ACPI table, never their own. Some firmware declares version 1 and ships version 2 tables. The entries then start a byte early, and the 6 GHz limits the table carries are dropped, because the older layout has no room for them. ASUS GA503RM, MTCL declares v1: MTGS 28 bytes v1: (28-6) % 5 = 2, no fit v2: 3 tables MTDS 19 bytes v1: (19-6) % 6 = 1, no fit v2: 1 table The layouts accept disjoint lengths, so take whichever one fits and keep it per table. Reported-by: Sagun Kayastha Closes: https://github.com/morrownr/mt76/issues/62 Fixes: f965333e491e ("mt76: mt7921: introduce ACPI SAR support") Signed-off-by: Devin Wittmayer Link: https://patch.msgid.link/20260905013551.24635-1-lucid_duck@justthetip.ca Signed-off-by: Felix Fietkau --- .../wireless/mediatek/mt76/mt792x_acpi_sar.c | 116 +++++++++++------- .../wireless/mediatek/mt76/mt792x_acpi_sar.h | 2 + 2 files changed, 76 insertions(+), 42 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt792x_acpi_sar.c b/drivers/net/wireless/mediatek/mt76/mt792x_acpi_sar.c index 0ffb277f9909..e87dfb0f1b3a 100644 --- a/drivers/net/wireless/mediatek/mt76/mt792x_acpi_sar.c +++ b/drivers/net/wireless/mediatek/mt76/mt792x_acpi_sar.c @@ -108,69 +108,99 @@ mt792x_asar_acpi_read_mtcl(struct mt792x_dev *dev, u8 **table, u8 *version) return ret; } +/* A table's layout is decided by the version byte in MTCL, but some firmware + * labels a version 2 table as version 1. Accept whichever layout the table + * actually has: for every accepted entry count the two do not overlap, since + * one is 6 + 5n bytes and the other 7 + 7n. + */ +static bool +mt792x_asar_fits(u8 *table, int len, int prelen, int sarlen, int nr_off, + int min, int max) +{ + int tblcnt; + + if (len < prelen || (len - prelen) % sarlen) + return false; + + tblcnt = (len - prelen) / sarlen; + if (tblcnt < min || tblcnt > max) + return false; + + return tblcnt == table[nr_off]; +} + /* MTDS : Dynamic SAR Power Table */ static int -mt792x_asar_acpi_read_mtds(struct mt792x_dev *dev, u8 **table, u8 version) +mt792x_asar_acpi_read_mtds(struct mt792x_dev *dev, u8 **table, u8 version, + u8 *used) { - int len, ret, sarlen, prelen, tblcnt; - bool enable; + int len, ret; + u8 *t; ret = mt792x_acpi_read(dev, MT792x_ACPI_MTDS, table, &len); if (ret) return ret; - /* Table content validation */ - switch (version) { - case 1: - enable = ((struct mt792x_asar_dyn *)*table)->enable; - sarlen = sizeof(struct mt792x_asar_dyn_limit); - prelen = sizeof(struct mt792x_asar_dyn); - break; - case 2: - enable = ((struct mt792x_asar_dyn_v2 *)*table)->enable; - sarlen = sizeof(struct mt792x_asar_dyn_limit_v2); - prelen = sizeof(struct mt792x_asar_dyn_v2); - break; - default: - return -EINVAL; - } + t = *table; - tblcnt = (len - prelen) / sarlen; - if (!enable || - tblcnt > MT792x_ASAR_MAX_DYN || tblcnt < MT792x_ASAR_MIN_DYN) + /* Table content validation */ + if (mt792x_asar_fits(t, len, sizeof(struct mt792x_asar_dyn), + sizeof(struct mt792x_asar_dyn_limit), + offsetof(struct mt792x_asar_dyn, nr_tbl), + MT792x_ASAR_MIN_DYN, MT792x_ASAR_MAX_DYN)) + *used = 1; + else if (mt792x_asar_fits(t, len, sizeof(struct mt792x_asar_dyn_v2), + sizeof(struct mt792x_asar_dyn_limit_v2), + offsetof(struct mt792x_asar_dyn_v2, nr_tbl), + MT792x_ASAR_MIN_DYN, MT792x_ASAR_MAX_DYN)) + *used = 2; + else return -EINVAL; + if (!t[offsetof(struct mt792x_asar_dyn, enable)]) + return -EINVAL; + + if (version && *used != version) + dev_info(dev->mt76.dev, + "MTDS is v%u, MTCL says v%u; using v%u\n", + *used, version, *used); + return 0; } /* MTGS : Geo SAR Power Table */ static int -mt792x_asar_acpi_read_mtgs(struct mt792x_dev *dev, u8 **table, u8 version) +mt792x_asar_acpi_read_mtgs(struct mt792x_dev *dev, u8 **table, u8 version, + u8 *used) { - int len, ret, sarlen, prelen, tblcnt; + int len, ret; + u8 *t; ret = mt792x_acpi_read(dev, MT792x_ACPI_MTGS, table, &len); if (ret) return ret; - /* Table content validation */ - switch (version) { - case 1: - sarlen = sizeof(struct mt792x_asar_geo_limit); - prelen = sizeof(struct mt792x_asar_geo); - break; - case 2: - sarlen = sizeof(struct mt792x_asar_geo_limit_v2); - prelen = sizeof(struct mt792x_asar_geo_v2); - break; - default: - return -EINVAL; - } + t = *table; - tblcnt = (len - prelen) / sarlen; - if (tblcnt > MT792x_ASAR_MAX_GEO || tblcnt < MT792x_ASAR_MIN_GEO) + /* Table content validation */ + if (mt792x_asar_fits(t, len, sizeof(struct mt792x_asar_geo), + sizeof(struct mt792x_asar_geo_limit), + offsetof(struct mt792x_asar_geo, nr_tbl), + MT792x_ASAR_MIN_GEO, MT792x_ASAR_MAX_GEO)) + *used = 1; + else if (mt792x_asar_fits(t, len, sizeof(struct mt792x_asar_geo_v2), + sizeof(struct mt792x_asar_geo_limit_v2), + offsetof(struct mt792x_asar_geo_v2, nr_tbl), + MT792x_ASAR_MIN_GEO, MT792x_ASAR_MAX_GEO)) + *used = 2; + else return -EINVAL; + if (version && *used != version) + dev_info(dev->mt76.dev, + "MTGS is v%u, MTCL says v%u; using v%u\n", + *used, version, *used); + return 0; } @@ -205,14 +235,16 @@ int mt792x_init_acpi_sar(struct mt792x_dev *dev) asar->countrylist = NULL; } - ret = mt792x_asar_acpi_read_mtds(dev, (u8 **)&asar->dyn, asar->ver); + ret = mt792x_asar_acpi_read_mtds(dev, (u8 **)&asar->dyn, asar->ver, + &asar->dyn_ver); if (ret) { devm_kfree(dev->mt76.dev, asar->dyn); asar->dyn = NULL; } /* MTGS is optional */ - ret = mt792x_asar_acpi_read_mtgs(dev, (u8 **)&asar->geo, asar->ver); + ret = mt792x_asar_acpi_read_mtgs(dev, (u8 **)&asar->geo, asar->ver, + &asar->geo_ver); if (ret) { devm_kfree(dev->mt76.dev, asar->geo); asar->geo = NULL; @@ -254,7 +286,7 @@ mt792x_asar_get_geo_pwr(struct mt792x_phy *phy, break; } - if (asar->ver == 1) { + if (asar->geo_ver == 1) { band_pwr = &asar->geo->tbl[idx].band[0]; max = ARRAY_SIZE(asar->geo->tbl[idx].band); } else { @@ -305,7 +337,7 @@ mt792x_asar_range_pwr(struct mt792x_phy *phy, if (!capa) return 127; - if (asar->ver == 1) { + if (asar->dyn_ver == 1) { limit = &asar->dyn->tbl[0].frp[0]; max = ARRAY_SIZE(asar->dyn->tbl[0].frp); } else { diff --git a/drivers/net/wireless/mediatek/mt76/mt792x_acpi_sar.h b/drivers/net/wireless/mediatek/mt76/mt792x_acpi_sar.h index 474033073831..1d42f24b05f6 100644 --- a/drivers/net/wireless/mediatek/mt76/mt792x_acpi_sar.h +++ b/drivers/net/wireless/mediatek/mt76/mt792x_acpi_sar.h @@ -105,6 +105,8 @@ struct mt792x_asar_fg { struct mt792x_acpi_sar { u8 ver; + u8 dyn_ver; + u8 geo_ver; union { struct mt792x_asar_dyn *dyn; struct mt792x_asar_dyn_v2 *dyn_v2; From 52f6a065e0810c739a5dabd33a58047f08100f50 Mon Sep 17 00:00:00 2001 From: Lu Huang Date: Wed, 30 Sep 2026 16:54:35 +0800 Subject: [PATCH 234/367] wifi: mt76: mt7996: fix uninitialized buf read in mt7996_variant_fem_init() When the eFuse block covering MT7976C_EFUSE_OFFSET is invalid, mt7996_mcu_get_eeprom() returns -EINVAL without writing buf, but mt7996_variant_fem_init() tolerates -EINVAL and keeps running, so buf[idx] == 0xc reads uninitialized stack memory. A stale 0x0c byte misclassifies a 0x7976 aDie as 7976C, selects MT7996_FEM_INT and the wrong default EEPROM, overriding valid calibration data. Only consult the eFuse byte when the read succeeded; with an invalid eFuse, fall back to the three explicit aDie version checks. Fixes: e8cb33ad546a ("wifi: mt76: mt7996: add support for more variants") Cc: stable@vger.kernel.org Signed-off-by: Lu Huang Link: https://patch.msgid.link/20260930085435.1424493-1-huanglu@kylinos.cn Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7996/init.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/init.c b/drivers/net/wireless/mediatek/mt76/mt7996/init.c index 2db63c3be9d1..c1d860029801 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/init.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/init.c @@ -1248,7 +1248,7 @@ static int mt7996_variant_fem_init(struct mt7996_dev *dev) adie_ver = u32_get_bits(regval, MT_ADIE_VERSION_MASK); idx = MT7976C_EFUSE_OFFSET % MT7996_EEPROM_BLOCK_SIZE; is_7976c = adie_ver == 0x8a10 || adie_ver == 0x8b00 || - adie_ver == 0x8c10 || buf[idx] == 0xc; + adie_ver == 0x8c10 || (!ret && buf[idx] == 0xc); adie_id = u32_get_bits(regval, MT_ADIE_CHIP_ID_MASK); if (adie_id == 0x7975 || adie_id == 0x7979 || From 71ec70ea26e399eb8371fe9d2d5bf5dab09570fb Mon Sep 17 00:00:00 2001 From: Sajal Gupta Date: Tue, 22 Sep 2026 14:10:08 +0530 Subject: [PATCH 235/367] drm/gud: don't keep a connector without a CRTC as connector_state If the 'for_each_new_connector_in_state()' loop finishes without hitting the break, connector_state is left pointing at the last connector visited, which has no CRTC, instead of staying NULL. That skips the "if (!connector_state)" fallback below the loop, which looks up the active connector when the state contains none. The function then carries on with a connector that is not driven. Fix by resetting connector_state to NULL for each connector visited that has no CRTC, so that it is NULL when none was found and the fallback runs. Fixes: 40e1a70b4aed ("drm: Add GUD USB Display driver") Reported-by: Sashiko Link: https://sashiko.dev/#/patchset/20260902123254.36987-1-sajal2005gupta@gmail.com?part=1 Signed-off-by: Sajal Gupta Acked-by: Ruben Wauters Cc: Signed-off-by: Ruben Wauters Link: https://patch.msgid.link/20260922084026.15481-1-sajal2005gupta@gmail.com --- drivers/gpu/drm/gud/gud_pipe.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/gpu/drm/gud/gud_pipe.c b/drivers/gpu/drm/gud/gud_pipe.c index aa7792966287..5aa5ac2f3727 100644 --- a/drivers/gpu/drm/gud/gud_pipe.c +++ b/drivers/gpu/drm/gud/gud_pipe.c @@ -507,6 +507,7 @@ int gud_plane_atomic_check(struct drm_plane *plane, for_each_new_connector_in_state(state, connector, connector_state, i) { if (connector_state->crtc) break; + connector_state = NULL; } /* From ebe162db655881a38de43301151ff7ac940602b5 Mon Sep 17 00:00:00 2001 From: Krzysztof Karas Date: Fri, 11 Sep 2026 06:11:45 +0000 Subject: [PATCH 236/367] drm/i915/gem: Prevent overstepping exec array boundary It is possible for eb_relocate_parse_slow() to call kvfree() on entries outside the original exec array during following scenario inside eb_relocate_parse_slow(): 1) eb_copy_relocations() allocates as many as eb->buffer_count copies for exec entries; 2) eb_parse() appends up to two VMAs, right after incrementing eb->buffer_count; 3) cleanup under "out" label uses this incremented buffer_count to release relocation pointers from exec array via kvfree(), not from VMA array. To amend this problem, ensure that relocation cleanup uses the original exec object count, excluding the VMAs appended by the command parser. Fixes: 8e4ba491b0ba ("drm/i915: Parse command buffer earlier in eb_relocate(slow)") Signed-off-by: Krzysztof Karas Assisted-by: GitHub-Copilot:gpt-6-astra Cc: stable@vger.kernel.org # 5.10+ Reviewed-by: Andi Shyti Signed-off-by: Andi Shyti Link: https://patch.msgid.link/20260911061145.3535489-1-krzysztof.karas@intel.com (cherry picked from commit 2a0158925fb943c7ba8f3216e2734952128defc9) Signed-off-by: Jani Nikula --- drivers/gpu/drm/i915/gem/i915_gem_execbuffer.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/i915/gem/i915_gem_execbuffer.c b/drivers/gpu/drm/i915/gem/i915_gem_execbuffer.c index 1f303d4eaa4d..d5aadced7da0 100644 --- a/drivers/gpu/drm/i915/gem/i915_gem_execbuffer.c +++ b/drivers/gpu/drm/i915/gem/i915_gem_execbuffer.c @@ -1766,6 +1766,7 @@ static int eb_reinit_userptr(struct i915_execbuffer *eb) static noinline int eb_relocate_parse_slow(struct i915_execbuffer *eb) { + const unsigned int count = eb->buffer_count; bool have_copy = false; struct eb_vma *ev; int err = 0; @@ -1868,7 +1869,6 @@ static noinline int eb_relocate_parse_slow(struct i915_execbuffer *eb) out: if (have_copy) { - const unsigned int count = eb->buffer_count; unsigned int i; for (i = 0; i < count; i++) { From 5346b1cec6afa4f9b4b49a9f84e3cfef95a3f7bb Mon Sep 17 00:00:00 2001 From: Krzysztof Karas Date: Thu, 10 Sep 2026 08:13:29 +0000 Subject: [PATCH 237/367] drm/i915/gt: Unmask interrupts only when ACTIVE is true There is a small race window between intel_rps_boost() reading a stale ACTIVE state and queuing rps->work, where ACTIVE may be changed via asynchronous calls triggered by wakeref drop. This means that rps_work() could be triggered after the GT has been parked, so unmasking RPS PM interrupts would happen without holding PM wakelock. This sequence of events may be observed during live_rc6_ctx_wa() selftest: 1) live_rc6_ctx_wa() -> intel_engine_pm_put() -> wakeref_put() ASYNC1) wakeref_put() callback -> __engine_park() -> intel_gt_pm_put_async() -> intel_gt_pm_put_async_untracked() -> intel_wakeref_put_async() ASYNC2) wakeref_put_async() callback -> __gt_park() -> intel_rps_park() -> intel_rps_clear_active() [clear ACTIVE] WITH INTERRUPTS: -> rps_disable_interrupts() -> cancel_work_sync(rps->work) -> rps_reset_interrupts() [rps->pm_iir = 0] WITHOUT INTERRUPTS (TIMER mode): -> rps_stop_timer() -> cancel_work_sync(rps->work) 2) live_rc6_ctx_wa() -> intel_gt_wait_for_idle() -> intel_gt_retire_requests_timeout() -> dma_fence_wait_timeout() -> ops->wait() [i915_fence_wait()] -> i915_request_wait_timeout() -> intel_rps_boost() -> intel_rps_is_active() [check ACTIVE] [OPEN RACE WINDOW] -> queue_work(rps->work) -> retire_requests() -> i915_request_retire() -> intel_rps_dec_waiters() [from 1 to 0] If ASYNC1 and ASYNC2 would execute during [OPEN RACE WINDOW], then: 3) rps_work() executes from the workqueue and may satisfy "(!pm_iir && !client_boost)" test (in TIMER mode rps->pm_iir is zeroed by an earlier rps_work() call or never gets a new value after previous intel_rps_unpark() event), so it jumps to "out" label, bypassing intel_rps_is_active() guard and executes gen6_gt_pm_unmask_irq(); 4) GT is parked and runtime PM wakelock is dropped, so only a raw reference in __gt_park() -> intel_display_power_put_async() remains for 100ms, then the MMIO write in gen6_gt_pm_unmask_irq() detects missing wakelock via assert_rpm_wakelock_held() and emits "RPM wakelock ref not held during HW access" tainting kernel. Commit 3e7abf814193 ("drm/i915: Extract GT render power state management") dropped the "if (rps->interrupts_enabled)" guard that gen6_pm_rps_work() had around this unmask in i915_irq.c. INTEL_RPS_ACTIVE brackets the same window, since it is set in intel_rps_unpark() before rps_enable_interrupts() and rps_start_timer() and cleared in intel_rps_park() before rps_stop_timer() and rps_disable_interrupts(). Skip unmasking when RPS is no longer active (reaching unmask operation is independent of INTERRUPT mode). This does not prevent RPS operation from resuming on the next unpark. In interrupt mode, intel_rps_unpark() calls rps_enable_interrupts(), which enables and unmasks the PM interrupts. In timer mode, it calls rps_start_timer(), restarting periodic engine-busyness evaluation. The timer generates software RPS events and queues rps_work() directly, independently of PM interrupt delivery. Neither mode requires the late worker to unmask interrupts while the GT is parked Fixes: 3e7abf814193 ("drm/i915: Extract GT render power state management") Closes: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/16562 Cc: stable@vger.kernel.org # v5.5+ Assisted-by: GitHub-Copilot:claude-opus-5 Assisted-by: GitHub-Copilot:gpt-6-astra Signed-off-by: Krzysztof Karas Reviewed-by: Andi Shyti Signed-off-by: Andi Shyti Link: https://patch.msgid.link/20260910081329.3300417-1-krzysztof.karas@intel.com (cherry picked from commit ca4a6deadad15b91ee083afe445fa6bdbfb68206) Signed-off-by: Jani Nikula --- drivers/gpu/drm/i915/gt/intel_rps.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/i915/gt/intel_rps.c b/drivers/gpu/drm/i915/gt/intel_rps.c index a33b19c04737..0f9b4f1dd3cb 100644 --- a/drivers/gpu/drm/i915/gt/intel_rps.c +++ b/drivers/gpu/drm/i915/gt/intel_rps.c @@ -1904,7 +1904,8 @@ static void rps_work(struct work_struct *work) out: spin_lock_irq(gt->irq_lock); - gen6_gt_pm_unmask_irq(gt, rps->pm_events); + if (intel_rps_is_active(rps)) + gen6_gt_pm_unmask_irq(gt, rps->pm_events); spin_unlock_irq(gt->irq_lock); } From f0a4447789ef28ba1aded2db19543d69c640b5e5 Mon Sep 17 00:00:00 2001 From: Mallesh Koujalagi Date: Thu, 24 Sep 2026 11:39:07 +0530 Subject: [PATCH 238/367] drm/xe/sysctrl: Fix mailbox header handling The mailbox header was defined as __le32, which required cpu_to_le32() and le32_to_cpu() conversions whenever it was write or read. These conversions are unnecessary because the header is always handled as a 32-bit value, not as raw bytes. It is written with xe_mmio_write32() and read with xe_mmio_read32(), which already handle any required byte-order conversion through the underlying writel()/readl() accessors. As a result, the header value is already in CPU byte order when used by the driver. Keeping the extra conversions had no effect on little-endian systems and could cause an incorrect on big-endian systems. So use u32 for the mailbox header and remove the unnecessary endianness conversions. Fixes: 1f95f618182b ("drm/xe/xe_sysctrl: Add System Controller mailbox communication support") Suggested-by: Michal Wajdeczko Signed-off-by: Mallesh Koujalagi Tested-by: Anoop Vijay Reviewed-by: Umesh Nerlige Ramappa Signed-off-by: Umesh Nerlige Ramappa Link: https://patch.msgid.link/20260924060900.146167-18-mallesh.koujalagi@intel.com (cherry picked from commit 5137b391599579371c32f555958d3cf55e19bd88) Signed-off-by: Rodrigo Vivi --- drivers/gpu/drm/xe/xe_sysctrl_mailbox.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/drivers/gpu/drm/xe/xe_sysctrl_mailbox.c b/drivers/gpu/drm/xe/xe_sysctrl_mailbox.c index 72baf1aa4b3a..a0179e62abaa 100644 --- a/drivers/gpu/drm/xe/xe_sysctrl_mailbox.c +++ b/drivers/gpu/drm/xe/xe_sysctrl_mailbox.c @@ -19,20 +19,20 @@ #include "xe_sysctrl_mailbox_types.h" struct xe_sysctrl_mailbox_msg_hdr { - __le32 data; -} __packed; + u32 data; +}; #define XE_SYSCTRL_HDR_GROUP_ID(hdr) \ - FIELD_GET(SYSCTRL_HDR_GROUP_ID_MASK, le32_to_cpu((hdr)->data)) + FIELD_GET(SYSCTRL_HDR_GROUP_ID_MASK, (hdr)->data) #define XE_SYSCTRL_HDR_COMMAND(hdr) \ - FIELD_GET(SYSCTRL_HDR_COMMAND_MASK, le32_to_cpu((hdr)->data)) + FIELD_GET(SYSCTRL_HDR_COMMAND_MASK, (hdr)->data) #define XE_SYSCTRL_HDR_IS_RESPONSE(hdr) \ - FIELD_GET(SYSCTRL_HDR_IS_RESPONSE, le32_to_cpu((hdr)->data)) + FIELD_GET(SYSCTRL_HDR_IS_RESPONSE, (hdr)->data) #define XE_SYSCTRL_HDR_RESULT(hdr) \ - FIELD_GET(SYSCTRL_HDR_RESULT_MASK, le32_to_cpu((hdr)->data)) + FIELD_GET(SYSCTRL_HDR_RESULT_MASK, (hdr)->data) static bool sysctrl_wait_bit_clear(struct xe_sysctrl *sc, u32 bit_mask, unsigned int timeout_ms) @@ -126,8 +126,8 @@ static int sysctrl_prepare_command(struct xe_device *xe, return -ENOMEM; hdr = (struct xe_sysctrl_mailbox_msg_hdr *)buffer; - hdr->data = cpu_to_le32(FIELD_PREP(SYSCTRL_HDR_GROUP_ID_MASK, group_id) | - FIELD_PREP(SYSCTRL_HDR_COMMAND_MASK, command)); + hdr->data = FIELD_PREP(SYSCTRL_HDR_GROUP_ID_MASK, group_id) | + FIELD_PREP(SYSCTRL_HDR_COMMAND_MASK, command); if (data_in && data_in_len) memcpy(buffer + sizeof(*hdr), data_in, data_in_len); From ff9b465ddb95d7842282998348df88ff55f62c4a Mon Sep 17 00:00:00 2001 From: Linmao Li Date: Fri, 7 Aug 2026 21:25:53 +0800 Subject: [PATCH 239/367] wifi: nxpwifi: protect sta_list against concurrent add and delete nxpwifi_del_sta_entry() unlinks an entry from priv->sta_list without holding priv->sta_list_spinlock. The comment above it gives the reason: the function is only reached from cfg80211_ops.del_station, which runs under the wiphy mutex. That mutex serialises this callback against other users of the same lock, including wiphy_work items. It covers neither of the two other writers of the same list: nxpwifi_add_sta_entry() from nxpwifi_uap_event_sta_assoc() nxpwifi_del_all_sta_list() from nxpwifi_uap_event_bss_idle() Both run from nxpwifi_process_event() on the driver's main work queue, a plain workqueue that never takes the wiphy lock, and both take sta_list_spinlock around their update. A station associating, or the BSS going idle, can therefore modify priv->sta_list while a hostapd del_station is walking it and unlinking an entry. Take the lock here too. As the traversal then runs under the write lock, list_for_each_entry() replaces the _rcu variant, matching nxpwifi_del_all_sta_list(). The same call path reaches nxpwifi_get_sta_entry() without rcu_read_lock(). Entries are freed with kfree_rcu() and every other caller holds it, so take it around the lookup as well. Fixes: 73b01e57ed3e ("wifi: nxp: add nxpwifi driver for IW61x") Signed-off-by: Linmao Li Tested-by: Jeff Chen Reviewed-by: Jeff Chen Signed-off-by: Jeff Chen --- drivers/net/wireless/nxp/nxpwifi/cfg80211.c | 2 ++ drivers/net/wireless/nxp/nxpwifi/util.c | 8 ++++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/nxp/nxpwifi/cfg80211.c b/drivers/net/wireless/nxp/nxpwifi/cfg80211.c index 86304ea3331c..161697e038c0 100644 --- a/drivers/net/wireless/nxp/nxpwifi/cfg80211.c +++ b/drivers/net/wireless/nxp/nxpwifi/cfg80211.c @@ -1539,9 +1539,11 @@ nxpwifi_cfg80211_del_station(struct wiphy *wiphy, struct wireless_dev *wdev, eth_zero_addr(deauth_mac); + rcu_read_lock(); sta_node = nxpwifi_get_sta_entry(priv, params->mac); if (sta_node) ether_addr_copy(deauth_mac, params->mac); + rcu_read_unlock(); if (is_valid_ether_addr(deauth_mac)) { ret = nxpwifi_uap_sta_deauth(priv, deauth_mac); diff --git a/drivers/net/wireless/nxp/nxpwifi/util.c b/drivers/net/wireless/nxp/nxpwifi/util.c index bbfefb81d8d3..ae8444a14105 100644 --- a/drivers/net/wireless/nxp/nxpwifi/util.c +++ b/drivers/net/wireless/nxp/nxpwifi/util.c @@ -1041,19 +1041,23 @@ nxpwifi_set_sta_ht_cap(struct nxpwifi_private *priv, const u8 *ies, } } -/* Delete a station from list; called under cfg80211 mutex. */ +/* Delete a station from list. */ void nxpwifi_del_sta_entry(struct nxpwifi_private *priv, const u8 *mac) { struct nxpwifi_sta_node *node; - list_for_each_entry_rcu(node, &priv->sta_list, list) { + spin_lock_bh(&priv->sta_list_spinlock); + + list_for_each_entry(node, &priv->sta_list, list) { if (!memcmp(node->mac_addr, mac, ETH_ALEN)) { list_del_rcu(&node->list); kfree_rcu(node, rcu); break; } } + + spin_unlock_bh(&priv->sta_list_spinlock); } /* Delete all stations from list. */ From 128411832c62ca517e4abd938229f0ee40823fdb Mon Sep 17 00:00:00 2001 From: Linmao Li Date: Fri, 7 Aug 2026 21:25:54 +0800 Subject: [PATCH 240/367] wifi: nxpwifi: delete the station entry on the uAP deauth event When a station leaves, the firmware reports EVENT_UAP_STA_DEAUTH. nxpwifi_uap_event_sta_deauth() tears down the reorder, BA and WMM state for that peer but leaves its nxpwifi_sta_node on priv->sta_list. Nothing else removes it. cfg80211_del_sta() only reports the departure upwards, it does not call back into cfg80211_ops.del_station, and that callback is the only caller of nxpwifi_del_sta_entry(). The entry therefore survives until the BSS goes idle and nxpwifi_del_all_sta_list() drops everything. Until then the departed station still looks associated to the driver. nxpwifi_handle_uap_rx_forward() finds the stale node for that address, treats the frame as intra-BSS traffic and requeues it for transmission instead of passing it up the stack, so traffic for that address is misrouted. The list also keeps one entry per station that has ever associated, and a station that comes back reuses the stale node along with its old statistics. mwifiex, which nxpwifi was derived from, deletes the entry in the same event handler. Fixes: 73b01e57ed3e ("wifi: nxp: add nxpwifi driver for IW61x") Signed-off-by: Linmao Li Tested-by: Jeff Chen Reviewed-by: Jeff Chen Signed-off-by: Jeff Chen --- drivers/net/wireless/nxp/nxpwifi/uap_event.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/wireless/nxp/nxpwifi/uap_event.c b/drivers/net/wireless/nxp/nxpwifi/uap_event.c index ab5c15686f54..20b7b53db236 100644 --- a/drivers/net/wireless/nxp/nxpwifi/uap_event.c +++ b/drivers/net/wireless/nxp/nxpwifi/uap_event.c @@ -75,6 +75,7 @@ nxpwifi_uap_event_sta_deauth(struct nxpwifi_private *priv) nxpwifi_del_tx_ba_stream_tbl_by_ra(priv, deauth_mac); } nxpwifi_wmm_del_peer_ra_list(priv, deauth_mac); + nxpwifi_del_sta_entry(priv, deauth_mac); return 0; } From 5c9260d7c6990875fc788ebb86ea2bf08b492704 Mon Sep 17 00:00:00 2001 From: Linmao Li Date: Thu, 13 Aug 2026 16:23:13 +0800 Subject: [PATCH 241/367] wifi: nxpwifi: wait for the wakeup timer before the adapter is freed nxpwifi_adapter_cleanup() stops adapter->wakeup_timer with timer_delete(), which does not wait for a running callback. The callback goes on using the adapter: wakeup_timer_fn() sets adapter->hw_status, walks the command queues through nxpwifi_cancel_all_pending_cmd() and calls adapter->if_ops.card_reset(). Both paths that reach nxpwifi_adapter_cleanup() free the adapter right afterwards, through nxpwifi_free_adapter() in nxpwifi_remove_card() and in the nxpwifi_add_card() error unwind. Use timer_delete_sync() so the callback has finished before the adapter is released. mwifiex fixed the same issue in commit ae5e95d41574 ("wifi: mwifiex: fix use-after-free in mwifiex_adapter_cleanup()"). Fixes: 73b01e57ed3e ("wifi: nxp: add nxpwifi driver for IW61x") Signed-off-by: Linmao Li Reviewed-by: Jeff Chen Signed-off-by: Jeff Chen --- drivers/net/wireless/nxp/nxpwifi/init.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/wireless/nxp/nxpwifi/init.c b/drivers/net/wireless/nxp/nxpwifi/init.c index 7c75dca5d0a8..3c4df37333d8 100644 --- a/drivers/net/wireless/nxp/nxpwifi/init.c +++ b/drivers/net/wireless/nxp/nxpwifi/init.c @@ -328,7 +328,7 @@ static void nxpwifi_invalidate_lists(struct nxpwifi_adapter *adapter) static void nxpwifi_adapter_cleanup(struct nxpwifi_adapter *adapter) { - timer_delete(&adapter->wakeup_timer); + timer_delete_sync(&adapter->wakeup_timer); nxpwifi_cancel_all_pending_cmd(adapter); wake_up_interruptible(&adapter->cmd_wait_q.wait); wake_up_interruptible(&adapter->hs_activate_wait_q); From d68d6d8d8bbb5596909479dc6cac58132d8f0dbf Mon Sep 17 00:00:00 2001 From: Linmao Li Date: Thu, 13 Aug 2026 16:23:14 +0800 Subject: [PATCH 242/367] wifi: nxpwifi: free the aggregation buffer when the RA list disappears nxpwifi_11n_aggregate_pkt() drops ra_list_spinlock while it copies each subframe, so it rechecks the RA list after taking the lock again. The check inside the aggregation loop returns without releasing the skb_aggr it has been filling, leaking one tx_buf_size buffer along with the subframes already aggregated into it. Release skb_aggr there, the way the same check on the -EBUSY path already does. mwifiex fixed the same issue in commit 990a73dec3fd ("wifi: mwifiex: Fix memory leak in mwifiex_11n_aggregate_pkt()"). Fixes: 73b01e57ed3e ("wifi: nxp: add nxpwifi driver for IW61x") Signed-off-by: Linmao Li Reviewed-by: Jeff Chen Signed-off-by: Jeff Chen --- drivers/net/wireless/nxp/nxpwifi/11n_aggr.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/wireless/nxp/nxpwifi/11n_aggr.c b/drivers/net/wireless/nxp/nxpwifi/11n_aggr.c index be7080f2a6ce..54933c42c960 100644 --- a/drivers/net/wireless/nxp/nxpwifi/11n_aggr.c +++ b/drivers/net/wireless/nxp/nxpwifi/11n_aggr.c @@ -168,6 +168,7 @@ nxpwifi_11n_aggregate_pkt(struct nxpwifi_private *priv, if (!nxpwifi_is_ralist_valid(priv, pra_list, ptrindex)) { spin_unlock_bh(&priv->wmm.ra_list_spinlock); + nxpwifi_write_data_complete(adapter, skb_aggr, 1, -1); return -ENOENT; } From 050f03bb0effd773c877cfba3b651c6d380b1c15 Mon Sep 17 00:00:00 2001 From: Linmao Li Date: Thu, 13 Aug 2026 16:23:15 +0800 Subject: [PATCH 243/367] wifi: nxpwifi: zero the channel statistics array adapter->chan_stats comes from vmalloc(), which does not clear the memory, and nxpwifi_cfg80211_dump_survey() reads it as soon as user space asks for survey data. For the entries no scan has filled in, a non-zero cca_scan_dur passes the validity check and stale bytes are reported to user space as noise, time and time_busy. Use kcalloc() instead. The array holds two entries per supported channel, small enough not to need vmalloc(), and the two callers change to kfree() accordingly. mwifiex fixed the same issue in commit 0e20450829ca ("wifi: mwifiex: Initialize the chan_stats array to zero"). Fixes: 73b01e57ed3e ("wifi: nxp: add nxpwifi driver for IW61x") Signed-off-by: Linmao Li Reviewed-by: Jeff Chen Signed-off-by: Jeff Chen --- drivers/net/wireless/nxp/nxpwifi/cfg80211.c | 5 +++-- drivers/net/wireless/nxp/nxpwifi/main.c | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/drivers/net/wireless/nxp/nxpwifi/cfg80211.c b/drivers/net/wireless/nxp/nxpwifi/cfg80211.c index 161697e038c0..788b83356981 100644 --- a/drivers/net/wireless/nxp/nxpwifi/cfg80211.c +++ b/drivers/net/wireless/nxp/nxpwifi/cfg80211.c @@ -3731,8 +3731,9 @@ int nxpwifi_init_channel_scan_gap(struct nxpwifi_adapter *adapter) * additional active scan request for hidden SSIDs on passive channels. */ adapter->num_in_chan_stats = 2 * (n_channels_bg + n_channels_a); - adapter->chan_stats = vmalloc(array_size(sizeof(*adapter->chan_stats), - adapter->num_in_chan_stats)); + adapter->chan_stats = kcalloc(adapter->num_in_chan_stats, + sizeof(*adapter->chan_stats), + GFP_KERNEL); if (!adapter->chan_stats) return -ENOMEM; diff --git a/drivers/net/wireless/nxp/nxpwifi/main.c b/drivers/net/wireless/nxp/nxpwifi/main.c index 55b962430f37..9e13348105c3 100644 --- a/drivers/net/wireless/nxp/nxpwifi/main.c +++ b/drivers/net/wireless/nxp/nxpwifi/main.c @@ -644,7 +644,7 @@ static int _nxpwifi_fw_dpc(const struct firmware *firmware, void *context) goto done; err_add_intf: - vfree(adapter->chan_stats); + kfree(adapter->chan_stats); err_init_chan_scan: wiphy_unregister(adapter->wiphy); wiphy_free(adapter->wiphy); @@ -1384,7 +1384,7 @@ static void nxpwifi_uninit_sw(struct nxpwifi_adapter *adapter) wiphy_free(adapter->wiphy); adapter->wiphy = NULL; - vfree(adapter->chan_stats); + kfree(adapter->chan_stats); nxpwifi_free_cmd_buffers(adapter); } From 7769bb2457fcfa8216aeae84f32cb7084cab5637 Mon Sep 17 00:00:00 2001 From: Linmao Li Date: Thu, 20 Aug 2026 16:16:33 +0800 Subject: [PATCH 244/367] wifi: nxpwifi: fix the authentication frame length handling nxpwifi_cfg80211_authenticate() builds a 3-address management frame in a temporary buffer and hands it to nxpwifi_form_mgmt_frame(), which copies it into the skb and splices in address4. The frame length is tracked in a single u16 that conflates the two lengths, and it gets both wrong. Truncation. req->ie_len and req->auth_data_len are both size_t. The IE policy caps req->ie_len at IEEE80211_MAX_DATA_LEN, but NL80211_ATTR_AUTH_DATA only has a four-byte minimum length policy. Since nla_len is a u16, a single authentication data attribute can carry up to 65531 bytes, enough for the combined frame length to exceed U16_MAX before it is assigned to pkt_len. With auth_data_len == 65510 and no IEs the length wraps to 6, kzalloc(6) succeeds, and the memcpy() below then writes 65506 user-provided bytes past a 6-byte heap object. Geometry. NXPWIFI_MGMT_HEADER_LEN is 30, which is the 24-byte 3-address header plus the address4 that the firmware expects. The temporary buffer only holds a struct ieee80211_hdr_3addr, so six of the bytes accounted for are never used, and passing that length to nxpwifi_form_mgmt_frame() makes it append ETH_ALEN on top of a length that already included it. The skb is sized without those six bytes, so the last skb_put_data() overruns the tailroom by exactly ETH_ALEN. It usually goes unnoticed because SKB_DATA_ALIGN() rounding leaves slack. The driver's other caller of the helper, nxpwifi_cfg80211_mgmt_tx(), adds ETH_ALEN to the skb size before allocating and passes the 3-address length, which is what this path should do too. Keep the two lengths apart. frame_len is the 3-address frame the driver builds: it sizes the temporary buffer and is what the helper is told. pkt_len is what the firmware sees, frame_len plus address4: it sizes the skb and goes into tx_info. Computing frame_len in size_t and rejecting anything that cannot be represented once ETH_ALEN is added removes the wrap. The value handed to the firmware is unchanged, so this is not a behavioural change for frames that were already valid. A tighter 802.11 bound may make sense, but that depends on the firmware's frame format and is a separate decision. Fixes: 73b01e57ed3e ("wifi: nxp: add nxpwifi driver for IW61x") Signed-off-by: Linmao Li Signed-off-by: Jeff Chen --- drivers/net/wireless/nxp/nxpwifi/cfg80211.c | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/drivers/net/wireless/nxp/nxpwifi/cfg80211.c b/drivers/net/wireless/nxp/nxpwifi/cfg80211.c index 788b83356981..3221ae167c58 100644 --- a/drivers/net/wireless/nxp/nxpwifi/cfg80211.c +++ b/drivers/net/wireless/nxp/nxpwifi/cfg80211.c @@ -3325,6 +3325,7 @@ nxpwifi_cfg80211_authenticate(struct wiphy *wiphy, struct nxpwifi_adapter *adapter = priv->adapter; struct sk_buff *skb; u16 pkt_len, auth_alg; + size_t frame_len; int ret; struct ieee80211_mgmt *mgmt; struct nxpwifi_txinfo *tx_info; @@ -3398,13 +3399,23 @@ nxpwifi_cfg80211_authenticate(struct wiphy *wiphy, nxpwifi_cancel_scan(adapter); - pkt_len = (u16)req->ie_len + req->auth_data_len + - NXPWIFI_MGMT_HEADER_LEN + NXPWIFI_AUTH_BODY_LEN; + frame_len = req->ie_len + req->auth_data_len + + sizeof(struct ieee80211_hdr_3addr) + NXPWIFI_AUTH_BODY_LEN; if (req->auth_data_len >= 4) - pkt_len -= 4; + frame_len -= 4; - mgmt = kzalloc(pkt_len, GFP_KERNEL); + /* nxpwifi_form_mgmt_frame() inserts address4, so the frame handed to + * the firmware is ETH_ALEN longer than the one built here. + */ + if (frame_len > U16_MAX - ETH_ALEN) { + nxpwifi_dbg(adapter, ERROR, + "auth frame too long: %zu bytes\n", frame_len); + return -EINVAL; + } + pkt_len = frame_len + ETH_ALEN; + + mgmt = kzalloc(frame_len, GFP_KERNEL); skb = dev_alloc_skb(NXPWIFI_MIN_DATA_HEADER_LEN + NXPWIFI_MGMT_FRAME_HEADER_SIZE + @@ -3450,7 +3461,7 @@ nxpwifi_cfg80211_authenticate(struct wiphy *wiphy, memcpy((u8 *)varptr, req->ie, req->ie_len); } - nxpwifi_form_mgmt_frame(skb, (const u8 *)mgmt, pkt_len); + nxpwifi_form_mgmt_frame(skb, (const u8 *)mgmt, frame_len); kfree(mgmt); priv->auth_flag = HOST_MLME_AUTH_PENDING; priv->auth_alg = auth_alg; From 7fa5fb9976c4532007a666624dc241021f74c8a9 Mon Sep 17 00:00:00 2001 From: Linmao Li Date: Thu, 20 Aug 2026 16:16:34 +0800 Subject: [PATCH 245/367] wifi: nxpwifi: handle authentication frame allocation failures nxpwifi_cfg80211_authenticate() allocates the buffer it builds the management frame in and never checks the result: mgmt = kzalloc(frame_len, GFP_KERNEL); skb = dev_alloc_skb(...); if (!skb) { ... return -ENOMEM; } ... memcpy(mgmt->da, req->bss->bssid, ETH_ALEN); On allocation failure the memcpy() a dozen lines later dereferences NULL. The same sequence leaks mgmt when dev_alloc_skb() fails: that path returns without freeing it, while the success path drops it after nxpwifi_form_mgmt_frame(). Bail out when the allocation fails, and free it before returning on the skb error path. Fixes: 73b01e57ed3e ("wifi: nxp: add nxpwifi driver for IW61x") Signed-off-by: Linmao Li Signed-off-by: Jeff Chen --- drivers/net/wireless/nxp/nxpwifi/cfg80211.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/net/wireless/nxp/nxpwifi/cfg80211.c b/drivers/net/wireless/nxp/nxpwifi/cfg80211.c index 3221ae167c58..473e81304ca8 100644 --- a/drivers/net/wireless/nxp/nxpwifi/cfg80211.c +++ b/drivers/net/wireless/nxp/nxpwifi/cfg80211.c @@ -3416,6 +3416,8 @@ nxpwifi_cfg80211_authenticate(struct wiphy *wiphy, pkt_len = frame_len + ETH_ALEN; mgmt = kzalloc(frame_len, GFP_KERNEL); + if (!mgmt) + return -ENOMEM; skb = dev_alloc_skb(NXPWIFI_MIN_DATA_HEADER_LEN + NXPWIFI_MGMT_FRAME_HEADER_SIZE + @@ -3423,6 +3425,7 @@ nxpwifi_cfg80211_authenticate(struct wiphy *wiphy, if (!skb) { nxpwifi_dbg(adapter, ERROR, "allocate skb failed for management frame\n"); + kfree(mgmt); return -ENOMEM; } From 32d1a000e99612e010335760363936d85591fd69 Mon Sep 17 00:00:00 2001 From: David Carlier Date: Wed, 26 Aug 2026 00:17:09 +0100 Subject: [PATCH 246/367] wifi: nxpwifi: fix inverted check in Tx BA stream entry deletion nxpwifi_is_tx_ba_stream_ptr_valid() returns true when the entry is still linked, and every caller passes an entry that is on the list, so the early return always fires and nothing is ever unlinked or freed. Entries leak on every teardown and, since nxpwifi_space_avail_for_new_ba_stream() counts them, Tx aggregation stops being negotiated once the stale count reaches the maximum. Changing the original dead && test to || to silence a NULL dereference report inverted the validity test along with it. Fixes: 00c786a7581e ("wifi: nxpwifi: fix multiple static analysis errors and warnings") Assisted-by: Claude:claude-opus-5 Signed-off-by: David Carlier Signed-off-by: Jeff Chen --- drivers/net/wireless/nxp/nxpwifi/11n.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/wireless/nxp/nxpwifi/11n.c b/drivers/net/wireless/nxp/nxpwifi/11n.c index c2a54d781b42..25b2e430f3f9 100644 --- a/drivers/net/wireless/nxp/nxpwifi/11n.c +++ b/drivers/net/wireless/nxp/nxpwifi/11n.c @@ -451,7 +451,7 @@ void nxpwifi_11n_delete_tx_ba_stream_tbl_entry(struct nxpwifi_private *priv, struct nxpwifi_tx_ba_stream_tbl *tbl) { - if (!tbl || nxpwifi_is_tx_ba_stream_ptr_valid(priv, tbl)) + if (!tbl || !nxpwifi_is_tx_ba_stream_ptr_valid(priv, tbl)) return; nxpwifi_dbg(priv->adapter, INFO, From 5e99bdb94720331e2fd38c10c5b1afa1ce640835 Mon Sep 17 00:00:00 2001 From: David Carlier Date: Wed, 26 Aug 2026 00:17:10 +0100 Subject: [PATCH 247/367] wifi: nxpwifi: do not delete Rx reorder entries under RCU The RCU guard is declared in the body of the per-TID loop, so it is still held across the teardown pass. nxpwifi_del_rx_reorder_entry() flushes the Rx workqueue and deletes the reorder timer synchronously, both of which sleep, so a station deauthenticating from the AP splats under CONFIG_DEBUG_ATOMIC_SLEEP. Scope the guard to the collection walk. The teardown does not need RCU: it serialises on priv->rx_reorder_tbl_lock[] and frees with kfree_rcu(). Fixes: 73b01e57ed3e ("wifi: nxp: add nxpwifi driver for IW61x") Assisted-by: Claude:claude-opus-5 Signed-off-by: David Carlier Signed-off-by: Jeff Chen --- drivers/net/wireless/nxp/nxpwifi/11n_rxreorder.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/drivers/net/wireless/nxp/nxpwifi/11n_rxreorder.c b/drivers/net/wireless/nxp/nxpwifi/11n_rxreorder.c index 65b628411543..87cf2ec85991 100644 --- a/drivers/net/wireless/nxp/nxpwifi/11n_rxreorder.c +++ b/drivers/net/wireless/nxp/nxpwifi/11n_rxreorder.c @@ -205,11 +205,12 @@ void nxpwifi_11n_del_rx_reorder_tbl_by_ta(struct nxpwifi_private *priv, u8 *ta) return; for (i = 0; i < MAX_NUM_TID; i++) { - guard(rcu)(); - list_for_each_entry_rcu(tbl, &priv->rx_reorder_tbl_ptr[i], list) { - if (!memcmp(tbl->ta, ta, ETH_ALEN)) { - INIT_LIST_HEAD(&tbl->tmp_list); - list_add_tail(&tbl->tmp_list, &to_delete); + scoped_guard(rcu) { + list_for_each_entry_rcu(tbl, &priv->rx_reorder_tbl_ptr[i], list) { + if (!memcmp(tbl->ta, ta, ETH_ALEN)) { + INIT_LIST_HEAD(&tbl->tmp_list); + list_add_tail(&tbl->tmp_list, &to_delete); + } } } From 05165f7b3b9ab6817fe216c24f90755b7c5e15f3 Mon Sep 17 00:00:00 2001 From: Yingying Tang Date: Thu, 11 Jun 2026 20:23:32 -0700 Subject: [PATCH 248/367] Revert "wifi: ath12k: add panic handler" ath12k_core_panic_handler() is invoked via atomic_notifier_call_chain(), which runs inside an RCU read-side critical section. The current code calls ath12k_pci_sw_reset() synchronously from this context, which eventually reaches mhi_device_get_sync() and schedule_timeout(), triggering a voluntary context switch within RCU. Call trace: rcu_note_context_switch+0x4c4/0x508 (P) __schedule+0xbc/0x1204 schedule+0x34/0x110 schedule_timeout+0x84/0x11c __mhi_device_get_sync+0x164/0x228 [mhi] mhi_device_get_sync+0x1c/0x3c [mhi] ath12k_wifi7_pci_bus_wake_up+0x20/0x2c [ath12k_wifi7] ath12k_pci_read32+0x58/0x350 [ath12k] ath12k_pci_clear_dbg_registers+0x28/0xb8 [ath12k] ath12k_pci_panic_handler+0x20/0x44 [ath12k] ath12k_core_panic_handler+0x28/0x3c [ath12k] notifier_call_chain+0x78/0x1c0 atomic_notifier_call_chain+0x3c/0x5c Revert change "wifi: ath12k: add panic handler" to avoid this issue. Tested-on: WCN7850 hw2.0 PCI WLAN.HMT.1.1.c7-00108-QCAHMTSWPL_V1.0_V2.0_SILICONZ_UPSTREAM-3 Signed-off-by: Yingying Tang Tested-by: Joonhoe Kim <26rote@gmail.com> Reviewed-by: Rameshkumar Sundaram Fixes: 809055628bce ("wifi: ath12k: add panic handler") Link: https://patch.msgid.link/20260612032332.2278338-1-yingying.tang@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/core.c | 33 +------------------------- drivers/net/wireless/ath/ath12k/core.h | 3 --- drivers/net/wireless/ath/ath12k/hif.h | 9 ------- drivers/net/wireless/ath/ath12k/pci.c | 8 ------- 4 files changed, 1 insertion(+), 52 deletions(-) diff --git a/drivers/net/wireless/ath/ath12k/core.c b/drivers/net/wireless/ath/ath12k/core.c index 262a2045309b..b0107cde99ff 100644 --- a/drivers/net/wireless/ath/ath12k/core.c +++ b/drivers/net/wireless/ath/ath12k/core.c @@ -1776,29 +1776,6 @@ int ath12k_core_pre_init(struct ath12k_base *ab) return 0; } -static int ath12k_core_panic_handler(struct notifier_block *nb, - unsigned long action, void *data) -{ - struct ath12k_base *ab = container_of(nb, struct ath12k_base, - panic_nb); - - return ath12k_hif_panic_handler(ab); -} - -static int ath12k_core_panic_notifier_register(struct ath12k_base *ab) -{ - ab->panic_nb.notifier_call = ath12k_core_panic_handler; - - return atomic_notifier_chain_register(&panic_notifier_list, - &ab->panic_nb); -} - -static void ath12k_core_panic_notifier_unregister(struct ath12k_base *ab) -{ - atomic_notifier_chain_unregister(&panic_notifier_list, - &ab->panic_nb); -} - static inline bool ath12k_core_hw_group_create_ready(struct ath12k_hw_group *ag) { @@ -2214,18 +2191,13 @@ int ath12k_core_init(struct ath12k_base *ab) struct ath12k_hw_group *ag; int ret; - ret = ath12k_core_panic_notifier_register(ab); - if (ret) - ath12k_warn(ab, "failed to register panic handler: %d\n", ret); - mutex_lock(&ath12k_hw_group_mutex); ag = ath12k_core_hw_group_assign(ab); if (!ag) { mutex_unlock(&ath12k_hw_group_mutex); ath12k_warn(ab, "unable to get hw group\n"); - ret = -ENODEV; - goto err_unregister_notifier; + return -ENODEV; } mutex_unlock(&ath12k_hw_group_mutex); @@ -2250,8 +2222,6 @@ int ath12k_core_init(struct ath12k_base *ab) err_unassign_hw_group: ath12k_core_hw_group_unassign(ab); -err_unregister_notifier: - ath12k_core_panic_notifier_unregister(ab); return ret; } @@ -2260,7 +2230,6 @@ void ath12k_core_deinit(struct ath12k_base *ab) { ath12k_core_hw_group_destroy(ab->ag); ath12k_core_hw_group_unassign(ab); - ath12k_core_panic_notifier_unregister(ab); } void ath12k_core_free(struct ath12k_base *ab) diff --git a/drivers/net/wireless/ath/ath12k/core.h b/drivers/net/wireless/ath/ath12k/core.h index a98fc6e0699d..93fd227b4d19 100644 --- a/drivers/net/wireless/ath/ath12k/core.h +++ b/drivers/net/wireless/ath/ath12k/core.h @@ -15,7 +15,6 @@ #include #include #include -#include #include #include #include @@ -1139,8 +1138,6 @@ struct ath12k_base { #endif /* CONFIG_ACPI */ - struct notifier_block panic_nb; - struct ath12k_hw_group *ag; struct ath12k_wsi_info wsi_info; enum ath12k_qmi_firmware_mode fw_mode; diff --git a/drivers/net/wireless/ath/ath12k/hif.h b/drivers/net/wireless/ath/ath12k/hif.h index 4de8667690e9..aecdb0c5f285 100644 --- a/drivers/net/wireless/ath/ath12k/hif.h +++ b/drivers/net/wireless/ath/ath12k/hif.h @@ -30,7 +30,6 @@ struct ath12k_hif_ops { void (*ce_irq_enable)(struct ath12k_base *ab); void (*ce_irq_disable)(struct ath12k_base *ab); void (*get_ce_msi_idx)(struct ath12k_base *ab, u32 ce_id, u32 *msi_idx); - int (*panic_handler)(struct ath12k_base *ab); void (*coredump_download)(struct ath12k_base *ab); }; @@ -149,14 +148,6 @@ static inline void ath12k_hif_power_down(struct ath12k_base *ab, bool is_suspend ab->hif.ops->power_down(ab, is_suspend); } -static inline int ath12k_hif_panic_handler(struct ath12k_base *ab) -{ - if (!ab->hif.ops->panic_handler) - return NOTIFY_DONE; - - return ab->hif.ops->panic_handler(ab); -} - static inline void ath12k_hif_coredump_download(struct ath12k_base *ab) { if (ab->hif.ops->coredump_download) diff --git a/drivers/net/wireless/ath/ath12k/pci.c b/drivers/net/wireless/ath/ath12k/pci.c index 6441927b5382..8978edb44cec 100644 --- a/drivers/net/wireless/ath/ath12k/pci.c +++ b/drivers/net/wireless/ath/ath12k/pci.c @@ -1484,13 +1484,6 @@ void ath12k_pci_power_down(struct ath12k_base *ab, bool is_suspend) ath12k_pci_sw_reset(ab_pci->ab, false); } -static int ath12k_pci_panic_handler(struct ath12k_base *ab) -{ - ath12k_pci_sw_reset(ab, false); - - return NOTIFY_OK; -} - static const struct ath12k_hif_ops ath12k_pci_hif_ops = { .start = ath12k_pci_start, .stop = ath12k_pci_stop, @@ -1508,7 +1501,6 @@ static const struct ath12k_hif_ops ath12k_pci_hif_ops = { .ce_irq_enable = ath12k_pci_hif_ce_irq_enable, .ce_irq_disable = ath12k_pci_hif_ce_irq_disable, .get_ce_msi_idx = ath12k_pci_get_ce_msi_idx, - .panic_handler = ath12k_pci_panic_handler, #ifdef CONFIG_ATH12K_COREDUMP .coredump_download = ath12k_pci_coredump_download, #endif From 64dd172bbffe6135d79a06ddad53dd2da4d8bd62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alvin=20=C5=A0ipraga?= Date: Tue, 6 Oct 2026 14:55:56 +0200 Subject: [PATCH 249/367] ASoC: adau1372: allow sleepy powerdown GPIOs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit gpiod_set_value() emits a WARN when the GPIO provider indicates that it can sleep, as is typical for I2C controlled GPIO expanders. Since the powerdown GPIO is only manipulated in process context, use the _cansleep variant instead. This lets the driver use sleepy GPIOs to power off the chip. Fixes: 6cd4c6459e47 ("ASoC: Add ADAU1372 audio CODEC support") Signed-off-by: Alvin Šipraga Acked-by: Nuno Sá Link: https://patch.msgid.link/20261006-asoc-adau1372-fixes-v1-1-599cc17fb5f6@analog.com Signed-off-by: Mark Brown --- sound/soc/codecs/adau1372.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/sound/soc/codecs/adau1372.c b/sound/soc/codecs/adau1372.c index cc174ec3a1f8..b84495371b0b 100644 --- a/sound/soc/codecs/adau1372.c +++ b/sound/soc/codecs/adau1372.c @@ -798,7 +798,7 @@ static int adau1372_set_power(struct adau1372 *adau1372, bool enable) if (ret) return ret; if (adau1372->pd_gpio) - gpiod_set_value(adau1372->pd_gpio, 0); + gpiod_set_value_cansleep(adau1372->pd_gpio, 0); if (adau1372->switch_mode) adau1372->switch_mode(adau1372->dev); @@ -818,8 +818,7 @@ static int adau1372_set_power(struct adau1372 *adau1372, bool enable) ADAU1372_CLK_CTRL_PLL_EN, 0); regcache_cache_only(adau1372->regmap, true); - if (adau1372->pd_gpio) - gpiod_set_value(adau1372->pd_gpio, 1); + gpiod_set_value_cansleep(adau1372->pd_gpio, 1); clk_disable_unprepare(adau1372->mclk); return ret; } @@ -836,7 +835,7 @@ static int adau1372_set_power(struct adau1372 *adau1372, bool enable) * map. No need to do any register writes to manually * turn things off. */ - gpiod_set_value(adau1372->pd_gpio, 1); + gpiod_set_value_cansleep(adau1372->pd_gpio, 1); regcache_mark_dirty(adau1372->regmap); } else { regmap_update_bits(adau1372->regmap, ADAU1372_REG_CLK_CTRL, From 36d0e05815ce84266c8b138cd4f84c3feeaf15f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alvin=20=C5=A0ipraga?= Date: Tue, 6 Oct 2026 14:55:57 +0200 Subject: [PATCH 250/367] ASoC: adau1372: fix invalid default state of output ASRC and DAC muxes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The chip has strange power-on reset values for the registers that control the output ASRC and DAC source muxes - the values are actually marked "Reserved" in the datasheet [1]. This means that until the underlying kcontrols are modified, the codec will not function as expected. The registers in question are: register / field reset value valid values ASRCO_SOURCE_0_1 0x10 ASRC_OUT_SOURCE0 0 4 .. 16 ASRC_OUT_SOURCE1 1 4 .. 16 ASRCO_SOURCE_2_3 0x10 ASRC_OUT_SOURCE2 2 4 .. 16 ASRC_OUT_SOURCE3 3 4 .. 16 DAC_SOURCE_0_1 0x10 DAC_SOURCE_0 0 12 .. 13 DAC_SOURCE_1 1 12 .. 13 Set some sane defaults during the driver probe: ADC Decimator0 -> Output ASRC0 ADC Decimator1 -> Output ASRC1 ADC Decimator2 -> Output ASRC2 ADC Decimator3 -> Output ASRC3 Input ASRC0 -> DAC0 Input ASRC1 -> DAC1 With this, the chip will behave as advertised by the default kcontrol state exposed by the driver. [1] https://www.analog.com/media/en/technical-documentation/data-sheets/ADAU1372.pdf Fixes: 6cd4c6459e47 ("ASoC: Add ADAU1372 audio CODEC support") Signed-off-by: Alvin Šipraga Acked-by: Nuno Sá Link: https://patch.msgid.link/20261006-asoc-adau1372-fixes-v1-2-599cc17fb5f6@analog.com Signed-off-by: Mark Brown --- sound/soc/codecs/adau1372.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/sound/soc/codecs/adau1372.c b/sound/soc/codecs/adau1372.c index b84495371b0b..c3d3be898ba6 100644 --- a/sound/soc/codecs/adau1372.c +++ b/sound/soc/codecs/adau1372.c @@ -1002,6 +1002,17 @@ int adau1372_probe(struct device *dev, struct regmap *regmap, regmap_write(regmap, ADAU1372_REG_OP_STAGE_MUTE, 0x0); + /* + * Set sane default values for the Output ASRC and DAC input muxes, + * since the power-on reset defaults are invalid "Reserved" states. + */ + regmap_write(regmap, ADAU1372_REG_ASRCO_SOURCE_0_1, + 0x54); /* Decimator0, Decimator1 */ + regmap_write(regmap, ADAU1372_REG_ASRCO_SOURCE_2_3, + 0x76); /* Decimator2, Decimator3 */ + regmap_write(regmap, ADAU1372_REG_DAC_SOURCE, + 0xdc); /* Input ASRC0, Input ASRC1 */ + regmap_write(regmap, 0x7, 0x01); /* CLOCK OUT */ return devm_snd_soc_register_component(dev, &adau1372_driver, &adau1372_dai_driver, 1); From 7529254014764ae0b8c33d5c61a58f36c65b1a1c Mon Sep 17 00:00:00 2001 From: Marek Szyprowski Date: Fri, 2 Oct 2026 11:21:12 +0200 Subject: [PATCH 251/367] ASoC: samsung: i2s: enable op_clk in probe to balance runtime PM The probe function puts the device into the runtime PM active state with only the "iis" bus clock enabled, but it also sets priv->op_clk to the parent of the RCLK_SRC mux without enabling it. The runtime PM suspend callback disables both clocks, so the first runtime suspend after probe disables op_clk once more than it was enabled. On Exynos5250 Arndale both clocks resolve to the same EXYNOS_I2S_BUS gate, so this triggers the following warning during boot: WARNING: drivers/clk/clk.c:1270 at clk_core_disable+0xe4/0x23c, CPU#0: kworker/u8:3/45 i2s_bus already disabled ... Workqueue: pm pm_runtime_work Call trace: ... clk_core_disable from clk_disable+0x28/0x34 clk_disable from i2s_runtime_suspend+0x58/0x68 [snd_soc_i2s] i2s_runtime_suspend [snd_soc_i2s] from genpd_runtime_suspend+0xc4/0x298 genpd_runtime_suspend from __rpm_callback+0x48/0x18c __rpm_callback from rpm_callback+0x4c/0x50 rpm_callback from rpm_suspend+0xf4/0x718 rpm_suspend from pm_runtime_work+0x9c/0xa0 ... Fix this by enabling op_clk in probe right after getting it, so the clock state matches the runtime PM active state, and by disabling it in remove. Fixes: 48279c53fd1d ("ASoC: samsung: i2s: Prevent external abort on exynos5433 I2S1 access") Assisted-by: Claude Opus 5.5 Signed-off-by: Marek Szyprowski Link: https://patch.msgid.link/20261002092116.585466-1-m.szyprowski@samsung.com Signed-off-by: Mark Brown --- sound/soc/samsung/i2s.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/sound/soc/samsung/i2s.c b/sound/soc/samsung/i2s.c index 3e76cb81462f..442e70b107da 100644 --- a/sound/soc/samsung/i2s.c +++ b/sound/soc/samsung/i2s.c @@ -1534,9 +1534,16 @@ static int samsung_i2s_probe(struct platform_device *pdev) goto err_disable_pm; priv->op_clk = clk_get_parent(priv->clk_table[CLK_I2S_RCLK_SRC]); + ret = clk_prepare_enable(priv->op_clk); + if (ret < 0) { + priv->op_clk = NULL; + goto err_unregister_clk; + } return 0; +err_unregister_clk: + i2s_unregister_clock_provider(priv); err_disable_pm: pm_runtime_disable(&pdev->dev); err_del_sec: @@ -1559,6 +1566,7 @@ static void samsung_i2s_remove(struct platform_device *pdev) i2s_unregister_clock_provider(priv); i2s_delete_secondary_device(priv); + clk_disable_unprepare(priv->op_clk); clk_disable_unprepare(priv->clk); pm_runtime_put_noidle(&pdev->dev); From e130c54fbb603f9aeacf5029789b4a2c8c48f416 Mon Sep 17 00:00:00 2001 From: Han Gao Date: Thu, 1 Oct 2026 02:22:50 +0800 Subject: [PATCH 252/367] spi: sg2044-nor: Return transfer errors The transfer helpers return errors, but both dispatchers discard them. Return these errors to the SPI memory layer instead of reporting success. Fixes: de16c322eefb ("spi: sophgo: add SG2044 SPI NOR controller driver") Signed-off-by: Han Gao Link: https://patch.msgid.link/20261001-fix-sg204xspi-v2-1-014dbaf22b5e@iscas.ac.cn Signed-off-by: Mark Brown --- drivers/spi/spi-sg2044-nor.c | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/drivers/spi/spi-sg2044-nor.c b/drivers/spi/spi-sg2044-nor.c index f3bcb8a1b92b..ff026ca0f766 100644 --- a/drivers/spi/spi-sg2044-nor.c +++ b/drivers/spi/spi-sg2044-nor.c @@ -307,15 +307,15 @@ static ssize_t sg2044_spifmc_tran_cmd(struct sg2044_spifmc *spifmc, return 0; } -static void sg2044_spifmc_trans(struct sg2044_spifmc *spifmc, - const struct spi_mem_op *op) +static int sg2044_spifmc_trans(struct sg2044_spifmc *spifmc, + const struct spi_mem_op *op) { if (op->data.dir == SPI_MEM_DATA_IN) - sg2044_spifmc_read(spifmc, op); + return sg2044_spifmc_read(spifmc, op); else if (op->data.dir == SPI_MEM_DATA_OUT) - sg2044_spifmc_write(spifmc, op); + return sg2044_spifmc_write(spifmc, op); else - sg2044_spifmc_tran_cmd(spifmc, op); + return sg2044_spifmc_tran_cmd(spifmc, op); } static ssize_t sg2044_spifmc_trans_reg(struct sg2044_spifmc *spifmc, @@ -389,19 +389,20 @@ static int sg2044_spifmc_exec_op(struct spi_mem *mem, const struct spi_mem_op *op) { struct sg2044_spifmc *spifmc; + int ret; spifmc = spi_controller_get_devdata(mem->spi->controller); mutex_lock(&spifmc->lock); if (op->addr.nbytes == 0) - sg2044_spifmc_trans_reg(spifmc, op); + ret = sg2044_spifmc_trans_reg(spifmc, op); else - sg2044_spifmc_trans(spifmc, op); + ret = sg2044_spifmc_trans(spifmc, op); mutex_unlock(&spifmc->lock); - return 0; + return ret; } static const struct spi_controller_mem_ops sg2044_spifmc_mem_ops = { From 61d44a2c07b2ad3f7db167f17ff2461ffe78441c Mon Sep 17 00:00:00 2001 From: Han Gao Date: Thu, 1 Oct 2026 02:22:51 +0800 Subject: [PATCH 253/367] spi: sg2044-nor: Honor SPI clock limits The fixed divider ignores device and operation clock limits. Calculate the divider before each transfer so SCK does not exceed either limit. Advertise the controller maximum speed so the SPI core supplies a default when the device omits its maximum speed. Use the fastest divider when no speed limit is supplied. Fixes: de16c322eefb ("spi: sophgo: add SG2044 SPI NOR controller driver") Signed-off-by: Han Gao Link: https://patch.msgid.link/20261001-fix-sg204xspi-v2-2-014dbaf22b5e@iscas.ac.cn Signed-off-by: Mark Brown --- drivers/spi/spi-sg2044-nor.c | 39 ++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/drivers/spi/spi-sg2044-nor.c b/drivers/spi/spi-sg2044-nor.c index ff026ca0f766..bc9a8ee9bce4 100644 --- a/drivers/spi/spi-sg2044-nor.c +++ b/drivers/spi/spi-sg2044-nor.c @@ -385,6 +385,29 @@ static ssize_t sg2044_spifmc_trans_reg(struct sg2044_spifmc *spifmc, return 0; } +static int sg2044_spifmc_set_speed(struct sg2044_spifmc *spifmc, + unsigned int speed_hz) +{ + unsigned long rate = clk_get_rate(spifmc->clk); + u64 div; + u32 reg; + + if (!rate) + return -EINVAL; + + /* SCK = HCLK / (2 * (SckDiv + 1)); round down the resulting SCK. */ + div = speed_hz ? DIV_ROUND_UP_ULL(rate, 2ULL * speed_hz) : 1; + if (div > SPIFMC_CTRL_SCK_DIV_MASK + 1) + return -EINVAL; + + reg = readl(spifmc->io_base + SPIFMC_CTRL); + reg &= ~SPIFMC_CTRL_SCK_DIV_MASK; + reg |= div - 1; + writel(reg, spifmc->io_base + SPIFMC_CTRL); + + return 0; +} + static int sg2044_spifmc_exec_op(struct spi_mem *mem, const struct spi_mem_op *op) { @@ -395,11 +418,18 @@ static int sg2044_spifmc_exec_op(struct spi_mem *mem, mutex_lock(&spifmc->lock); + ret = sg2044_spifmc_set_speed(spifmc, op->max_freq ? + min(op->max_freq, mem->spi->max_speed_hz) : + mem->spi->max_speed_hz); + if (ret) + goto out_unlock; + if (op->addr.nbytes == 0) ret = sg2044_spifmc_trans_reg(spifmc, op); else ret = sg2044_spifmc_trans(spifmc, op); +out_unlock: mutex_unlock(&spifmc->lock); return ret; @@ -409,6 +439,10 @@ static const struct spi_controller_mem_ops sg2044_spifmc_mem_ops = { .exec_op = sg2044_spifmc_exec_op, }; +static const struct spi_controller_mem_caps sg2044_spifmc_mem_caps = { + .per_op_freq = true, +}; + static void sg2044_spifmc_init(struct sg2044_spifmc *spifmc) { u32 tran_csr; @@ -448,6 +482,10 @@ static int sg2044_spifmc_probe(struct platform_device *pdev) if (IS_ERR(spifmc->clk)) return dev_err_probe(dev, PTR_ERR(spifmc->clk), "Cannot get and enable AHB clock\n"); + ctrl->max_speed_hz = clk_get_rate(spifmc->clk) / 2; + if (!ctrl->max_speed_hz) + return dev_err_probe(dev, -EINVAL, "Invalid AHB clock rate\n"); + spifmc->dev = &pdev->dev; spifmc->ctrl = ctrl; @@ -459,6 +497,7 @@ static int sg2044_spifmc_probe(struct platform_device *pdev) ctrl->bits_per_word_mask = SPI_BPW_MASK(8); ctrl->auto_runtime_pm = false; ctrl->mem_ops = &sg2044_spifmc_mem_ops; + ctrl->mem_caps = &sg2044_spifmc_mem_caps; ctrl->mode_bits = SPI_RX_DUAL | SPI_TX_DUAL | SPI_RX_QUAD | SPI_TX_QUAD; ret = devm_mutex_init(dev, &spifmc->lock); From 28c2388ee24cdbefbcc458fa41ef7bffaaa1cc83 Mon Sep 17 00:00:00 2001 From: Abhishek Ojha Date: Tue, 1 Sep 2026 21:07:01 -0400 Subject: [PATCH 254/367] regulator: ltc3676: don't read the write-only HRST and CLIRQ registers HRST (0x1e) and CLIRQ (0x1f) are write-only command strobes. Writing HRST triggers a hard reset of the chip and writing CLIRQ clears the interrupt; reading them returns nothing useful, and the read itself fires the command. The result is that a plain register dump resets the board: cat /sys/kernel/debug/regmap/2-003c/registers walks every readable register, hits HRST, and the board reboots. Split the callback back in two so HRST and CLIRQ stay writable but are no longer readable. The readable range is left unchanged (LTC3676_BUCK1 ... LTC3676_IRQSTAT). Fixes: 37b918a034fe ("regulator: Add LTC3676 support") Cc: stable@vger.kernel.org Signed-off-by: Abhishek Ojha Link: https://patch.msgid.link/20260902010701.3360310-1-Abhishek.ojha@savoirfairelinux.com Signed-off-by: Mark Brown --- drivers/regulator/ltc3676.c | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/drivers/regulator/ltc3676.c b/drivers/regulator/ltc3676.c index 307c6f7a155d..d67cee4e0be0 100644 --- a/drivers/regulator/ltc3676.c +++ b/drivers/regulator/ltc3676.c @@ -232,7 +232,7 @@ static const struct regulator_desc ltc3676_regulators[LTC3676_NUM_REGULATORS] = LTC3676_FIXED_REG(LDO4, ldo4, LDOB, 2), }; -static bool ltc3676_readable_writeable_reg(struct device *dev, unsigned int reg) +static bool ltc3676_writeable_reg(struct device *dev, unsigned int reg) { switch (reg) { case LTC3676_BUCK1 ... LTC3676_IRQSTAT: @@ -243,6 +243,15 @@ static bool ltc3676_readable_writeable_reg(struct device *dev, unsigned int reg) return false; } +static bool ltc3676_readable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case LTC3676_BUCK1 ... LTC3676_IRQSTAT: + return true; + } + return false; +} + static bool ltc3676_volatile_reg(struct device *dev, unsigned int reg) { switch (reg) { @@ -255,8 +264,8 @@ static bool ltc3676_volatile_reg(struct device *dev, unsigned int reg) static const struct regmap_config ltc3676_regmap_config = { .reg_bits = 8, .val_bits = 8, - .writeable_reg = ltc3676_readable_writeable_reg, - .readable_reg = ltc3676_readable_writeable_reg, + .writeable_reg = ltc3676_writeable_reg, + .readable_reg = ltc3676_readable_reg, .volatile_reg = ltc3676_volatile_reg, .max_register = LTC3676_CLIRQ, .use_single_read = true, From b8eb5fd5bdb4c03758b056c70e5e1d962ca89757 Mon Sep 17 00:00:00 2001 From: Waiman Long Date: Mon, 5 Oct 2026 06:43:10 -0400 Subject: [PATCH 255/367] cgroup/cpuset: Call is_in_v2_mode() after acquiring cpuset_mutex in cpuset_handle_hotplug() It is reported by sashiko that calling is_in_v2_mode() outside of cpuset_mutex critical section in cpuset_handle_hotplug() can introduce a TOCTOU race where cgroup hierarchy may have changed from v1 to v2 or vice versa after is_in_v2_mode() is called leading to erroneously skip the allocation of tmpmasks or incorrectly modify cpus_allowed masks, resulting in cpuset state corruption. Fix that by calling is_in_v2_mode() after acquiring the cpuset_mutex. Fixes: b8d1b8ee93df ("cpuset: Allow v2 behavior in v1 cgroup") Signed-off-by: Waiman Long Signed-off-by: Tejun Heo --- kernel/cgroup/cpuset.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/kernel/cgroup/cpuset.c b/kernel/cgroup/cpuset.c index d2ec7b128960..b61bc3ed1037 100644 --- a/kernel/cgroup/cpuset.c +++ b/kernel/cgroup/cpuset.c @@ -4062,15 +4062,16 @@ static void cpuset_handle_hotplug(void) static cpumask_t new_cpus; static nodemask_t new_mems; bool cpus_updated, mems_updated; - bool on_dfl = is_in_v2_mode(); + bool on_dfl; struct tmpmasks tmp, *ptmp = NULL; - if (on_dfl && !alloc_tmpmasks(&tmp)) - ptmp = &tmp; - lockdep_assert_cpus_held(); mutex_lock(&cpuset_mutex); + on_dfl = is_in_v2_mode(); + if (on_dfl && !alloc_tmpmasks(&tmp)) + ptmp = &tmp; + /* fetch the available cpus/mems and find out which changed how */ cpumask_copy(&new_cpus, cpu_active_mask); new_mems = node_states[N_MEMORY]; From 99bda1ecbd56905f8bd934b9cdccd3bd251192b1 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Thu, 1 Oct 2026 19:11:38 +0000 Subject: [PATCH 256/367] flow_dissector: avoid u16 truncation of skb->len when computing thoff __skb_flow_dissect() computes key_control->thoff using: key_control->thoff = min_t(u16, nhoff, skb ? skb->len : hlen); min_t(u16, ...) casts both arguments to u16 before comparing them. Whenever skb->len (or hlen) modulo 65536 is smaller than nhoff, the truncated length wins and thoff is set to a bogus small value, even when the dissection succeeded. For example, an IPv4/TCP frame with skb->len == 65540 and nhoff == 34 gets thoff == 4, so callers such as skb_probe_transport_header() point the transport header inside the Ethernet header. Such skbs are not exotic: - At the time of commit d0c081b49137 ("flow_dissector: properly cap thoff field"), AF_PACKET with PACKET_VNET_HDR could already build GSO skbs larger than 64KB (MTU checks are skipped for GSO, and alloc_skb_with_frags() accepted up to MAX_SKB_FRAGS (17) order-0 pages on top of the linear part). - BIG TCP now makes skbs larger than 64KB common. - The following patch makes tun_get_user() dissect IFF_TAP frames before eth_type_trans() pulls the Ethernet header, so a GSO frame carrying a 65522..65535 byte L3 packet will be dissected with skb->len in [65536, 65549]. Compare as u32 instead. If the resulting offset cannot be represented in the u16 key_control->thoff, cap it to U16_MAX and report the dissection as failed rather than silently returning a wrong transport offset. thoff is still set on failure, as some callers (such as eth_get_headlen()) use it regardless of the return value. Fixes: d0c081b49137 ("flow_dissector: properly cap thoff field") Signed-off-by: Eric Dumazet Reviewed-by: Willem de Bruijn Link: https://patch.msgid.link/20261001191140.2818991-2-edumazet@kernel.org Signed-off-by: Jakub Kicinski --- net/core/flow_dissector.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/net/core/flow_dissector.c b/net/core/flow_dissector.c index 8aa4f9b4df81..27d8a01bc923 100644 --- a/net/core/flow_dissector.c +++ b/net/core/flow_dissector.c @@ -1071,6 +1071,7 @@ bool __skb_flow_dissect(const struct net *net, int mpls_lse = 0; int num_hdrs = 0; u8 ip_proto = 0; + u32 thoff; bool ret; if (!data) { @@ -1692,7 +1693,13 @@ bool __skb_flow_dissect(const struct net *net, ret = true; out: - key_control->thoff = min_t(u16, nhoff, skb ? skb->len : hlen); + thoff = min_t(u32, nhoff, skb ? skb->len : hlen); + if (unlikely(thoff > U16_MAX)) { + /* Cannot be represented in key_control->thoff. */ + thoff = U16_MAX; + ret = false; + } + key_control->thoff = thoff; key_basic->n_proto = proto; key_basic->ip_proto = ip_proto; From 44378d02c5aaae24b60a75fbbb539b4aa4db503d Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Thu, 1 Oct 2026 19:11:39 +0000 Subject: [PATCH 257/367] net: always dissect GSO packets in __virtio_net_hdr_to_skb() Commit 9e8db5913264 ("net: avoid false positives in untrusted gso validation") added a '&& skb->network_header' check before flow-dissecting GSO packets without VIRTIO_NET_HDR_F_NEEDS_CSUM in __virtio_net_hdr_to_skb(), because some callers (such as tun_get_user(), tun_xdp_one(), virtnet_receive_done(), and raw_verify_header()) called virtio_net_hdr_*_to_skb() before initializing skb->network_header and skb->dev. Because __alloc_skb() and __build_skb_around() zero-initialize skb->network_header to 0 (unlike mac_header and transport_header which are initialized to ~0U), those four callers always had skb->network_header == 0 and bypassed flow dissection in __virtio_net_hdr_to_skb(). More generally, skb->network_header is an offset from skb->head (where 0 is also a valid offset whenever skb_headroom(skb) is 0), not a boolean flag. Whenever the 'if (gso_type && skb->network_header)' branch was skipped, the fallback 'else if (gso_type)' only pulled nh_min_len + thlen (40 bytes for TCPv4) without dissecting the packet, without validating ip_proto or n_proto, and without setting skb->transport_header. If the packet has a malformed network header, it is not rejected and a subsequent skb_probe_transport_header() also fails, leaving skb->transport_header at ~0U (0xffff). Similarly, if an IPv4 packet carries IP options (ihl > 5) or an IPv6 packet carries extension headers, pulling only nh_min_len + thlen can leave the TCP header outside skb->head. In both cases, tcp_hdrlen(skb) in skb_gso_transport_seglen() reads out-of-bounds: BUG: KASAN: slab-out-of-bounds in skb_gso_transport_seglen Read of size 2 by task poc/133 skb_gso_transport_seglen (net/core/gso.c:155) skb_gso_validate_mac_len (net/core/gso.c:270) tbf_enqueue (net/sched/sch_tbf.c:260) dev_qdisc_enqueue (net/core/dev.c:4227) __dev_queue_xmit (net/core/dev.c:4884) In addition, checking virtio_net_hdr_match_proto() only inside 'if (!skb->protocol)' before flow dissection both skipped validation when skb->protocol was pre-set by the caller and rejected VLAN-tagged frames whose outer L2 protocol is ETH_P_8021Q or ETH_P_8021AD. Fix this by: 1. Initializing skb->dev and skb->network_header (plus skb->protocol for IFF_TUN) before virtio_net_hdr_*_to_skb() in tun_get_user(), tun_xdp_one(), virtnet_receive_done(), and raw_verify_header(). In tun_get_user(), drop the redundant skb_reset_mac_header(skb) in the IFF_TUN case since __virtio_net_hdr_to_skb() unconditionally resets mac_header. 2. Removing '&& skb->network_header' and the unvalidated 'else if (gso_type)' fallback in __virtio_net_hdr_to_skb() so all GSO packets without VIRTIO_NET_HDR_F_NEEDS_CSUM are flow-dissected, have their transport header pulled into linear data, and have skb->transport_header set. 3. Moving the virtio_net_hdr_match_proto() check to after skb_flow_dissect_flow_keys_basic(), validating keys.basic.n_proto against hdr_gso_type. Fixes: 9e8db5913264 ("net: avoid false positives in untrusted gso validation") Fixes: d5be7f632bad ("net: validate untrusted gso packets without csum offload") Fixes: 924a9bc362a5 ("net: check if protocol extracted by virtio_net_hdr_set_proto is correct") Reported-by: Weiming Shi Closes: https://lore.kernel.org/netdev/20260927163117.746432-2-bestswngs@gmail.com/ Signed-off-by: Eric Dumazet Reviewed-by: Willem de Bruijn Cc: Michael S. Tsirkin Link: https://patch.msgid.link/20261001191140.2818991-3-edumazet@kernel.org Signed-off-by: Jakub Kicinski --- arch/um/drivers/vector_transports.c | 1 + drivers/net/tun.c | 23 +++++++---- drivers/net/virtio_net.c | 2 + include/linux/virtio_net.h | 64 ++++++++++++----------------- 4 files changed, 44 insertions(+), 46 deletions(-) diff --git a/arch/um/drivers/vector_transports.c b/arch/um/drivers/vector_transports.c index ddd127ee9678..e1fb2a76fdff 100644 --- a/arch/um/drivers/vector_transports.c +++ b/arch/um/drivers/vector_transports.c @@ -209,6 +209,7 @@ static int raw_verify_header( if ((vheader->flags & VIRTIO_NET_HDR_F_DATA_VALID) > 0) return 1; + skb_set_network_header(skb, ETH_HLEN); virtio_net_hdr_to_skb(skb, vheader, virtio_legacy_is_little_endian()); return 0; } diff --git a/drivers/net/tun.c b/drivers/net/tun.c index 5a302709a68a..242899f7fd07 100644 --- a/drivers/net/tun.c +++ b/drivers/net/tun.c @@ -1897,12 +1897,7 @@ static ssize_t tun_get_user(struct tun_struct *tun, struct tun_file *tfile, } } - if (tun_vnet_hdr_tnl_to_skb(tun->flags, features, skb, &hdr)) { - atomic_long_inc(&tun->rx_frame_errors); - err = -EINVAL; - goto free_skb; - } - + skb->dev = tun->dev; switch (tun->flags & TUN_TYPE_MASK) { case IFF_TUN: if (tun->flags & IFF_NO_PI) { @@ -1927,9 +1922,8 @@ static ssize_t tun_get_user(struct tun_struct *tun, struct tun_file *tfile, } } - skb_reset_mac_header(skb); + skb_reset_network_header(skb); skb->protocol = pi.proto; - skb->dev = tun->dev; break; case IFF_TAP: if (!pskb_may_pull(skb, ETH_HLEN)) { @@ -1937,10 +1931,19 @@ static ssize_t tun_get_user(struct tun_struct *tun, struct tun_file *tfile, drop_reason = SKB_DROP_REASON_HDR_TRUNC; goto drop; } - skb->protocol = eth_type_trans(skb, tun->dev); + skb_set_network_header(skb, ETH_HLEN); break; } + if (tun_vnet_hdr_tnl_to_skb(tun->flags, features, skb, &hdr)) { + atomic_long_inc(&tun->rx_frame_errors); + err = -EINVAL; + goto free_skb; + } + + if ((tun->flags & TUN_TYPE_MASK) == IFF_TAP) + skb->protocol = eth_type_trans(skb, tun->dev); + /* copy skb_ubuf_info for callback when skb has no error */ if (zerocopy) { skb_zcopy_init(skb, msg_control); @@ -2600,6 +2603,8 @@ static int tun_xdp_one(struct tun_struct *tun, features = tun_vnet_hdr_guest_features(READ_ONCE(tun->vnet_hdr_sz)); tnl_hdr = (struct virtio_net_hdr_v1_hash_tunnel *)gso; + skb->dev = tun->dev; + skb_set_network_header(skb, ETH_HLEN); if (tun_vnet_hdr_tnl_to_skb(tun->flags, features, skb, tnl_hdr)) { atomic_long_inc(&tun->rx_frame_errors); kfree_skb(skb); diff --git a/drivers/net/virtio_net.c b/drivers/net/virtio_net.c index bf82ef9874ab..daab43ac92ce 100644 --- a/drivers/net/virtio_net.c +++ b/drivers/net/virtio_net.c @@ -2515,6 +2515,8 @@ static void virtnet_receive_done(struct virtnet_info *vi, struct receive_queue * goto frame_err; } + skb->dev = dev; + skb_set_network_header(skb, ETH_HLEN); if (virtio_net_hdr_tnl_to_skb(skb, &hdr->tnl_hdr, vi->rx_tnl, vi->rx_tnl_csum, virtio_is_little_endian(vi->vdev))) { diff --git a/include/linux/virtio_net.h b/include/linux/virtio_net.h index c381b916c1b5..d6466f96cdd0 100644 --- a/include/linux/virtio_net.h +++ b/include/linux/virtio_net.h @@ -111,48 +111,38 @@ static inline int __virtio_net_hdr_to_skb(struct sk_buff *skb, p_off = nh_min_len + thlen; if (!pskb_may_pull(skb, p_off)) return -EINVAL; - } else { + } else if (gso_type) { /* gso packets without NEEDS_CSUM do not set transport_offset. * probe and drop if does not match one of the above types. */ - if (gso_type && skb->network_header) { - struct flow_keys_basic keys; + struct flow_keys_basic keys; - if (!skb->protocol) { - __be16 protocol = dev_parse_header_protocol(skb); - - if (!protocol) - virtio_net_hdr_set_proto(skb, hdr); - else if (!virtio_net_hdr_match_proto(protocol, - hdr_gso_type)) - return -EINVAL; - else - skb->protocol = protocol; - } -retry: - if (!skb_flow_dissect_flow_keys_basic(NULL, skb, &keys, - NULL, 0, 0, 0, - 0)) { - /* UFO does not specify ipv4 or 6: try both */ - if (gso_type & SKB_GSO_UDP && - skb->protocol == htons(ETH_P_IP)) { - skb->protocol = htons(ETH_P_IPV6); - goto retry; - } - return -EINVAL; - } - - p_off = keys.control.thoff + thlen; - if (!pskb_may_pull(skb, p_off) || - keys.basic.ip_proto != ip_proto) - return -EINVAL; - - skb_set_transport_header(skb, keys.control.thoff); - } else if (gso_type) { - p_off = nh_min_len + thlen; - if (!pskb_may_pull(skb, p_off)) - return -EINVAL; + if (!skb->protocol) { + skb->protocol = dev_parse_header_protocol(skb); + if (!skb->protocol) + virtio_net_hdr_set_proto(skb, hdr); } +retry: + if (!skb_flow_dissect_flow_keys_basic(NULL, skb, &keys, + NULL, 0, 0, 0, + 0)) { + /* UFO does not specify ipv4 or 6: try both */ + if (gso_type & SKB_GSO_UDP && + skb->protocol == htons(ETH_P_IP)) { + skb->protocol = htons(ETH_P_IPV6); + goto retry; + } + return -EINVAL; + } + + p_off = keys.control.thoff + thlen; + if (!pskb_may_pull(skb, p_off) || + keys.basic.ip_proto != ip_proto || + !virtio_net_hdr_match_proto(keys.basic.n_proto, + hdr_gso_type)) + return -EINVAL; + + skb_set_transport_header(skb, keys.control.thoff); } if (hdr_gso_type != VIRTIO_NET_HDR_GSO_NONE) { From ee2d4c2d7cc18a2cd504044a75ea614e720248eb Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Thu, 1 Oct 2026 19:11:40 +0000 Subject: [PATCH 258/367] selftests: net: tun: add test for VLAN-tagged GSO without NEEDS_CSUM Add selftests in tun.c verifying that a VLAN-tagged (802.1Q) TCPv4 GSO packet without VIRTIO_NET_HDR_F_NEEDS_CSUM (both flags = 0 and flags = VIRTIO_NET_HDR_F_DATA_VALID) is accepted when written to a TAP device (/dev/net/tun with IFF_TAP | IFF_NO_PI | IFF_VNET_HDR). Also verify that the following are rejected with -EINVAL: - a mismatched GSO type (VIRTIO_NET_HDR_GSO_TCPV6 on a VLAN-tagged IPv4 packet). - a frame whose TCP header is truncated after 10 bytes. Pulling only sizeof(struct iphdr) + sizeof(struct tcphdr) bytes would accept it, so this requires the transport offset found by flow dissection. Finally, verify that a 65540-byte frame is accepted. Its skb->len is above U16_MAX while it is flow-dissected, before eth_type_trans() pulls the Ethernet header. Based on a reproducer by Michael S. Tsirkin . Signed-off-by: Eric Dumazet Reviewed-by: Willem de Bruijn Link: https://patch.msgid.link/20261001191140.2818991-4-edumazet@kernel.org Signed-off-by: Jakub Kicinski --- tools/testing/selftests/net/tun.c | 136 ++++++++++++++++++++++++++++++ 1 file changed, 136 insertions(+) diff --git a/tools/testing/selftests/net/tun.c b/tools/testing/selftests/net/tun.c index abe488bac50b..afaa81c9bac5 100644 --- a/tools/testing/selftests/net/tun.c +++ b/tools/testing/selftests/net/tun.c @@ -9,6 +9,7 @@ #include #include #include +#include #include #include @@ -542,6 +543,141 @@ TEST_F(tun, reattach_close_delete) EXPECT_EQ(tun_delete(self->ifname), 0); } +FIXTURE(tun_vnet_gso) +{ + char ifname[IFNAMSIZ]; + int fd; +}; + +FIXTURE_SETUP(tun_vnet_gso) +{ + int flags = IFF_TAP | IFF_NO_PI | IFF_VNET_HDR; + + memset(self->ifname, 0, sizeof(self->ifname)); + self->fd = tun_open(self->ifname, flags, 0, 0, NULL); + ASSERT_GE(self->fd, 0); +} + +FIXTURE_TEARDOWN(tun_vnet_gso) +{ + if (self->fd >= 0) + close(self->fd); +} + +static int build_vlan_tcpv4_gso_packet(uint8_t *buf, int payload_len) +{ + uint16_t vlan_tag[2] = { htons(100), htons(ETH_P_IP) }; + uint8_t *cur = buf + sizeof(struct virtio_net_hdr); + struct virtio_net_hdr vh = { 0 }; + struct tcphdr tcph = { 0 }; + uint32_t sum; + + cur += build_eth(cur, ETH_P_8021Q, param_hwaddr_outer_src, + param_hwaddr_outer_dst); + + /* 802.1Q tag: VID=100, inner protocol=ETH_P_IP */ + memcpy(cur, vlan_tag, sizeof(vlan_tag)); + cur += sizeof(vlan_tag); + + cur += build_ipv4_header(cur, IPPROTO_TCP, + sizeof(tcph) + payload_len, + ¶m_ipaddr4_outer_src, + ¶m_ipaddr4_outer_dst); + + tcph.source = htons(12345); + tcph.dest = htons(80); + tcph.seq = htonl(1); + tcph.doff = sizeof(tcph) / 4; + tcph.ack = 1; + tcph.window = htons(65535); + memcpy(cur, &tcph, sizeof(tcph)); + memset(cur + sizeof(tcph), PKT_DATA, payload_len); + + sum = add_csum((const uint8_t *)¶m_ipaddr4_outer_src, + sizeof(param_ipaddr4_outer_src)); + sum += add_csum((const uint8_t *)¶m_ipaddr4_outer_dst, + sizeof(param_ipaddr4_outer_dst)); + sum += htons(IPPROTO_TCP) + htons(sizeof(tcph) + payload_len); + sum += add_csum(cur, sizeof(tcph) + payload_len); + tcph.check = finish_ip_csum(sum); + memcpy(cur, &tcph, sizeof(tcph)); + cur += sizeof(tcph) + payload_len; + + vh.gso_type = VIRTIO_NET_HDR_GSO_TCPV4; + vh.gso_size = 1400; + vh.hdr_len = (cur - buf) - sizeof(vh) - payload_len; + memcpy(buf, &vh, sizeof(vh)); + + return cur - buf; +} + +TEST_F(tun_vnet_gso, vlan_tcpv4_gso_no_csum) +{ + struct virtio_net_hdr vh; + uint8_t pkt[4096] = { 0 }; + int len, ret; + + len = build_vlan_tcpv4_gso_packet(pkt, 2800); + memcpy(&vh, pkt, sizeof(vh)); + + /* Valid VLAN-tagged TCPv4 GSO with flags = 0 (no NEEDS_CSUM) */ + vh.flags = 0; + memcpy(pkt, &vh, sizeof(vh)); + ret = write(self->fd, pkt, len); + ASSERT_EQ(ret, len); + + /* Valid VLAN-tagged TCPv4 GSO with flags = DATA_VALID */ + vh.flags = VIRTIO_NET_HDR_F_DATA_VALID; + memcpy(pkt, &vh, sizeof(vh)); + ret = write(self->fd, pkt, len); + ASSERT_EQ(ret, len); + + /* Mismatched GSO type (TCPV6 on VLAN-tagged IPv4 packet) */ + vh.flags = 0; + vh.gso_type = VIRTIO_NET_HDR_GSO_TCPV6; + memcpy(pkt, &vh, sizeof(vh)); + ret = write(self->fd, pkt, len); + ASSERT_EQ(ret, -1); + ASSERT_EQ(errno, EINVAL); + + /* TCP header truncated after 10 bytes, without NEEDS_CSUM. + * Requires the transport header offset found by flow dissection: + * pulling only sizeof(struct iphdr) + sizeof(struct tcphdr) bytes + * from the mac header would accept this frame. + */ + vh.gso_type = VIRTIO_NET_HDR_GSO_TCPV4; + vh.hdr_len = ETH_HLEN + 4 + sizeof(struct iphdr) + 10; + memcpy(pkt, &vh, sizeof(vh)); + ret = write(self->fd, pkt, sizeof(vh) + vh.hdr_len); + ASSERT_EQ(ret, -1); + ASSERT_EQ(errno, EINVAL); +} + +TEST_F(tun_vnet_gso, vlan_tcpv4_gso_no_csum_64k) +{ + /* Ethernet frame of 65540 bytes: skb->len is above U16_MAX when the + * frame is flow-dissected, before eth_type_trans() pulls ETH_HLEN. + */ + const int payload_len = 65540 - ETH_HLEN - 4 - + sizeof(struct iphdr) - sizeof(struct tcphdr); + struct virtio_net_hdr vh; + uint8_t *pkt; + int len, ret; + + pkt = calloc(1, sizeof(vh) + 65540); + ASSERT_NE(pkt, NULL); + + len = build_vlan_tcpv4_gso_packet(pkt, payload_len); + ASSERT_EQ(len, sizeof(vh) + 65540); + memcpy(&vh, pkt, sizeof(vh)); + + vh.flags = 0; + memcpy(pkt, &vh, sizeof(vh)); + ret = write(self->fd, pkt, len); + free(pkt); + ASSERT_EQ(ret, len); +} + FIXTURE(tun_vnet_udptnl) { char ifname[IFNAMSIZ]; From 54867884fef503e7a4aaede93ef1bf540ab10b53 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Fri, 2 Oct 2026 16:14:27 +0200 Subject: [PATCH 259/367] nullfs: add an empty immutable regular file Add nullfs_new_file() to allocate an empty immutable regular file on a nullfs instance as a dentry of its own. It is never hashed under the root and so can't be found by lookup. Reads return nothing, changes are refused, file locks, leases and delegations are refused as. Link: https://patch.msgid.link/20261002-work-mount-cover-v1-1-232a8f52b43c@kernel.org Signed-off-by: Christian Brauner (Amutable) --- fs/mount.h | 1 + fs/namespace.c | 25 ++++++++++++++ fs/nullfs.c | 88 ++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 114 insertions(+) diff --git a/fs/mount.h b/fs/mount.h index 4e68e5cbc254..2e29cdbaeb74 100644 --- a/fs/mount.h +++ b/fs/mount.h @@ -6,6 +6,7 @@ #include extern struct file_system_type nullfs_fs_type; +extern struct dentry *nullfs_new_file(struct super_block *sb); extern struct vfsmount *knullfs; extern struct list_head notify_list; diff --git a/fs/namespace.c b/fs/namespace.c index e1b0ade95b0d..61cb03ce5241 100644 --- a/fs/namespace.c +++ b/fs/namespace.c @@ -81,6 +81,7 @@ static struct hlist_head *mount_hashtable __ro_after_init; static struct hlist_head *mountpoint_hashtable __ro_after_init; static struct kmem_cache *mnt_cache __ro_after_init; struct vfsmount *knullfs __ro_after_init; /* private nullfs instance */ +static struct vfsmount *knullfs_file __ro_after_init; /* its regular file */ static DECLARE_RWSEM(namespace_sem); static HLIST_HEAD(unmounted); /* protected by namespace_sem */ static LIST_HEAD(ex_mountpoints); /* protected by namespace_sem */ @@ -6330,6 +6331,25 @@ static void __init mount_rootfs_on_nullfs(struct vfsmount *mnt, attach_mnt(real_mount(mnt), mp.parent, mp.mp); } +static struct vfsmount *__init knullfs_file_mount(void) +{ + struct dentry *file; + struct mount *mnt; + + file = nullfs_new_file(knullfs->mnt_sb); + if (IS_ERR(file)) + return ERR_CAST(file); + mnt = clone_mnt(real_mount(knullfs), file, CL_PRIVATE); + dput(file); + if (IS_ERR(mnt)) + return ERR_CAST(mnt); + mnt->mnt_ns = MNT_NS_INTERNAL; + mnt->mnt.mnt_flags |= MNT_INTERNAL; + mnt->mnt.mnt_flags &= ~MNT_READONLY; + dont_mount(mnt->mnt.mnt_root); + return &mnt->mnt; +} + static void __init init_mount_tree(void) { struct vfsmount *mnt, *nullfs_mnt; @@ -6342,6 +6362,8 @@ static void __init init_mount_tree(void) * (1) nullfs with mount id 1 * (2) mutable rootfs with mount id 2 * (3) private nullfs for kthreads (SB_KERNMOUNT), kept in knullfs + * (4) a second mount of (3) rooted on a regular file, kept in + * knullfs_file * * with (2) mounted on top of (1). The init_task's root and pwd * are pointed at (3) so all kthreads start isolated in nullfs. @@ -6383,6 +6405,9 @@ static void __init init_mount_tree(void) dont_mount(knullfs->mnt_root); /* and nothing is ever written through it */ knullfs->mnt_flags |= MNT_READONLY; + knullfs_file = knullfs_file_mount(); + if (IS_ERR(knullfs_file)) + panic("VFS: Failed to create the nullfs file stand-in"); root.mnt = knullfs; root.dentry = knullfs->mnt_root; diff --git a/fs/nullfs.c b/fs/nullfs.c index ca0dbe3dc259..e7221bc8aedb 100644 --- a/fs/nullfs.c +++ b/fs/nullfs.c @@ -3,6 +3,8 @@ #include #include #include +#include +#include #include "mount.h" @@ -39,6 +41,92 @@ static const struct file_operations nullfs_dir_operations = { .fop_flags = FOP_IMMUTABLE, }; +static ssize_t nullfs_file_read_iter(struct kiocb *iocb, struct iov_iter *to) +{ + return 0; +} + +static ssize_t nullfs_file_splice_read(struct file *in, loff_t *ppos, + struct pipe_inode_info *pipe, + size_t len, unsigned int flags) +{ + return 0; +} + +static ssize_t nullfs_file_write_iter(struct kiocb *iocb, struct iov_iter *from) +{ + size_t count = iov_iter_count(from); + + iov_iter_advance(from, count); + return count; +} + +static int nullfs_pipe_to_null(struct pipe_inode_info *pipe, + struct pipe_buffer *buf, struct splice_desc *sd) +{ + return sd->len; +} + +static ssize_t nullfs_file_splice_write(struct pipe_inode_info *pipe, + struct file *out, loff_t *ppos, + size_t len, unsigned int flags) +{ + return splice_from_pipe(pipe, out, ppos, len, flags, nullfs_pipe_to_null); +} + +static int nullfs_file_setattr(struct mnt_idmap *idmap, struct dentry *dentry, + struct iattr *attr) +{ + if (attr->ia_valid & (ATTR_MODE | ATTR_UID | ATTR_GID)) + return -EPERM; + return 0; +} + +static const struct inode_operations nullfs_file_inode_operations = { + .setattr = nullfs_file_setattr, +}; + +static const struct file_operations nullfs_file_operations = { + .llseek = generic_file_llseek, + .read_iter = nullfs_file_read_iter, + .write_iter = nullfs_file_write_iter, + .splice_read = nullfs_file_splice_read, + .splice_write = nullfs_file_splice_write, + .fsync = noop_fsync, + .lock = nullfs_nolock, + .flock = nullfs_nolock, +}; + +/* + * An empty regular file on @sb as a dentry of its own, never hashed under + * the root so no lookup finds it. + */ +struct dentry *nullfs_new_file(struct super_block *sb) +{ + struct dentry *dentry; + struct inode *inode; + + inode = new_inode(sb); + if (!inode) + return ERR_PTR(-ENOMEM); + + /* the root directory is 1 */ + inode->i_ino = 2; + inode->i_mode = S_IFREG | 0666; + inode->i_op = &nullfs_file_inode_operations; + inode->i_fop = &nullfs_file_operations; + simple_inode_init_ts(inode); + inode->i_flags |= S_NOATIME; + + dentry = d_alloc_anon(sb); + if (!dentry) { + iput(inode); + return ERR_PTR(-ENOMEM); + } + d_instantiate(dentry, inode); + return dentry; +} + static int nullfs_fs_fill_super(struct super_block *s, struct fs_context *fc) { struct inode *inode; From 01cbebb420709af722a769dddac26b30270ae4b2 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Fri, 2 Oct 2026 16:14:28 +0200 Subject: [PATCH 260/367] namespace: rework connected mounts UMOUNT_CONNECTED as implemented allows for the creation of reference count cycles. Here's a simple example mkdir /x; mkfifo /ready /go unshare -m sh -c 'mount -t tmpfs tmpfs /x truncate -s 8M /x/img; mkfs.ext4 -q /x/img dev=$(losetup -f --show /x/img) mkdir /x/mp; mount $dev /x/mp echo $dev > /ready; read r < /go' & read dev < /ready rmdir /x echo > /go wait losetup -d $dev losetup -a Take a directory /x on the host, create a new mount namespace, mount a tmpfs on /x, use a file on that tmpfs as the backing file for a loop device, mount that loop device on that tmpfs. Now rmdir /x on the host. This will lazily unmount the mount on top of /x in the container with UMOUNT_CONNECTED. Once the namespace exits nothing references the mount anymore. Now the tmpfs is pinned by the backing file of the loop device and the loop mount is owned by the tmpfs superblock. Fun fact, such cycles can be formed by at least the following subsystems and I have added reproducers for all of them: (1) a loop mount P from an image on a tmpfs next to it, so that P's death shows as the loop device giving up its backing file (2) autofs with a FIFO on P as its pipe, zram with a device node on P as its writeback device, both on a minix image since vfat has neither (3) ecryptfs with its lower directory on P, under a passphrase token added to the session keyring (4) binfmt_misc in a new user namespace with an 'F' interpreter on P (5) a fuse server that answers FUSE_INIT with passthrough on and registers a file on P as a backing file (6) zloop with its zone files in a directory on P (7) a mass storage gadget on the dummy UDC with its LUN file on P, mounted from the SCSI disk the gadget shows up as (8) md with a RAID1 of one loop device and its bitmap file on P, which skips while SET_BITMAP_FILE has no way to succeed (9) rmdir of P's mountpoint from the parent, then the child exits, then the device must be free and LOOP_CLR_FD must release the file The underlying mechanism is UMOUNT_CONNECTED (MNT_LOCKED falls into the same class). With UMOUNT_CONNECTED an unmounted mount stays attached to its parent. This is used to protect revealing the underlying mount and is a non-negotiable security mechanism. So now the parent owns that mount and is put on the parent's final mntput(). That moves it to mnt_stuck_children and ultimately it's cleaned up by cleanup_mnt(). The fact that ownership of the child mount gets transferred to the parent turns every reference from a child's superblock back to one of its ancestors into a cycle. Don't keep the child attached at all. What the parent needs is that a lookup at the child's mountpoint keeps finding some mount, not the child itself and it's not a guarantee we have given really. When an unmounted mount would have stayed attached to its unmounted parent disconnect it like every other unmounted mount and leave a marker behind. A lookup on the parent that misses the mount hash and hits a marker finds knullfs. Either a file or a directory. Nothing leads from a marker to any other mount. The marker is owned by the parent and dropped by the parent's final mntput() or by __detach_mounts() when the mountpoint is deleted from under it. It is allocated together with the mount. With that every unmounted mount is a root and holds only its own reference which namespace_unlock() drops. No unmounted mount owns another one. A superblock that pins an ancestor can't form a cycle. It has a visible change. Mounts left connected (rmdir etc.) used to stay traversable through the parent for as long as something held the parent. Now it is detached with the umount. It lives as long as something references it but it isn't reachable through the parent anymore and ".." inside it leads nowhere which is the same as for every other lazily unmounted mount. Link: https://gist.github.com/mvo5/63ef46482349f3b1c3957d463a0c9c6f Link: https://patch.msgid.link/20261002-work-mount-cover-v1-2-232a8f52b43c@kernel.org Signed-off-by: Christian Brauner (Amutable) --- .../filesystems/propagate_umount.txt | 12 +- fs/mount.h | 10 +- fs/namespace.c | 129 ++++++++++++------ fs/pnode.c | 4 +- 4 files changed, 104 insertions(+), 51 deletions(-) diff --git a/Documentation/filesystems/propagate_umount.txt b/Documentation/filesystems/propagate_umount.txt index 9a7eb96df300..de58ce817582 100644 --- a/Documentation/filesystems/propagate_umount.txt +++ b/Documentation/filesystems/propagate_umount.txt @@ -32,12 +32,12 @@ of set. They can be reparented to the place where the bottom of stack is attached to a mount that will survive. NOTE: doing that will violate a constraint on having no more than one mount with the same parent/mountpoint pair; however, the caller (umount_tree()) -will immediately remedy that - it may keep unmounted element attached -to parent, but only if the parent itself is unmounted. Since all -conflicts created by reparenting have common parent *not* in the -set and one side of the conflict (bottom of the stack of overmounts) -is in the set, it will be resolved. However, we rely upon umount_tree() -doing that pretty much immediately after the call of propagate_umount(). +will immediately remedy that - it detaches every element of the set +from its parent. Since all conflicts created by reparenting have +common parent *not* in the set and one side of the conflict (bottom +of the stack of overmounts) is in the set, it will be resolved. +However, we rely upon umount_tree() doing that pretty much immediately +after the call of propagate_umount(). Algorithm is based on two statements: 1) for any set S, there is a maximal non-shifting subset of S diff --git a/fs/mount.h b/fs/mount.h index 2e29cdbaeb74..4d23cade9f6c 100644 --- a/fs/mount.h +++ b/fs/mount.h @@ -43,9 +43,12 @@ struct mnt_pcp { struct mountpoint { struct hlist_node m_hash; struct dentry *m_dentry; - struct hlist_head m_list; + struct hlist_head m_list; /* mounts on it and pins */ + struct hlist_head m_covers; /* covers of unmounted parents */ }; +struct mnt_cover; + struct mount { struct hlist_node mnt_hash; struct mount *mnt_parent; @@ -87,7 +90,7 @@ struct mount { struct mountpoint *mnt_mp; /* where is it mounted */ union { struct hlist_node mnt_mp_list; /* list mounts with the same mountpoint */ - struct hlist_node mnt_umount; + struct hlist_node mnt_umount; /* on the unmounted list */ }; #ifdef CONFIG_FSNOTIFY struct fsnotify_mark_connector __rcu *mnt_fsnotify_marks; @@ -101,7 +104,8 @@ struct mount { int mnt_group_id; /* peer group identifier */ int mnt_expiry_mark; /* true if marked for expiry */ struct hlist_head mnt_pins; - struct hlist_head mnt_stuck_children; + struct mnt_cover *mnt_cover; /* the one it may leave behind */ + struct hlist_head mnt_covers; /* left behind by its unmounted children */ struct hlist_node mnt_ns_visible; /* link in ns->mnt_visible_mounts */ struct mount *overmount; /* mounted on ->mnt_root */ } __randomize_layout; diff --git a/fs/namespace.c b/fs/namespace.c index 61cb03ce5241..9ebcb8b35c48 100644 --- a/fs/namespace.c +++ b/fs/namespace.c @@ -211,6 +211,19 @@ static inline struct hlist_head *mp_hash(struct dentry *dentry) return &mountpoint_hashtable[tmp & mp_hash_mask]; } +/* + * What an unmounted mount leaves behind at its unmounted parent instead of + * staying attached to it. A lookup on the parent at the mountpoint finds + * a stand-in for as long as the cover is there. The parent frees it. + */ +struct mnt_cover { + struct hlist_node node; /* parent->mnt_covers, RCU */ + struct hlist_node pin; /* mp->m_covers, keeps the mountpoint */ + struct dentry *dentry; + struct mountpoint *mp; + struct rcu_head rcu; +}; + static int mnt_alloc_id(struct mount *mnt) { int res; @@ -300,6 +313,10 @@ static struct mount *alloc_vfsmnt(const char *name) if (mnt) { int err; + mnt->mnt_cover = kzalloc_obj(struct mnt_cover, GFP_KERNEL_ACCOUNT); + if (!mnt->mnt_cover) + goto out_free_cache; + err = mnt_alloc_id(mnt); if (err) goto out_free_cache; @@ -332,7 +349,7 @@ static struct mount *alloc_vfsmnt(const char *name) INIT_HLIST_HEAD(&mnt->mnt_slave_list); INIT_HLIST_NODE(&mnt->mnt_slave); INIT_HLIST_NODE(&mnt->mnt_mp_list); - INIT_HLIST_HEAD(&mnt->mnt_stuck_children); + INIT_HLIST_HEAD(&mnt->mnt_covers); INIT_HLIST_NODE(&mnt->mnt_ns_visible); #ifdef CONFIG_FSNOTIFY INIT_LIST_HEAD(&mnt->to_notify); @@ -349,6 +366,7 @@ static struct mount *alloc_vfsmnt(const char *name) out_free_id: mnt_free_id(mnt); out_free_cache: + kfree(mnt->mnt_cover); kmem_cache_free(mnt_cache, mnt); return NULL; } @@ -740,6 +758,8 @@ int sb_prepare_remount_readonly(struct super_block *sb) static void free_vfsmnt(struct mount *mnt) { mnt_idmap_put(mnt_idmap(&mnt->mnt)); + /* NULL if it left it behind */ + kfree(mnt->mnt_cover); kfree_const(mnt->mnt_devname); #ifdef CONFIG_SMP free_percpu(mnt->mnt_pcp); @@ -796,20 +816,31 @@ static bool legitimize_mnt(struct vfsmount *bastard, unsigned seq) * @dentry: dentry of mountpoint * * If @mnt has a child mount @c mounted on @dentry find and return it. + * If @mnt is unmounted and a child that was unmounted with it left its + * cover behind at @dentry, return the stand-in for it instead: knullfs + * for a directory, its regular file for anything else. * Caller must either hold the spinlock component of @mount_lock or * hold rcu_read_lock(), sample the seqcount component before the call * and recheck it afterwards. * - * Return: The child of @mnt mounted on @dentry or %NULL. + * Return: The child of @mnt mounted on @dentry, a stand-in or %NULL. */ struct mount *__lookup_mnt(struct vfsmount *mnt, struct dentry *dentry) { struct hlist_head *head = m_hash(mnt, dentry); + struct mnt_cover *cover; struct mount *p; hlist_for_each_entry_rcu(p, head, mnt_hash) if (&p->mnt_parent->mnt == mnt && p->mnt_mountpoint == dentry) return p; + /* an unmounted mount keeps the covers its unmounted children left */ + /* a lockless caller rechecks mount_lock after a miss, a stale flag is harmless */ + if (unlikely(data_race(mnt->mnt_flags) & MNT_UMOUNT)) { + hlist_for_each_entry_rcu(cover, &real_mount(mnt)->mnt_covers, node) + if (cover->dentry == dentry) + return real_mount(d_is_dir(dentry) ? knullfs : knullfs_file); + } return NULL; } @@ -925,6 +956,7 @@ static int get_mountpoint(struct dentry *dentry, struct pinned_mountpoint *m) mp->m_dentry = dget(dentry); hlist_add_head(&mp->m_hash, mp_hash(dentry)); INIT_HLIST_HEAD(&mp->m_list); + INIT_HLIST_HEAD(&mp->m_covers); hlist_add_head(&m->node, &mp->m_list); m->mp = no_free_ptr(mp); read_sequnlock_excl(&mount_lock); @@ -937,7 +969,7 @@ static int get_mountpoint(struct dentry *dentry, struct pinned_mountpoint *m) */ static void maybe_free_mountpoint(struct mountpoint *mp, struct list_head *list) { - if (hlist_empty(&mp->m_list)) { + if (hlist_empty(&mp->m_list) && hlist_empty(&mp->m_covers)) { struct dentry *dentry = mp->m_dentry; spin_lock(&dentry->d_lock); dentry->d_flags &= ~DCACHE_MOUNTED; @@ -1024,6 +1056,36 @@ static void umount_mnt(struct mount *mnt) __umount_mnt(mnt, &ex_mountpoints); } +/* + * @mnt is unmounted together with its parent and would have stayed attached + * to it. Leave its cover behind before it is detached so that a lookup on the + * parent at the mountpoint keeps finding a mount instead of what @mnt covered. + * + * locks: mount_lock[write_seqlock] + */ +static void leave_cover(struct mount *mnt) +{ + struct mnt_cover *cover = mnt->mnt_cover; + + mnt->mnt_cover = NULL; + cover->dentry = mnt->mnt_mountpoint; + cover->mp = mnt->mnt_mp; + /* keeps the mountpoint once @mnt has let go of it */ + hlist_add_head(&cover->pin, &cover->mp->m_covers); + hlist_add_head_rcu(&cover->node, &mnt->mnt_parent->mnt_covers); +} + +/* + * locks: mount_lock[write_seqlock] + */ +static void drop_cover(struct mnt_cover *cover, struct list_head *shrink_list) +{ + hlist_del_rcu(&cover->node); + hlist_del(&cover->pin); + maybe_free_mountpoint(cover->mp, shrink_list); + kfree_rcu(cover, rcu); +} + /* * vfsmount lock must be held for write */ @@ -1315,8 +1377,6 @@ static struct mount *clone_mnt(struct mount *old, struct dentry *root, static void cleanup_mnt(struct mount *mnt) { - struct hlist_node *p; - struct mount *m; /* * The warning here probably indicates that somebody messed * up a mnt_want/drop_write() pair. If this happens, the @@ -1327,10 +1387,6 @@ static void cleanup_mnt(struct mount *mnt) WARN_ON(mnt_get_writers(mnt)); if (unlikely(mnt->mnt_pins.first)) mnt_pin_kill(mnt); - hlist_for_each_entry_safe(m, p, &mnt->mnt_stuck_children, mnt_umount) { - hlist_del(&m->mnt_umount); - mntput(&m->mnt); - } fsnotify_vfsmount_delete(&mnt->mnt); dput(mnt->mnt.mnt_root); deactivate_super(mnt->mnt.mnt_sb); @@ -1356,6 +1412,8 @@ static DECLARE_DELAYED_WORK(delayed_mntput_work, delayed_mntput); static void noinline mntput_no_expire_slowpath(struct mount *mnt) { + struct mnt_cover *cover; + struct hlist_node *n; LIST_HEAD(list); int count; @@ -1386,13 +1444,10 @@ static void noinline mntput_no_expire_slowpath(struct mount *mnt) if (unlikely(!list_empty(&mnt->mnt_expire))) list_del(&mnt->mnt_expire); - if (unlikely(!list_empty(&mnt->mnt_mounts))) { - struct mount *p, *tmp; - list_for_each_entry_safe(p, tmp, &mnt->mnt_mounts, mnt_child) { - __umount_mnt(p, &list); - hlist_add_head(&p->mnt_umount, &mnt->mnt_stuck_children); - } - } + /* nothing stays attached to an unmounted mount */ + VFS_WARN_ON_ONCE(!list_empty(&mnt->mnt_mounts)); + hlist_for_each_entry_safe(cover, n, &mnt->mnt_covers, node) + drop_cover(cover, &list); unlock_mount_hash(); shrink_dentry_list(&list); @@ -1757,7 +1812,7 @@ static inline void namespace_lock(void) enum umount_tree_flags { UMOUNT_SYNC = 1, UMOUNT_PROPAGATE = 2, - UMOUNT_CONNECTED = 4, + UMOUNT_COVER = 4, }; static bool disconnect_mount(struct mount *mnt, enum umount_tree_flags how) @@ -1770,18 +1825,15 @@ static bool disconnect_mount(struct mount *mnt, enum umount_tree_flags how) if (!mnt_has_parent(mnt)) return true; - /* Because the reference counting rules change when mounts are - * unmounted and connected, umounted mounts may not be - * connected to mounted mounts. - */ + /* Only an unmounted parent has a mountpoint to keep covered */ if (!(mnt->mnt_parent->mnt.mnt_flags & MNT_UMOUNT)) return true; - /* Has it been requested that the mount remain connected? */ - if (how & UMOUNT_CONNECTED) + /* Has it been requested that the mountpoint stays covered? */ + if (how & UMOUNT_COVER) return false; - /* Is the mount locked such that it needs to remain connected? */ + /* Is the mount locked such that its mountpoint must stay covered? */ if (IS_MNT_LOCKED(mnt)) return false; @@ -1824,7 +1876,6 @@ static void umount_tree(struct mount *mnt, enum umount_tree_flags how) while (!list_empty(&tmp_list)) { struct mnt_namespace *ns; - bool disconnect; p = list_first_entry(&tmp_list, struct mount, mnt_list); list_del_init(&p->mnt_expire); list_del_init(&p->mnt_list); @@ -1837,17 +1888,12 @@ static void umount_tree(struct mount *mnt, enum umount_tree_flags how) if (how & UMOUNT_SYNC) p->mnt.mnt_flags |= MNT_SYNC_UMOUNT; - disconnect = disconnect_mount(p, how); if (mnt_has_parent(p)) { - if (!disconnect) { - /* Don't forget about p */ - list_add_tail(&p->mnt_child, &p->mnt_parent->mnt_mounts); - } else { - umount_mnt(p); - } + if (!disconnect_mount(p, how)) + leave_cover(p); + umount_mnt(p); } - if (disconnect) - hlist_add_head(&p->mnt_umount, &unmounted); + hlist_add_head(&p->mnt_umount, &unmounted); /* * At this point p->mnt_ns is NULL, notification will be queued @@ -2006,6 +2052,8 @@ static int do_umount(struct mount *mnt, int flags) void __detach_mounts(struct dentry *dentry) { struct pinned_mountpoint mp = {}; + struct mnt_cover *cover; + struct hlist_node *n; struct mount *mnt; guard(namespace_excl)(); @@ -2019,12 +2067,11 @@ void __detach_mounts(struct dentry *dentry) event++; while (mp.node.next) { mnt = hlist_entry(mp.node.next, struct mount, mnt_mp_list); - if (mnt->mnt.mnt_flags & MNT_UMOUNT) { - umount_mnt(mnt); - hlist_add_head(&mnt->mnt_umount, &unmounted); - } - else umount_tree(mnt, UMOUNT_CONNECTED); + umount_tree(mnt, UMOUNT_COVER); } + /* the dentry goes away, so do the covers left behind on it */ + hlist_for_each_entry_safe(cover, n, &mp.mp->m_covers, pin) + drop_cover(cover, &ex_mountpoints); unpin_mountpoint(&mp); } @@ -2348,7 +2395,7 @@ void dissolve_on_fput(struct vfsmount *mnt) emptied_ns = m->mnt_ns; lock_mount_hash(); - umount_tree(m, UMOUNT_CONNECTED); + umount_tree(m, UMOUNT_COVER); unlock_mount_hash(); mntput(no_free_ptr(p)); } @@ -6367,6 +6414,8 @@ static void __init init_mount_tree(void) * * with (2) mounted on top of (1). The init_task's root and pwd * are pointed at (3) so all kthreads start isolated in nullfs. + * A lookup at the cover an unmounted mount left behind finds (3) + * or (4), see __lookup_mnt(). */ nullfs_mnt = vfs_kern_mount(&nullfs_fs_type, 0, "nullfs", NULL); if (IS_ERR(nullfs_mnt)) diff --git a/fs/pnode.c b/fs/pnode.c index 2cd667958efe..5f07fa14099b 100644 --- a/fs/pnode.c +++ b/fs/pnode.c @@ -697,8 +697,8 @@ static void handle_locked(struct mount *m, struct list_head *to_umount) * the same parent and mountpoint; that will be remedied as soon as we * return from propagate_umount() - its caller (umount_tree()) will detach * the stack from the parent it (and now @m) is attached to. umount_tree() - * might choose to keep unmounted pieces stuck to each other, but it always - * detaches them from the mounts that remain in the tree. + * detaches every unmounted mount from its parent, whether that parent + * remains in the tree or not. */ static void reparent(struct mount *m) { From 3e78bf8433e0bdb013e2681c96fdb93e1966eb17 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Fri, 2 Oct 2026 16:14:29 +0200 Subject: [PATCH 261/367] selftests/filesystems: test covered mounts Test that mount cycles are resolved and test that mount covers behave as expected. Link: https://patch.msgid.link/20261002-work-mount-cover-v1-3-232a8f52b43c@kernel.org Signed-off-by: Christian Brauner (Amutable) --- .../filesystems/mount_cycle/.gitignore | 3 + .../filesystems/mount_cycle/Makefile | 7 +- .../selftests/filesystems/mount_cycle/config | 39 + .../mount_cycle/locked_handle_test.c | 377 ++++ .../filesystems/mount_cycle/loop_cycle_test.c | 1542 +++++++++++++++++ .../mount_cycle/mount_cover_test.c | 579 +++++++ .../filesystems/mount_cycle/settings | 1 + 7 files changed, 2547 insertions(+), 1 deletion(-) create mode 100644 tools/testing/selftests/filesystems/mount_cycle/config create mode 100644 tools/testing/selftests/filesystems/mount_cycle/locked_handle_test.c create mode 100644 tools/testing/selftests/filesystems/mount_cycle/loop_cycle_test.c create mode 100644 tools/testing/selftests/filesystems/mount_cycle/mount_cover_test.c create mode 100644 tools/testing/selftests/filesystems/mount_cycle/settings diff --git a/tools/testing/selftests/filesystems/mount_cycle/.gitignore b/tools/testing/selftests/filesystems/mount_cycle/.gitignore index d11f5b720d5b..30f7dd071bcb 100644 --- a/tools/testing/selftests/filesystems/mount_cycle/.gitignore +++ b/tools/testing/selftests/filesystems/mount_cycle/.gitignore @@ -1,5 +1,8 @@ # SPDX-License-Identifier: GPL-2.0-only +loop_cycle_test unmounted_tree_test overmount_reparent_test +mount_cover_test +locked_handle_test nsfs_rbind_loop_test overmount_ns_file_test diff --git a/tools/testing/selftests/filesystems/mount_cycle/Makefile b/tools/testing/selftests/filesystems/mount_cycle/Makefile index 49a8402ca858..fbe8c5e19d42 100644 --- a/tools/testing/selftests/filesystems/mount_cycle/Makefile +++ b/tools/testing/selftests/filesystems/mount_cycle/Makefile @@ -1,7 +1,12 @@ # SPDX-License-Identifier: GPL-2.0 -TEST_GEN_PROGS := unmounted_tree_test overmount_reparent_test +TEST_GEN_PROGS := loop_cycle_test unmounted_tree_test overmount_reparent_test mount_cover_test locked_handle_test TEST_GEN_PROGS += nsfs_rbind_loop_test overmount_ns_file_test CFLAGS += -Wall -O2 -g $(KHDR_INCLUDES) +LOCAL_HDRS += ../readdir_hold.h + include ../../lib.mk + +$(OUTPUT)/locked_handle_test: LDLIBS += -pthread +$(OUTPUT)/mount_cover_test: LDLIBS += -pthread diff --git a/tools/testing/selftests/filesystems/mount_cycle/config b/tools/testing/selftests/filesystems/mount_cycle/config new file mode 100644 index 000000000000..3bd5ce46af74 --- /dev/null +++ b/tools/testing/selftests/filesystems/mount_cycle/config @@ -0,0 +1,39 @@ +CONFIG_USER_NS=y +CONFIG_TMPFS=y +CONFIG_BLK_DEV_LOOP=y +CONFIG_VFAT_FS=y +CONFIG_MSDOS_FS=y +CONFIG_NLS_CODEPAGE_437=y +CONFIG_NLS_ISO8859_1=y +CONFIG_MINIX_FS=y +CONFIG_AUTOFS_FS=y +CONFIG_ZRAM=y +CONFIG_ZRAM_WRITEBACK=y +CONFIG_KEYS=y +CONFIG_ECRYPT_FS=y +CONFIG_BINFMT_MISC=y +CONFIG_FUSE_FS=y +CONFIG_FUSE_PASSTHROUGH=y +CONFIG_BLK_DEV_ZONED=y +CONFIG_BLK_DEV_ZONED_LOOP=y +CONFIG_CONFIGFS_FS=y +CONFIG_USB_SUPPORT=y +CONFIG_USB=y +CONFIG_USB_STORAGE=y +CONFIG_SCSI=y +CONFIG_BLK_DEV_SD=y +CONFIG_USB_GADGET=y +CONFIG_USB_DUMMY_HCD=y +CONFIG_USB_CONFIGFS=y +CONFIG_USB_CONFIGFS_MASS_STORAGE=y +CONFIG_MD=y +CONFIG_BLK_DEV_MD=y +CONFIG_MD_RAID1=y +CONFIG_MD_BITMAP=y +CONFIG_MD_BITMAP_FILE=y +CONFIG_INOTIFY_USER=y +CONFIG_DNOTIFY=y +CONFIG_FANOTIFY=y +CONFIG_FILE_LOCKING=y +CONFIG_CRYPTO_AES=y +CONFIG_USERFAULTFD=y diff --git a/tools/testing/selftests/filesystems/mount_cycle/locked_handle_test.c b/tools/testing/selftests/filesystems/mount_cycle/locked_handle_test.c new file mode 100644 index 000000000000..b21f43471e49 --- /dev/null +++ b/tools/testing/selftests/filesystems/mount_cycle/locked_handle_test.c @@ -0,0 +1,377 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * may_decode_fh() refuses a file handle below a mount with locked children + * to root in a user namespace. That has to hold while the mount is lazily + * unmounted and its children, the locked ones too, are taken off it. + */ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../../kselftest_harness.h" + +#ifndef OPEN_TREE_CLONE +#define OPEN_TREE_CLONE 1 +#endif +#ifndef OPEN_TREE_CLOEXEC +#define OPEN_TREE_CLOEXEC O_CLOEXEC +#endif +#ifndef AT_RECURSIVE +#define AT_RECURSIVE 0x8000 +#endif + +#define DIR_LEN 64 +#define PATH_LEN 128 + +#define ROUNDS 100 /* lazy umounts raced per test */ +#define RACERS 3 /* threads in open_by_handle_at() */ +#define EXTRA_MOUNTS 64 /* make umount_tree() hold mount_lock longer */ +#define MAX_DELAY_US 3000 /* before the umount */ +#define TAIL_US 2000 /* after it */ + +struct handle { + struct file_handle fh; + unsigned char buf[MAX_HANDLE_SZ]; +}; + +/* exit codes of the child */ +enum { + CHILD_OK, + CHILD_SETUP, + CHILD_DECODED, /* decoded past a locked child */ + CHILD_MOUNTED, /* not refused while mounted */ + CHILD_UNMOUNTED, /* not refused once unmounted */ + CHILD_ALLOWED, /* refused where nothing is locked */ + CHILD_NOUSERNS, /* no user namespace to be had */ +}; + +static int write_file(const char *path, const char *s) +{ + ssize_t n = -1; + int fd; + + fd = open(path, O_WRONLY | O_CLOEXEC); + if (fd >= 0) { + n = write(fd, s, strlen(s)); + close(fd); + } + return n == (ssize_t)strlen(s) ? 0 : -1; +} + +/* Become root in a new user namespace with a private mount namespace. */ +static int enter_userns(void) +{ + uid_t uid = getuid(); + gid_t gid = getgid(); + char map[32]; + + prctl(PR_SET_DUMPABLE, 1); + /* EINVAL: no USER_NS, ENOSPC: user.max_user_namespaces is 0, EPERM: an LSM */ + if (unshare(CLONE_NEWUSER | CLONE_NEWNS)) + return errno == EINVAL || errno == ENOSPC || errno == EPERM ? + CHILD_NOUSERNS : CHILD_SETUP; + if (write_file("/proc/self/setgroups", "deny") && errno != ENOENT) + return CHILD_SETUP; + snprintf(map, sizeof(map), "0 %d 1", uid); + if (write_file("/proc/self/uid_map", map)) + return CHILD_SETUP; + snprintf(map, sizeof(map), "0 %d 1", gid); + if (write_file("/proc/self/gid_map", map)) + return CHILD_SETUP; + if (setgid(0) || setuid(0)) + return CHILD_SETUP; + if (mount("", "/", NULL, MS_REC | MS_PRIVATE, NULL)) + return CHILD_SETUP; + return CHILD_OK; +} + +static int get_handle(const char *path, struct handle *h) +{ + int mntid; + + h->fh.handle_bytes = MAX_HANDLE_SZ; + return name_to_handle_at(AT_FDCWD, path, &h->fh, &mntid, 0); +} + +static int decode(int dfd, struct handle *h) +{ + return open_by_handle_at(dfd, &h->fh, O_RDONLY | O_DIRECTORY | O_CLOEXEC); +} + +struct race { + int dfd; + struct handle *h; + atomic_int go; + atomic_int stop; + atomic_int won; /* the first decoded descriptor */ +}; + +static void *racer(void *arg) +{ + struct race *r = arg; + + while (!atomic_load(&r->go)) + ; + while (!atomic_load(&r->stop)) { + int none = -1; + int fd; + + fd = decode(r->dfd, r->h); + if (fd < 0) + continue; + if (!atomic_compare_exchange_strong(&r->won, &none, fd)) + close(fd); + } + return NULL; +} + +/* stop the @n racers started so far and wait for them, they spin on @r */ +static void stop_racers(struct race *r, pthread_t *th, int n) +{ + int i; + + atomic_store(&r->stop, 1); + atomic_store(&r->go, 1); /* one still waiting for the start sees stop next */ + for (i = 0; i < n; i++) + pthread_join(th[i], NULL); +} + +/* + * Bind @h recursively at @w, or take a detached copy of it, and race + * open_by_handle_at() of @inner against the lazy umount. The decoded + * descriptor, if there was one, is left in @won. + */ +static int race_round(const char *h, const char *w, struct handle *inner, + bool dissolve, int *won) +{ + struct race r = { .h = inner, .won = -1 }; + pthread_t th[RACERS]; + int treefd = -1, fd, i; + + if (dissolve) { + treefd = syscall(__NR_open_tree, AT_FDCWD, h, + OPEN_TREE_CLONE | AT_RECURSIVE | OPEN_TREE_CLOEXEC); + if (treefd < 0) + return CHILD_SETUP; + /* an ordinary descriptor on the copy, treefd's close dissolves it */ + r.dfd = openat(treefd, ".", O_RDONLY | O_DIRECTORY | O_CLOEXEC); + } else { + if (mount(h, w, NULL, MS_BIND | MS_REC, NULL)) + return CHILD_SETUP; + r.dfd = open(w, O_RDONLY | O_DIRECTORY | O_CLOEXEC); + } + if (r.dfd < 0) + return CHILD_SETUP; + + fd = decode(r.dfd, inner); + if (fd >= 0 || errno != EPERM) { + if (fd >= 0) + close(fd); + return CHILD_MOUNTED; + } + + for (i = 0; i < RACERS; i++) { + if (pthread_create(&th[i], NULL, racer, &r)) { + stop_racers(&r, th, i); + return CHILD_SETUP; + } + } + atomic_store(&r.go, 1); + usleep(rand() % MAX_DELAY_US); + if (dissolve) + close(treefd); + else if (umount2(w, MNT_DETACH)) { + stop_racers(&r, th, RACERS); + return CHILD_SETUP; + } + usleep(TAIL_US); + stop_racers(&r, th, RACERS); + + fd = decode(r.dfd, inner); + if (fd >= 0) { + close(fd); + return CHILD_UNMOUNTED; + } + close(r.dfd); + *won = atomic_load(&r.won); + return CHILD_OK; +} + +static int race_child(const char *h, const char *w, struct handle *inner, + bool dissolve) +{ + int ret, won, i; + + srand(getpid()); + ret = enter_userns(); + if (ret) + return ret; + for (i = 0; i < ROUNDS; i++) { + ret = race_round(h, w, inner, dissolve, &won); + if (ret) + return ret; + if (won >= 0) + return CHILD_DECODED; + } + return CHILD_OK; +} + +/* a bind without locked children decodes while mounted and not after */ +static int allowed_child(const char *plain, const char *w, struct handle *h) +{ + int dfd, fd, ret; + + ret = enter_userns(); + if (ret) + return ret; + if (mount(plain, w, NULL, MS_BIND | MS_REC, NULL)) + return CHILD_SETUP; + dfd = open(w, O_RDONLY | O_DIRECTORY | O_CLOEXEC); + if (dfd < 0) + return CHILD_SETUP; + fd = decode(dfd, h); + if (fd < 0) + return CHILD_ALLOWED; + close(fd); + if (umount2(w, MNT_DETACH)) + return CHILD_SETUP; + fd = decode(dfd, h); + if (fd >= 0) { + close(fd); + return CHILD_UNMOUNTED; + } + return errno == EPERM ? CHILD_OK : CHILD_UNMOUNTED; +} + +FIXTURE(locked_handle) { + char base[DIR_LEN]; + char h[PATH_LEN]; /* the tree with the covered directory */ + char plain[PATH_LEN]; /* a tree with no mount in it */ + char w[PATH_LEN]; /* where the child binds either */ + struct handle inner; /* h/top/secret/inner, covered */ + struct handle uncovered; /* plain/inner */ +}; + +FIXTURE_SETUP(locked_handle) +{ + char p[PATH_LEN], e[PATH_LEN]; + int i; + + if (geteuid()) + SKIP(return, "test requires root"); + + snprintf(self->base, sizeof(self->base), "/tmp/locked_handle.XXXXXX"); + ASSERT_NE(mkdtemp(self->base), NULL); + ASSERT_EQ(unshare(CLONE_NEWNS), 0); + ASSERT_EQ(mount("", "/", NULL, MS_REC | MS_PRIVATE, NULL), 0); + ASSERT_EQ(mount("tmpfs", self->base, "tmpfs", 0, NULL), 0); + + snprintf(self->h, sizeof(self->h), "%s/h", self->base); + snprintf(self->plain, sizeof(self->plain), "%s/plain", self->base); + snprintf(self->w, sizeof(self->w), "%s/w", self->base); + ASSERT_EQ(mkdir(self->h, 0755), 0); + ASSERT_EQ(mkdir(self->plain, 0755), 0); + ASSERT_EQ(mkdir(self->w, 0755), 0); + snprintf(p, sizeof(p), "%s/plain/inner", self->base); + ASSERT_EQ(mkdir(p, 0755), 0); + ASSERT_EQ(get_handle(p, &self->uncovered), 0); + + /* the handle is for this filesystem */ + ASSERT_EQ(mount("tmpfs", self->h, "tmpfs", 0, NULL), 0); + snprintf(p, sizeof(p), "%s/h/top", self->base); + ASSERT_EQ(mkdir(p, 0755), 0); + snprintf(p, sizeof(p), "%s/h/top/secret", self->base); + ASSERT_EQ(mkdir(p, 0755), 0); + snprintf(p, sizeof(p), "%s/h/top/secret/inner", self->base); + ASSERT_EQ(mkdir(p, 0755), 0); + ASSERT_EQ(get_handle(p, &self->inner), 0); + snprintf(e, sizeof(e), "%s/h/empty", self->base); + ASSERT_EQ(mkdir(e, 0755), 0); + + /* cover it, and some more so the umount takes longer */ + snprintf(p, sizeof(p), "%s/h/top/secret", self->base); + ASSERT_EQ(mount("tmpfs", p, "tmpfs", 0, NULL), 0); + for (i = 0; i < EXTRA_MOUNTS; i++) { + snprintf(p, sizeof(p), "%s/h/c%d", self->base, i); + ASSERT_EQ(mkdir(p, 0755), 0); + ASSERT_EQ(mount(e, p, NULL, MS_BIND, NULL), 0); + } +} + +FIXTURE_TEARDOWN(locked_handle) +{ + umount2(self->base, MNT_DETACH); + rmdir(self->base); +} + +static int wait_child(pid_t pid) +{ + int status; + + if (waitpid(pid, &status, 0) != pid || !WIFEXITED(status)) + return -1; + return WEXITSTATUS(status); +} + +TEST_F(locked_handle, lazy_umount) +{ + pid_t pid; + int ret; + + pid = fork(); + ASSERT_GE(pid, 0); + if (pid == 0) + _exit(race_child(self->h, self->w, &self->inner, false)); + ret = wait_child(pid); + TH_LOG("child exit code %d", ret); + if (ret == CHILD_NOUSERNS) + SKIP(return, "no user namespaces"); + EXPECT_EQ(ret, CHILD_OK); +} + +TEST_F(locked_handle, dissolve) +{ + pid_t pid; + int ret; + + pid = fork(); + ASSERT_GE(pid, 0); + if (pid == 0) + _exit(race_child(self->h, self->w, &self->inner, true)); + ret = wait_child(pid); + TH_LOG("child exit code %d", ret); + if (ret == CHILD_NOUSERNS) + SKIP(return, "no user namespaces"); + EXPECT_EQ(ret, CHILD_OK); +} + +TEST_F(locked_handle, allowed_use) +{ + pid_t pid; + int ret; + + pid = fork(); + ASSERT_GE(pid, 0); + if (pid == 0) + _exit(allowed_child(self->plain, self->w, &self->uncovered)); + ret = wait_child(pid); + TH_LOG("child exit code %d", ret); + if (ret == CHILD_NOUSERNS) + SKIP(return, "no user namespaces"); + EXPECT_EQ(ret, CHILD_OK); +} + +TEST_HARNESS_MAIN diff --git a/tools/testing/selftests/filesystems/mount_cycle/loop_cycle_test.c b/tools/testing/selftests/filesystems/mount_cycle/loop_cycle_test.c new file mode 100644 index 000000000000..6b4f5304c2e4 --- /dev/null +++ b/tools/testing/selftests/filesystems/mount_cycle/loop_cycle_test.c @@ -0,0 +1,1542 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * A mount that another namespace's rmdir detached, or that went down with + * the detached tree it was in when the tree's last fd was closed, keeps + * its submounts connected, and a connected submount is put by its + * parent's final mntput(). A submount whose filesystem keeps a file open + * on the parent then holds the parent's count above zero for good: + * nothing in userspace refers to either mount any more and nothing can + * release them. A loop device is the simplest such filesystem, its + * backing file sits on the parent. + */ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../wrappers.h" +#include "../../kselftest_harness.h" + +#define IMAGE_SIZE (1440 * 1024) +#define SECTOR 512 + +/* + * The tmpfs of the fixture. A directory of its own from mkdtemp(), so that + * the test needs no writable root directory and two instances don't take + * each other's mounts down. + */ +static char base[64]; + +/* The configfs directory of the gadget, named after the test process. */ +static char gadget[96]; + +/* A path below the base directory. Eight of them can be in use at a time. */ +static const char *at(const char *rel) +{ + static char buf[8][PATH_MAX]; + static unsigned int next; + char *p = buf[next++ % 8]; + + snprintf(p, PATH_MAX, "%s/%s", base, rel); + return p; +} + +/* A path below the gadget's configfs directory. */ +static const char *gat(const char *rel) +{ + static char buf[4][PATH_MAX]; + static unsigned int next; + char *p = buf[next++ % 4]; + + snprintf(p, PATH_MAX, "%s%s", gadget, rel); + return p; +} + +/* The loop devices this process has bound, to release them on every path. */ +#define MAX_LOOPS 4 +static int bound[MAX_LOOPS]; +static int nr_bound; + +/* What a child tells its parent: the loop devices it has bound, -1 for none. */ +struct report { + int n[2]; +}; + +/* A blank FAT12 floppy image: boot sector, two FATs, an empty root directory. */ +static int write_fat12(int fd) +{ + struct stat st; + unsigned char sector[SECTOR] = { + 0xeb, 0x3c, 0x90, 'M', 'S', 'W', 'I', 'N', '4', '.', '1', + [11] = 0x00, 0x02, /* bytes per sector: 512 */ + [13] = 1, /* sectors per cluster */ + [14] = 1, 0, /* reserved sectors */ + [16] = 2, /* FATs */ + [17] = 0xe0, 0x00, /* root directory entries: 224 */ + [19] = 0x40, 0x0b, /* total sectors: 2880 */ + [21] = 0xf0, /* media descriptor */ + [22] = 9, 0, /* sectors per FAT */ + [24] = 18, 0, /* sectors per track */ + [26] = 2, 0, /* heads */ + [38] = 0x29, /* extended boot signature */ + [39] = 0x12, 0x34, 0x56, 0x78, + [43] = 'N', 'O', ' ', 'N', 'A', 'M', 'E', ' ', ' ', ' ', ' ', + [54] = 'F', 'A', 'T', '1', '2', ' ', ' ', ' ', + [510] = 0x55, 0xaa, + }; + unsigned char fat[SECTOR] = { 0xf0, 0xff, 0xff }; + + if (pwrite(fd, sector, SECTOR, 0) != SECTOR) + return -1; + /* the first FAT and the second one, one sector each is enough */ + if (pwrite(fd, fat, SECTOR, 1 * SECTOR) != SECTOR || + pwrite(fd, fat, SECTOR, 10 * SECTOR) != SECTOR) + return -1; + if (fstat(fd, &st) || S_ISBLK(st.st_mode)) + return 0; + return ftruncate(fd, IMAGE_SIZE); +} + +/* + * A blank FAT12 image with 4 KiB sectors and @sectors of them, for a device + * with 4 KiB logical blocks (zram) or for a bigger image than a floppy. + */ +static int write_fat12_4k(int fd, unsigned int sectors) +{ + unsigned int fat_sectors = (sectors * 3 / 2 + 4095) / 4096; + unsigned char sector[4096] = { + 0xeb, 0x3c, 0x90, 'M', 'S', 'W', 'I', 'N', '4', '.', '1', + [11] = 0x00, 0x10, /* bytes per sector: 4096 */ + [13] = 1, /* sectors per cluster */ + [14] = 1, 0, /* reserved sectors */ + [16] = 2, /* FATs */ + [17] = 128, 0, /* root directory entries: one sector */ + [19] = sectors & 0xff, sectors >> 8, + [21] = 0xf8, /* media descriptor */ + [22] = fat_sectors, 0, + [24] = 63, 0, /* sectors per track */ + [26] = 255, 0, /* heads */ + [38] = 0x29, /* extended boot signature */ + [39] = 0x12, 0x34, 0x56, 0x78, + [43] = 'N', 'O', ' ', 'N', 'A', 'M', 'E', ' ', ' ', ' ', ' ', + [54] = 'F', 'A', 'T', '1', '2', ' ', ' ', ' ', + [510] = 0x55, 0xaa, + }; + unsigned char fat[4096] = { 0xf8, 0xff, 0xff }; + struct stat st; + + if (pwrite(fd, sector, sizeof(sector), 0) != sizeof(sector)) + return -1; + if (pwrite(fd, fat, sizeof(fat), 1 * 4096) != sizeof(fat) || + pwrite(fd, fat, sizeof(fat), (1 + fat_sectors) * 4096) != sizeof(fat)) + return -1; + if (fstat(fd, &st) || S_ISBLK(st.st_mode)) + return 0; + return ftruncate(fd, (off_t)sectors * 4096); +} + +#define MINIX_BLOCK 1024 +#define MINIX_BLOCKS 4096 /* a 4 MiB image */ +#define MINIX_INODES 512 +#define MINIX_ITABLE (MINIX_INODES * 32 / MINIX_BLOCK) +#define MINIX_FIRSTDATA (2 + 1 + 1 + MINIX_ITABLE) /* boot, super, imap, zmap, inodes */ + +/* + * A blank minix v1 image, for the holders that need a FIFO or a device + * node on the dying mount, which vfat can't hold. Superblock in block 1, + * one block each for the inode and zone bitmaps, the inode table, and + * the root directory in the first data zone. + */ +static int write_minix(int fd) +{ + struct { + __u16 s_ninodes, s_nzones, s_imap_blocks, s_zmap_blocks; + __u16 s_firstdatazone, s_log_zone_size; + __u32 s_max_size; + __u16 s_magic, s_state; + } sb = { + .s_ninodes = MINIX_INODES, + .s_nzones = MINIX_BLOCKS, + .s_imap_blocks = 1, + .s_zmap_blocks = 1, + .s_firstdatazone = MINIX_FIRSTDATA, + .s_max_size = (7 + 512 + 512 * 512) * MINIX_BLOCK, + .s_magic = MINIX_SUPER_MAGIC, + .s_state = 1, /* MINIX_VALID_FS */ + }; + struct { + __u16 i_mode, i_uid; + __u32 i_size, i_time; + __u8 i_gid, i_nlinks; + __u16 i_zone[9]; + } root = { + .i_mode = S_IFDIR | 0755, + .i_size = 2 * 16, + .i_nlinks = 2, + .i_zone = { MINIX_FIRSTDATA }, + }; + unsigned char imap[MINIX_BLOCK], zmap[MINIX_BLOCK], dir[MINIX_BLOCK] = {}; + int i; + + /* bit 0 is reserved in both maps, the root inode and its zone are in use */ + memset(imap, 0xff, sizeof(imap)); + for (i = 2; i <= MINIX_INODES; i++) + imap[i / 8] &= ~(1 << (i % 8)); + memset(zmap, 0xff, sizeof(zmap)); + for (i = 2; i <= MINIX_BLOCKS - MINIX_FIRSTDATA; i++) + zmap[i / 8] &= ~(1 << (i % 8)); + dir[0] = 1; + dir[2] = '.'; + dir[16] = 1; + dir[18] = '.'; + dir[19] = '.'; + + if (pwrite(fd, &sb, sizeof(sb), 1 * MINIX_BLOCK) != sizeof(sb) || + pwrite(fd, imap, sizeof(imap), 2 * MINIX_BLOCK) != sizeof(imap) || + pwrite(fd, zmap, sizeof(zmap), 3 * MINIX_BLOCK) != sizeof(zmap) || + pwrite(fd, &root, sizeof(root), 4 * MINIX_BLOCK) != sizeof(root) || + pwrite(fd, dir, sizeof(dir), MINIX_FIRSTDATA * MINIX_BLOCK) != sizeof(dir)) + return -1; + return ftruncate(fd, (off_t)MINIX_BLOCKS * MINIX_BLOCK); +} + +static int read_sysfs(const char *path, char *buf, size_t size) +{ + ssize_t n; + int fd; + + fd = open(path, O_RDONLY); + if (fd < 0) + return -1; + n = read(fd, buf, size - 1); + close(fd); + if (n < 0) + return -1; + buf[n] = '\0'; + return 0; +} + +/* Is @dev the mount source of this line of mountinfo? loop1 is not loop10. */ +static bool line_has_source(const char *line, const char *dev) +{ + const char *sep, *src, *end; + + sep = strstr(line, " - "); + if (!sep) + return false; + src = strchr(sep + 3, ' '); /* skip the filesystem type */ + if (!src) + return false; + src++; + end = strchr(src, ' '); + if (!end) + return false; + return (size_t)(end - src) == strlen(dev) && !strncmp(src, dev, end - src); +} + +/* + * Does a mount namespace of a process that /proc shows have a mount of @dev? + * The mounts under test are in no namespace at this point on any kernel, so + * this only checks that the test got as far as it thinks. + */ +static bool mounted_anywhere(const char *dev) +{ + char path[PATH_MAX], line[4096]; + struct dirent *de; + bool found = false; + DIR *proc; + FILE *f; + + proc = opendir("/proc"); + if (!proc) + return false; + while (!found && (de = readdir(proc))) { + if (de->d_name[0] < '0' || de->d_name[0] > '9') + continue; + snprintf(path, sizeof(path), "/proc/%s/mountinfo", de->d_name); + f = fopen(path, "re"); + if (!f) + continue; + while (fgets(line, sizeof(line), f)) { + if (line_has_source(line, dev)) { + found = true; + break; + } + } + fclose(f); + } + closedir(proc); + return found; +} + +/* Wait up to @ms milliseconds for the loop device to give up its backing file. */ +static bool loop_released(const char *sysfs, int ms) +{ + char buf[PATH_MAX]; + + for (; ms > 0; ms -= 100) { + if (read_sysfs(sysfs, buf, sizeof(buf)) < 0) + return errno == ENOENT; + usleep(100000); + } + return read_sysfs(sysfs, buf, sizeof(buf)) < 0 && errno == ENOENT; +} + +/* Tell the loop device to give its file up, now or when its last user is gone. */ +static void loop_clear(int n) +{ + char dev[32]; + int lfd; + + if (n < 0) + return; + snprintf(dev, sizeof(dev), "/dev/loop%d", n); + lfd = open(dev, O_RDWR); + if (lfd < 0) + return; + ioctl(lfd, LOOP_CLR_FD); + close(lfd); +} + +FIXTURE(loop_cycle) { + char dev[32]; /* the loop device the child set up */ + char sysfs[64]; /* its backing_file attribute */ + int loops[MAX_LOOPS]; /* every loop device the case has bound */ + int nr_loops; + bool gadget; /* the gadget's configfs directory exists */ +}; + +static void remember_loop(FIXTURE_DATA(loop_cycle) *self, int n) +{ + if (n >= 0 && self->nr_loops < MAX_LOOPS) + self->loops[self->nr_loops++] = n; +} + +FIXTURE_SETUP(loop_cycle) +{ + if (geteuid() != 0) + SKIP(return, "test requires CAP_SYS_ADMIN"); + if (access("/dev/loop-control", R_OK | W_OK)) + SKIP(return, "test requires loop devices"); + + ASSERT_EQ(unshare(CLONE_NEWNS), 0); + ASSERT_EQ(mount("", "/", NULL, MS_REC | MS_PRIVATE, NULL), 0); + + snprintf(base, sizeof(base), "/tmp/loop_cycle.XXXXXX"); + ASSERT_NE(mkdtemp(base), NULL); + /* a failed assertion in the setup does not run the teardown */ + ASSERT_EQ(mount("tmpfs", base, "tmpfs", 0, NULL), 0) + rmdir(base); + snprintf(gadget, sizeof(gadget), + "/sys/kernel/config/usb_gadget/kselftest_cycle_%d", getpid()); + self->dev[0] = '\0'; + self->nr_loops = 0; + self->gadget = false; +} + +static int mount_configfs(void); +static void gadget_remove(void); + +FIXTURE_TEARDOWN(loop_cycle) +{ + if (self->gadget) + gadget_remove(); + /* whatever a failed assertion has left bound */ + for (int i = 0; i < self->nr_loops; i++) + loop_clear(self->loops[i]); + umount2(base, MNT_DETACH); + rmdir(base); +} + +/* Child exit codes. */ +enum { + CHILD_OK, + CHILD_NS, /* could not set up the namespace or the tmpfs */ + CHILD_IMAGE, /* could not write the image */ + CHILD_LOOP, /* could not set up the loop device */ + CHILD_MOUNT, /* could not mount it (vfat and msdos both refused) */ + CHILD_PIPE, /* the parent went away */ + CHILD_HOLDER, /* could not set the holder up below the mount */ + CHILD_SKIP, /* the kernel lacks what the holder needs */ + CHILD_NOFS, /* the kernel lacks the filesystem of the image */ +}; + +/* Bind the loop device that LOOP_CTL_GET_FREE names to @ifd; the device number. */ +static int loop_bind_free(int ifd) +{ + int cfd, lfd, n; + char dev[32]; + + cfd = open("/dev/loop-control", O_RDWR); + if (cfd < 0) + return -1; + n = ioctl(cfd, LOOP_CTL_GET_FREE); + close(cfd); + if (n < 0) + return -1; + snprintf(dev, sizeof(dev), "/dev/loop%d", n); + lfd = open(dev, O_RDWR); + if (lfd < 0) + return -1; + if (ioctl(lfd, LOOP_SET_FD, ifd)) + n = -1; + close(lfd); + return n; +} + +/* + * Bind a free loop device to the open image @ifd; the device number. The + * device is free when LOOP_CTL_GET_FREE names it and may be somebody else's + * a moment later, so try again when it is busy. + */ +static int loop_bind(int ifd) +{ + int n = -1; + + for (int i = 0; i < 64 && n < 0; i++) { + n = loop_bind_free(ifd); + if (n < 0 && errno != EBUSY) + return -1; + } + if (n >= 0 && nr_bound < MAX_LOOPS) + bound[nr_bound++] = n; + return n; +} + +/* Mount a FAT image, as vfat or as msdos; -1 with ENODEV if the kernel has neither. */ +static int mount_fat(const char *dev, const char *mp) +{ + int err; + + if (!mount(dev, mp, "vfat", 0, NULL)) + return 0; + err = errno; + if (!mount(dev, mp, "msdos", 0, NULL)) + return 0; + if (err != ENODEV) + errno = err; + return -1; +} + +/* + * A child that gives up has to take down what it has set up: nobody else + * knows about it. Unmount, then tell the loop devices to let go. The mounts + * are released after this process is gone and the devices follow them. + */ +static int child_fails(int ret) +{ + umount2(at("vol"), MNT_DETACH); + umount2(at("vol2"), MNT_DETACH); + umount2(at("p"), MNT_DETACH); + umount2(at("img"), MNT_DETACH); + for (int i = 0; i < nr_bound; i++) + loop_clear(bound[i]); + return ret; +} + +/* Write an image to @img, bind a loop device to it and mount that at @mp; the device number. */ +static int loop_mount(const char *img, const char *mp) +{ + char dev[32]; + int ifd, n; + + ifd = open(img, O_RDWR | O_CREAT | O_EXCL, 0600); + if (ifd < 0 || write_fat12(ifd)) + return -CHILD_IMAGE; + n = loop_bind(ifd); + close(ifd); /* the loop device holds the file from now on */ + if (n < 0) + return -CHILD_LOOP; + snprintf(dev, sizeof(dev), "/dev/loop%d", n); + if (mkdir(mp, 0755)) + return -CHILD_MOUNT; + if (mount_fat(dev, mp)) + return errno == ENODEV ? -CHILD_NOFS : -CHILD_MOUNT; + return n; +} + +/* + * Two tmpfs mounts, each carrying the image of the loop mount below the + * other: the loop mount below vol has its image on vol2 and the other way + * round. + */ +static int crossed_child(int to_parent, int from_parent) +{ + struct report r; + char c; + + if (unshare(CLONE_NEWNS) || mount("", "/", NULL, MS_REC | MS_PRIVATE, NULL)) + return CHILD_NS; + if (mkdir(at("vol"), 0755) || mount("tmpfs", at("vol"), "tmpfs", 0, NULL) || + mkdir(at("vol2"), 0755) || mount("tmpfs", at("vol2"), "tmpfs", 0, NULL)) + return child_fails(CHILD_NS); + r.n[0] = loop_mount(at("vol2/img"), at("vol/mnt")); + if (r.n[0] < 0) + return child_fails(-r.n[0]); + r.n[1] = loop_mount(at("vol/img"), at("vol2/mnt")); + if (r.n[1] < 0) + return child_fails(-r.n[1]); + if (write(to_parent, &r, sizeof(r)) != sizeof(r)) + return child_fails(CHILD_PIPE); + if (read(from_parent, &c, 1) != 1) + return child_fails(CHILD_PIPE); + return CHILD_OK; +} + +/* + * In its own mount namespace the child mounts a tmpfs on vol, + * puts a filesystem image on it, binds a loop device to the image and + * mounts that loop device below. The loop device's backing file is a + * reference on the mount the image is on, held by the loop device, held + * by the mounted filesystem, held by the mount below. + */ +static int loop_child(int to_parent, int from_parent) +{ + struct report r = { { -1, -1 } }; + char c; + + if (unshare(CLONE_NEWNS) || mount("", "/", NULL, MS_REC | MS_PRIVATE, NULL)) + return CHILD_NS; + if (mkdir(at("vol"), 0755) || mount("tmpfs", at("vol"), "tmpfs", 0, NULL)) + return child_fails(CHILD_NS); + r.n[0] = loop_mount(at("vol/img"), at("vol/mnt")); + if (r.n[0] < 0) + return child_fails(-r.n[0]); + + if (write(to_parent, &r, sizeof(r)) != sizeof(r)) + return child_fails(CHILD_PIPE); + /* keep the namespace alive while the parent removes the directory */ + if (read(from_parent, &c, 1) != 1) + return child_fails(CHILD_PIPE); + return CHILD_OK; +} + +/* + * The child did not report. Skip if the kernel lacks something, fail with + * what the child said otherwise. A child that a signal killed has failed. + */ +#define CHILD_GAVE_UP(pid) do { \ + int __status; \ + \ + ASSERT_EQ(waitpid(pid, &__status, 0), pid); \ + if (WIFEXITED(__status) && WEXITSTATUS(__status) == CHILD_NOFS) \ + SKIP(return, "test requires the filesystem of the image (FAT or minix)"); \ + if (WIFEXITED(__status) && WEXITSTATUS(__status) == CHILD_SKIP) \ + SKIP(return, "the kernel lacks what this holder needs"); \ + ASSERT_TRUE(false) \ + TH_LOG("child failed to set up: %s %d", \ + WIFEXITED(__status) ? "exit status" : "signal", \ + WIFEXITED(__status) ? WEXITSTATUS(__status) : WTERMSIG(__status)); \ +} while (0) + +/* The child has to leave by itself and with nothing to complain about. */ +#define CHILD_LEFT(pid) do { \ + int __status; \ + \ + ASSERT_EQ(waitpid(pid, &__status, 0), pid); \ + ASSERT_TRUE(WIFEXITED(__status)); \ + ASSERT_EQ(WEXITSTATUS(__status), CHILD_OK); \ +} while (0) + +/* + * An exclusive open of the device fails while a filesystem holds it, and + * the only filesystem that ever did is the one mounted below the dead + * mount. Give a release in flight a moment. Then the device must clear + * right away rather than only be marked for autoclear. + */ +static void assert_loop_released(struct __test_metadata *_metadata, + FIXTURE_DATA(loop_cycle) *self) +{ + int lfd; + + for (int i = 0; i < 20; i++) { + lfd = open(self->dev, O_RDONLY | O_EXCL); + if (lfd >= 0) + break; + usleep(100000); + } + EXPECT_GE(lfd, 0) + TH_LOG("%s is still held by the loop mount below the dead mount: nothing refers to either mount and nothing can release them", + self->dev); + if (lfd >= 0) + close(lfd); + + lfd = open(self->dev, O_RDWR); + ASSERT_GE(lfd, 0); + ASSERT_EQ(ioctl(lfd, LOOP_CLR_FD), 0); + close(lfd); + ASSERT_TRUE(loop_released(self->sysfs, 5000)) + TH_LOG("%s kept its backing file after LOOP_CLR_FD: the filesystem on it is still mounted somewhere nobody can reach", + self->dev); + /* somebody else may bind it from now on, so the teardown leaves it alone */ + for (int i = 0; i < self->nr_loops; i++) { + char dev[32]; + + snprintf(dev, sizeof(dev), "/dev/loop%d", self->loops[i]); + if (!strcmp(dev, self->dev)) + self->loops[i] = -1; + } +} + +/* + * rmdir of vol from here, where it is not a mountpoint, detaches + * the child's tmpfs with the loop mount connected below it. Once the child + * is gone nothing refers to either mount. The loop device must then be + * free to give up its backing file, which only happens when the mounted + * filesystem below the detached tmpfs has been released. + */ +TEST_F(loop_cycle, detached_loop_mount_released) +{ + int to_parent[2], to_child[2]; + struct report r = { { -1, -1 } }; + char buf[PATH_MAX]; + pid_t pid; + + ASSERT_EQ(pipe(to_parent), 0); + ASSERT_EQ(pipe(to_child), 0); + + pid = fork(); + ASSERT_GE(pid, 0); + if (pid == 0) { + close(to_parent[0]); + close(to_child[1]); + _exit(loop_child(to_parent[1], to_child[0])); + } + close(to_parent[1]); + close(to_child[0]); + + if (read(to_parent[0], &r, sizeof(r)) != sizeof(r)) + CHILD_GAVE_UP(pid); + remember_loop(self, r.n[0]); + snprintf(self->dev, sizeof(self->dev), "/dev/loop%d", r.n[0]); + snprintf(self->sysfs, sizeof(self->sysfs), "/sys/block/loop%d/loop/backing_file", r.n[0]); + ASSERT_EQ(read_sysfs(self->sysfs, buf, sizeof(buf)), 0); + ASSERT_NE(strstr(buf, "/vol/img"), NULL); + + /* not a mountpoint in this namespace, so the directory can go */ + ASSERT_EQ(rmdir(at("vol")), 0); + + /* the child leaves: its namespace and every reference it held are gone */ + ASSERT_EQ(write(to_child[1], "", 1), 1); + CHILD_LEFT(pid); + close(to_parent[0]); + close(to_child[1]); + + /* nothing can reach the two mounts any more */ + ASSERT_EQ(access(at("vol"), F_OK), -1); + ASSERT_FALSE(mounted_anywhere(self->dev)); + + assert_loop_released(_metadata, self); +} + +/* + * The same two mounts in a detached tree: a clone of vol from + * open_tree(), the image opened through the clone so that the loop device + * holds the clone, and the loop mount moved below the clone. The last + * close of the tree's fd dissolves the tree with the loop mount left + * connected below the dead clone, and nothing refers to either afterwards. + */ +TEST_F(loop_cycle, dissolved_tree_loop_mount_released) +{ + int tfd, ifd, fsfd, mfd, n; + char buf[PATH_MAX]; + + fsfd = sys_fsopen("vfat", 0); + if (fsfd < 0) + fsfd = sys_fsopen("msdos", 0); + if (fsfd < 0) + SKIP(return, "test requires a FAT filesystem"); + + ASSERT_EQ(mkdir(at("vol"), 0755), 0); + ASSERT_EQ(mount("tmpfs", at("vol"), "tmpfs", 0, NULL), 0); + tfd = sys_open_tree(AT_FDCWD, at("vol"), OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC); + ASSERT_GE(tfd, 0); + + /* the image, opened through the clone: the loop device holds the clone */ + ifd = openat(tfd, "img", O_RDWR | O_CREAT | O_EXCL, 0600); + ASSERT_GE(ifd, 0); + ASSERT_EQ(write_fat12(ifd), 0); + n = loop_bind(ifd); + close(ifd); + ASSERT_GE(n, 0); + remember_loop(self, n); + snprintf(self->dev, sizeof(self->dev), "/dev/loop%d", n); + snprintf(self->sysfs, sizeof(self->sysfs), "/sys/block/loop%d/loop/backing_file", n); + + /* the loop mount, moved below the clone */ + ASSERT_EQ(mkdirat(tfd, "mnt", 0755), 0); + ASSERT_EQ(sys_fsconfig(fsfd, FSCONFIG_SET_STRING, "source", self->dev, 0), 0); + ASSERT_EQ(sys_fsconfig(fsfd, FSCONFIG_CMD_CREATE, NULL, NULL, 0), 0); + mfd = sys_fsmount(fsfd, 0, 0); + ASSERT_GE(mfd, 0); + close(fsfd); + ASSERT_EQ(sys_move_mount(mfd, "", tfd, "mnt", MOVE_MOUNT_F_EMPTY_PATH), 0); + close(mfd); + + ASSERT_EQ(read_sysfs(self->sysfs, buf, sizeof(buf)), 0); + ASSERT_NE(strstr(buf, "img"), NULL); + + /* the last fd of the tree: both mounts die, the loop mount connected */ + close(tfd); + + /* nothing can reach the two mounts any more */ + ASSERT_FALSE(mounted_anywhere(self->dev)); + + assert_loop_released(_metadata, self); +} + +/* + * The cycle in two steps: rmdir of vol leaves the loop mount below it + * connected while its image's mount, vol2, is alive; then rmdir of vol2 + * takes that one with the loop mount whose image is on the dead vol. Each + * dead mount now owns a loop mount whose filesystem pins the other. + */ +TEST_F(loop_cycle, crossed_images_released) +{ + int to_parent[2], to_child[2]; + struct report r = { { -1, -1 } }; + char sysfs[2][64], dev[2][32]; + pid_t pid; + + ASSERT_EQ(pipe(to_parent), 0); + ASSERT_EQ(pipe(to_child), 0); + + pid = fork(); + ASSERT_GE(pid, 0); + if (pid == 0) { + close(to_parent[0]); + close(to_child[1]); + _exit(crossed_child(to_parent[1], to_child[0])); + } + close(to_parent[1]); + close(to_child[0]); + + if (read(to_parent[0], &r, sizeof(r)) != sizeof(r)) + CHILD_GAVE_UP(pid); + for (int i = 0; i < 2; i++) { + remember_loop(self, r.n[i]); + snprintf(dev[i], sizeof(dev[i]), "/dev/loop%d", r.n[i]); + snprintf(sysfs[i], sizeof(sysfs[i]), "/sys/block/loop%d/loop/backing_file", r.n[i]); + } + + /* step one: the mount with the first loop mount below it goes */ + ASSERT_EQ(rmdir(at("vol")), 0); + + /* step two: the other one, with the loop mount whose image is on the first */ + ASSERT_EQ(rmdir(at("vol2")), 0); + + /* the child leaves: its namespace and every reference it held are gone */ + ASSERT_EQ(write(to_child[1], "", 1), 1); + CHILD_LEFT(pid); + close(to_parent[0]); + close(to_child[1]); + + /* nothing can reach the four mounts any more */ + for (int i = 0; i < 2; i++) { + ASSERT_FALSE(mounted_anywhere(dev[i])); + strcpy(self->dev, dev[i]); + strcpy(self->sysfs, sysfs[i]); + assert_loop_released(_metadata, self); + } +} + +/* + * The holders. Each keeps a file or a path on a mount P for as long as + * its own filesystem or device lives, and each has that filesystem or + * device mounted at C below P. Once another namespace's rmdir has + * detached P with C connected below it and the child is gone, P is owned + * by nobody, C by P, and P is kept by whatever the holder still holds. + * + * P is a loop mount so that its death can be observed: the loop device + * gives its backing file up when P's superblock goes. The image sits on + * a tmpfs next to P, not above it, so the loop device's own reference is + * not part of the picture. + */ +enum holder { + HOLDER_AUTOFS, /* a FIFO on P as the daemon's pipe */ + HOLDER_ZRAM, /* a device node on P as the writeback device */ + HOLDER_ECRYPTFS, /* a directory on P as the lower directory */ + HOLDER_BINFMT_MISC, /* an executable on P as an 'F' interpreter */ + HOLDER_FUSE, /* a file on P as a passthrough backing file */ + HOLDER_ZLOOP, /* a directory on P for the zone files */ + HOLDER_GADGET, /* a file on P as a mass storage LUN, over dummy_hcd */ + HOLDER_MD, /* a file on P as an array's bitmap file */ +}; + +#define HOLDER_IMG at("img/p.img") +#define HOLDER_MNT at("p") +#define HOLDER_BELOW at("p/c") + +static int write_file(const char *path, const char *s) +{ + int fd = open(path, O_WRONLY); + ssize_t n; + + if (fd < 0) + return -1; + n = write(fd, s, strlen(s)); + close(fd); + return n == (ssize_t)strlen(s) ? 0 : -1; +} + +/* Bind a free loop device to @img; the device number. */ +static int loop_attach(const char *img) +{ + int ifd, n; + + ifd = open(img, O_RDWR); + if (ifd < 0) + return -1; + n = loop_bind(ifd); + close(ifd); + return n; +} + +static int holder_autofs(void) +{ + char opts[64]; + int pfd; + + if (mkfifo(at("p/pipe"), 0600)) + return CHILD_HOLDER; + pfd = open(at("p/pipe"), O_RDWR); + if (pfd < 0) + return CHILD_HOLDER; + snprintf(opts, sizeof(opts), "fd=%d,minproto=5,maxproto=5", pfd); + if (mount("autofs", HOLDER_BELOW, "autofs", 0, opts)) + return errno == ENODEV ? CHILD_SKIP : CHILD_HOLDER; + close(pfd); /* the mount keeps its own */ + return CHILD_OK; +} + +/* + * zram's writeback device has to be a block device node, and that node has + * to be on P. Point it at a second loop device. + */ +static void zram_reset(void); + +static int holder_zram(void) +{ + char dev[32], buf[64]; + struct stat st; + int fd, n; + + if (access("/sys/block/zram0/backing_dev", W_OK)) + return CHILD_SKIP; + /* somebody else's device, leave it alone */ + if (read_sysfs("/sys/block/zram0/initstate", buf, sizeof(buf)) || buf[0] != '0') + return CHILD_SKIP; + fd = open(at("img/wb.img"), O_RDWR | O_CREAT | O_EXCL, 0600); + if (fd < 0 || ftruncate(fd, IMAGE_SIZE)) + return CHILD_HOLDER; + close(fd); + n = loop_attach(at("img/wb.img")); + if (n < 0) + return CHILD_HOLDER; + /* the minor is not the number of the device when loop has partitions */ + snprintf(dev, sizeof(dev), "/dev/loop%d", n); + if (stat(dev, &st) || mknod(at("p/wbdev"), S_IFBLK | 0600, st.st_rdev)) + return CHILD_HOLDER; + if (write_file("/sys/block/zram0/backing_dev", at("p/wbdev"))) + return CHILD_HOLDER; + snprintf(buf, sizeof(buf), "%d", 4 * 1024 * 1024); + if (write_file("/sys/block/zram0/disksize", buf)) + goto undo; + fd = open("/dev/zram0", O_RDWR); + if (fd < 0) + goto undo; + n = write_fat12_4k(fd, 1024); /* zram has 4 KiB blocks */ + close(fd); /* the reset in undo is refused while the device is open */ + if (n) + goto undo; + if (mount_fat("/dev/zram0", HOLDER_BELOW)) { + n = errno == ENODEV ? CHILD_NOFS : CHILD_HOLDER; + zram_reset(); + return n; + } + return CHILD_OK; +undo: + zram_reset(); + return CHILD_HOLDER; +} + +/* Is @alg in /proc/crypto? A module is listed once a request has loaded it. */ +static bool crypto_has(const char *alg) +{ + char line[256], name[64]; + bool found = false; + FILE *f; + + f = fopen("/proc/crypto", "r"); + if (!f) + return true; /* no way to tell, assume it is */ + while (!found && fgets(line, sizeof(line), f)) + found = sscanf(line, "name : %63s", name) == 1 && !strcmp(name, alg); + fclose(f); + return found; +} + +/* The kernel's auth token layout, which userspace has to match byte for byte. */ +struct ecryptfs_auth_tok { + __u16 version; + __u16 token_type; + __u32 flags; + struct { + __u32 flags, encrypted_key_size, decrypted_key_size; + __u8 encrypted_key[512], decrypted_key[64]; + } session_key; + __u8 reserved[32]; + struct { + __u32 password_bytes; + __s32 hash_algo; + __u32 hash_iterations, session_key_encryption_key_bytes, flags; + __u8 session_key_encryption_key[64]; + __u8 signature[17]; + __u8 salt[8]; + } password; +} __attribute__((packed)); + +#define ECRYPTFS_SIG "0123456789abcdef" + +static int holder_ecryptfs(void) +{ + struct ecryptfs_auth_tok tok = { + .version = 0x0004, + .token_type = 0, /* ECRYPTFS_PASSWORD */ + .password.session_key_encryption_key_bytes = 16, + .password.flags = 0x02, /* ECRYPTFS_SESSION_KEY_ENCRYPTION_KEY_SET */ + .password.signature = ECRYPTFS_SIG, + }; + + /* a session keyring of this child's own, so that the key goes with it */ + if (syscall(__NR_keyctl, KEYCTL_JOIN_SESSION_KEYRING, NULL) < 0) + return errno == ENOSYS ? CHILD_SKIP : CHILD_HOLDER; + if (syscall(__NR_add_key, "user", ECRYPTFS_SIG, &tok, sizeof(tok), + KEY_SPEC_SESSION_KEYRING) < 0) + return CHILD_HOLDER; + if (mkdir(at("p/lower"), 0755)) + return CHILD_HOLDER; + if (mount(at("p/lower"), HOLDER_BELOW, "ecryptfs", 0, + "ecryptfs_sig=" ECRYPTFS_SIG ",ecryptfs_cipher=aes,ecryptfs_key_bytes=16")) { + /* EINVAL without the cipher; a module is loaded by the attempt */ + if (errno == ENODEV || (errno == EINVAL && !crypto_has("aes"))) + return CHILD_SKIP; + return CHILD_HOLDER; + } + return CHILD_OK; +} + +/* + * binfmt_misc instances are per user namespace, so the mount below P is + * made from a new one, which gets a copy of P. + */ +static int holder_binfmt_misc(void) +{ + static const char interp[] = "#!/bin/true\n"; + char reg[PATH_MAX]; + int out; + + /* 'F' opens the interpreter at registration, nothing runs it here */ + out = open(at("p/interp"), O_WRONLY | O_CREAT | O_EXCL, 0755); + if (out < 0 || write(out, interp, sizeof(interp) - 1) != sizeof(interp) - 1) + return CHILD_HOLDER; + close(out); + + /* ENOSPC: user.max_user_namespaces is 0, EPERM: an LSM says no */ + if (unshare(CLONE_NEWUSER | CLONE_NEWNS)) { + if (errno == EINVAL || errno == ENOSPC || errno == EPERM) + return CHILD_SKIP; + return CHILD_HOLDER; + } + if (write_file("/proc/self/setgroups", "deny") || + write_file("/proc/self/uid_map", "0 0 1") || + write_file("/proc/self/gid_map", "0 0 1")) + return CHILD_HOLDER; + if (mount("binfmt_misc", HOLDER_BELOW, "binfmt_misc", 0, NULL)) + return errno == ENODEV ? CHILD_SKIP : CHILD_HOLDER; + snprintf(reg, sizeof(reg), ":cycle:E::cyc::%s:F", at("p/interp")); + if (write_file(at("p/c/register"), reg)) + return CHILD_HOLDER; + return CHILD_OK; +} + +/* + * A fuse server that only ever answers FUSE_INIT, with passthrough on, and + * then registers a file on P as a backing file. The registration alone + * makes the fuse superblock hold the file. + */ +static int holder_fuse(void) +{ + struct fuse_backing_map map = {}; + struct fuse_in_header *ih; + struct fuse_init_out init = { + .major = FUSE_KERNEL_VERSION, + .minor = FUSE_KERNEL_MINOR_VERSION, + .flags = FUSE_INIT_EXT, + .flags2 = FUSE_PASSTHROUGH >> 32, + .max_write = 4096, + .max_stack_depth = 1, + }; + struct fuse_out_header oh = { .len = sizeof(oh) + sizeof(init) }; + struct iovec iov[2] = { { &oh, sizeof(oh) }, { &init, sizeof(init) } }; + char opts[64], buf[8192]; + int ffd, bfd; + ssize_t n; + + bfd = open(at("p/backing"), O_RDWR | O_CREAT | O_EXCL, 0600); + if (bfd < 0) + return CHILD_HOLDER; + ffd = open("/dev/fuse", O_RDWR); + if (ffd < 0) + return CHILD_SKIP; + snprintf(opts, sizeof(opts), "fd=%d,rootmode=40000,user_id=0,group_id=0", ffd); + if (mount("fuse", HOLDER_BELOW, "fuse", 0, opts)) + return errno == ENODEV ? CHILD_SKIP : CHILD_HOLDER; + + n = read(ffd, buf, sizeof(buf)); + ih = (void *)buf; + if (n < (ssize_t)sizeof(*ih) || ih->opcode != FUSE_INIT) + return CHILD_HOLDER; + oh.unique = ih->unique; + if (writev(ffd, iov, 2) != (ssize_t)oh.len) + return CHILD_HOLDER; + + map.fd = bfd; + if (ioctl(ffd, FUSE_DEV_IOC_BACKING_OPEN, &map) < 0) { + /* EOPNOTSUPP: no FUSE_PASSTHROUGH, ENOTTY: no such ioctl */ + if (errno == EPERM || errno == EOPNOTSUPP || errno == ENOTTY) + return CHILD_SKIP; + return CHILD_HOLDER; + } + close(bfd); /* the connection keeps its own */ + return CHILD_OK; /* ffd stays open until the child exits */ +} + +/* Wait for a device node the kernel is about to create. */ +static int open_when_there(const char *dev, int flags, int ms) +{ + int fd; + + for (; ms > 0; ms -= 100) { + fd = open(dev, flags); + if (fd >= 0) + return fd; + usleep(100000); + } + return -1; +} + +/* + * zloop keeps every zone file open. One conventional zone is enough for a + * FAT image, the sequential one stays empty. + */ +static int holder_zloop(void) +{ + char cmd[PATH_MAX]; + int fd, ret = CHILD_HOLDER; + + if (access("/dev/zloop-control", W_OK)) + return CHILD_SKIP; + /* somebody else's device, leave it alone */ + if (!access("/sys/block/zloop0", F_OK)) + return CHILD_SKIP; + if (mkdir(at("p/zl"), 0755) || mkdir(at("p/zl/0"), 0755)) + return CHILD_HOLDER; + snprintf(cmd, sizeof(cmd), + "add id=0,capacity_mb=8,zone_size_mb=4,conv_zones=1,base_dir=%s", + at("p/zl")); + if (write_file("/dev/zloop-control", cmd)) + return CHILD_HOLDER; + fd = open_when_there("/dev/zloop0", O_RDWR, 5000); + if (fd < 0 || write_fat12_4k(fd, 1024)) /* 4 KiB blocks, like P */ + goto undo; + close(fd); + fd = -1; + if (mount_fat("/dev/zloop0", HOLDER_BELOW)) { + ret = errno == ENODEV ? CHILD_NOFS : CHILD_HOLDER; + goto undo; + } + return CHILD_OK; +undo: + if (fd >= 0) + close(fd); + write_file("/dev/zloop-control", "remove id=0"); + return ret; +} + +static int mount_configfs(void) +{ + if (access("/sys/kernel/config", F_OK)) + return -1; + if (mount("configfs", "/sys/kernel/config", "configfs", 0, NULL) && errno != EBUSY) + return -1; + return 0; +} + +/* Take the gadget out of configfs again, in the reverse order of its creation. */ +static void gadget_remove(void) +{ + if (mount_configfs()) + return; + write_file(gat("/functions/mass_storage.0/lun.0/file"), "\n"); + write_file(gat("/UDC"), "\n"); + unlink(gat("/configs/c.1/mass_storage.0")); + rmdir(gat("/configs/c.1/strings/0x409")); + rmdir(gat("/configs/c.1")); + rmdir(gat("/functions/mass_storage.0")); + rmdir(gat("/strings/0x409")); + rmdir(gat("")); +} + +/* The disk usb-storage created for the gadget, by the LUN's inquiry string. */ +static int find_gadget_disk(char *dev, size_t len, int ms) +{ + char path[PATH_MAX], model[64]; + struct dirent *de; + DIR *d; + + for (; ms > 0; ms -= 100, usleep(100000)) { + d = opendir("/sys/block"); + if (!d) + return -1; + while ((de = readdir(d))) { + if (strncmp(de->d_name, "sd", 2)) + continue; + snprintf(path, sizeof(path), "/sys/block/%s/device/model", de->d_name); + if (read_sysfs(path, model, sizeof(model)) || + strncmp(model, "File-Stor Gadget", 16)) + continue; + snprintf(dev, len, "/dev/%s", de->d_name); + closedir(d); + return 0; + } + closedir(d); + } + return -1; +} + +/* + * A mass storage gadget bound to the dummy UDC, so that this kernel is + * also the USB host that sees the LUN as a SCSI disk. sd locks the + * medium on open, which is what keeps the LUN's file from being ejected. + */ +#define GADGET_STEP(x) do { \ + if (x) { \ + fprintf(stderr, "gadget: %s failed: %s\n", #x, strerror(errno)); \ + gadget_remove(); \ + return CHILD_HOLDER; \ + } \ +} while (0) + +static int holder_gadget(void) +{ + char dev[PATH_MAX]; + int fd; + + if (mount_configfs()) + return CHILD_SKIP; + if (access("/sys/kernel/config/usb_gadget", F_OK) || + access("/sys/class/udc/dummy_udc.0", F_OK)) + return CHILD_SKIP; + + fd = open(at("p/lun.img"), O_RDWR | O_CREAT | O_EXCL, 0600); + if (fd < 0 || write_fat12(fd)) + return CHILD_HOLDER; + close(fd); + + GADGET_STEP(mkdir(gat(""), 0755)); + GADGET_STEP(write_file(gat("/idVendor"), "0x1d6b")); + GADGET_STEP(write_file(gat("/idProduct"), "0x0104")); + GADGET_STEP(mkdir(gat("/strings/0x409"), 0755)); + GADGET_STEP(write_file(gat("/strings/0x409/serialnumber"), "1")); + GADGET_STEP(write_file(gat("/strings/0x409/manufacturer"), "kselftest")); + GADGET_STEP(write_file(gat("/strings/0x409/product"), "cycle")); + GADGET_STEP(mkdir(gat("/configs/c.1"), 0755)); + GADGET_STEP(mkdir(gat("/configs/c.1/strings/0x409"), 0755)); + GADGET_STEP(write_file(gat("/configs/c.1/strings/0x409/configuration"), "c")); + GADGET_STEP(mkdir(gat("/functions/mass_storage.0"), 0755)); + GADGET_STEP(write_file(gat("/functions/mass_storage.0/lun.0/removable"), "1")); + GADGET_STEP(write_file(gat("/functions/mass_storage.0/lun.0/file"), at("p/lun.img"))); + GADGET_STEP(symlink(gat("/functions/mass_storage.0"), gat("/configs/c.1/mass_storage.0"))); + /* EBUSY: the controller is somebody else's, leave it alone */ + if (write_file(gat("/UDC"), "dummy_udc.0")) { + fd = errno == EBUSY ? CHILD_SKIP : CHILD_HOLDER; + gadget_remove(); + return fd; + } + + /* usb-storage waits a second before it scans the device */ + if (find_gadget_disk(dev, sizeof(dev), 15000)) { + /* without usb-storage and sd the LUN never shows up as a disk */ + fd = (access("/sys/bus/usb/drivers/usb-storage", F_OK) || + access("/sys/bus/scsi/drivers/sd", F_OK)) ? CHILD_SKIP : CHILD_HOLDER; + gadget_remove(); + return fd; + } + fd = open_when_there(dev, O_RDONLY, 5000); + GADGET_STEP(fd < 0); + close(fd); + if (mount_fat(dev, HOLDER_BELOW)) { + fd = errno == ENODEV ? CHILD_NOFS : CHILD_HOLDER; + gadget_remove(); + return fd; + } + return CHILD_OK; +} + +#define BITMAP_MAGIC 0x6d746962 + +/* + * A RAID1 of one loop device, not persistent, with its bitmap in a file + * on P. The bitmap file needs a superblock the kernel accepts; sync_size, + * uuid and events are not looked at for a non-persistent array. + */ +static int holder_md(void) +{ + struct { + __u32 magic, version; + __u8 uuid[16]; + __u64 events, events_cleared, sync_size; + __u32 state, chunksize, daemon_sleep, write_behind; + } bsb = { + .magic = BITMAP_MAGIC, + .version = 4, + .chunksize = 64 * 1024, + .daemon_sleep = 5, + }; + mdu_array_info_t info = { + .level = 1, + .raid_disks = 1, + .size = 8 * 1024, /* KiB */ + .not_persistent = 1, + }; + mdu_disk_info_t disk = { + .major = 7, + .state = (1 << MD_DISK_ACTIVE) | (1 << MD_DISK_SYNC), + }; + int fd, mdfd, bfd, n, ret = CHILD_HOLDER; + char dev[32], buf[4096]; + struct stat st; + + fd = open(at("img/md.img"), O_RDWR | O_CREAT | O_EXCL, 0600); + if (fd < 0 || ftruncate(fd, 8 * 1024 * 1024)) + return CHILD_HOLDER; + close(fd); + n = loop_attach(at("img/md.img")); + if (n < 0) + return CHILD_HOLDER; + /* the minor is not the number of the device when loop has partitions */ + snprintf(dev, sizeof(dev), "/dev/loop%d", n); + if (stat(dev, &st)) + return CHILD_HOLDER; + disk.major = major(st.st_rdev); + disk.minor = minor(st.st_rdev); + + bfd = open(at("p/bitmap"), O_RDWR | O_CREAT | O_EXCL, 0600); + if (bfd < 0 || write(bfd, &bsb, sizeof(bsb)) != sizeof(bsb) || ftruncate(bfd, 4096)) + return CHILD_HOLDER; + + if (read_sysfs("/proc/mdstat", buf, sizeof(buf))) { + fprintf(stderr, "md: no /proc/mdstat\n"); + return CHILD_SKIP; + } + if (!strstr(buf, "[raid1]")) { + fprintf(stderr, "md: no raid1 personality: %s\n", buf); + return CHILD_SKIP; + } + /* a node of our own: /dev may not have one and is not ours to change */ + if (mknod(at("img/md0"), S_IFBLK | 0600, makedev(MD_MAJOR, 0))) + return CHILD_HOLDER; + mdfd = open(at("img/md0"), O_RDWR); + if (mdfd < 0) + return CHILD_SKIP; + /* somebody else's array: SET_ARRAY_INFO would say EINVAL, not EBUSY */ + if (!read_sysfs("/sys/block/md0/md/array_state", buf, sizeof(buf)) && + strncmp(buf, "clear", 5)) { + fprintf(stderr, "md: md0 is in use: %s", buf); + close(mdfd); + return CHILD_SKIP; + } + /* the bitmap ops are only installed once a bitmap type is chosen */ + write_file("/sys/block/md0/md/bitmap_type", "bitmap"); + /* EBUSY: somebody else's array, leave it alone */ + if (ioctl(mdfd, SET_ARRAY_INFO, &info)) + return errno == EBUSY ? CHILD_SKIP : CHILD_HOLDER; + /* the array is ours from here on and has to be stopped on every path */ + if (ioctl(mdfd, ADD_NEW_DISK, &disk)) + goto undo; + /* attach the bitmap file before the array runs, the way mdadm does */ + if (ioctl(mdfd, SET_BITMAP_FILE, bfd)) { + fprintf(stderr, "md: SET_BITMAP_FILE: %s\n", strerror(errno)); + if (errno == EINVAL) + ret = CHILD_SKIP; + goto undo; + } + close(bfd); /* the array keeps its own */ + if (ioctl(mdfd, RUN_ARRAY, NULL)) { + fprintf(stderr, "md: RUN_ARRAY: %s\n", strerror(errno)); + goto undo; + } + if (write_fat12(mdfd)) + goto undo; + close(mdfd); + if (mount_fat(at("img/md0"), HOLDER_BELOW)) { + ret = errno == ENODEV ? CHILD_NOFS : CHILD_HOLDER; + mdfd = open(at("img/md0"), O_RDWR); + goto undo; + } + return CHILD_OK; +undo: + if (mdfd >= 0) { + ioctl(mdfd, STOP_ARRAY); + close(mdfd); + } + return ret; +} + +/* + * A device keeps its file for as long as it is configured, so once nothing + * below the dead mount is left the device has to be told to let go. With + * the cycle unbroken C's filesystem still holds the device and every one + * of these refuses. + */ +static int holder_let_go_once(enum holder holder) +{ + int fd, ret; + + switch (holder) { + case HOLDER_ZRAM: + return write_file("/sys/block/zram0/reset", "1"); + case HOLDER_ZLOOP: + return write_file("/dev/zloop-control", "remove id=0"); + case HOLDER_GADGET: + if (mount_configfs()) + return -1; + /* a zero-length write is a no-op for configfs; a newline ejects */ + return write_file(gat("/functions/mass_storage.0/lun.0/file"), "\n"); + case HOLDER_MD: + fd = open(at("img/md0"), O_RDONLY); + if (fd < 0) { + /* the child's node went with its tmpfs */ + mknod(at("md0"), S_IFBLK | 0600, makedev(MD_MAJOR, 0)); + fd = open(at("md0"), O_RDONLY); + } + if (fd < 0) + return -1; + ret = ioctl(fd, STOP_ARRAY); + close(fd); + return ret; + default: + return 0; + } +} + +static void zram_reset(void) +{ + holder_let_go_once(HOLDER_ZRAM); +} + +/* + * In its own mount namespace the child puts the image on a tmpfs next to + * P, mounts P from a loop device, and sets the holder up with its own + * filesystem or device mounted at C below P. + */ +static int holder_child(int to_parent, int from_parent, enum holder holder) +{ + bool fifo = holder == HOLDER_AUTOFS || holder == HOLDER_ZRAM; + /* room for a 4 MiB zone file or a floppy image on P */ + bool big = holder == HOLDER_ZLOOP || holder == HOLDER_GADGET; + struct report r = { { -1, -1 } }; + char dev[32], c; + int ifd, n, ret; + + if (unshare(CLONE_NEWNS) || mount("", "/", NULL, MS_REC | MS_PRIVATE, NULL)) + return CHILD_NS; + if (mkdir(at("img"), 0755) || mount("tmpfs", at("img"), "tmpfs", 0, NULL)) + return child_fails(CHILD_NS); + + ifd = open(HOLDER_IMG, O_RDWR | O_CREAT | O_EXCL, 0600); + if (ifd < 0 || (fifo ? write_minix(ifd) : + big ? write_fat12_4k(ifd, 3072) : write_fat12(ifd))) + return child_fails(CHILD_IMAGE); + close(ifd); + n = loop_attach(HOLDER_IMG); + if (n < 0) + return child_fails(CHILD_LOOP); + snprintf(dev, sizeof(dev), "/dev/loop%d", n); + if (mkdir(HOLDER_MNT, 0755)) + return child_fails(CHILD_MOUNT); + if (fifo ? mount(dev, HOLDER_MNT, "minix", 0, NULL) : mount_fat(dev, HOLDER_MNT)) + return child_fails(errno == ENODEV ? CHILD_NOFS : CHILD_MOUNT); + if (mkdir(HOLDER_BELOW, 0755)) + return child_fails(CHILD_MOUNT); + + switch (holder) { + case HOLDER_AUTOFS: + ret = holder_autofs(); + break; + case HOLDER_ZRAM: + ret = holder_zram(); + break; + case HOLDER_ECRYPTFS: + ret = holder_ecryptfs(); + break; + case HOLDER_BINFMT_MISC: + ret = holder_binfmt_misc(); + break; + case HOLDER_FUSE: + ret = holder_fuse(); + break; + case HOLDER_ZLOOP: + ret = holder_zloop(); + break; + case HOLDER_GADGET: + ret = holder_gadget(); + break; + case HOLDER_MD: + ret = holder_md(); + break; + default: + ret = CHILD_HOLDER; + } + if (ret != CHILD_OK) + return child_fails(ret); + + /* P's device first, then the one the holder has bound for itself */ + r.n[0] = n; + if (nr_bound > 1) + r.n[1] = bound[1]; + if (write(to_parent, &r, sizeof(r)) != sizeof(r) || + read(from_parent, &c, 1) != 1) { + /* the parent went away and will not tell the devices to let go */ + umount2(HOLDER_BELOW, MNT_DETACH); + for (int i = 0; i < 50 && holder_let_go_once(holder); i++) + usleep(100000); + if (holder == HOLDER_GADGET) + gadget_remove(); + return child_fails(CHILD_PIPE); + } + return CHILD_OK; +} + +/* + * The release of C's filesystem may still be in flight when the child is + * gone, and a device that is still held refuses to let go, so try for a + * while. With the cycle unbroken it refuses for good. + */ +static void holder_let_go(enum holder holder) +{ + for (int i = 0; i < 50; i++) { + if (!holder_let_go_once(holder)) + return; + usleep(100000); + } +} + +/* + * rmdir of p from here, where it is a plain directory, detaches + * P in the child's namespace with C connected below it. Once the child is + * gone the holder's file on P is the only thing left that refers to P, + * and it is dropped only when C's filesystem dies, which waits for P. + * The loop device backing P tells whether that resolved. + */ +static void holder_cycle(struct __test_metadata *_metadata, + FIXTURE_DATA(loop_cycle) *self, enum holder holder) +{ + struct report r = { { -1, -1 } }; + int to_parent[2], from_parent[2]; + pid_t pid; + + ASSERT_EQ(pipe(to_parent), 0); + ASSERT_EQ(pipe(from_parent), 0); + pid = fork(); + ASSERT_GE(pid, 0); + if (pid == 0) { + close(to_parent[0]); + close(from_parent[1]); + _exit(holder_child(to_parent[1], from_parent[0], holder)); + } + close(to_parent[1]); + close(from_parent[0]); + + if (read(to_parent[0], &r, sizeof(r)) != sizeof(r)) + CHILD_GAVE_UP(pid); + self->gadget = holder == HOLDER_GADGET; + remember_loop(self, r.n[0]); + remember_loop(self, r.n[1]); + snprintf(self->dev, sizeof(self->dev), "/dev/loop%d", r.n[0]); + snprintf(self->sysfs, sizeof(self->sysfs), "/sys/block/loop%d/loop/backing_file", r.n[0]); + + ASSERT_EQ(rmdir(HOLDER_MNT), 0); + ASSERT_EQ(write(from_parent[1], "x", 1), 1); + CHILD_LEFT(pid); + close(to_parent[0]); + close(from_parent[1]); + + holder_let_go(holder); + assert_loop_released(_metadata, self); + /* the teardown takes the gadget and the holder's own loop device away */ +} + +TEST_F(loop_cycle, autofs_pipe_on_dead_mount_released) +{ + holder_cycle(_metadata, self, HOLDER_AUTOFS); +} + +TEST_F(loop_cycle, zram_writeback_node_on_dead_mount_released) +{ + holder_cycle(_metadata, self, HOLDER_ZRAM); +} + +TEST_F(loop_cycle, ecryptfs_lower_on_dead_mount_released) +{ + holder_cycle(_metadata, self, HOLDER_ECRYPTFS); +} + +TEST_F(loop_cycle, binfmt_misc_interpreter_on_dead_mount_released) +{ + holder_cycle(_metadata, self, HOLDER_BINFMT_MISC); +} + +TEST_F(loop_cycle, fuse_backing_file_on_dead_mount_released) +{ + holder_cycle(_metadata, self, HOLDER_FUSE); +} + +TEST_F(loop_cycle, zloop_zone_files_on_dead_mount_released) +{ + holder_cycle(_metadata, self, HOLDER_ZLOOP); +} + +/* up to 15 s for the disk to show up and 12 s for a device that is held */ +TEST_F_TIMEOUT(loop_cycle, mass_storage_lun_on_dead_mount_released, 120) +{ + holder_cycle(_metadata, self, HOLDER_GADGET); +} + +TEST_F(loop_cycle, md_bitmap_file_on_dead_mount_released) +{ + holder_cycle(_metadata, self, HOLDER_MD); +} + +TEST_HARNESS_MAIN diff --git a/tools/testing/selftests/filesystems/mount_cycle/mount_cover_test.c b/tools/testing/selftests/filesystems/mount_cycle/mount_cover_test.c new file mode 100644 index 000000000000..0a0b0c037691 --- /dev/null +++ b/tools/testing/selftests/filesystems/mount_cycle/mount_cover_test.c @@ -0,0 +1,579 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * An unmounted mount that would have stayed attached to its unmounted parent + * leaves a cover behind instead. A lookup on the parent at the mountpoint + * finds knullfs: an empty read-only directory that is shared by every cover + * and every kernel thread, so it can't be watched or locked and nothing can + * be mounted on it. The mount itself is a root from then on. Where a file + * was mounted, the stand-in is an empty regular file of the same instance. + */ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../../kselftest_harness.h" +#include "../readdir_hold.h" + +#ifndef NULL_FS_MAGIC +#define NULL_FS_MAGIC 0x4E554C4C +#endif + +#ifndef TMPFS_MAGIC +#define TMPFS_MAGIC 0x01021994 +#endif + +#ifndef F_SETDELEG +#define F_SETDELEG (F_SETLEASE + 16) +#endif + +/* struct delegation of , which doesn't mix with */ +struct delegation_req { + uint32_t d_flags; + uint16_t d_type; + uint16_t __pad; +}; + +#define DIR_LEN 64 +#define PATH_LEN 128 + +/* what the parent asks the child to do */ +#define CMD_RMDIR 'r' +#define CMD_CLOSE_T 't' +#define CMD_QUIT 'q' + +static int write_file(const char *path, const char *s) +{ + ssize_t n = -1; + int fd; + + fd = open(path, O_WRONLY | O_CLOEXEC); + if (fd >= 0) { + n = write(fd, s, strlen(s)); + close(fd); + } + return n == (ssize_t)strlen(s) ? 0 : -1; +} + +static int touch(const char *path) +{ + int fd; + + fd = open(path, O_WRONLY | O_CREAT | O_EXCL | O_CLOEXEC, 0644); + if (fd < 0) + return -1; + close(fd); + return 0; +} + +/* Become root in a new user namespace with a private mount namespace. */ +static int enter_userns(void) +{ + uid_t uid = getuid(); + gid_t gid = getgid(); + char map[32]; + + prctl(PR_SET_DUMPABLE, 1); + if (unshare(CLONE_NEWUSER | CLONE_NEWNS)) + return -1; + if (write_file("/proc/self/setgroups", "deny") && errno != ENOENT) + return -1; + snprintf(map, sizeof(map), "0 %d 1", uid); + if (write_file("/proc/self/uid_map", map)) + return -1; + snprintf(map, sizeof(map), "0 %d 1", gid); + if (write_file("/proc/self/gid_map", map)) + return -1; + if (setgid(0) || setuid(0)) + return -1; + return mount("", "/", NULL, MS_REC | MS_PRIVATE, NULL); +} + +/* + * The child mounts P on @base/p, C on P/covered and the file P/src on P/file + * in a mount namespace of its own, binds P a second time at @base/q and hands + * out descriptors on P and on C. rmdir() of @base/p from here unmounts P + * together with C and the file bind. P and C are held by the descriptors and + * the unmounted children leave their covers behind. On request the child + * removes C's mountpoint through the bind. + * + * It also mounts T on @base/t with a child on T/covered, binds T at @base/u + * with a second child on the same dentry and hands out descriptors on T and + * U. rmdir() of both from here leaves two covers on one mountpoint. On request + * the child lets go of T. + */ +static int cover_child(const char *base, int to_parent, int from_parent) +{ + char p[PATH_LEN], c[PATH_LEN], q[PATH_LEN], qc[PATH_LEN]; + char f[PATH_LEN], src[PATH_LEN], t[PATH_LEN], u[PATH_LEN], tc[PATH_LEN]; + int fds[4], ret; + char cmd; + + if (unshare(CLONE_NEWNS) || mount("", "/", NULL, MS_REC | MS_PRIVATE, NULL)) + return 1; + snprintf(p, sizeof(p), "%s/p", base); + if (mkdir(p, 0755) || mount("tmpfs", p, "tmpfs", 0, NULL)) + return 2; + snprintf(c, sizeof(c), "%s/p/covered", base); + if (mkdir(c, 0755) || mount("tmpfs", c, "tmpfs", 0, NULL)) + return 3; + snprintf(f, sizeof(f), "%s/p/file", base); + snprintf(src, sizeof(src), "%s/p/src", base); + if (touch(src) || touch(f) || mount(src, f, NULL, MS_BIND, NULL)) + return 4; + snprintf(q, sizeof(q), "%s/q", base); + if (mkdir(q, 0755) || mount(p, q, NULL, MS_BIND, NULL)) + return 5; + snprintf(t, sizeof(t), "%s/t", base); + if (mkdir(t, 0755) || mount("tmpfs", t, "tmpfs", 0, NULL)) + return 6; + snprintf(tc, sizeof(tc), "%s/t/covered", base); + if (mkdir(tc, 0755) || mount("tmpfs", tc, "tmpfs", 0, NULL)) + return 7; + snprintf(u, sizeof(u), "%s/u", base); + if (mkdir(u, 0755) || mount(t, u, NULL, MS_BIND, NULL)) + return 8; + snprintf(tc, sizeof(tc), "%s/u/covered", base); + if (mount("tmpfs", tc, "tmpfs", 0, NULL)) + return 9; + fds[0] = open(p, O_PATH | O_DIRECTORY | O_CLOEXEC); + fds[1] = open(c, O_RDONLY | O_DIRECTORY | O_CLOEXEC); + fds[2] = open(t, O_PATH | O_DIRECTORY | O_CLOEXEC); + fds[3] = open(u, O_PATH | O_DIRECTORY | O_CLOEXEC); + if (fds[0] < 0 || fds[1] < 0 || fds[2] < 0 || fds[3] < 0) + return 10; + if (write(to_parent, fds, sizeof(fds)) != sizeof(fds)) + return 11; + + snprintf(qc, sizeof(qc), "%s/q/covered", base); + for (;;) { + if (read(from_parent, &cmd, 1) != 1) + return 12; + switch (cmd) { + case CMD_RMDIR: + ret = rmdir(qc) ? errno : 0; + if (write(to_parent, &ret, sizeof(ret)) != sizeof(ret)) + return 13; + break; + case CMD_CLOSE_T: + ret = close(fds[2]) ? errno : 0; + if (write(to_parent, &ret, sizeof(ret)) != sizeof(ret)) + return 13; + break; + case CMD_QUIT: + return 0; + default: + return 14; + } + } +} + +FIXTURE(mount_cover) { + char base[DIR_LEN]; + pid_t child; + int to_child; + int from_child; + int dfd; /* P, unmounted, held */ + int cfd; /* C, unmounted, held */ + int fd; /* what a lookup on P finds at C's mountpoint */ + int tfd; /* T, unmounted, held */ + int ufd; /* U, a bind of T, unmounted, held */ + int fan; /* fanotify group from before the user namespace, or -1 */ + struct readdir_hold hold; /* likewise from before, uffd -1 without */ +}; + +/* + * Everything the setup has made. The harness does not run the teardown + * when an assertion of the setup fails, so the setup calls this itself. + */ +static void cover_cleanup(FIXTURE_DATA(mount_cover) *self) +{ + char cmd = CMD_QUIT; + int status; + + if (self->fd >= 0) + close(self->fd); + if (self->cfd >= 0) + close(self->cfd); + if (self->dfd >= 0) + close(self->dfd); + if (self->tfd >= 0) + close(self->tfd); + if (self->ufd >= 0) + close(self->ufd); + if (self->fan >= 0) + close(self->fan); + readdir_hold_destroy(&self->hold); + if (self->child > 0) { + if (write(self->to_child, &cmd, 1) != 1) + kill(self->child, SIGKILL); + waitpid(self->child, &status, 0); + } + if (self->to_child >= 0) + close(self->to_child); + if (self->from_child >= 0) + close(self->from_child); + umount2(self->base, MNT_DETACH); + rmdir(self->base); +} + +static int same_file(int fd1, int fd2) +{ + struct stat st1, st2; + + if (fstat(fd1, &st1) || fstat(fd2, &st2)) + return 0; + return st1.st_dev == st2.st_dev && st1.st_ino == st2.st_ino; +} + +FIXTURE_SETUP(mount_cover) +{ + int to_parent[2], to_child[2], fds[4], pidfd, status; + char dir[PATH_LEN]; + struct statfs sf; + + self->child = 0; + self->to_child = self->from_child = -1; + self->dfd = self->cfd = self->fd = self->tfd = self->ufd = -1; + + /* + * A group for plain events takes CAP_SYS_ADMIN in the initial user + * namespace, so get one before that is gone. An inode mark can be + * added to it from anywhere. + */ + self->fan = fanotify_init(FAN_CLASS_NOTIF | FAN_CLOEXEC, O_RDONLY); + /* same for the userfaultfd that holds a readdir in its fault */ + readdir_hold_init(&self->hold); + + snprintf(self->base, sizeof(self->base), "/tmp/mount_cover.XXXXXX"); + ASSERT_NE(mkdtemp(self->base), NULL); + if (enter_userns()) { + cover_cleanup(self); + SKIP(return, "test requires user namespaces"); + } + ASSERT_EQ(mount("tmpfs", self->base, "tmpfs", 0, NULL), 0) + cover_cleanup(self); + + snprintf(dir, sizeof(dir), "%s/p", self->base); + ASSERT_EQ(pipe(to_parent), 0) + cover_cleanup(self); + ASSERT_EQ(pipe(to_child), 0) + cover_cleanup(self); + self->child = fork(); + ASSERT_GE(self->child, 0) + cover_cleanup(self); + if (self->child == 0) { + close(to_parent[0]); + close(to_child[1]); + _exit(cover_child(self->base, to_parent[1], to_child[0])); + } + close(to_parent[1]); + close(to_child[0]); + self->to_child = to_child[1]; + self->from_child = to_parent[0]; + if (read(self->from_child, fds, sizeof(fds)) != sizeof(fds)) { + pid_t pid = self->child; + + waitpid(pid, &status, 0); + self->child = 0; + cover_cleanup(self); + ASSERT_TRUE(false) + TH_LOG("child failed to set up: %s %d", + WIFEXITED(status) ? "step" : "signal", + WIFEXITED(status) ? WEXITSTATUS(status) : WTERMSIG(status)); + } + + pidfd = syscall(__NR_pidfd_open, self->child, 0); + ASSERT_GE(pidfd, 0) + cover_cleanup(self); + self->dfd = syscall(__NR_pidfd_getfd, pidfd, fds[0], 0); + self->cfd = syscall(__NR_pidfd_getfd, pidfd, fds[1], 0); + self->tfd = syscall(__NR_pidfd_getfd, pidfd, fds[2], 0); + self->ufd = syscall(__NR_pidfd_getfd, pidfd, fds[3], 0); + close(pidfd); + ASSERT_GE(self->dfd, 0) + cover_cleanup(self); + ASSERT_GE(self->cfd, 0) + cover_cleanup(self); + ASSERT_GE(self->tfd, 0) + cover_cleanup(self); + ASSERT_GE(self->ufd, 0) + cover_cleanup(self); + + /* unmounts P and C, C leaves its cover behind */ + ASSERT_EQ(rmdir(dir), 0) + cover_cleanup(self); + /* unmounts T and U with their children, two covers on one mountpoint */ + snprintf(dir, sizeof(dir), "%s/t", self->base); + ASSERT_EQ(rmdir(dir), 0) + cover_cleanup(self); + snprintf(dir, sizeof(dir), "%s/u", self->base); + ASSERT_EQ(rmdir(dir), 0) + cover_cleanup(self); + + self->fd = openat(self->dfd, "covered", O_RDONLY | O_DIRECTORY | O_CLOEXEC); + ASSERT_GE(self->fd, 0) + cover_cleanup(self); + ASSERT_EQ(fstatfs(self->fd, &sf), 0) + cover_cleanup(self); + ASSERT_EQ(sf.f_type, NULL_FS_MAGIC) + cover_cleanup(self); +} + +FIXTURE_TEARDOWN(mount_cover) +{ + cover_cleanup(self); +} + +TEST_F(mount_cover, not_watchable) +{ + char p[PATH_LEN]; + int ifd; + + snprintf(p, sizeof(p), "/proc/self/fd/%d", self->fd); + + ifd = inotify_init1(IN_CLOEXEC); + if (ifd < 0 && errno == ENOSYS) { + TH_LOG("no inotify in this kernel, skipping that part"); + } else { + ASSERT_GE(ifd, 0); + EXPECT_EQ(inotify_add_watch(ifd, p, IN_OPEN), -1); + EXPECT_EQ(errno, EINVAL); + close(ifd); + } + + /* dnotify ends up at the same place */ + EXPECT_EQ(fcntl(self->fd, F_NOTIFY, DN_ACCESS), -1); + EXPECT_EQ(errno, EINVAL); + + if (self->fan < 0) { + TH_LOG("no fanotify group without CAP_SYS_ADMIN in the initial user namespace, skipping the fanotify part"); + return; + } + EXPECT_EQ(fanotify_mark(self->fan, FAN_MARK_ADD, FAN_OPEN, self->fd, NULL), -1); + EXPECT_EQ(errno, EINVAL); +} + +TEST_F(mount_cover, not_lockable) +{ + struct flock fl = { + .l_type = F_RDLCK, + .l_whence = SEEK_SET, + }; + + EXPECT_EQ(flock(self->fd, LOCK_EX | LOCK_NB), -1); + EXPECT_EQ(errno, ENOLCK); + EXPECT_EQ(fcntl(self->fd, F_SETLK, &fl), -1); + EXPECT_EQ(errno, ENOLCK); + EXPECT_EQ(fcntl(self->fd, F_GETLK, &fl), -1); + EXPECT_EQ(errno, ENOLCK); +} + +/* a lease is refused for the owner and for everybody else alike */ +TEST_F(mount_cover, not_leasable) +{ + struct delegation_req deleg = { + .d_type = F_RDLCK, + }; + + EXPECT_EQ(fcntl(self->fd, F_SETLEASE, F_RDLCK), -1); + EXPECT_TRUE(errno == EINVAL || errno == EACCES); + EXPECT_EQ(fcntl(self->fd, F_SETDELEG, &deleg), -1); + EXPECT_TRUE(errno == EINVAL || errno == EACCES); +} + +TEST_F(mount_cover, not_mountable) +{ + char p[PATH_LEN]; + + snprintf(p, sizeof(p), "/proc/self/fd/%d", self->fd); + EXPECT_EQ(mount("tmpfs", p, "tmpfs", 0, NULL), -1); + EXPECT_EQ(errno, ENOENT); +} + +TEST_F(mount_cover, read_only) +{ + struct statvfs sv; + + EXPECT_EQ(mkdirat(self->fd, "x", 0755), -1); + EXPECT_EQ(errno, ENOENT); + EXPECT_EQ(fchmod(self->fd, 0777), -1); + EXPECT_EQ(errno, EROFS); + /* the immutable inode is checked before the read-only mount */ + EXPECT_EQ(faccessat(self->fd, ".", W_OK, 0), -1); + EXPECT_EQ(errno, EPERM); + ASSERT_EQ(fstatvfs(self->fd, &sv), 0); + EXPECT_TRUE(sv.f_flag & ST_RDONLY); +} + +/* the stand-in is a root of its own, ".." stays put */ +TEST_F(mount_cover, island) +{ + int fd; + + fd = openat(self->fd, "..", O_RDONLY | O_DIRECTORY | O_CLOEXEC); + ASSERT_GE(fd, 0); + EXPECT_TRUE(same_file(fd, self->fd)); + close(fd); +} + +/* + * C is alive for as long as the child holds it, but it can't be reached + * through P anymore and it's a root of its own as well. + */ +TEST_F(mount_cover, held_child_detached) +{ + struct statfs sf; + int fd; + + ASSERT_EQ(fstatfs(self->cfd, &sf), 0); + EXPECT_EQ(sf.f_type, TMPFS_MAGIC); + + fd = openat(self->cfd, "..", O_RDONLY | O_DIRECTORY | O_CLOEXEC); + ASSERT_GE(fd, 0); + EXPECT_TRUE(same_file(fd, self->cfd)); + close(fd); + + fd = openat(self->dfd, "covered", O_RDONLY | O_DIRECTORY | O_CLOEXEC); + ASSERT_GE(fd, 0); + ASSERT_EQ(fstatfs(fd, &sf), 0); + EXPECT_EQ(sf.f_type, NULL_FS_MAGIC); + close(fd); +} + +/* + * The cover goes with its mountpoint: once the child has removed C's + * mountpoint through the bind of P, the name is gone from P as well. + * What was opened through the cover before stays open. + */ +TEST_F(mount_cover, cover_goes_with_mountpoint) +{ + char cmd = CMD_RMDIR; + struct statfs sf; + int ret; + + ASSERT_EQ(write(self->to_child, &cmd, 1), 1); + ASSERT_EQ(read(self->from_child, &ret, sizeof(ret)), sizeof(ret)); + ASSERT_EQ(ret, 0); + + EXPECT_EQ(openat(self->dfd, "covered", O_RDONLY | O_DIRECTORY | O_CLOEXEC), -1); + EXPECT_EQ(errno, ENOENT); + ASSERT_EQ(fstatfs(self->fd, &sf), 0); + EXPECT_EQ(sf.f_type, NULL_FS_MAGIC); +} + +/* + * A readdir of the stand-in holds nothing that others wait for: one holder + * sticks in the page fault of its buffer, and a create and a lookup of + * another come back meanwhile. + */ +TEST_F(mount_cover, readdir_blocks_nobody) +{ + bool stalled; + + if (self->hold.uffd < 0) + SKIP(return, "test requires userfaultfd"); + ASSERT_EQ(readdir_hold_check(&self->hold, self->fd, &stalled), 0); + EXPECT_FALSE(stalled); +} + +/* where a file was mounted, the stand-in is an empty regular file */ +TEST_F(mount_cover, file_stand_in) +{ + char src[PATH_LEN], p[PATH_LEN]; + struct statfs sf; + struct stat st; + char c; + int fd; + + fd = openat(self->dfd, "file", O_RDONLY | O_CLOEXEC); + ASSERT_GE(fd, 0); + ASSERT_EQ(fstat(fd, &st), 0); + EXPECT_TRUE(S_ISREG(st.st_mode)); + ASSERT_EQ(fstatfs(fd, &sf), 0); + EXPECT_EQ(sf.f_type, NULL_FS_MAGIC); + /* the two stand-ins are two inodes */ + EXPECT_FALSE(same_file(fd, self->fd)); + EXPECT_EQ(read(fd, &c, 1), 0); + EXPECT_EQ(flock(fd, LOCK_EX | LOCK_NB), -1); + EXPECT_EQ(errno, ENOLCK); + + snprintf(src, sizeof(src), "%s/src", self->base); + ASSERT_EQ(touch(src), 0); + snprintf(p, sizeof(p), "/proc/self/fd/%d", fd); + EXPECT_EQ(mount(src, p, NULL, MS_BIND, NULL), -1); + EXPECT_EQ(errno, ENOENT); + close(fd); + + /* writes and truncates go nowhere, like /dev/null */ + fd = openat(self->dfd, "file", O_WRONLY | O_TRUNC | O_CLOEXEC); + ASSERT_GE(fd, 0); + EXPECT_EQ(write(fd, "xy", 2), 2); + EXPECT_EQ(ftruncate(fd, 4), 0); + ASSERT_EQ(fstat(fd, &st), 0); + EXPECT_EQ(st.st_size, 0); + /* the shared inode keeps its identity */ + EXPECT_EQ(fchmod(fd, 0600), -1); + EXPECT_EQ(errno, EPERM); + close(fd); + fd = openat(self->dfd, "file", O_RDONLY | O_CLOEXEC); + ASSERT_GE(fd, 0); + EXPECT_EQ(read(fd, &c, 1), 0); + close(fd); + + EXPECT_EQ(openat(self->dfd, "file", O_RDONLY | O_DIRECTORY | O_CLOEXEC), -1); + EXPECT_EQ(errno, ENOTDIR); +} + +/* + * Two unmounted parents left covers on the same dentry. A lookup on + * either finds a stand-in, and U's cover stays when T goes. + */ +TEST_F(mount_cover, shared_mountpoint) +{ + char cmd = CMD_CLOSE_T; + struct statfs sf; + int fd, ret; + + fd = openat(self->tfd, "covered", O_RDONLY | O_DIRECTORY | O_CLOEXEC); + ASSERT_GE(fd, 0); + ASSERT_EQ(fstatfs(fd, &sf), 0); + EXPECT_EQ(sf.f_type, NULL_FS_MAGIC); + close(fd); + + fd = openat(self->ufd, "covered", O_RDONLY | O_DIRECTORY | O_CLOEXEC); + ASSERT_GE(fd, 0); + ASSERT_EQ(fstatfs(fd, &sf), 0); + EXPECT_EQ(sf.f_type, NULL_FS_MAGIC); + close(fd); + + /* the last references to T go, with T its cover */ + ASSERT_EQ(write(self->to_child, &cmd, 1), 1); + ASSERT_EQ(read(self->from_child, &ret, sizeof(ret)), sizeof(ret)); + ASSERT_EQ(ret, 0); + close(self->tfd); + self->tfd = -1; + + fd = openat(self->ufd, "covered", O_RDONLY | O_DIRECTORY | O_CLOEXEC); + ASSERT_GE(fd, 0); + ASSERT_EQ(fstatfs(fd, &sf), 0); + EXPECT_EQ(sf.f_type, NULL_FS_MAGIC); + close(fd); +} + +TEST_HARNESS_MAIN diff --git a/tools/testing/selftests/filesystems/mount_cycle/settings b/tools/testing/selftests/filesystems/mount_cycle/settings new file mode 100644 index 000000000000..694d70710ff0 --- /dev/null +++ b/tools/testing/selftests/filesystems/mount_cycle/settings @@ -0,0 +1 @@ +timeout=300 From 3d399224425573875b6f6f1181bd8cf9a28cc7d1 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Sun, 4 Oct 2026 13:02:20 +0200 Subject: [PATCH 262/367] namespace: simplify disconnect_mount() Rename disconnect_mount() and simplify it. Signed-off-by: Christian Brauner (Amutable) --- fs/namespace.c | 26 +++++--------------------- 1 file changed, 5 insertions(+), 21 deletions(-) diff --git a/fs/namespace.c b/fs/namespace.c index 9ebcb8b35c48..3ed3766f3a18 100644 --- a/fs/namespace.c +++ b/fs/namespace.c @@ -1815,30 +1815,14 @@ enum umount_tree_flags { UMOUNT_COVER = 4, }; -static bool disconnect_mount(struct mount *mnt, enum umount_tree_flags how) +/* Do we need to leave the mountpoint on the parent covered? */ +static bool needs_cover(struct mount *mnt, enum umount_tree_flags how) { - /* Leaving mounts connected is only valid for lazy umounts */ if (how & UMOUNT_SYNC) - return true; - - /* A mount without a parent has nothing to be connected to */ - if (!mnt_has_parent(mnt)) - return true; - - /* Only an unmounted parent has a mountpoint to keep covered */ + return false; if (!(mnt->mnt_parent->mnt.mnt_flags & MNT_UMOUNT)) - return true; - - /* Has it been requested that the mountpoint stays covered? */ - if (how & UMOUNT_COVER) return false; - - /* Is the mount locked such that its mountpoint must stay covered? */ - if (IS_MNT_LOCKED(mnt)) - return false; - - /* By default disconnect the mount */ - return true; + return (how & UMOUNT_COVER) || IS_MNT_LOCKED(mnt); } /* @@ -1889,7 +1873,7 @@ static void umount_tree(struct mount *mnt, enum umount_tree_flags how) p->mnt.mnt_flags |= MNT_SYNC_UMOUNT; if (mnt_has_parent(p)) { - if (!disconnect_mount(p, how)) + if (needs_cover(p, how)) leave_cover(p); umount_mnt(p); } From 8bc60db48e8cba35488bbd6e1f94aa990afa275a Mon Sep 17 00:00:00 2001 From: Daehyeon Ko <4ncienth@gmail.com> Date: Fri, 2 Oct 2026 12:30:58 +0900 Subject: [PATCH 263/367] ip6_gre: remove obsolete ERSPAN PMTU update A tc tunnel_key action can attach a METADATA_IP_TUNNEL dst to an skb, and mirred can redirect it to a native ip6erspan device. The legacy PMTU update in ip6erspan_tunnel_xmit() treats that metadata dst as a route. Since it has no output device, dst_dev(dst)->mtu dereferences NULL. On v7.2 with KASAN, an initial UID/GID 65534 process with CapEff 0 used self-created user and network namespaces to trigger: KASAN: null-ptr-deref RIP: ip6erspan_tunnel_xmit+0x10fc/0x2cc0 Kernel panic - not syncing: Fatal exception in interrupt The update is obsolete. Commit fe1a4ca0a2b7 ("ip6_gre: process toobig in a better way") changed IPv6 GRE Too Big handling to update the underlay route and removed the equivalent block from __gre6_xmit(). ip6_tnl_xmit() then propagates the underlay PMTU to the overlay route. Remove the stale ERSPAN copy and its now-unused dst variable. This eliminates the metadata-dst dereference and keeps ERSPAN aligned with IPv6 GRE PMTU handling. Fixes: 5a963eb61b7c ("ip6_gre: Add ERSPAN native tunnel support") Cc: stable@vger.kernel.org Suggested-by: Ido Schimmel Signed-off-by: Daehyeon Ko <4ncienth@gmail.com> Reviewed-by: Ido Schimmel Link: https://patch.msgid.link/20261002033058.358137-1-4ncienth@gmail.com Signed-off-by: Jakub Kicinski --- net/ipv6/ip6_gre.c | 7 ------- 1 file changed, 7 deletions(-) diff --git a/net/ipv6/ip6_gre.c b/net/ipv6/ip6_gre.c index e61cb10b50dc..04f7c70b7320 100644 --- a/net/ipv6/ip6_gre.c +++ b/net/ipv6/ip6_gre.c @@ -923,7 +923,6 @@ static netdev_tx_t ip6erspan_tunnel_xmit(struct sk_buff *skb, { struct ip_tunnel_info *tun_info = NULL; struct ip6_tnl *t = netdev_priv(dev); - struct dst_entry *dst = skb_dst(skb); IP_TUNNEL_DECLARE_FLAGS(flags) = { }; bool truncate = false; int encap_limit = -1; @@ -1058,12 +1057,6 @@ static netdev_tx_t ip6erspan_tunnel_xmit(struct sk_buff *skb, gre_build_header(skb, 8, flags, proto, 0, htonl(atomic_fetch_inc(&t->o_seqno))); - /* TooBig packet may have updated dst->dev's mtu */ - if (!t->parms.collect_md && dst) { - mtu = READ_ONCE(dst_dev(dst)->mtu); - if (dst_mtu(dst) > mtu) - dst->ops->update_pmtu(dst, NULL, skb, mtu, false); - } err = ip6_tnl_xmit(skb, dev, dsfield, &fl6, encap_limit, &mtu, NEXTHDR_GRE); if (err != 0) { From a463f55c791b96cfd12df8b10ea737d45c80bb18 Mon Sep 17 00:00:00 2001 From: Daehyeon Ko <4ncienth@gmail.com> Date: Fri, 2 Oct 2026 00:13:10 +0900 Subject: [PATCH 264/367] vsock/virtio: account only unread bytes in read_skb() After a partial stream receive, rx_bytes tracks the unread suffix while buf_used and peer credit still cover the whole packet. virtio_transport_read_skb() dequeues that packet but passes pkt_len for both counters, underflowing rx_bytes. On a connected CID_LOCAL stream with an SK_SKB verdict, a 40-byte read from a 100-byte packet made SIOCINQ report -40, an empty recv return ELOOP, and poll report the empty socket readable. After verdict detach, receiving 40 bytes wrapped the counter to zero and hid those queued bytes until one more byte arrived. The fixed run kept the counter balanced and exposed all queued data immediately. This was found during an LLM-assisted manual source audit of VSOCK receive accounting while re-evaluating virtio_transport_read_skb() after CVE-2024-50169. Subtract only skb->len minus the VSOCK offset from rx_bytes. Retain the full packet length for buf_used and peer credit. Fixes: 45ca7e9f0730 ("vsock/virtio: fix `rx_bytes` accounting for stream sockets") Cc: stable@vger.kernel.org Reviewed-by: Luigi Leonardi Signed-off-by: Daehyeon Ko <4ncienth@gmail.com> Link: https://patch.msgid.link/20261001151310.4101006-1-4ncienth@gmail.com Signed-off-by: Jakub Kicinski --- net/vmw_vsock/virtio_transport_common.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/net/vmw_vsock/virtio_transport_common.c b/net/vmw_vsock/virtio_transport_common.c index f225f53ed4ba..1e762a480df4 100644 --- a/net/vmw_vsock/virtio_transport_common.c +++ b/net/vmw_vsock/virtio_transport_common.c @@ -1927,6 +1927,7 @@ int virtio_transport_read_skb(struct vsock_sock *vsk, skb_read_actor_t recv_acto struct sock *sk = sk_vsock(vsk); struct virtio_vsock_hdr *hdr; struct sk_buff *skb; + u32 bytes_read; u32 pkt_len; int off = 0; int err; @@ -1946,7 +1947,8 @@ int virtio_transport_read_skb(struct vsock_sock *vsk, skb_read_actor_t recv_acto vvs->msg_count--; pkt_len = le32_to_cpu(hdr->len); - virtio_transport_dec_rx_pkt(vvs, pkt_len, pkt_len); + bytes_read = skb->len - VIRTIO_VSOCK_SKB_CB(skb)->offset; + virtio_transport_dec_rx_pkt(vvs, bytes_read, pkt_len); spin_unlock_bh(&vvs->rx_lock); virtio_transport_send_credit_update(vsk); From 6619e01855e65ef35723685a557e3eb106f4ecf5 Mon Sep 17 00:00:00 2001 From: Daehyeon Ko <4ncienth@gmail.com> Date: Fri, 2 Oct 2026 13:50:58 +0900 Subject: [PATCH 265/367] ipv4: stop PMTU walk when nexthop group shrinks Commit 7d3f3b4367f3 ("net: ipv4: Cache pmtu for all packet paths if multipath enabled") made __ip_rt_update_pmtu() update every path. For nexthop objects, fib_info_num_path() and fib_info_nhc() independently load nh->nh_grp. RCU protects each group's lifetime but does not make the loads observe the same group. If replacement shrinks the group after the loop accepts an index, fib_info_nhc() returns NULL and update_or_create_fnhe() dereferences it. A deterministic probe only widened the existing window between the real operations. On v7.2, an RTM_NEWNEXTHOP replacement published a one-member group after the PMTU reader accepted index 1 from a two-member group, producing: KASAN: null-ptr-deref in range [0x0000000000000000-0x0000000000000007] RIP: update_or_create_fnhe+0x45/0x15b0 Stop when the indexed path is absent. Groups are dense, so no later index exists in that snapshot. The same real-writer window completed 114,423 PMTU iterations without an oops with this check. A reproducer is available on request. Replacement requires CAP_NET_ADMIN in the network namespace. Where unprivileged user namespaces are permitted, a local user can obtain it in a new user and network namespace. Fixes: 7d3f3b4367f3 ("net: ipv4: Cache pmtu for all packet paths if multipath enabled") Cc: stable@vger.kernel.org Signed-off-by: Daehyeon Ko <4ncienth@gmail.com> Reviewed-by: Ido Schimmel Link: https://patch.msgid.link/4fb87ed994dfc067178fc845cdd8bc9b5446b039.1790915964.git.4ncienth@gmail.com Signed-off-by: Jakub Kicinski --- net/ipv4/route.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/net/ipv4/route.c b/net/ipv4/route.c index 37674d76f90f..5f2197874beb 100644 --- a/net/ipv4/route.c +++ b/net/ipv4/route.c @@ -1082,6 +1082,8 @@ static void __ip_rt_update_pmtu(struct rtable *rt, struct flowi4 *fl4, u32 mtu) for (nhsel = 0; nhsel < fib_info_num_path(res.fi); nhsel++) { nhc = fib_info_nhc(res.fi, nhsel); + if (!nhc) + break; update_or_create_fnhe(nhc, fl4->daddr, 0, mtu, lock, jiffies + net->ipv4.ip_rt_mtu_expires); } From 4f4afc07dcb2edd4f9afa36595373f8cdb9b58c2 Mon Sep 17 00:00:00 2001 From: Daehyeon Ko <4ncienth@gmail.com> Date: Fri, 2 Oct 2026 13:50:59 +0900 Subject: [PATCH 266/367] ipv4: stop exception dump when nexthop group shrinks Commit 4ce5dc9316de ("inet: switch inet_dump_fib() to RCU protection") allowed IPv4 FIB dumps to run concurrently with nexthop replacement. fib_dump_info_fnhe() uses fib_info_num_path() as the loop bound, then fib_info_nhc() independently reloads nh->nh_grp. If RTM_NEWNEXTHOP replaces a group with fewer paths between those loads, fib_info_nhc() returns NULL and the dump dereferences it through nhc_flags. Stop the walk when the indexed path is absent. Nexthop groups are dense, so the current snapshot has no later path. Fixes: 4ce5dc9316de ("inet: switch inet_dump_fib() to RCU protection") Reported-by: Ido Schimmel Link: https://lore.kernel.org/netdev/20261001170513.GA1657889@shredder/ Cc: stable@vger.kernel.org Signed-off-by: Daehyeon Ko <4ncienth@gmail.com> Reviewed-by: Ido Schimmel Link: https://patch.msgid.link/5bb7bd97411eb9db454464bf449107395e760f26.1790915964.git.4ncienth@gmail.com Signed-off-by: Jakub Kicinski --- net/ipv4/route.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/net/ipv4/route.c b/net/ipv4/route.c index 5f2197874beb..268d8821f7e0 100644 --- a/net/ipv4/route.c +++ b/net/ipv4/route.c @@ -3170,6 +3170,9 @@ int fib_dump_info_fnhe(struct sk_buff *skb, struct netlink_callback *cb, struct fnhe_hash_bucket *bucket; int err; + if (!nhc) + break; + if (nhc->nhc_flags & RTNH_F_DEAD) continue; From d3daa33c7af92e38f2274bbd2576f58bfbaf89e6 Mon Sep 17 00:00:00 2001 From: Daehyeon Ko <4ncienth@gmail.com> Date: Fri, 2 Oct 2026 13:51:00 +0900 Subject: [PATCH 267/367] ipv4: stop route notification sizing when nexthop group shrinks Commit 680aea08e78c ("net: ipv4: Emit notification when fib hardware flags are changed") added an RCU-only fib_nlmsg_size() call for asynchronous hardware flag notifications. fib_nlmsg_size() checks fib_info_num_path() before each iteration, then fib_info_nhc() independently reloads nh->nh_grp. If RTM_NEWNEXTHOP replaces a group with fewer paths between those loads, fib_info_nhc() returns NULL and fib_nexthop_nlmsg_size() dereferences it. Stop sizing when the indexed path is absent. Nexthop groups are dense, so the current snapshot has no later path. Fixes: 680aea08e78c ("net: ipv4: Emit notification when fib hardware flags are changed") Cc: stable@vger.kernel.org Signed-off-by: Daehyeon Ko <4ncienth@gmail.com> Reviewed-by: Ido Schimmel Link: https://patch.msgid.link/1054fa46165bba1b473782fa41367804aa908e86.1790915964.git.4ncienth@gmail.com Signed-off-by: Jakub Kicinski --- net/ipv4/fib_semantics.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/net/ipv4/fib_semantics.c b/net/ipv4/fib_semantics.c index 5c9021ea3a79..47469b775808 100644 --- a/net/ipv4/fib_semantics.c +++ b/net/ipv4/fib_semantics.c @@ -542,6 +542,9 @@ size_t fib_nlmsg_size(struct fib_info *fi) struct fib_nh_common *nhc = fib_info_nhc(fi, i); size_t nhsize; + if (!nhc) + break; + nhsize = fib_nexthop_nlmsg_size(nhc, nhs != 1); if (nhs != 1) From 838bb60dc21553e32df90f2f3bac5b905376e7aa Mon Sep 17 00:00:00 2001 From: "Lorenzo Stoakes (ARM)" Date: Wed, 30 Sep 2026 19:47:09 +0100 Subject: [PATCH 268/367] mm/mremap: fix locked_vm leak from MREMAP_DONTUNMAP self-merge Patch series "mm/mremap: fix two issues with MREMAP_DONTUNMAP", v2. The MREMAP_DONTUNMAP feature is highly unusual in that it permits mremap() operations that keep the original VMA in place. Historically this has led to a lot of bugs where non-obvious interactions occur between existing mremap() operations and the original VMA. Commit 397432cab17b ("mm/mremap: account mm->locked_vm correctly for MREMAP_DONTUNMAP") fixed an accidentally introduced bug around mm->locked_vm accounting, but this wasn't the only issue. And thus history repeats itself, as it turns out that mm->locked_vm accounting is broken by MREMAP_DONTUNMAP yet again by two further cases, and has been broken ever since the feature was introduced. Both relate to the fact that VMA_LOCKED_BIT is cleared on the source VMA (it has to be as all page tables are moved): 1. If an unfaulted VMA_LOCKONFAULT_BIT anonymous VMA self-merges it clears the VMA_LOCKED_BIT flag and permanently leaks mm->locked_vm pages. 2. If a partial mremap() is performed on a locked VMA there is a leak equal to the number of pages not copied. (Both for MREMAP_DONTUNMAP operations only) Both issues can be fixed by treating the source range as distinct from the destination range, which is the definition of what MREMAP_DONTUNMAP does so is appropriate. In case 1, simply disallow the self-merge, keeping adjacent source and destination VMAs distinct. In case 2, split the source range ahead of time if the VMA is mlock()'d, so accounting is always correct. Both changes were tested locally and confirmed to fix the issues. For the purposes of a backport, the fixes are kept distinct, a follow-up series can add self-tests. This patch (of 2): The MREMAP_DONTUNMAP feature is highly unusual in that it permits mremap() operations that keep the original VMA in place. Historically this has led to a lot of bugs where non-obvious interactions occur between existing mremap() operations and the original VMA. Fix another of these - self-merge. Self-merge occurs when a VMA is moved in front of or behind itself and the attributes of the VMA permit such a merge. Practically this can only happen for unfaulted anonymous VMAs due to the page offset equality requirement for merge: |------------| | | | v |...........||-----------||...........| | || unfaulted || | |...........||-----------||...........| ^ | | | |------------| This becomes problematic if the VMA is configured by the user to mlock-on-fault, i.e. the VMA_LOCKED_BIT, VMA_LOCKONFAULT_BIT VMA flags are set. MREMAP_DONTUNMAP clears mlock flags for the source VMA and maintains them for the destination VMA. Self-merge makes this impossible (there is only one VMA) and incorrectly clears the destination VMA's mlock flags. This causes a leak in mm->locked_vm as clearing this flag does not decrement the counter and the VMA no longer has VMA_LOCKED_BIT set so it is not decremented on unmap. Resolve this by simply disallowing a self-merge in this case - the source and destination VMAs are kept distinct and then are able to have distinct mlock() flags. Update dontunmap_complete() to make the now-redundant self-merge check a VM_WARN_ON_ONCE() instead to guard against future regressions. Also update the VMA userland tests to reflect the change. Link: https://lore.kernel.org/20260930-fix-dontunmap-partial-self-merge-v2-0-f388985a0f0a@kernel.org Link: https://lore.kernel.org/20260930-fix-dontunmap-partial-self-merge-v2-1-f388985a0f0a@kernel.org Fixes: e346b3813067 ("mm/mremap: add MREMAP_DONTUNMAP to mremap()") Signed-off-by: Lorenzo Stoakes (ARM) Signed-off-by: Andrew Morton Reviewed-by: Pedro Falcato Acked-by: Kiryl Shutsemau (Meta) Reviewed-by: Jose A. Perez de Azpillaga Tested-by: Anirudh Srinivasan Cc: Liam R. Howlett Cc: Vlastimil Babka Cc: Jann Horn Cc: Brian Geffon Cc: Minchan Kim Cc: --- mm/mremap.c | 8 ++++++-- mm/vma.c | 17 ++++++++++++++++- mm/vma.h | 2 +- tools/testing/vma/tests/vma.c | 10 +++++----- 4 files changed, 28 insertions(+), 9 deletions(-) diff --git a/mm/mremap.c b/mm/mremap.c index 7c368440fafe..058dd66fe1d7 100644 --- a/mm/mremap.c +++ b/mm/mremap.c @@ -1275,7 +1275,8 @@ static int copy_vma_and_data(struct vma_remap_struct *vrm, PAGETABLE_MOVE(pmc, NULL, NULL, vrm->addr, vrm->new_addr, vrm->old_len); new_vma = copy_vma(&vma, vrm->new_addr, vrm->new_len, new_pgoff, - new_anon_pgoff, &pmc.need_rmap_locks); + new_anon_pgoff, &pmc.need_rmap_locks, + vrm->flags & MREMAP_DONTUNMAP); if (!new_vma) { vrm_uncharge(vrm); *new_vma_ptr = NULL; @@ -1335,6 +1336,9 @@ static void dontunmap_complete(struct vma_remap_struct *vrm, unsigned long old_start = vma->vm_start; unsigned long old_end = vma->vm_end; + /* Self-merge is disallowed. */ + VM_WARN_ON_ONCE(new_vma == vma); + /* We always clear VMA_LOCKED[ONFAULT]_BIT on the old VMA. */ vma_clear_flags_mask(vma, VMA_LOCKED_MASK); @@ -1342,7 +1346,7 @@ static void dontunmap_complete(struct vma_remap_struct *vrm, * anon_vma links of the old vma is no longer needed after its page * table has been moved. */ - if (new_vma != vma && start == old_start && end == old_end) { + if (start == old_start && end == old_end) { const pgoff_t pgoff_unfaulted = vma->vm_start >> PAGE_SHIFT; unlink_anon_vmas(vma); diff --git a/mm/vma.c b/mm/vma.c index 6cde67883fb0..d8308c319daa 100644 --- a/mm/vma.c +++ b/mm/vma.c @@ -1943,7 +1943,7 @@ static int vma_link(struct mm_struct *mm, struct vm_area_struct *vma) */ struct vm_area_struct *copy_vma(struct vm_area_struct **vmap, unsigned long addr, unsigned long len, pgoff_t pgoff, - pgoff_t anon_pgoff, bool *need_rmap_locks) + pgoff_t anon_pgoff, bool *need_rmap_locks, bool keep_source) { struct vm_area_struct *vma = *vmap; unsigned long old_vma_start = vma->vm_start; @@ -1981,6 +1981,21 @@ struct vm_area_struct *copy_vma(struct vm_area_struct **vmap, vmg.pgoff = pgoff; vmg.anon_pgoff = anon_pgoff; vmg.next = vma_iter_next_rewind(&vmi, NULL); + + /* + * If the original VMA is kept (MREMAP_DONTUNMAP), the source and + * destination VMA must be treated distinctly. + * + * A merge violates this, so in this case disallow a self-merge. + */ + if (can_self_merge && keep_source) { + if (vmg.prev == vma) + vmg.prev = NULL; + if (vmg.next == vma) + vmg.next = NULL; + can_self_merge = false; + } + new_vma = vma_merge_copied_range(&vmg); if (new_vma) { diff --git a/mm/vma.h b/mm/vma.h index 024fabe63560..39514fac72fc 100644 --- a/mm/vma.h +++ b/mm/vma.h @@ -535,7 +535,7 @@ void unlink_file_vma_batch_add(struct unlink_vma_file_batch *vb, struct vm_area_struct *copy_vma(struct vm_area_struct **vmap, unsigned long addr, unsigned long len, pgoff_t pgoff, - pgoff_t anon_pgoff, bool *need_rmap_locks); + pgoff_t anon_pgoff, bool *need_rmap_locks, bool keep_source); struct anon_vma *find_mergeable_anon_vma(struct vm_area_struct *vma); diff --git a/tools/testing/vma/tests/vma.c b/tools/testing/vma/tests/vma.c index c8ef7b8cd46b..e973e0a6d1a8 100644 --- a/tools/testing/vma/tests/vma.c +++ b/tools/testing/vma/tests/vma.c @@ -40,7 +40,7 @@ static bool test_copy_vma(void) vma = alloc_and_link_vma(&mm, 0x1000, 0x2000, 1, vma_flags); vma_set_anonymous(vma); vma_orig = vma; - vma_new = copy_vma(&vma, 0x2000, 0x1000, 1, 1, &need_locks); + vma_new = copy_vma(&vma, 0x2000, 0x1000, 1, 1, &need_locks, false); ASSERT_EQ(vma_new, vma_orig); ASSERT_EQ(vma, vma_orig); ASSERT_EQ(vma_new->vm_start, 0x1000); @@ -53,7 +53,7 @@ static bool test_copy_vma(void) vma = alloc_and_link_vma(&mm, 0x2000, 0x3000, 2, vma_flags); vma_set_anonymous(vma); vma_orig = vma; - vma_new = copy_vma(&vma, 0x1000, 0x1000, 2, 2, &need_locks); + vma_new = copy_vma(&vma, 0x1000, 0x1000, 2, 2, &need_locks, false); ASSERT_EQ(vma_new, vma_orig); ASSERT_EQ(vma, vma_orig); ASSERT_EQ(vma_new->vm_start, 0x1000); @@ -71,7 +71,7 @@ static bool test_copy_vma(void) vma = alloc_and_link_vma(&mm, 0x3000, 0x4000, 3, vma_flags); vma_set_anonymous(vma); vma_orig = vma; - vma_new = copy_vma(&vma, 0x2000, 0x1000, 3, 3, &need_locks); + vma_new = copy_vma(&vma, 0x2000, 0x1000, 3, 3, &need_locks, false); ASSERT_NE(vma_new, vma_orig); ASSERT_EQ(vma_new, vma); ASSERT_EQ(vma_new->vm_start, 0x1000); @@ -82,7 +82,7 @@ static bool test_copy_vma(void) /* Move backwards and do not merge. */ vma = alloc_and_link_vma(&mm, 0x3000, 0x5000, 3, vma_flags); - vma_new = copy_vma(&vma, 0, 0x2000, 0, 3, &need_locks); + vma_new = copy_vma(&vma, 0, 0x2000, 0, 3, &need_locks, false); ASSERT_NE(vma_new, vma); ASSERT_EQ(vma_new->vm_start, 0); ASSERT_EQ(vma_new->vm_end, 0x2000); @@ -95,7 +95,7 @@ static bool test_copy_vma(void) vma = alloc_and_link_vma(&mm, 0, 0x2000, 0, vma_flags); vma_next = alloc_and_link_vma(&mm, 0x6000, 0x8000, 6, vma_flags); - vma_new = copy_vma(&vma, 0x4000, 0x2000, 4, 4, &need_locks); + vma_new = copy_vma(&vma, 0x4000, 0x2000, 4, 4, &need_locks, false); vma_assert_attached(vma_new); ASSERT_EQ(vma_new, vma_next); From 597e4b9ab48a3f841080f627722064853d3f650e Mon Sep 17 00:00:00 2001 From: "Lorenzo Stoakes (ARM)" Date: Wed, 30 Sep 2026 19:47:10 +0100 Subject: [PATCH 269/367] mm/mremap: fix locked_vm leak by splitting VMA for MREMAP_DONTUNMAP The MREMAP_DONTUNMAP feature is highly unusual in that it permits mremap() operations that keep the original VMA in place. Historically this has led to a lot of bugs where non-obvious interactions occur between existing mremap() operations and the original VMA. Fix another of these - partial copies. The long-standing mremap() partial VMA logic has the baked-in assumption that the originating VMA is unmapped and thus moved. However MREMAP_DONTUNMAP defeats this by performing a partial copy instead since it keeps the source VMA around. An mremap(..., MREMAP_DONTUNMAP) operation disallows resizing of the VMA, but the operation can be performed partially: |-----------------| | | | v <------> <------> .new_sz. new_sz |--.------.--| |------| | .source. | | dest | |--.------.--| |------| <------------> old_sz The page tables in the specified range are moved, but the original VMA is kept intact. This interacts poorly with mlock()'d VMAs, as the VMA_LOCKED_BIT flag is cleared for the entire source VMA and set for the entire destination VMA. This results in an mm->locked_vm leak as the change is therefore not accounted correctly. The clear solution here is to make the portion of the source VMA which is mremap()'d distinct from the rest of it, a.k.a. split it. Therefore resolve this issue by splitting it ahead of the rest of the mremap() operation if the VMA is mlock()'d. This is valid, as the source VMA will lose its VMA_LOCKED_BIT flag, so if a partial remap it will become distinct from the rest of the VMA. In order to make this change re-expose split_vma() in vma.h for CONFIG_MMU (nommu doesn't compile mremap.c and uses a static helper instead). Finally, update the sys_map_count check to account for this case. Note that the early check does not use needs_pre_split() - this is because the VMA has not been looked up by this point, so be conservative and assume that the VMA is mlock()'d in this case for the purposes of the sys_map_count check. Link: https://lore.kernel.org/20260930-fix-dontunmap-partial-self-merge-v2-2-f388985a0f0a@kernel.org Fixes: e346b3813067 ("mm/mremap: add MREMAP_DONTUNMAP to mremap()") Signed-off-by: Lorenzo Stoakes (ARM) Signed-off-by: Andrew Morton Reviewed-by: Pedro Falcato Acked-by: Kiryl Shutsemau (Meta) Reviewed-by: Jose A. Perez de Azpillaga Tested-by: Anirudh Srinivasan Cc: Liam R. Howlett Cc: Vlastimil Babka Cc: Jann Horn Cc: Brian Geffon Cc: Minchan Kim Cc: --- mm/mremap.c | 62 +++++++++++++++++++++++++++++++++++++++-------------- mm/vma.c | 4 ++-- mm/vma.h | 5 +++++ 3 files changed, 53 insertions(+), 18 deletions(-) diff --git a/mm/mremap.c b/mm/mremap.c index 058dd66fe1d7..444f37d7cd28 100644 --- a/mm/mremap.c +++ b/mm/mremap.c @@ -1037,6 +1037,7 @@ static void vrm_stat_account(struct vma_remap_struct *vrm, } static bool __check_map_count_against_split(struct mm_struct *mm, + bool pre_split, bool before_unmaps) { const int sys_map_count = get_sysctl_max_map_count(); @@ -1088,26 +1089,42 @@ static bool __check_map_count_against_split(struct mm_struct *mm, * Therefore we must check to ensure we have headroom of 2 additional * VMAs. */ - return map_count + 2 <= sys_map_count; + map_count += 2; + + /* If pre-split, the -1 observed above doesn't apply. */ + if (pre_split) + map_count++; + + return map_count <= sys_map_count; +} + +static bool needs_pre_split(struct vma_remap_struct *vrm) +{ + /* + * An MREMAP_DONTUNMAP of a mlock()'d VMA needs to unlock the + * source VMA, so split in this case. + */ + return (vrm->flags & MREMAP_DONTUNMAP) && + vma_test(vrm->vma, VMA_LOCKED_BIT); } /* Do we violate the map count limit if we split VMAs when moving the VMA? */ -static bool check_map_count_against_split(void) +static bool check_map_count_against_split(struct vma_remap_struct *vrm) { return __check_map_count_against_split(current->mm, - /*before_unmaps=*/false); + needs_pre_split(vrm), /*before_unmaps=*/false); } /* Do we violate the map count limit if we split VMAs prior to early unmaps? */ -static bool check_map_count_against_split_early(void) +static bool check_map_count_against_split_early(struct vma_remap_struct *vrm) { return __check_map_count_against_split(current->mm, - /*before_unmaps=*/true); + vrm->flags & MREMAP_DONTUNMAP, /*before_unmaps=*/true); } /* - * Perform checks before attempting to write a VMA prior to it being - * moved. + * Perform checks and preparation before attempting to write a VMA prior to it + * being moved. */ static unsigned long prep_move_vma(struct vma_remap_struct *vrm) { @@ -1116,19 +1133,17 @@ static unsigned long prep_move_vma(struct vma_remap_struct *vrm) unsigned long old_addr = vrm->addr; unsigned long old_len = vrm->old_len; vm_flags_t dummy = vma->vm_flags; + const bool split_before = vma->vm_start != old_addr; + const bool split_after = vma->vm_end != old_addr + old_len; - /* - * We'd prefer to avoid failure later on in do_munmap: we copy a VMA, - * which may not merge, then (if MREMAP_DONTUNMAP is not set) unmap the - * source, which may split, causing a net increase of 2 mappings. - */ - if (!check_map_count_against_split()) + /* Avoid failure later on. */ + if (!check_map_count_against_split(vrm)) return -ENOMEM; if (vma->vm_ops && vma->vm_ops->may_split) { - if (vma->vm_start != old_addr) + if (split_before) err = vma->vm_ops->may_split(vma, old_addr); - if (!err && vma->vm_end != old_addr + old_len) + if (!err && split_after) err = vma->vm_ops->may_split(vma, old_addr + old_len); if (err) return err; @@ -1146,6 +1161,21 @@ static unsigned long prep_move_vma(struct vma_remap_struct *vrm) if (err) return err; + /* + * To account mlock()'d pages correctly in the MREMAP_DONTUNMAP + * case perform any split ahead of time for an mlock()'d VMA. + */ + if (needs_pre_split(vrm)) { + VMA_ITERATOR(vmi, vma->vm_mm, old_addr); + + if (split_before) + err = split_vma(&vmi, vma, old_addr, 1); + if (!err && split_after) + err = split_vma(&vmi, vma, old_addr + old_len, 0); + vrm->vmi_needs_invalidate = true; + return err; + } + return 0; } @@ -2006,7 +2036,7 @@ static unsigned long do_mremap(struct vma_remap_struct *vrm) return -EINTR; vrm->mmap_locked = true; - if (!check_map_count_against_split_early()) { + if (!check_map_count_against_split_early(vrm)) { mmap_write_unlock(mm); return -ENOMEM; } diff --git a/mm/vma.c b/mm/vma.c index d8308c319daa..6c35f0d775ab 100644 --- a/mm/vma.c +++ b/mm/vma.c @@ -634,8 +634,8 @@ __split_vma(struct vma_iterator *vmi, struct vm_area_struct *vma, * Split a vma into two pieces at address 'addr', a new vma is allocated * either for the first part or the tail. */ -static int split_vma(struct vma_iterator *vmi, struct vm_area_struct *vma, - unsigned long addr, int new_below) +int split_vma(struct vma_iterator *vmi, struct vm_area_struct *vma, + unsigned long addr, int new_below) { if (vma->vm_mm->map_count >= get_sysctl_max_map_count()) return -ENOMEM; diff --git a/mm/vma.h b/mm/vma.h index 39514fac72fc..36973abaa015 100644 --- a/mm/vma.h +++ b/mm/vma.h @@ -556,6 +556,11 @@ int do_brk_flags(struct vma_iterator *vmi, struct vm_area_struct *brkvma, unsigned long unmapped_area(struct vm_unmapped_area_info *info); unsigned long unmapped_area_topdown(struct vm_unmapped_area_info *info); +#ifdef CONFIG_MMU +int split_vma(struct vma_iterator *vmi, struct vm_area_struct *vma, + unsigned long addr, int new_below); +#endif + static inline bool vma_wants_manual_pte_write_upgrade(struct vm_area_struct *vma) { /* From 29e0dd1f2ef82d0931ed1a4e6b1d8aa04650835d Mon Sep 17 00:00:00 2001 From: John Garry Date: Wed, 23 Sep 2026 08:53:24 +0100 Subject: [PATCH 270/367] mailmap: update addresses for John Garry Point any employment addresses at my personal dev address. Link: https://lore.kernel.org/20260923075324.1382927-1-john.garry@linux.dev Signed-off-by: John Garry Signed-off-by: Andrew Morton --- .mailmap | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.mailmap b/.mailmap index b8d0dc185714..39f449f9c42d 100644 --- a/.mailmap +++ b/.mailmap @@ -461,7 +461,8 @@ Johan Hovold Johan Hovold John Crispin John Fastabend -John Garry +John Garry +John Garry John Keeping John Moon John Paul Adrian Glaubitz From 2fb552b0bee70c24c092720028ab4620f5eb2646 Mon Sep 17 00:00:00 2001 From: Mikhail Gavrilov Date: Fri, 25 Sep 2026 10:06:47 +0500 Subject: [PATCH 271/367] mm: don't schedule deferred kernel page table freeing while booting Booting with a boot-time function tracer and a filter, for example ftrace=function ftrace_filter=pud_free_pmd_page panics on 7.3-rc4 as soon as the tracer starts: [ 23.531178] Starting tracer 'function' [ 23.675800] Oops: general protection fault, probably for non-canonical address 0xdffffc0000000038: 0000 [#1] SMP KASAN NOPTI [ 23.819917] KASAN: null-ptr-deref in range [0x00000000000001c0-0x00000000000001c7] [ 23.964025] CPU: 0 UID: 0 PID: 0 Comm: swapper Not tainted 7.3.0-rc4-fe2ec83746e5-with-fixes-v2+ #195 PREEMPT(undef) [ 24.252248] RIP: 0010:__queue_work+0xab/0xf00 [ 25.981629] Call Trace: [ 26.125727] [ 26.413912] ? pagetable_free_kernel+0x20/0x120 [ 26.990283] queue_work_on+0x97/0xf0 [ 27.134382] __cpa_collapse_large_pages+0x501/0x6f0 [ 27.566662] cpa_flush+0x394/0x620 [ 27.998953] change_page_attr_set_clr+0x321/0x4a0 [ 29.151729] set_memory_rox+0xa2/0xf0 [ 29.584018] create_trampoline+0x431/0x6f0 ... [ 44.343347] Kernel panic - not syncing: Attempted to kill the idle task! The boot-time tracer is started from early_trace_init(), which runs before workqueue_init_early(). Making its trampoline read-only splits a large page, and CPA collapses it again right away. The split table has been a kernel page table since commit 9e4a3ec3411b ("x86/mm/pat: Allocate split page tables as kernel page tables"), so the collapse frees it through pagetable_free_kernel(), which queues work on system_percpu_wq - still NULL at that point. That commit is correct in itself; it only lets CPA reach pagetable_free_kernel() before the workqueue that function relies on exists. Keep putting the table on the list, but don't schedule the work while the system is still booting. A core_initcall schedules it once to free whatever was queued by then. Link: https://lore.kernel.org/20260925050647.86913-1-mikhail.v.gavrilov@gmail.com Link: https://lore.kernel.org/20260924064321.23787-1-mikhail.v.gavrilov@gmail.com Fixes: 9e4a3ec3411b ("x86/mm/pat: Allocate split page tables as kernel page tables") Signed-off-by: Mikhail Gavrilov Signed-off-by: Andrew Morton Suggested-by: David Hildenbrand (Arm) Suggested-by: Lorenzo Stoakes (ARM) Reviewed-by: Andrew Morton Reviewed-by: David Hildenbrand (Arm) Reviewed-by: Lorenzo Stoakes (ARM) Reviewed-by: Jose A. Perez de Azpillaga Tested-by: Jose A. Perez de Azpillaga Cc: Dave Hansen Cc: Liam R. Howlett Cc: Vlastimil Babka Cc: Mike Rapoport Cc: Suren Baghdasaryan Cc: Michal Hocko Cc: Vishal Moola (Oracle) Cc: Ingo Molnar Cc: Lu Baolu Cc: Jason Gunthorpe Cc: Steven Rostedt Cc: --- mm/pgtable-generic.c | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/mm/pgtable-generic.c b/mm/pgtable-generic.c index b91b1a98029c..cd227fc05d2d 100644 --- a/mm/pgtable-generic.c +++ b/mm/pgtable-generic.c @@ -438,12 +438,30 @@ static void kernel_pgtable_work_func(struct work_struct *work) __pagetable_free(pt); } +static void schedule_kernel_pgtable_free(void) +{ + schedule_work(&kernel_pgtable_work.work); +} + void pagetable_free_kernel(struct ptdesc *pt) { spin_lock(&kernel_pgtable_work.lock); list_add(&pt->pt_list, &kernel_pgtable_work.list); spin_unlock(&kernel_pgtable_work.lock); - schedule_work(&kernel_pgtable_work.work); + /* + * The workqueue may not exist yet while the system is booting. + * kernel_pgtable_drain_early() schedules the work once it does. + */ + if (system_state != SYSTEM_BOOTING) + schedule_kernel_pgtable_free(); } + +static int __init kernel_pgtable_drain_early(void) +{ + /* Free the kernel page tables queued while booting. */ + schedule_kernel_pgtable_free(); + return 0; +} +core_initcall(kernel_pgtable_drain_early); #endif From 28eed9906e01d01cbdfb352e799413b2e8b33b76 Mon Sep 17 00:00:00 2001 From: Carlos Llamas Date: Sun, 27 Sep 2026 16:24:18 +0000 Subject: [PATCH 272/367] selftests/mm: cleanup -Wformat issues in hugetlb-mmap Commit ae571cd6015c ("selftests/mm: hugetlb-mmap: add setup of HugeTLB pages") and commit 9c5a65f374f8 ("selftests/mm: merge map_hugetlb into hugepage-mmap") added logs of 'hugepage_size' which has a size_t type. However, the incorrect format specifier '%lu' was used which triggers -Wformat warnings when building for 32-bit: hugetlb-mmap.c:125:55: warning: format specifies type 'unsigned long' but the argument has type 'size_t' (aka 'unsigned int') [-Wformat] 125 | ksft_print_msg("Default size hugepages (%lu kB)\n", hugepage_size >> 10); | ~~~ ^~~~~~~~~~~~~~~~~~~ | %zu hugetlb-mmap.c:134:47: warning: format specifies type 'unsigned long' but the argument has type 'size_t' (aka 'unsigned int') [-Wformat] 134 | ksft_exit_skip("Not enough %lu Kb pages\n", hugepage_size >> 10); | ~~~ ^~~~~~~~~~~~~~~~~~~ | %zu Fix this by switching to the expected '%zu' format specifier. Link: https://lore.kernel.org/20260927162419.820609-1-cmllamas@google.com Fixes: ae571cd6015c ("selftests/mm: hugetlb-mmap: add setup of HugeTLB pages") Fixes: 9c5a65f374f8 ("selftests/mm: merge map_hugetlb into hugepage-mmap") Signed-off-by: Carlos Llamas Signed-off-by: Andrew Morton Reviewed-by: Sarthak Sharma Reviewed-by: SJ Park Acked-by: Lorenzo Stoakes (ARM) Cc: Mike Rapoport Cc: David Hildenbrand Cc: Liam R. Howlett Cc: Vlastimil Babka Cc: Suren Baghdasaryan Cc: Michal Hocko Cc: Shuah Khan Cc: --- tools/testing/selftests/mm/hugetlb-mmap.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/testing/selftests/mm/hugetlb-mmap.c b/tools/testing/selftests/mm/hugetlb-mmap.c index 0f2aad1b7dbd..2edbc992e6bc 100644 --- a/tools/testing/selftests/mm/hugetlb-mmap.c +++ b/tools/testing/selftests/mm/hugetlb-mmap.c @@ -122,7 +122,7 @@ int main(int argc, char **argv) hugepage_size = default_huge_page_size(); if (!hugepage_size) ksft_exit_skip("Could not detect default hugetlb page size."); - ksft_print_msg("Default size hugepages (%lu kB)\n", hugepage_size >> 10); + ksft_print_msg("Default size hugepages (%zu kB)\n", hugepage_size >> 10); } /* munmap will fail if the length is not page aligned */ @@ -131,7 +131,7 @@ int main(int argc, char **argv) hugetlb_set_nr_pages(hugepage_size, nr); if (hugetlb_free_pages(hugepage_size) < nr) - ksft_exit_skip("Not enough %lu Kb pages\n", hugepage_size >> 10); + ksft_exit_skip("Not enough %zu Kb pages\n", hugepage_size >> 10); ksft_set_plan(2); ksft_print_msg("Mapping %lu Mbytes\n", (unsigned long)length >> 20); From d3ed3a40951c964d30dc7fc63d13e87a873ea300 Mon Sep 17 00:00:00 2001 From: "Lorenzo Stoakes (ARM)" Date: Thu, 24 Sep 2026 15:48:24 +0100 Subject: [PATCH 273/367] drivers/char/mem: mmap readonly MAP_SHARED-/dev/zero correctly Rather surprisingly, opening /dev/zero read-only then mmap()'ing it MAP_SHARED gets you true anonymous memory (albeit in a VMA with non-NULL vma->vm_file). This is a by-product of MAP_PRIVATE-/dev/zero being how anonymous memory was mapped in Linux's distant past. It happens because mmap_zero_prepare() gates on VMA_SHARED_BIT and when mapping a read-only file MAP_SHARED, do_mmap() clears VMA_SHARED_BIT and VMA_MAYWRITE_BIT. The gating is incorrect - the (poorly named) VMA_MAYSHARE_BIT flag exists explicitly to tell you if something was originally mapped MAP_SHARED. So the fix is simple - gate on this instead. This isn't exactly a common use case, but it's unexpected behaviour which now causes an assert if CONFIG_DEBUG_VM is set. While this bug has existed since the dawn of time for linux (or at least since 2.6.12), it hasn't caused issues in the past, so while it's incorrect behaviour, it doesn't seem necessary to backport that far. The mapping is now accounted at mmap time and can fail with -ENOMEM under strict overcommit, and read faults allocate folios. However this is normal behaviour for a read-only shmem mapping. Commit 93c0c8dc87f6 ("mm/rmap: use anon pgoff to track MAP_PRIVATE file-backed anon folios") is the first patch at which the debug assert fires, so target that instead. Link: https://lore.kernel.org/20260924-fix-dev-zero-readonly-shared-v1-1-153c2111e323@kernel.org Fixes: 93c0c8dc87f6 ("mm/rmap: use anon pgoff to track MAP_PRIVATE file-backed anon folios") Signed-off-by: Lorenzo Stoakes (ARM) Signed-off-by: Andrew Morton Reported-by: Closes: https://lore.kernel.org/linux-mm/6ab4ae75.80e1c6cc.1e8e5f.000d.GAE@google.com/ Acked-by: David Hildenbrand (Arm) Cc: Liam R. Howlett Cc: Vlastimil Babka Cc: Jann Horn Cc: Pedro Falcato Cc: Mike Rapoport Cc: Suren Baghdasaryan Cc: Michal Hocko Cc: Arnd Bergmann Cc: Greg Kroah-Hartman Cc: Lance Yang --- drivers/char/mem.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/char/mem.c b/drivers/char/mem.c index 63253d1de5d7..5b93c92c2cf1 100644 --- a/drivers/char/mem.c +++ b/drivers/char/mem.c @@ -503,7 +503,7 @@ static int mmap_zero_prepare(struct vm_area_desc *desc) #ifndef CONFIG_MMU return -ENOSYS; #endif - if (vma_desc_test(desc, VMA_SHARED_BIT)) + if (vma_desc_test(desc, VMA_MAYSHARE_BIT)) return shmem_zero_setup_desc(desc); /* From 80ce29104123be4d61b5912dae543c70af0fcb75 Mon Sep 17 00:00:00 2001 From: Andy Yan Date: Thu, 24 Sep 2026 18:50:33 +0800 Subject: [PATCH 274/367] mailmap: update entry for Andy Yan I will use andyshrk@163.com for future review and discussion. Link: https://lore.kernel.org/20260924105052.768760-1-andyshrk@163.com Signed-off-by: Andy Yan Signed-off-by: Andrew Morton Reviewed-by: Shawn Lin Cc: Alexander Sverdlin Cc: Chuck Lever Cc: Jakub Kicinski Cc: Martin Kepplinger --- .mailmap | 1 + 1 file changed, 1 insertion(+) diff --git a/.mailmap b/.mailmap index 39f449f9c42d..b325ccb35201 100644 --- a/.mailmap +++ b/.mailmap @@ -101,6 +101,7 @@ Andy Chiu Andy Chiu Andy Shevchenko Andy Shevchenko +Andy Yan Anilkumar Kolli Anirudh Ghayal Antoine Tenart From ca5012757bf027df83d51593a07656cb8c80caee Mon Sep 17 00:00:00 2001 From: Quentin Freimanis Date: Sun, 4 Oct 2026 22:28:26 -0700 Subject: [PATCH 275/367] net: sparx5: start the TOD counters on non-PTP lan969x variants Currently the base non-PTP-capable lan969x variants don't forward any Ethernet frames: $ ip link set eth10 up $ ip addr add 10.0.0.54/24 dev eth10 $ ping 10.0.0.1 PING 10.0.0.1 (10.0.0.1): 56 data bytes --- 10.0.0.1 ping statistics --- 7 packets transmitted, 0 packets received, 100% packet loss $ cat /proc/interrupts | grep fdma 20: 0 GIC-0 120 Level sparx5-fdma $ ip -s link show dev eth10 RX: bytes packets errors dropped missed mcast 20828 102 0 0 0 72 TX: bytes packets errors dropped carrier collsns 0 0 0 0 0 0 Testing showed that starting the domain 0 TOD counter gets networking working again, but start all three for consistency with the PTP variants. Fixes: 207966787b71 ("net: sparx5: add feature support") Suggested-by: Daniel Machon Signed-off-by: Quentin Freimanis Reviewed-by: Daniel Machon Link: https://patch.msgid.link/20261005052826.14251-1-quentin@q-lab.dev Signed-off-by: Jakub Kicinski --- .../ethernet/microchip/sparx5/sparx5_ptp.c | 28 +++++++++++-------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/drivers/net/ethernet/microchip/sparx5/sparx5_ptp.c b/drivers/net/ethernet/microchip/sparx5/sparx5_ptp.c index 5953e04ef14e..2826e48af103 100644 --- a/drivers/net/ethernet/microchip/sparx5/sparx5_ptp.c +++ b/drivers/net/ethernet/microchip/sparx5/sparx5_ptp.c @@ -622,18 +622,9 @@ int sparx5_ptp_init(struct sparx5 *sparx5) sparx5->ptp = 1; } - if (!sparx5->ptp) - return 0; - - for (i = 0; i < SPARX5_PHC_COUNT; ++i) { - err = sparx5_ptp_phc_init(sparx5, i, &sparx5_ptp_clock_info); - if (err) - return err; - } - - spin_lock_init(&sparx5->ptp_clock_lock); - spin_lock_init(&sparx5->ptp_ts_id_lock); - mutex_init(&sparx5->ptp_lock); + /* The base, non-PTP-capable lan969x variants need the + * first TOD counter running to forward frames. + */ /* Disable master counters */ spx5_wr(PTP_PTP_DOM_CFG_PTP_ENA_SET(0), sparx5, PTP_PTP_DOM_CFG); @@ -657,6 +648,19 @@ int sparx5_ptp_init(struct sparx5 *sparx5) /* Enable master counters */ spx5_wr(PTP_PTP_DOM_CFG_PTP_ENA_SET(0x7), sparx5, PTP_PTP_DOM_CFG); + if (!sparx5->ptp) + return 0; + + for (i = 0; i < SPARX5_PHC_COUNT; ++i) { + err = sparx5_ptp_phc_init(sparx5, i, &sparx5_ptp_clock_info); + if (err) + return err; + } + + spin_lock_init(&sparx5->ptp_clock_lock); + spin_lock_init(&sparx5->ptp_ts_id_lock); + mutex_init(&sparx5->ptp_lock); + for (i = 0; i < sparx5->data->consts->n_ports; i++) { port = sparx5->ports[i]; if (!port) From c5381ae1cd1481cc431c2bd79460472c6f1d3ce3 Mon Sep 17 00:00:00 2001 From: Haishuang Yan Date: Thu, 1 Oct 2026 12:22:32 +0800 Subject: [PATCH 276/367] devlink: fix devlink_rel reference leak when notify work is pending devlink_rel_nested_in_notify_work_schedule() takes a reference on the devlink_rel for the notify work and then queues the work, ignoring the return value of schedule_delayed_work(). If the work is already pending, nothing new is queued, the work runs only once and drops only one reference, so the extra one is leaked together with the devlink_rel and its index in devlink_rels. This is easy to hit. devl_register() of a nested instance queues the work, and if the instance is unregistered before the work has run, devlink_rel_put() queues it again while it is still pending. The work also keeps rescheduling itself for as long as the parent devlink lock cannot be taken, which widens the window. Registering and unregistering a nested devlink instance 100 times while holding the parent lock leaks all 100 devlink_rel objects. The reschedule path in devlink_rel_nested_in_notify_work() has the same problem: if the work was queued again while it was running, the reference it holds is never dropped. Drop the reference in both places when the work was already pending. The pending work holds its own reference, so this can not be the last one. Fixes: c137743bce02 ("devlink: introduce object and nested devlink relationship infra") Signed-off-by: Haishuang Yan Reviewed-by: Simon Horman Link: https://patch.msgid.link/20261001042232.222987-1-yanhaishuang@cmss.chinamobile.com Signed-off-by: Jakub Kicinski --- net/devlink/core.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/net/devlink/core.c b/net/devlink/core.c index c53a42e17a58..bddbbbf000fe 100644 --- a/net/devlink/core.c +++ b/net/devlink/core.c @@ -112,13 +112,19 @@ static void devlink_rel_nested_in_notify_work(struct work_struct *work) return; reschedule_work: - schedule_delayed_work(&rel->nested_in.notify_work, 1); + /* The work may have been queued again meanwhile, which took its own + * reference. Drop ours in that case. + */ + if (!schedule_delayed_work(&rel->nested_in.notify_work, 1)) + __devlink_rel_put(rel); } static void devlink_rel_nested_in_notify_work_schedule(struct devlink_rel *rel) { __devlink_rel_get(rel); - schedule_delayed_work(&rel->nested_in.notify_work, 0); + /* The pending work holds a reference already, drop the new one. */ + if (!schedule_delayed_work(&rel->nested_in.notify_work, 0)) + __devlink_rel_put(rel); } static struct devlink_rel *devlink_rel_alloc(void) From b7454f1940c5e6d92e753853c476a769e452f30a Mon Sep 17 00:00:00 2001 From: Weiming Shi Date: Fri, 2 Oct 2026 02:23:25 +0800 Subject: [PATCH 277/367] net: team: stop reusing skb after queue override A nonzero dev_queue_xmit() result does not return ownership of the skb. When an override port's qdisc drops it, the queue override helper used to try another port or the mode transmit op with the freed skb. Return the handoff decision separately from transmit success. Select only the first override port under RCU and stop after handing it the skb, regardless of the lower transmit result. Keep the existing success and drop accounting. BUG: KASAN: slab-use-after-free in sk_skb_reason_drop Write of size 4 by task poc/5040 sk_skb_reason_drop (net/core/skbuff.c:1240) dev_kfree_skb_any_reason (net/core/dev.c:3470) ab_transmit (drivers/net/team/team_mode_activebackup.c:49) team_xmit (drivers/net/team/team_core.c:1849) __dev_direct_xmit (net/core/dev.c:4934) Freed by task 5040: __tcf_kfree_skb_list (net/sched/sch_generic.c:43) __dev_queue_xmit (net/core/dev.c:4831) team_xmit (drivers/net/team/team_core.c:1846) Kernel panic - not syncing: KASAN: panic_on_warn set ... Cc: stable@vger.kernel.org Fixes: 8ff5105a2b9d ("team: add support for queue override by setting queue_id for port") Reported-by: Signed-off-by: Weiming Shi Link: https://patch.msgid.link/20261001182326.572045-2-bestswngs@gmail.com Signed-off-by: Jakub Kicinski --- drivers/net/team/team_core.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/drivers/net/team/team_core.c b/drivers/net/team/team_core.c index beffbe450612..ca75f08e3c59 100644 --- a/drivers/net/team/team_core.c +++ b/drivers/net/team/team_core.c @@ -820,7 +820,8 @@ static struct list_head *__team_get_qom_list(struct team *team, u16 queue_id) /* * note: already called with rcu_read_lock */ -static bool team_queue_override_transmit(struct team *team, struct sk_buff *skb) +static bool team_queue_override_transmit(struct team *team, struct sk_buff *skb, + bool *tx_success) { struct list_head *qom_list; struct team_port *port; @@ -828,11 +829,11 @@ static bool team_queue_override_transmit(struct team *team, struct sk_buff *skb) if (!team->queue_override_enabled || !skb->queue_mapping) return false; qom_list = __team_get_qom_list(team, skb->queue_mapping); - list_for_each_entry_rcu(port, qom_list, qom_list) { - if (!team_dev_queue_xmit(team, port, skb)) - return true; - } - return false; + port = list_first_or_null_rcu(qom_list, struct team_port, qom_list); + if (!port) + return false; + *tx_success = !team_dev_queue_xmit(team, port, skb); + return true; } static void __team_queue_override_port_del(struct team *team, @@ -1864,8 +1865,7 @@ static netdev_tx_t team_xmit(struct sk_buff *skb, struct net_device *dev) bool tx_success; unsigned int len = skb->len; - tx_success = team_queue_override_transmit(team, skb); - if (!tx_success) + if (!team_queue_override_transmit(team, skb, &tx_success)) tx_success = READ_ONCE(team->ops.transmit)(team, skb); if (tx_success) { struct team_pcpu_stats *pcpu_stats; From 489114c9ecae44e523361a4b3e610ee5ebd9189c Mon Sep 17 00:00:00 2001 From: Weiming Shi Date: Fri, 2 Oct 2026 02:23:26 +0800 Subject: [PATCH 278/367] net: team: free skb when broadcast has no txable port bc_transmit() sends the original skb through the last txable port. When no port is txable, it returns false without consuming the skb, but team_xmit() still returns NETDEV_TX_OK. AF_PACKET sends then retain the skb and its socket write-memory charge. A TX-enabled, link-down port keeps the broadcast transmit op installed. Carrier can stay up through a second link-up, TX-disabled port or be forced on by userspace. Free the original skb on the no-port path while leaving the existing drop accounting intact. Discovered by Sashiko during review of previous commit. Cc: stable@vger.kernel.org Fixes: 5fc889911a99 ("team: add broadcast mode") Signed-off-by: Weiming Shi Link: https://patch.msgid.link/20261001182326.572045-3-bestswngs@gmail.com Signed-off-by: Jakub Kicinski --- drivers/net/team/team_mode_broadcast.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/net/team/team_mode_broadcast.c b/drivers/net/team/team_mode_broadcast.c index 61d7d79f0c36..69dc25a5847a 100644 --- a/drivers/net/team/team_mode_broadcast.c +++ b/drivers/net/team/team_mode_broadcast.c @@ -37,6 +37,8 @@ static bool bc_transmit(struct team *team, struct sk_buff *skb) ret = !team_dev_queue_xmit(team, last, skb); if (!sum_ret) sum_ret = ret; + } else { + dev_kfree_skb_any(skb); } return sum_ret; } From b72e025ac556984766392001e74b7587e145bcd0 Mon Sep 17 00:00:00 2001 From: Victor Nogueira Date: Thu, 1 Oct 2026 12:32:00 -0300 Subject: [PATCH 279/367] net/sched: cls_route: reject change with no routing attribute route4_change on a filter change that supplies no routing attribute at all (no to/from/iif) reaches route4_set_parms with fold set, so the handle-mismatch check -- which only fires when creating -- is skipped. nhandle is built as the all-wildcard bucket (0x8000 | 0xFFFF << 16) and the filter is rekeyed to 0xFFFF8000, so the handle userspace already stored no longer addresses the filter. (The netlink reply carries the new handle; what goes stale is the handle the caller holds.) The rekey actually misclassifies the packets. The new handle's from_hash is the wildcard chain (route4_hash_wild), whose filters route4_classify applies without any id/iif comparison, and its bucket (256) is reached by every packet through the h < 256 restart. A filter that matched only a specific realm therefore becomes an unconditional catch-all that applies its classid and actions to all otherwise-unmatched traffic. Reject a change that would rekey the filter this way, with the routing-attributes-required error. A change that names at least one of to/from/iif, and an in-place replace that keeps the routing identity via a supplied attribute, are unaffected. A change of a filter that already sits at the wildcard handle is a metadata-only update and remains valid, so only a change that actually moves the handle is rejected. This is a follow-up to commit 41e85e54e564 ("net/sched: cls_route: Fix in-place replace"); this patch closes the no-routing-attribute variant that the parent series did not address. Conditions to recreate the bug: tc qdisc add dev lo clsact tc filter add dev lo ingress protocol ip pref 100 route from 1 to 1 tc filter change dev lo ingress protocol ip pref 100 handle 0x10001 \ route classid 1:2 tc filter show dev lo ingress # handle became 0xffff8000; iproute2's # stored 0x10001 no longer addresses it A change of a filter already at the wildcard handle, e.g. one created by "tc filter add dev lo ingress protocol ip pref 100 route classid 1:1" (iproute2 defaults the handle to 0xffff8000), stays a valid metadata-only update. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Reported-by: Sashiko Closes: https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260907192133.2639067-1-victor@mojatatu.com Reviewed-by: Jamal Hadi Salim Signed-off-by: Victor Nogueira Reviewed-by: Simon Horman Link: https://patch.msgid.link/QDISC-0JB3.v1.20260929143022@mojatatu.com Signed-off-by: Jakub Kicinski --- net/sched/cls_route.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/net/sched/cls_route.c b/net/sched/cls_route.c index 0f211f030fd9..dca812a75269 100644 --- a/net/sched/cls_route.c +++ b/net/sched/cls_route.c @@ -434,6 +434,12 @@ static int route4_set_parms(struct net *net, struct tcf_proto *tp, if (handle && (!fold || nhandle == (handle & ~0x7F00))) nhandle |= handle & 0x7F00; + if (fold && !tb[TCA_ROUTE4_TO] && !tb[TCA_ROUTE4_FROM] && + !tb[TCA_ROUTE4_IIF] && nhandle != fold->handle) { + NL_SET_ERR_MSG(extack, "Routing attributes required"); + return -EINVAL; + } + if (handle && !fold && nhandle != handle) { NL_SET_ERR_MSG_FMT(extack, "Handle mismatch constructed: %x (expected: %x)", From 6fa2e7db99fb9ed67496c652fcfce5a31c6b73d0 Mon Sep 17 00:00:00 2001 From: Victor Nogueira Date: Thu, 1 Oct 2026 12:32:01 -0300 Subject: [PATCH 280/367] selftests: tc-testing: add cls_route no-routing-attribute change tests Add a boundary pair for route4 change without a routing attribute: - a classid-only change of a wildcard filter (handle 0xffff8000) is a metadata-only update and must succeed; - the same change on a non-wildcard handle (0x10001) would rekey the filter to the wildcard bucket, leaving the handle userspace stored stale, and must be rejected with -EINVAL. The change of a wildcard filter is the flow iproute2 uses by default ("tc filter add ... route classid X:Y" gives handle 0xffff8000). Reviewed-by: Jamal Hadi Salim Signed-off-by: Victor Nogueira Reviewed-by: Simon Horman Link: https://patch.msgid.link/QDISC-0JB3.v1.20260923100921@mojatatu.com.2 Signed-off-by: Jakub Kicinski --- .../tc-testing/tc-tests/filters/route.json | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/tools/testing/selftests/tc-testing/tc-tests/filters/route.json b/tools/testing/selftests/tc-testing/tc-tests/filters/route.json index 2d5843aebd72..c3b6ff157d3e 100644 --- a/tools/testing/selftests/tc-testing/tc-tests/filters/route.json +++ b/tools/testing/selftests/tc-testing/tc-tests/filters/route.json @@ -412,5 +412,51 @@ "teardown": [ "$TC qdisc del dev $DEV1 ingress" ] + }, + { + "id": "4000", + "name": "Change a wildcard route filter classid only (no routing attribute)", + "category": [ + "filter", + "route" + ], + "plugins": { + "requires": "nsPlugin" + }, + "setup": [ + "$TC qdisc add dev $DEV1 ingress", + "$TC filter add dev $DEV1 parent ffff: protocol ip prio 100 route classid 1:1" + ], + "cmdUnderTest": "$TC filter change dev $DEV1 parent ffff: protocol ip prio 100 route classid 1:9", + "expExitCode": "0", + "verifyCmd": "$TC filter ls dev $DEV1 parent ffff:", + "matchPattern": "fh 0xffff8000 flowid 1:9", + "matchCount": "1", + "teardown": [ + "$TC qdisc del dev $DEV1 ingress" + ] + }, + { + "id": "907a", + "name": "Try to change a route filter with no routing attribute and a non-wildcard handle", + "category": [ + "filter", + "route" + ], + "plugins": { + "requires": "nsPlugin" + }, + "setup": [ + "$TC qdisc add dev $DEV1 ingress", + "$TC filter add dev $DEV1 parent ffff: protocol ip prio 100 route from 1 to 1 classid 1:1" + ], + "cmdUnderTest": "$TC filter change dev $DEV1 parent ffff: protocol ip prio 100 handle 0x10001 route classid 1:2", + "expExitCode": "2", + "verifyCmd": "$TC filter ls dev $DEV1 parent ffff:", + "matchPattern": "fh 0x00010001 flowid 1:1 to 1 from 1", + "matchCount": "1", + "teardown": [ + "$TC qdisc del dev $DEV1 ingress" + ] } ] From 86b785b18f8f4a6b4a7392d1e2b29d6bb095011a Mon Sep 17 00:00:00 2001 From: Or Har-Toov Date: Sun, 4 Oct 2026 11:35:31 +0300 Subject: [PATCH 281/367] Revert "net/mlx5: E-Switch, preserve max tx speed on vport state modification" This reverts commit ad0ae7aefa7a ("net/mlx5: E-Switch, preserve max tx speed on vport state modification"). mlx5_modify_vport_admin_state() and mlx5_esw_adj_vport_modify() query the vport's current max_tx_speed before modifying its state, and write that value back, to avoid resetting it to 0 as a side effect of an unrelated admin-state change. That's unnecessary: max_tx_speed is optional in MODIFY_VPORT_STATE - FW skips writing it whenever it's 0, treating that as "not provided" rather than "reset to zero". Leaving it unset already preserves FW's current value, with no query needed. Fixes: ad0ae7aefa7a ("net/mlx5: E-Switch, preserve max tx speed on vport state modification") Signed-off-by: Or Har-Toov Reviewed-by: Mark Bloch Signed-off-by: Tariq Toukan Link: https://patch.msgid.link/20261004083531.216988-1-tariqt@nvidia.com Signed-off-by: Jakub Kicinski --- .../mellanox/mlx5/core/esw/adj_vport.c | 20 ---------------- .../net/ethernet/mellanox/mlx5/core/vport.c | 24 ------------------- 2 files changed, 44 deletions(-) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/esw/adj_vport.c b/drivers/net/ethernet/mellanox/mlx5/core/esw/adj_vport.c index 7950b82d8b8a..3624c680a861 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/esw/adj_vport.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/esw/adj_vport.c @@ -11,26 +11,6 @@ int mlx5_esw_adj_vport_modify(struct mlx5_core_dev *dev, u16 vport, lockdep_assert_held(&dev->priv.eswitch->state_lock); - if (MLX5_CAP_ESW(dev, esw_vport_state_max_tx_speed)) { - u8 op_mod = MLX5_VPORT_STATE_OP_MOD_ESW_VPORT; - struct mlx5_vport *esw_vport; - u32 speed = 0; - int err; - - err = mlx5_query_vport_max_tx_speed(dev, op_mod, vport, - true, &speed, NULL); - if (err) { - esw_vport = mlx5_eswitch_get_vport(dev->priv.eswitch, - vport); - speed = IS_ERR(esw_vport) ? 0 : - esw_vport->agg_max_tx_speed; - mlx5_core_dbg(dev, - "Failed to query vport %d max tx speed, err=%d, using cached %u\n", - vport, err, speed); - } - MLX5_SET(modify_vport_state_in, in, max_tx_speed, speed); - } - MLX5_SET(modify_vport_state_in, in, opcode, MLX5_CMD_OP_MODIFY_VPORT_STATE); MLX5_SET(modify_vport_state_in, in, op_mod, diff --git a/drivers/net/ethernet/mellanox/mlx5/core/vport.c b/drivers/net/ethernet/mellanox/mlx5/core/vport.c index 5774ec117428..78f3d15b599d 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/vport.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/vport.c @@ -93,30 +93,6 @@ int mlx5_modify_vport_admin_state(struct mlx5_core_dev *mdev, u8 opmod, lockdep_assert_held(&mdev->priv.eswitch->state_lock); #endif - if (MLX5_CAP_ESW(mdev, esw_vport_state_max_tx_speed) && - opmod == MLX5_VPORT_STATE_OP_MOD_ESW_VPORT && - vport != MLX5_VPORT_UPLINK) { - u32 speed = 0; - int err; - - err = mlx5_query_vport_max_tx_speed(mdev, opmod, vport, - other_vport, &speed, NULL); - if (err) { -#ifdef CONFIG_MLX5_ESWITCH - struct mlx5_vport *esw_vport; - - esw_vport = mlx5_eswitch_get_vport(mdev->priv.eswitch, - vport); - speed = IS_ERR(esw_vport) ? 0 : - esw_vport->agg_max_tx_speed; -#endif - mlx5_core_dbg(mdev, - "Failed to query vport %d max tx speed, err=%d, using cached %u\n", - vport, err, speed); - } - MLX5_SET(modify_vport_state_in, in, max_tx_speed, speed); - } - MLX5_SET(modify_vport_state_in, in, opcode, MLX5_CMD_OP_MODIFY_VPORT_STATE); MLX5_SET(modify_vport_state_in, in, op_mod, opmod); From 538b529509c7ab0d909cb65bb7264edca1788d30 Mon Sep 17 00:00:00 2001 From: Hui Peng Date: Wed, 30 Sep 2026 04:46:11 +0000 Subject: [PATCH 282/367] sctp: copy zeroed stats to user in sctp_getsockopt_pr_streamstatus() when !streamoute When an SCTP stream output extension structure (streamoute) has not yet been allocated, sctp_getsockopt_pr_streamstatus() sets the local stack variables params.sprstat_abandoned_unsent = 0 and params.sprstat_abandoned_sent = 0, but then immediately jumps to 'out:', bypassing copy_to_user(). As a result, when an uninitialized user buffer is passed to getsockopt(SCTP_PR_STREAM_STATUS), the kernel returns success (0) without copying the zeroed stats back to user space, leaving uninitialized data in the user buffer. Refactor the !streamoute check into an if-else chain so that when streamoute is NULL, params is zeroed and falls through to copy_to_user(), copying the zeroed stats to user space and setting retval = 0 once before returning. Tested in QEMU against Linux 7.3.0-rc3 using an uninitialized sctp_prstatus user struct passed to getsockopt(SCTP_PR_STREAM_STATUS) on a freshly created stream: on the unfixed kernel the user struct contains uninitialized stack garbage; whereas with this fix applied, getsockopt() copies zeroed stats into user space and returns 0. Cc: stable@vger.kernel.org Reviewed-by: Xin Long Signed-off-by: Hui Peng Fixes: f952be79cebd ("sctp: introduce struct sctp_stream_out_ext") Link: https://patch.msgid.link/20260930044611.204739-1-benquike@gmail.com Signed-off-by: Jakub Kicinski --- net/sctp/socket.c | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/net/sctp/socket.c b/net/sctp/socket.c index 4652fd90d9a6..e35692dd8d30 100644 --- a/net/sctp/socket.c +++ b/net/sctp/socket.c @@ -7569,11 +7569,7 @@ static int sctp_getsockopt_pr_streamstatus(struct sock *sk, int len, /* Not allocated yet, means all stats are 0 */ params.sprstat_abandoned_unsent = 0; params.sprstat_abandoned_sent = 0; - retval = 0; - goto out; - } - - if (policy == SCTP_PR_SCTP_ALL) { + } else if (policy == SCTP_PR_SCTP_ALL) { params.sprstat_abandoned_unsent = 0; params.sprstat_abandoned_sent = 0; for (policy = 0; policy <= SCTP_PR_INDEX(MAX); policy++) { From 0984ebc631792acf8fff105112ee74a6b8f1efcc Mon Sep 17 00:00:00 2001 From: Sabrina Dubroca Date: Mon, 5 Oct 2026 15:50:02 +0200 Subject: [PATCH 283/367] strparser: make sure __strp_recv isn't running before tearing down the parser The comment above strp_done() claims that if strp is stopped, strp_recv will no longer be called. That's only true if the caller has a mechanism (eg locking) to guarantee that strp_recv() calls that started before strp_stop() have completed by the time we call strp_done(). This adds a dummy lock_sock/release_sock pair to guarantee that any in-flight strp_recv() (which runs under either bh_lock_sock or lock_sock, depending if it's called from ->sk_data_ready or strp_work) has completed. Only espintcp can be affected by this race condition, but this patch makes sure no future user of strp can have the bug. This could crash on strp->sk ("general mode" of strp), but this mode has been here for 9 years and has never been used. It'll be gone soon, no point worrying about it. Switch the stopped/paused/etc bits to u8's to avoid races between strp_stop() and strp_pause/unpause(). A reproducer has been published and turns the possible UAF into an exploit [1]. Reported-by: Hyunwoo Kim Link: https://lore.kernel.org/all/aZLn2Faeg1FB7XOf@v4bel/ Link: https://lore.kernel.org/all/aZgpkyTDU3aXe_V0@v4bel/ Link: https://github.com/m0x41nos/RustyTux [1] Fixes: e27cca96cd68 ("xfrm: add espintcp (RFC 8229)") Cc: stable@vger.kernel.org Signed-off-by: Sabrina Dubroca Reviewed-by: Hyunwoo Kim Link: https://patch.msgid.link/425d9d926709b542ed5331110c19fee4aee1f28d.1791206940.git.sd@queasysnail.net Signed-off-by: Jakub Kicinski --- Documentation/networking/strparser.rst | 2 +- include/net/strparser.h | 10 +++++----- net/strparser/strparser.c | 6 +++++- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/Documentation/networking/strparser.rst b/Documentation/networking/strparser.rst index 372106b61e65..d0804c88ad0a 100644 --- a/Documentation/networking/strparser.rst +++ b/Documentation/networking/strparser.rst @@ -77,7 +77,7 @@ Functions strp_done is called to release any resources held by the stream parser instance. This must be called after the stream processor - has been stopped. + has been stopped. The socket lock must not be held. :: diff --git a/include/net/strparser.h b/include/net/strparser.h index 0ed73e364faa..881b7ae34498 100644 --- a/include/net/strparser.h +++ b/include/net/strparser.h @@ -91,11 +91,11 @@ static inline struct strp_msg *strp_msg(struct sk_buff *skb) struct strparser { struct sock *sk; - u32 stopped : 1; - u32 paused : 1; - u32 aborted : 1; - u32 interrupted : 1; - u32 unrecov_intr : 1; + u8 stopped; + u8 paused; + u8 aborted; + u8 interrupted; + u8 unrecov_intr; struct sk_buff **skb_nextp; struct sk_buff *skb_head; diff --git a/net/strparser/strparser.c b/net/strparser/strparser.c index a23f4b4dfc67..d5b17d099a0e 100644 --- a/net/strparser/strparser.c +++ b/net/strparser/strparser.c @@ -505,12 +505,16 @@ void strp_unpause(struct strparser *strp) EXPORT_SYMBOL_GPL(strp_unpause); /* strp must already be stopped so that strp_recv will no longer be called. - * Note that strp_done is not called with the lower socket held. + * Note that strp_done must not be called with the lower socket held. */ void strp_done(struct strparser *strp) { WARN_ON(!strp->stopped); + lock_sock(strp->sk); + /* sync with pending strp_recv */ + release_sock(strp->sk); + cancel_delayed_work_sync(&strp->msg_timer_work); cancel_work_sync(&strp->work); From 5857e5a196b069766a39ceb754736326a43bc6bc Mon Sep 17 00:00:00 2001 From: Justin Chen Date: Wed, 30 Sep 2026 16:06:34 -0700 Subject: [PATCH 284/367] net: bcmgenet: if UMAC was suspended in SW_RESET, restore it to SW_RESET When the revised suspend/resume sequence was introduced this led to an edge case where the TX is left disabled in the following sequence. 1. phy link is down, so UMAC is held in reset and then network interface is WoL enabled 2. Enter suspend, bcmgenet_wol_power_down_cfg() enables UMAC_RX since MAC is in SW_RESET 4. Enter resume, UMAC_RX is left enabled. Since we only enable UMAC_TX and UMAC_RX in SW_RESET. The UMAC_TX is never enabled again on link up. The issue was hit in production and I verified the sequence keeps the TX disabled in my standalone test. I tested on an internal development board with bcm77122, but should be reproducible on any genet HW that supports power management. Fixes: 254f3239dd07 ("net: bcmgenet: revise suspend/resume") Signed-off-by: Justin Chen Reviewed-by: Florian Fainelli Reviewed-by: Nicolai Buchwitz Link: https://patch.msgid.link/20260930230634.3132729-1-justin.chen@broadcom.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/broadcom/genet/bcmgenet_wol.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/drivers/net/ethernet/broadcom/genet/bcmgenet_wol.c b/drivers/net/ethernet/broadcom/genet/bcmgenet_wol.c index 96d5d4f7f51f..984432952963 100644 --- a/drivers/net/ethernet/broadcom/genet/bcmgenet_wol.c +++ b/drivers/net/ethernet/broadcom/genet/bcmgenet_wol.c @@ -253,6 +253,17 @@ int bcmgenet_wol_power_up_cfg(struct bcmgenet_priv *priv, reg = bcmgenet_umac_readl(priv, UMAC_CMD); reg &= ~CMD_CRC_FWD; bcmgenet_umac_writel(priv, reg, UMAC_CMD); + + /* + * Mirror wol_power_down_cfg(). If only UMAC_RX + * is enabled, then we must place the UMAC back + * into SW_RESET. + */ + reg = bcmgenet_umac_readl(priv, UMAC_CMD); + if ((reg & CMD_RX_EN) && !(reg & CMD_TX_EN)) { + reg |= CMD_SW_RESET; + bcmgenet_umac_writel(priv, reg, UMAC_CMD); + } spin_unlock_bh(&priv->reg_lock); /* Resume link status tracking */ From 23609bce9e1de525d1d0e73fc68c6e7971d0b49e Mon Sep 17 00:00:00 2001 From: Haishuang Yan Date: Wed, 30 Sep 2026 20:48:18 +0800 Subject: [PATCH 285/367] pfcp: make sure the SEID is linear before reading it pfcp_encap_recv() only makes sure that the UDP header and the 4 byte PFCP header are in the linear area. When the S flag is set, pfcp_session_recv() then reads the 8 byte SEID that follows, which is not covered by the pskb_may_pull() check. A short PFCP packet with the S flag set, or one whose session header lies in a fragment, therefore makes pfcp_session_recv() read beyond the end of the packet data, and whatever it finds there is stored in the tunnel metadata that flower later classifies on. Pull up to the end of the SEID before reading it, and drop packets that are too short to contain it. Reload the header pointer afterwards since pskb_may_pull() may reallocate the skb head. Use offsetofend() rather than sizeof(struct pfcphdr_session): the structure is not packed, so its size is 16 bytes because of the alignment of the __be64 member, while the header on the wire is only 12 bytes, and valid session messages would be dropped. Fixes: 6dd514f48110 ("pfcp: always set pfcp metadata") Signed-off-by: Haishuang Yan Link: https://patch.msgid.link/20260930124818.114224-3-yanhaishuang@cmss.chinamobile.com Signed-off-by: Jakub Kicinski --- drivers/net/pfcp.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/net/pfcp.c b/drivers/net/pfcp.c index 3ecd43cafdf0..51c67c914fad 100644 --- a/drivers/net/pfcp.c +++ b/drivers/net/pfcp.c @@ -65,6 +65,13 @@ static int pfcp_encap_recv(struct sock *sk, struct sk_buff *skb) goto drop; unparsed = pfcp_hdr(skb); + if (unparsed->flags & PFCP_SEID_FLAG) { + if (unlikely(!pskb_may_pull(skb, PFCP_HLEN + + offsetofend(struct pfcphdr_session, + seid)))) + goto drop; + unparsed = pfcp_hdr(skb); + } ip_tunnel_flags_zero(flags); tun_dst = udp_tun_rx_dst(skb, sk->sk_family, flags, 0, From a9cd14bfb0c113f1cd77be4349784663bec71bb4 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Sat, 26 Sep 2026 21:13:25 +0200 Subject: [PATCH 286/367] arm64: irq: exclude the softirq stack switch from KCOV On IRQ exit, __irq_exit_rcu() drops HARDIRQ_OFFSET before calling invoke_softirq(). The arm64 do_softirq_own_stack() wrapper and its ____do_softirq() trampoline run before __do_softirq() establishes softirq context, so KCOV records their PCs in the interrupted task's coverage buffer. They also run outside softirq accounting when local_bh_enable() reaches do_softirq(). The IRQ-exit coverage makes the KCOV boot selftest fail even after the scheduler and timer coverage leaks are suppressed. The generic softirq.o is already excluded from KCOV, but that exclusion does not cover the separately compiled arm64 wrappers. Exclude irq.o from KCOV instrumentation, as is already done for the arm64 entry code. This covers both wrappers even with compilers that lack the no_sanitize_coverage attribute. The softirq action functions remain instrumented for explicit remote coverage. Fixes: 8eb858c44b98 ("arm64: run softirqs on the per-CPU IRQ stack") Cc: stable@vger.kernel.org Assisted-by: LLM Signed-off-by: Karl Mehltretter Signed-off-by: Will Deacon --- arch/arm64/kernel/Makefile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/arch/arm64/kernel/Makefile b/arch/arm64/kernel/Makefile index d2690c3ec528..ea66339435bf 100644 --- a/arch/arm64/kernel/Makefile +++ b/arch/arm64/kernel/Makefile @@ -25,6 +25,9 @@ KASAN_SANITIZE_stacktrace.o := n KCOV_INSTRUMENT_entry-common.o := n KCOV_INSTRUMENT_idle.o := n +# Softirq stack switching can run outside interrupt context. +KCOV_INSTRUMENT_irq.o := n + # Object file lists. obj-y := debug-monitors.o entry.o irq.o fpsimd.o \ entry-common.o process.o ptrace.o \ From f394cbd0980c95c9173f8551a5eb4c4f28d598eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Roux?= Date: Tue, 6 Oct 2026 10:37:51 +0200 Subject: [PATCH 287/367] wifi: mac80211: don't estimate airtime for unsupported rate widths MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ieee80211_fill_rate_info() copies the driver's rate_info bandwidth into the rx_status as is, but ieee80211_get_rate_duration() only handles 20/40/80/160/320 MHz. When a driver reports another width, e.g. an HE/EHT RU allocation, it hits WARN_ON_ONCE() and returns 0 without setting *overhead. ieee80211_rate_expected_tx_airtime() then goes on using the uninitialized overhead, as Dan's static checker report points out. Seen on a Microsoft Surface Pro 11 (WCN7850, ath12k) while NetworkManager dumps station info: WARNING: net/mac80211/airtime.c:532 at ieee80211_get_rate_duration.isra.0+0xc0/0x180 [mac80211] ieee80211_get_rate_duration.isra.0+0xc0/0x180 [mac80211] sta_estimate_expected_throughput.isra.0+0x38/0x64 [mac80211] sta_set_sinfo+0x7fc/0x8e4 [mac80211] ieee80211_dump_station+0x4c/0x84 [mac80211] nl80211_dump_station+0x594/0x7e8 [cfg80211] Only accept widths that ieee80211_get_rate_duration() handles in ieee80211_fill_rate_info(). Other widths then give no estimate instead of a warning. Also return 0 from ieee80211_rate_expected_tx_airtime() when no duration could be computed, as ieee80211_calc_rx_airtime() already does. Tested on the Surface Pro 11 with next-20260929: Wi-Fi works and "expected throughput" is still reported (about 1049 Mbit/s for a 1297 Mbit/s HE 160 MHz tx rate). The warning itself is rare (twice in several days of use), so this does not prove it is gone, only that the change does not break the normal path. Fixes: 094dc1619cb0 ("wifi: mac80211: factor out part of ieee80211_calc_expected_tx_airtime") Reported-by: Dan Carpenter Closes: https://lore.kernel.org/all/aneGlCMVnqRhlf6p@stanley.mountain/ Assisted-by: LLM Signed-off-by: François Roux Link: https://patch.msgid.link/20261006083751.4015-1-franzelfranzel@gmail.com Signed-off-by: Johannes Berg --- net/mac80211/airtime.c | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/net/mac80211/airtime.c b/net/mac80211/airtime.c index 0c54cdbd753c..a4e1f33c0377 100644 --- a/net/mac80211/airtime.c +++ b/net/mac80211/airtime.c @@ -632,6 +632,22 @@ static bool ieee80211_fill_rate_info(struct ieee80211_hw *hw, if (!ri || !sband) return false; + /* + * ieee80211_get_rate_duration() only handles these widths. Drivers + * may also report e.g. HE/EHT RU allocations, which cannot be used + * to estimate airtime here. + */ + switch (ri->bw) { + case RATE_INFO_BW_20: + case RATE_INFO_BW_40: + case RATE_INFO_BW_80: + case RATE_INFO_BW_160: + case RATE_INFO_BW_320: + break; + default: + return false; + } + stat->bw = ri->bw; stat->nss = ri->nss; stat->rate_idx = ri->mcs; @@ -770,6 +786,8 @@ u32 ieee80211_rate_expected_tx_airtime(struct ieee80211_hw *hw, return ieee80211_calc_rx_airtime(hw, &stat, len) * 1024; duration = ieee80211_get_rate_duration(hw, &stat, &overhead); + if (!duration) + return 0; /* * Assume that HT/VHT transmission on any AC except VO will From 08a3f02fea636e97a7422d1512f76dacdb5b0887 Mon Sep 17 00:00:00 2001 From: Michele Dionisio Date: Thu, 24 Sep 2026 14:30:27 +0200 Subject: [PATCH 288/367] wifi: brcmfmac: fix P2P device removal race in brcmf_detach() When the driver is removed while the user space process that created the P2P device (e.g. wpa_supplicant) is still exiting, the P2P device interface is removed twice and the kernel crashes. The two paths are: - wpa_supplicant, on exit, sends NL80211_CMD_DEL_INTERFACE for the P2P device. nl80211_del_interface() holds RTNL and the wiphy mutex and calls brcmf_p2p_del_vif(), which disables discovery and waits up to 1.5 s for BRCMF_E_IF_DEL. Then it calls brcmf_remove_interface(), which unregisters the wdev and frees the vif and the ifp. - brcmf_detach() sets the bus down, so BRCMF_E_IF_DEL never arrives, and calls brcmf_remove_interface(ifp, false) for the same ifp. brcmf_p2p_ifp_removed() reads ifp->vif and then blocks on rtnl_lock(). When brcmf_p2p_del_vif() releases RTNL, it calls cfg80211_unregister_wdev() on the wdev that was already unregistered and freed. The second list_del_rcu() of wdev->list faults on LIST_POISON2: brcmfmac: brcmf_p2p_del_vif delete P2P vif brcmfmac: brcmf_p2p_deinit_discovery enter brcmfmac: brcmf_p2p_del_vif P2P: GO_NEG_PHASE status cleared brcmfmac: brcmf_detach Enter brcmfmac: brcmf_bus_change_state 1 -> 0 brcmfmac: brcmf_remove_interface Enter, bsscfgidx=1, ifidx=0 brcmfmac: brcmf_del_if Enter, bsscfgidx=1, ifidx=0 brcmfmac: brcmf_p2p_ifp_removed P2P: device interface removed [1.5 s later, brcmf_p2p_del_vif() timed out] brcmfmac: brcmf_remove_interface Enter, bsscfgidx=1, ifidx=0 brcmfmac: brcmf_del_if Enter, bsscfgidx=1, ifidx=0 brcmfmac: brcmf_p2p_ifp_removed P2P: device interface removed Unable to handle kernel paging request at virtual address dead000000000122 Internal error: Oops: 96000044 [#1] PREEMPT_RT SMP pc : _cfg80211_unregister_wdev+0x6c/0x264 [cfg80211] Call trace: _cfg80211_unregister_wdev+0x6c/0x264 [cfg80211] cfg80211_unregister_wdev+0x10/0x1c [cfg80211] brcmf_p2p_ifp_removed+0x84/0xb0 [brcmfmac] brcmf_remove_interface+0x1f4/0x254 [brcmfmac] brcmf_detach+0x88/0x180 [brcmfmac] brcmf_sdio_remove+0x90/0x7b0 [brcmfmac] brcmf_sdiod_remove+0x20/0xa0 [brcmfmac] brcmf_ops_sdio_remove+0xbc/0x12c [brcmfmac] sdio_bus_remove+0x38/0x144 device_release_driver_internal+0x1e8/0x2c0 device_release_driver+0x14/0x20 brcmf_sdio_bus_remove+0x2c/0x40 [brcmfmac] brcmf_fwvid_unregister_vendor+0xd8/0x170 [brcmfmac] __exit_compat+0x18/0x418 [brcmfmac_cyw] __arm64_sys_delete_module+0x1ac/0x244 The log was taken with debug=0x406 on an i.MX 8M Plus board with a CYW55513 (Sona IF513) on SDIO. The driver is the Ezurio backport of brcmfmac from v6.18.22 on a 5.4-rt kernel. The code involved is the same in mainline. To reproduce: 1. Start wpa_supplicant on wlan0 and let it associate. It creates the P2P device (iw dev shows "type P2P-device"). 2. Stop wpa_supplicant and remove the driver without waiting for wpa_supplicant to exit: kill $(pidof wpa_supplicant) rmmod brcmfmac_cyw brcmfmac The crash is reproducible on that board. For the interface without a netdev (the P2P device), take RTNL and the wiphy mutex in brcmf_detach() before reading iflist, and remove it with locked=true. If brcmf_p2p_del_vif() runs first, brcmf_detach() finds the slot empty. If brcmf_detach() runs first, nl80211 does not find the wdev any more. The interfaces with a netdev are removed as before, because brcmf_del_if() takes RTNL on its own for the primary interface. Fixes: 9831bcb987df ("brcmfmac: Deleting of p2p device is leaking memory.") Cc: stable@vger.kernel.org Assisted-by: claude-opus-5-5 Signed-off-by: Michele Dionisio Acked-by: Arend van Spriel Link: https://patch.msgid.link/20260924123027.4122909-2-michele.dionisio@gmail.com Signed-off-by: Johannes Berg --- .../broadcom/brcm80211/brcmfmac/core.c | 27 +++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.c index d2ae67985606..92ec269f43b2 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.c @@ -1489,8 +1489,31 @@ void brcmf_detach(struct device *dev) brcmf_bus_change_state(bus_if, BRCMF_BUS_DOWN); /* make sure primary interface removed last */ for (i = BRCMF_MAX_IFS - 1; i > -1; i--) { - if (drvr->iflist[i]) - brcmf_remove_interface(drvr->iflist[i], false); + struct brcmf_if *ifp = drvr->iflist[i]; + + if (!ifp) + continue; + + if (ifp->ndev) { + brcmf_remove_interface(ifp, false); + continue; + } + + /* The P2P device interface has no netdev. Its removal can + * race with NL80211_CMD_DEL_INTERFACE issued by a user space + * process that is exiting (e.g. wpa_supplicant), which ends + * in brcmf_p2p_del_vif() under RTNL and the wiphy mutex. If + * both paths remove the interface, the wdev is unregistered + * twice and the vif is used after being freed. Take the same + * locks and re-read iflist, so only one path removes it. + */ + rtnl_lock(); + wiphy_lock(drvr->wiphy); + ifp = drvr->iflist[i]; + if (ifp) + brcmf_remove_interface(ifp, true); + wiphy_unlock(drvr->wiphy); + rtnl_unlock(); } brcmf_bus_stop(drvr->bus_if); From 86c8360c97f66fa7830fd930e2dc69374007284b Mon Sep 17 00:00:00 2001 From: Richard Fitzgerald Date: Mon, 5 Oct 2026 16:52:34 +0100 Subject: [PATCH 289/367] ASoC: cs35l56: Add DAI for SDCA OT25 stream Add a DAI for the SDCA OT25 stream on SoundWire DP4. The SDCA-defined OT25 stream is an amp reference feedback, typically used for AEC. The firmware outputs this on DP4. The firmware owns all control registers for this stream so there are no mixer controls or any other configuration options. Signed-off-by: Richard Fitzgerald Link: https://patch.msgid.link/20261005155235.1386525-2-rf@opensource.cirrus.com Signed-off-by: Mark Brown --- include/sound/cs35l56.h | 1 + sound/soc/codecs/cs35l56-sdw.c | 9 ++++-- sound/soc/codecs/cs35l56.c | 54 ++++++++++++++++++++++++++++++++++ 3 files changed, 62 insertions(+), 2 deletions(-) diff --git a/include/sound/cs35l56.h b/include/sound/cs35l56.h index 45a5df574aa6..747e10ec1a27 100644 --- a/include/sound/cs35l56.h +++ b/include/sound/cs35l56.h @@ -312,6 +312,7 @@ struct snd_ctl_elem_value; #define CS35L56_SDW1_PLAYBACK_PORT 1 #define CS35L56_SDW1_CAPTURE_PORT 3 +#define CS35L56_OT25_CAPTURE_PORT 4 #define CS35L56_NUM_BULK_SUPPLIES 3 #define CS35L56_NUM_DSP_REGIONS 5 diff --git a/sound/soc/codecs/cs35l56-sdw.c b/sound/soc/codecs/cs35l56-sdw.c index 98bb4542b914..e10f04d677f4 100644 --- a/sound/soc/codecs/cs35l56-sdw.c +++ b/sound/soc/codecs/cs35l56-sdw.c @@ -265,11 +265,11 @@ static int cs35l56_sdw_read_prop(struct sdw_slave *peripheral) if (ret == 0) prop->clk_stop_mode1 = !!clock_stop_1; - ports = devm_kcalloc(cs35l56->base.dev, 2, sizeof(*ports), GFP_KERNEL); + ports = devm_kcalloc(cs35l56->base.dev, 3, sizeof(*ports), GFP_KERNEL); if (!ports) return -ENOMEM; - prop->source_ports = BIT(CS35L56_SDW1_CAPTURE_PORT); + prop->source_ports = BIT(CS35L56_SDW1_CAPTURE_PORT) | BIT(CS35L56_OT25_CAPTURE_PORT); prop->sink_ports = BIT(CS35L56_SDW1_PLAYBACK_PORT); prop->paging_support = true; prop->use_domain_irq = true; @@ -286,6 +286,11 @@ static int cs35l56_sdw_read_prop(struct sdw_slave *peripheral) ports[1].num = CS35L56_SDW1_CAPTURE_PORT; ports[1].type = SDW_DPN_FULL; ports[1].ch_prep_timeout = 10; + + /* DP4 - capture */ + ports[2].num = CS35L56_OT25_CAPTURE_PORT; + ports[2].type = SDW_DPN_FULL; + ports[2].ch_prep_timeout = 10; prop->src_dpn_prop = &ports[1]; dev_dbg(&peripheral->dev, "clock stop mode 1 supported: %s\n", diff --git a/sound/soc/codecs/cs35l56.c b/sound/soc/codecs/cs35l56.c index 35d210626627..279db3f13f1e 100644 --- a/sound/soc/codecs/cs35l56.c +++ b/sound/soc/codecs/cs35l56.c @@ -32,6 +32,7 @@ #include #include #include +#include #include #include #include @@ -324,6 +325,7 @@ static const struct snd_soc_dapm_widget cs35l56_dapm_widgets[] = { SND_SOC_DAPM_SIGGEN("VDDBMON ADC"), SND_SOC_DAPM_SIGGEN("VBSTMON ADC"), SND_SOC_DAPM_SIGGEN("TEMPMON ADC"), + SND_SOC_DAPM_SIGGEN("OT25 Reference"), SND_SOC_DAPM_INPUT("Calibrate"), }; @@ -388,6 +390,8 @@ static const struct snd_soc_dapm_route cs35l56_audio_map[] = { { "SDW1 Capture", NULL, "SDW1 TX2 Source" }, { "SDW1 Capture", NULL, "SDW1 TX3 Source" }, { "SDW1 Capture", NULL, "SDW1 TX4 Source" }, + + { "OT25", NULL, "OT25 Reference" }, }; static int cs35l56_dsp_event(struct snd_soc_dapm_widget *w, @@ -712,6 +716,37 @@ static int cs35l56_sdw_dai_set_stream(struct snd_soc_dai *dai, return 0; } +static int cs35l56_ot25_dai_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct cs35l56_private *cs35l56 = snd_soc_component_get_drvdata(dai->component); + struct sdw_stream_runtime *sdw_stream = snd_soc_dai_get_dma_data(dai, substream); + struct sdw_stream_config sconfig = { }; + struct sdw_port_config pconfig = { }; + int ret; + + dev_dbg(cs35l56->base.dev, "%s: rate %d\n", __func__, params_rate(params)); + + if (!cs35l56->base.init_done) + return -ENODEV; + + if (!sdw_stream) + return -EINVAL; + + snd_sdw_params_to_config(substream, params, &sconfig, &pconfig); + pconfig.num = CS35L56_OT25_CAPTURE_PORT; + + ret = sdw_stream_add_slave(cs35l56->sdw_peripheral, &sconfig, &pconfig, + 1, sdw_stream); + if (ret) { + dev_err(dai->dev, "Failed to add OT25 stream: %d\n", ret); + return ret; + } + + return 0; +} + static const struct snd_soc_dai_ops cs35l56_sdw_dai_ops = { .set_tdm_slot = cs35l56_sdw_dai_set_tdm_slot, .shutdown = cs35l56_sdw_dai_shutdown, @@ -720,6 +755,13 @@ static const struct snd_soc_dai_ops cs35l56_sdw_dai_ops = { .set_stream = cs35l56_sdw_dai_set_stream, }; +static const struct snd_soc_dai_ops cs35l56_ot25_dai_ops = { + .shutdown = cs35l56_sdw_dai_shutdown, + .hw_params = cs35l56_ot25_dai_hw_params, + .hw_free = cs35l56_sdw_dai_hw_free, + .set_stream = cs35l56_sdw_dai_set_stream, +}; + static struct snd_soc_dai_driver cs35l56_dai[] = { { .name = "cs35l56-asp1", @@ -768,6 +810,18 @@ static struct snd_soc_dai_driver cs35l56_dai[] = { .symmetric_rate = 1, .ops = &cs35l56_sdw_dai_ops, }, + { + .name = "cs35l56-ot25", + .id = 3, + .capture = { + .stream_name = "OT25", + .channels_min = 1, + .channels_max = 2, + .rates = CS35L56_RATES, + .formats = CS35L56_TX_FORMATS, + }, + .ops = &cs35l56_ot25_dai_ops, + }, }; static int cs35l56_write_cal(struct cs35l56_private *cs35l56) From 4b7f393a01f88e8d03cba94cf4948652de14789b Mon Sep 17 00:00:00 2001 From: Richard Fitzgerald Date: Mon, 5 Oct 2026 16:52:35 +0100 Subject: [PATCH 290/367] ASoC: sdw_utils: Switch CS35L56/57/62/63 to use OT25 DAI Change the CS35L56/57/62/63 entries in codec_info_list[] to use the OT25 DAI for amp feedback. The AMP feedback was incorrectly using a DAI connected to DP3, but that is reserved for SDCA companion amp. The correct amp output is DP4, which is the SDCA OT25 feedback output. Fixes: 898cd43bde307 ("ASoC: intel: sof_sdw: Add support for CS35L63 into machine driver") Signed-off-by: Richard Fitzgerald Link: https://patch.msgid.link/20261005155235.1386525-3-rf@opensource.cirrus.com Signed-off-by: Mark Brown --- sound/soc/sdw_utils/soc_sdw_utils.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/sound/soc/sdw_utils/soc_sdw_utils.c b/sound/soc/sdw_utils/soc_sdw_utils.c index d2eeef4931c6..eda7c567f71e 100644 --- a/sound/soc/sdw_utils/soc_sdw_utils.c +++ b/sound/soc/sdw_utils/soc_sdw_utils.c @@ -808,7 +808,7 @@ struct asoc_sdw_codec_info codec_info_list[] = { }, { .direction = {false, true}, - .dai_name = "cs35l56-sdw1c", + .dai_name = "cs35l56-ot25", .dai_type = SOC_SDW_DAI_TYPE_AMP, .dailink = {SOC_SDW_UNUSED_DAI_ID, SOC_SDW_AMP_IN_DAI_ID}, }, @@ -836,7 +836,7 @@ struct asoc_sdw_codec_info codec_info_list[] = { }, { .direction = {false, true}, - .dai_name = "cs35l56-sdw1c", + .dai_name = "cs35l56-ot25", .dai_type = SOC_SDW_DAI_TYPE_AMP, .dailink = {SOC_SDW_UNUSED_DAI_ID, SOC_SDW_AMP_IN_DAI_ID}, }, @@ -864,7 +864,7 @@ struct asoc_sdw_codec_info codec_info_list[] = { }, { .direction = {false, true}, - .dai_name = "cs35l56-sdw1c", + .dai_name = "cs35l56-ot25", .dai_type = SOC_SDW_DAI_TYPE_AMP, .dailink = {SOC_SDW_UNUSED_DAI_ID, SOC_SDW_AMP_IN_DAI_ID}, }, @@ -892,7 +892,7 @@ struct asoc_sdw_codec_info codec_info_list[] = { }, { .direction = {false, true}, - .dai_name = "cs35l56-sdw1c", + .dai_name = "cs35l56-ot25", .dai_type = SOC_SDW_DAI_TYPE_AMP, .dailink = {SOC_SDW_UNUSED_DAI_ID, SOC_SDW_AMP_IN_DAI_ID}, }, From 611857adc9e7b0ee1239a0baf38498922ca5a12e Mon Sep 17 00:00:00 2001 From: Fan Wu Date: Mon, 5 Oct 2026 20:04:14 -0400 Subject: [PATCH 291/367] drm/xe/i2c: cancel the client work on remove xe_i2c_notifier() stores the DesignWare adapter in i2c->adapter and schedules i2c->work when the adapter is registered under the xe I2C platform device, and xe_i2c_client_work() then instantiates the AMC client device on that adapter. xe_i2c_remove() tears down the AMC, unregisters the client devices, the bus notifier and the adapter platform device, but it never drains i2c->work. A work item that is still queued or running when the adapter is unregistered dereferences i2c->adapter in i2c_new_client_device() after platform_device_unregister() has released the adapter. The work item is also embedded in the devm-allocated struct xe_i2c, so a work item still queued after the drm device devm unwind frees that allocation runs its callback on freed memory. The bus notifier is the only thing that schedules this work, and it is unregistered after the client devices. An instance that is still queued when the teardown runs can therefore write i2c->client[XE_I2C_CLIENT_AMC] while the loop is unregistering and clearing the same array, and an AMC client it instantiates late is only cleaned up by the adapter's own child sweep in i2c_del_adapter(). Move bus_unregister_notifier() in front of the client teardown loop and cancel the work right after it, so no new instance can be scheduled and a queued instance is drained before the client array is touched. A running instance still finds a live adapter, since the adapter is unregistered later. Fixes: f0e53aadd702 ("drm/xe: Support for I2C attached MCUs") Link: https://lore.kernel.org/intel-xe/20260912085932.101598-1-fanwu01@zju.edu.cn/ Cc: stable@vger.kernel.org Assisted-by: LLM, in-house static analysis tool Co-developed-by: Song Li Signed-off-by: Song Li Signed-off-by: Fan Wu Reviewed-by: Heikki Krogerus Link: https://patch.msgid.link/20260928013030.612592-1-fanwu01@zju.edu.cn Link: https://patch.msgid.link/20261006000413.318561-2-rodrigo.vivi@intel.com Signed-off-by: Rodrigo Vivi (cherry picked from commit a23f27d5ec11c3f418c5e5531eea34ccda2b5b20) Signed-off-by: Rodrigo Vivi --- drivers/gpu/drm/xe/xe_i2c.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/xe/xe_i2c.c b/drivers/gpu/drm/xe/xe_i2c.c index f4f381988289..b82caaff80d7 100644 --- a/drivers/gpu/drm/xe/xe_i2c.c +++ b/drivers/gpu/drm/xe/xe_i2c.c @@ -324,12 +324,15 @@ static void xe_i2c_remove(void *data) xe_i2c_irq_reset(xe); xe_amc_exit(i2c); + /* Stop the notifier from arming the client work before teardown. */ + bus_unregister_notifier(&i2c_bus_type, &i2c->bus_notifier); + cancel_work_sync(&i2c->work); + for (i = 0; i < XE_I2C_MAX_CLIENTS; i++) { i2c_unregister_device(i2c->client[i]); i2c->client[i] = NULL; } - bus_unregister_notifier(&i2c_bus_type, &i2c->bus_notifier); xe_i2c_unregister_adapter(i2c); xe->i2c = NULL; } From 7587fc10cba6c4a8d4c30ba480200370bc68dc74 Mon Sep 17 00:00:00 2001 From: Simon Trimmer Date: Wed, 7 Oct 2026 13:08:35 +0000 Subject: [PATCH 292/367] ASoC: amd: acp: Add DMI override for ASUS EXPERTBOOK AM7406CKA A BIOS for this SKU has been observed with an incorrect acp-audio-config-flag, add the override so that it is forced to the correct value. Signed-off-by: Simon Trimmer Link: https://patch.msgid.link/20261007130835.102325-1-simont@opensource.cirrus.com Signed-off-by: Mark Brown --- sound/soc/amd/acp-config.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/sound/soc/amd/acp-config.c b/sound/soc/amd/acp-config.c index b3b73096ac74..3d48bb0f100f 100644 --- a/sound/soc/amd/acp-config.c +++ b/sound/soc/amd/acp-config.c @@ -30,6 +30,13 @@ static const struct dmi_system_id acp70_acpi_flag_override_table[] = { DMI_MATCH(DMI_PRODUCT_NAME, "HN7306EA"), }, }, + { + /* ASUS EXPERTBOOK AM7406CKA (Kraken Point, ACP 7.0) */ + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "ASUSTeK COMPUTER INC."), + DMI_MATCH(DMI_PRODUCT_NAME, "ASUS EXPERTBOOK AM7406CKA"), + }, + }, { /* ASUS Zenbook S16 UM5606GA (Strix Point, ACP 7.0) */ .matches = { From 9ca0d0d0249230135dbf6fa1b9168e260151babc Mon Sep 17 00:00:00 2001 From: Simon Trimmer Date: Wed, 7 Oct 2026 13:08:57 +0000 Subject: [PATCH 293/367] ASoC: amd: acp: Add more ACP7.0 match entries for Cirrus Logic parts This adds the following SoundWire configuration: cs42l43b link 0 UID 1 cs35l57 link 0 UID 0 cs35l57 link 0 UID 1 Signed-off-by: Simon Trimmer Link: https://patch.msgid.link/20261007130857.102432-1-simont@opensource.cirrus.com Signed-off-by: Mark Brown --- sound/soc/amd/acp/amd-acp70-acpi-match.c | 35 ++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/sound/soc/amd/acp/amd-acp70-acpi-match.c b/sound/soc/amd/acp/amd-acp70-acpi-match.c index 2a9da6de7bbc..c2f0479301eb 100644 --- a/sound/soc/amd/acp/amd-acp70-acpi-match.c +++ b/sound/soc/amd/acp/amd-acp70-acpi-match.c @@ -335,6 +335,27 @@ static const struct snd_soc_acpi_adr_device cs42l43b_l0u1_adr[] = { } }; +static const struct snd_soc_acpi_adr_device cs42l43b_l0u1_cs35l57x2_l0u01_adr[] = { + { + .adr = 0x00003101FA2A3B01ull, + .num_endpoints = ARRAY_SIZE(cs42l43_endpoints), + .endpoints = cs42l43_endpoints, + .name_prefix = "cs42l43" + }, + { + .adr = 0x00003001FA355701ull, + .num_endpoints = 1, + .endpoints = &spk_l_endpoint, + .name_prefix = "AMP1" + }, + { + .adr = 0x00003101FA355701ull, + .num_endpoints = 1, + .endpoints = &spk_r_endpoint, + .name_prefix = "AMP2" + }, +}; + static const struct snd_soc_acpi_adr_device cs42l43_l1u0_cs35l56x4_l1u0123_adr[] = { { .adr = 0x00013001FA424301ull, @@ -438,6 +459,15 @@ static const struct snd_soc_acpi_link_adr acp70_cs42l43_l0u0_cs35l56x4_l1u3210[] {} }; +static const struct snd_soc_acpi_link_adr acp70_cs42l43b_l0u1_cs35l57x2_l0u01[] = { + { + .mask = BIT(0), + .num_adr = ARRAY_SIZE(cs42l43b_l0u1_cs35l57x2_l0u01_adr), + .adr_d = cs42l43b_l0u1_cs35l57x2_l0u01_adr, + }, + {} +}; + static const struct snd_soc_acpi_link_adr acp70_cs42l43_l1u0_cs35l56x4_l1u0123[] = { { .mask = BIT(1), @@ -820,6 +850,11 @@ struct snd_soc_acpi_mach snd_soc_acpi_amd_acp70_sdw_machines[] = { .links = acp70_cs42l45_l1u0_cs35l63x2_l0u01, .drv_name = "amd_sdw", }, + { + .link_mask = BIT(0), + .links = acp70_cs42l43b_l0u1_cs35l57x2_l0u01, + .drv_name = "amd_sdw", + }, { .link_mask = BIT(1), .links = acp70_cs42l43_l1u0_cs35l56x4_l1u0123, From 405290be962b32eec842776fbe3bdf81f15b1676 Mon Sep 17 00:00:00 2001 From: Ziyi Guo Date: Tue, 6 Oct 2026 14:02:00 +0000 Subject: [PATCH 294/367] riscv: futex: untag the user pointer before the atomic access access_ok() checks untagged_addr(uaddr), but arch_futex_atomic_op_inuser() and futex_atomic_cmpxchg_inatomic() hand the raw uaddr to the inline asm amoswap/amoadd/.../lr.w+sc.w through the "+m" (*uaddr) operand. When the tagged address ABI is enabled (CONFIG_RISCV_ISA_SUPM, prctl PR_SET_TAGGED_ADDR_CTRL with PMLEN != 0), those two addresses differ: a user pointer whose top PMLEN bits hold a tag passes access_ok() after untagging, but the atomic is then performed on the still-tagged raw address. Supervisor-mode data accesses are not subject to the U-mode pointer masking (that is governed by menvcfg.PMM, which the kernel does not set), so hardware does not strip the tag for the kernel's own access. With Sv57 and PMLEN=16 the tag bits overlap the canonical-address bits, so a tagged pointer can name a canonical kernel virtual address (e.g. in the linear map) whose untagged form is a valid user address. An unprivileged process can thus make FUTEX_WAKE_OP perform an atomic read-modify-write on an arbitrary kernel address, with the matching FUTEX_OP_CMP_* result serving as a read oracle. get_user()/put_user()/raw_copy_{to,from}_user() already untag the pointer after the access_ok() check; do the same in the futex helpers so the atomic operates on the address that was actually validated. Fixes: 2e1743085887 ("riscv: Add support for the tagged address ABI") Reviewed-by: Samuel Holland Signed-off-by: Ziyi Guo Link: https://patch.msgid.link/20261006140200.877263-1-guoziyi114@gmail.com Cc: stable@vger.kernel.org Signed-off-by: Paul Walmsley --- arch/riscv/include/asm/futex.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/arch/riscv/include/asm/futex.h b/arch/riscv/include/asm/futex.h index 90c86b115e00..6f3bc9bdb85e 100644 --- a/arch/riscv/include/asm/futex.h +++ b/arch/riscv/include/asm/futex.h @@ -40,6 +40,7 @@ arch_futex_atomic_op_inuser(int op, int oparg, int *oval, u32 __user *uaddr) if (!access_ok(uaddr, sizeof(u32))) return -EFAULT; + uaddr = untagged_addr(uaddr); switch (op) { case FUTEX_OP_SET: @@ -82,6 +83,7 @@ futex_atomic_cmpxchg_inatomic(u32 *uval, u32 __user *uaddr, if (!access_ok(uaddr, sizeof(u32))) return -EFAULT; + uaddr = untagged_addr(uaddr); __enable_user_access(); __asm__ __volatile__ ( From 298510bbc7c3017c0faf481c6ea66f1065b657b4 Mon Sep 17 00:00:00 2001 From: Zongmin Zhou Date: Thu, 17 Sep 2026 15:48:43 +0800 Subject: [PATCH 295/367] riscv: errata: select RISCV_ALTERNATIVE_EARLY for THEAD GHOSTWRITE errata_probe_ghostwrite() only marks the CPU as vulnerable when it is called at the RISCV_ALTERNATIVES_EARLY_BOOT stage, and the mitigation in ghostwrite_enable_mitigation() only takes effect if the vulnerable state was recorded before the boot alternatives are patched. The early boot stage is only executed when CONFIG_RISCV_ALTERNATIVE_EARLY is enabled, which currently is only selected by ERRATA_THEAD_MAE. In a kernel built with ERRATA_THEAD_GHOSTWRITE=y but ERRATA_THEAD_MAE=n, the early alternatives never run, so ghostwrite_state is never set to VULNERABLE: ghostwrite_enable_mitigation() returns false, xtheadvector remains enabled, and sysfs reports "Not affected" on a vulnerable T-Head C9xx CPU. Select RISCV_ALTERNATIVE_EARLY from ERRATA_THEAD_GHOSTWRITE so that the vulnerability is detected in the early boot stage and the default mitigation is applied. Fixes: 4bf97069239b ("riscv: Add ghostwrite vulnerability") Signed-off-by: Zongmin Zhou Link: https://patch.msgid.link/20260917074843.52716-1-min_halo@163.com Cc: stable@vger.kernel.org Signed-off-by: Paul Walmsley --- arch/riscv/Kconfig.errata | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/riscv/Kconfig.errata b/arch/riscv/Kconfig.errata index a6aac8774f25..622ade116a2d 100644 --- a/arch/riscv/Kconfig.errata +++ b/arch/riscv/Kconfig.errata @@ -160,6 +160,7 @@ config ERRATA_THEAD_PMU config ERRATA_THEAD_GHOSTWRITE bool "Apply T-Head Ghostwrite errata" depends on ERRATA_THEAD && RISCV_ISA_XTHEADVECTOR + select RISCV_ALTERNATIVE_EARLY default y help The T-Head C9xx cores have a vulnerability in the xtheadvector From 07e23d0b6f58eec351e52058b9a0a8af7b019c09 Mon Sep 17 00:00:00 2001 From: Leo Yu-Chi Liang Date: Tue, 6 Oct 2026 21:52:29 -0700 Subject: [PATCH 296/367] riscv: fix NR_CPUS range leaking 64-bit default onto 32-bit builds rv32_defconfig is built in two stages: - the generic (64-bit-oriented) "defconfig" target runs first and resolves a concrete NR_CPUS value using the 64BIT default. - then 32-bit.config is merged on top via merge_config.sh, which does not touch NR_CPUS. The final "make olddefconfig" pass sees NR_CPUS=256 is still within the "2 512" range therefore never recalculates it against the 32BIT default of 32, silently keeping the leaked 64-bit-stage value. The NR_CPUS-scaled regions (percpu areas, ...etc) of the resulting CONFIG_NR_CPUS=256 on a 32-bit build bloats and does not fit in the percpu/stack layout for rv32 and hangs early boot on QEMU's virt machine. Fix this by explicitly specify the range for 32/64 and SBI_V01/!SBI_V01. Fixes: 0fa749771993 ("riscv: Raise default NR_CPUS for 64BIT to 256") Signed-off-by: Leo Yu-Chi Liang Link: https://patch.msgid.link/20261007045229.36114-1-leo.liang@sifive.com Signed-off-by: Paul Walmsley --- arch/riscv/Kconfig | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/arch/riscv/Kconfig b/arch/riscv/Kconfig index d6c2dbf8455c..5874ca30f4f6 100644 --- a/arch/riscv/Kconfig +++ b/arch/riscv/Kconfig @@ -453,9 +453,10 @@ config SMP config NR_CPUS int "Maximum number of CPUs (2-512)" depends on SMP - range 2 512 if !RISCV_SBI_V01 - range 2 32 if RISCV_SBI_V01 && 32BIT + range 2 512 if !RISCV_SBI_V01 && 64BIT + range 2 128 if !RISCV_SBI_V01 && 32BIT range 2 64 if RISCV_SBI_V01 && 64BIT + range 2 32 if RISCV_SBI_V01 && 32BIT default "32" if 32BIT default "64" if RISCV_SBI_V01 && 64BIT default "256" if !RISCV_SBI_V01 && 64BIT From adadf6df0bd55aa40ccbc6519fb443133e103227 Mon Sep 17 00:00:00 2001 From: Chen Pei Date: Thu, 20 Aug 2026 14:32:33 +0800 Subject: [PATCH 297/367] riscv: ptrace: reject CFI regset access when extensions are absent riscv_cfi_get() and riscv_cfi_set() do not check whether the Zicfilp or Zicfiss extensions are present. On systems without them, PTRACE_GETREGSET on REGSET_CFI still succeeds and returns a zeroed user_cfi_state, misleading debuggers into believing the register set is available, and PTRACE_SETREGSET silently accepts writes that have no effect (e.g. clearing SR_ELP). Reject the access with -EINVAL when neither branch landing pads nor shadow stack is available to userspace, using the same availability helpers as the prctl path. Fixes: 2af7c9cf021c ("riscv/ptrace: expose riscv CFI status and state via ptrace and in core files") Signed-off-by: Chen Pei Link: https://patch.msgid.link/20260820063233.2567-1-cp0613@linux.alibaba.com Cc: stable@vger.kernel.org Signed-off-by: Paul Walmsley --- arch/riscv/kernel/ptrace.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/arch/riscv/kernel/ptrace.c b/arch/riscv/kernel/ptrace.c index f336a183667e..566856e42f65 100644 --- a/arch/riscv/kernel/ptrace.c +++ b/arch/riscv/kernel/ptrace.c @@ -299,6 +299,9 @@ static int riscv_cfi_get(struct task_struct *target, struct user_cfi_state user_cfi; struct pt_regs *regs; + if (!is_user_lpad_enabled() && !is_user_shstk_enabled()) + return -EINVAL; + memset(&user_cfi, 0, sizeof(user_cfi)); regs = task_pt_regs(target); @@ -337,6 +340,9 @@ static int riscv_cfi_set(struct task_struct *target, struct user_cfi_state user_cfi; struct pt_regs *regs; + if (!is_user_lpad_enabled() && !is_user_shstk_enabled()) + return -EINVAL; + regs = task_pt_regs(target); ret = user_regset_copyin(&pos, &count, &kbuf, &ubuf, &user_cfi, 0, -1); From 59052a88ba5fe4a7bb34b3a0a42aa5d453d3bb97 Mon Sep 17 00:00:00 2001 From: Zongmin Zhou Date: Wed, 26 Aug 2026 19:57:11 +0800 Subject: [PATCH 298/367] riscv: ptrace: Zero-initialize regset buffers before copyin user_regset_copyin() only copies `count` bytes, so a PTRACE_SETREGSET request with a short iov_len leaves the rest of the stack buffer uninitialized. In riscv_vr_set() that garbage is validated and copied into the target's vstate, and in riscv_cfi_set() it can be stored as the target's shadow stack pointer. Zero both buffers, as the get path already does. Fixes: 9300f0043974 ("RISC-V: Add ptrace support for vectors") Fixes: 2af7c9cf021c ("riscv/ptrace: expose riscv CFI status and state via ptrace and in core files") Signed-off-by: Zongmin Zhou Link: https://patch.msgid.link/20260826115711.86861-1-min_halo@163.com Signed-off-by: Paul Walmsley --- arch/riscv/kernel/ptrace.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/arch/riscv/kernel/ptrace.c b/arch/riscv/kernel/ptrace.c index 566856e42f65..a0055743d3ba 100644 --- a/arch/riscv/kernel/ptrace.c +++ b/arch/riscv/kernel/ptrace.c @@ -230,6 +230,7 @@ static int riscv_vr_set(struct task_struct *target, return -ENODATA; /* Copy rest of the vstate except datap */ + memset(&ptrace_vstate, 0, sizeof(ptrace_vstate)); ret = user_regset_copyin(&pos, &count, &kbuf, &ubuf, &ptrace_vstate, 0, sizeof(struct __riscv_v_regset_state)); if (unlikely(ret)) @@ -345,6 +346,7 @@ static int riscv_cfi_set(struct task_struct *target, regs = task_pt_regs(target); + memset(&user_cfi, 0, sizeof(user_cfi)); ret = user_regset_copyin(&pos, &count, &kbuf, &ubuf, &user_cfi, 0, -1); if (ret) return ret; From 7744262f5d63f9faa4d59dec127ae1c8b2d56495 Mon Sep 17 00:00:00 2001 From: Leo Li Date: Tue, 29 Sep 2026 17:32:32 -0400 Subject: [PATCH 299/367] drm/amd/display: Fix direct scanout alpha on some DRM_FORMATs [Why] DM was declaring it's own list of has_alpha DRM formats to check against for enabling alpha blending, when the format->has_alpha already does that for us. The self-declared list missed some has_alpha formats, causing direct-scanout planes with such formats to appear opaque. [How] Drop the self-declared list, instead use the format->has_alpha field directly. Signed-off-by: Leo Li Reviewed-by: Harry Wentland Signed-off-by: Alex Deucher (cherry picked from commit 9ecdac91ed43d836683d5daa467f4907405420e2) Cc: stable@vger.kernel.org --- .../amd/display/amdgpu_dm/amdgpu_dm_plane.c | 20 +------------------ 1 file changed, 1 insertion(+), 19 deletions(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.c index e13b96358208..a2b8f7033616 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.c @@ -112,25 +112,7 @@ void amdgpu_dm_plane_fill_blending_from_plane_state(const struct drm_plane_state if (plane_state->pixel_blend_mode == DRM_MODE_BLEND_PREMULTI || plane_state->pixel_blend_mode == DRM_MODE_BLEND_COVERAGE) { - static const uint32_t alpha_formats[] = { - DRM_FORMAT_ARGB8888, - DRM_FORMAT_RGBA8888, - DRM_FORMAT_ABGR8888, - DRM_FORMAT_ARGB2101010, - DRM_FORMAT_ABGR2101010, - DRM_FORMAT_ARGB16161616, - DRM_FORMAT_ABGR16161616, - DRM_FORMAT_ARGB16161616F, - }; - uint32_t format = plane_state->fb->format->format; - unsigned int i; - - for (i = 0; i < ARRAY_SIZE(alpha_formats); ++i) { - if (format == alpha_formats[i]) { - *per_pixel_alpha = true; - break; - } - } + *per_pixel_alpha = plane_state->fb->format->has_alpha; if (*per_pixel_alpha && plane_state->pixel_blend_mode == DRM_MODE_BLEND_COVERAGE) *pre_multiplied_alpha = false; From 7f7c88dbf4d543f6703e53bdeed380e886733167 Mon Sep 17 00:00:00 2001 From: Jesse Zhang Date: Wed, 30 Sep 2026 14:21:19 +0800 Subject: [PATCH 300/367] drm/amdgpu/userq: only accept doorbell BOs as queue doorbell MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit amdgpu_userq_get_doorbell_index() pins any BO passed as doorbell_handle into the doorbell domain. For a regular BO, TTM moves it with ttm_bo_move_null() and its contents are lost. Since an importer on the same device gets the exporter's GEM object, a client can do this to a buffer another client shared with it. Reject BOs that were not created in the doorbell domain. v2: drop the error message, use a local abo variable (Christian) Fixes: f09c1e6077ab ("drm/amdgpu: generate doorbell index for userqueue") Reviewed-by: Christian König Signed-off-by: Jesse Zhang Signed-off-by: Alex Deucher (cherry picked from commit f7497bbc19db4821c62b9d97b31c9086feb2d917) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c index cc8e7af18834..9316423f9340 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c @@ -472,6 +472,7 @@ amdgpu_userq_get_doorbell_index(struct amdgpu_userq_mgr *uq_mgr, u64 doorbell_index; struct drm_gem_object *gobj; struct amdgpu_userq_obj *db_obj = db_info->db_obj; + struct amdgpu_bo *abo; int r, db_size; gobj = drm_gem_object_lookup(filp, db_info->doorbell_handle); @@ -480,7 +481,17 @@ amdgpu_userq_get_doorbell_index(struct amdgpu_userq_mgr *uq_mgr, return -EINVAL; } - db_obj->obj = amdgpu_bo_ref(gem_to_amdgpu_bo(gobj)); + /* + * Pinning a regular BO into the doorbell domain would discard its + * contents, possibly those of a buffer shared by another client. + */ + abo = gem_to_amdgpu_bo(gobj); + if (!(abo->preferred_domains & AMDGPU_GEM_DOMAIN_DOORBELL)) { + drm_gem_object_put(gobj); + return -EINVAL; + } + + db_obj->obj = amdgpu_bo_ref(abo); drm_gem_object_put(gobj); r = amdgpu_bo_reserve(db_obj->obj, true); From 76ceb6ed610cbf14341a9ba78c5c0b2199b9030a Mon Sep 17 00:00:00 2001 From: Jesse Zhang Date: Wed, 30 Sep 2026 14:21:19 +0800 Subject: [PATCH 301/367] drm/amdgpu/userq: return the memdup_user() error for the user MQD MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit mes_userq_mqd_create() returns -ENOMEM when copying the user MQD fails, even though memdup_user() returns -EFAULT for a bad pointer or -EINTR when interrupted by a signal. Return PTR_ERR() instead. While at it drop the error messages. Neither case is a driver error and userspace shouldn't be able to spam the log with invalid IOCTL parameters. v2: drop the error messages (Christian) Fixes: 2c695d7c0720 ("drm/amdgpu: enable compute/gfx usermode queue") Fixes: 543b61453774 ("drm/amdgpu: enable SDMA usermode queues") Reviewed-by: Christian König Signed-off-by: Jesse Zhang Signed-off-by: Alex Deucher (cherry picked from commit 49a443fcb829be9fc780f10850652ddea2fe81f0) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/amdgpu/mes_userqueue.c | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/mes_userqueue.c b/drivers/gpu/drm/amd/amdgpu/mes_userqueue.c index 14a5abe42d1f..8d5dcaf4fea6 100644 --- a/drivers/gpu/drm/amd/amdgpu/mes_userqueue.c +++ b/drivers/gpu/drm/amd/amdgpu/mes_userqueue.c @@ -361,8 +361,7 @@ static int mes_userq_mqd_create(struct amdgpu_usermode_queue *queue, compute_mqd = memdup_user(u64_to_user_ptr(mqd_user->mqd), mqd_user->mqd_size); if (IS_ERR(compute_mqd)) { - DRM_ERROR("Failed to read user MQD\n"); - r = -ENOMEM; + r = PTR_ERR(compute_mqd); goto free_mqd; } @@ -406,8 +405,7 @@ static int mes_userq_mqd_create(struct amdgpu_usermode_queue *queue, mqd_gfx_v11 = memdup_user(u64_to_user_ptr(mqd_user->mqd), mqd_user->mqd_size); if (IS_ERR(mqd_gfx_v11)) { - DRM_ERROR("Failed to read user MQD\n"); - r = -ENOMEM; + r = PTR_ERR(mqd_gfx_v11); goto free_mqd; } @@ -451,8 +449,7 @@ static int mes_userq_mqd_create(struct amdgpu_usermode_queue *queue, mqd_sdma_v11 = memdup_user(u64_to_user_ptr(mqd_user->mqd), mqd_user->mqd_size); if (IS_ERR(mqd_sdma_v11)) { - DRM_ERROR("Failed to read sdma user MQD\n"); - r = -ENOMEM; + r = PTR_ERR(mqd_sdma_v11); goto free_mqd; } From 2e54a92e0136c3ff5c7b542b95bf0940ad6687bf Mon Sep 17 00:00:00 2001 From: Leo Li Date: Wed, 30 Sep 2026 12:30:52 -0400 Subject: [PATCH 302/367] drm/amd/display: disable self-refresh on tearing flips MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [Why & How] Immediate (tearing) flips breaks self refresh state machine assumptions, so disable PSR/Replay for them. Tested-by: Timur Kristóf Reviewed-by: Harry Wentland Signed-off-by: Leo Li Signed-off-by: Alex Deucher (cherry picked from commit acbdd213b37e095f15685f3e3ee6d35defa05616) Cc: stable@vger.kernel.org --- .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c index 1b42c4606002..47a0ff2ed035 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c @@ -3708,6 +3708,13 @@ static void amdgpu_dm_enable_self_refresh(struct amdgpu_display_manager *dm, struct amdgpu_dm_connector *aconn = (struct amdgpu_dm_connector *)acrtc_state->stream->dm_stream_context; + if (!acrtc_state->base.async_flip) { + amdgpu_dm_psr_set_event(dm, acrtc_state->stream, false, + psr_event_immediate_flip, false); + amdgpu_dm_replay_set_event(dm, acrtc_state->stream, false, + replay_event_immediate_flip, false); + } + /* Decrement skip count when SR is enabled and we're doing fast updates. */ if (acrtc_state->update_type == UPDATE_TYPE_FAST && (psr->psr_feature_enabled || pr->replay_feature_enabled)) { @@ -4118,6 +4125,18 @@ static void amdgpu_dm_commit_planes(struct drm_atomic_commit *state, spin_unlock_irqrestore(&pcrtc->dev->event_lock, flags); } mutex_lock(&dm->dc_lock); + /* + * Tearing (immediate) flips cannot work with panel self-refresh + * features. The event is cleared by + * amdgpu_dm_enable_self_refresh() once async flips stop. + */ + if (immediate_flip) { + dc_exit_ips_for_hw_access(dm->dc); + amdgpu_dm_psr_set_event(dm, acrtc_state->stream, true, + psr_event_immediate_flip, true); + amdgpu_dm_replay_set_event(dm, acrtc_state->stream, true, + replay_event_immediate_flip, true); + } update_planes_and_stream_adapter(dm->dc, acrtc_state->update_type, planes_count, From 1a6023edfbb5673f55fab6348f135fb3b1428ea5 Mon Sep 17 00:00:00 2001 From: Vitaly Prosyak Date: Tue, 29 Sep 2026 20:18:26 -0400 Subject: [PATCH 303/367] drm/amdgpu: serialize vblank counter reads against GPU reset MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The vblank counter callback reads display registers and scanout position without holding reset_domain->sem. During GPU recovery, display commit work can call the callback while reset owns the semaphore for writing. The hardware access then triggers the lockdep_assert_held() in amdgpu_device_skip_hw_access() and sets TAINT_WARN when lockdep is enabled. Try to acquire the reset semaphore for reading before accessing the hardware, and hold it across the counter and scanout-position reads. If reset is already in progress, return DRM's cached software vblank count instead. This avoids blocking behind reset and avoids accessing display hardware while it is being reset. The cached count is less accurate than the hardware count, but is used only when reset prevents a safe hardware read. This addresses the reset-time warning from this vblank path; it does not suppress or fix unrelated lockdep warnings. Cc: Christian König Cc: Alex Deucher Cc: Harry Wentland Cc: Sunpeng Li Signed-off-by: Vitaly Prosyak Reviewed-by: Sunpeng Li Signed-off-by: Alex Deucher (cherry picked from commit 51a4359f7431a4be5e42803c8bdaf7e0e5be2934) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/amdgpu/amdgpu_kms.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_kms.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_kms.c index 242c48e85912..7c081b338fe2 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_kms.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_kms.c @@ -30,6 +30,7 @@ #include #include #include +#include #include "amdgpu_uvd.h" #include "amdgpu_vce.h" #include "atom.h" @@ -1660,6 +1661,9 @@ u32 amdgpu_get_vblank_counter_kms(struct drm_crtc *crtc) DRM_ERROR("Invalid crtc %u\n", pipe); return -EINVAL; } + if (!down_read_trylock(&adev->reset_domain->sem)) + return drm_crtc_vblank_count(crtc); + /* The hw increments its frame counter at start of vsync, not at start * of vblank, as is required by DRM core vblank counter handling. @@ -1705,6 +1709,7 @@ u32 amdgpu_get_vblank_counter_kms(struct drm_crtc *crtc) DRM_DEBUG_VBL("NULL mode info! Returned count may be wrong.\n"); } + up_read(&adev->reset_domain->sem); return count; } From 16c64495859f13f75602f439588883f2a4a4d6a6 Mon Sep 17 00:00:00 2001 From: Mikhail Gavrilov Date: Thu, 1 Oct 2026 21:13:07 +0500 Subject: [PATCH 304/367] drm/amdgpu: copy debugfs register data outside the GRBM/SRBM locks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reading or writing a register through the amdgpu_regs2 debugfs file with GRBM bank selection, which umr does for every banked access (a single "umr -b x x x -r " is enough, as is the wave dump with "-O halt_waves"), makes lockdep report a circular locking dependency, after which lockdep stays off until reboot: WARNING: possible circular locking dependency detected umr/63306 is trying to acquire lock: ffff8882cf4b8350 (&mm->mmap_lock){++++}-{4:4}, at: __might_fault+0x9b/0x140 but task is already holding lock: ffff8881aec010d0 (&amdgpu_grbm_lock_key){+.+.}-{4:4}, at: amdgpu_debugfs_regs2_op.isra.0+0x20d/0xd00 [amdgpu] ... Chain exists of: &mm->mmap_lock --> &amdgpu_srbm_lock_key --> &amdgpu_grbm_lock_key ... __might_fault+0xc5/0x140 amdgpu_debugfs_regs2_op.isra.0+0x441/0xd00 [amdgpu] full_proxy_write+0x10e/0x1d0 vfs_write+0x1d0/0xf50 ksys_write+0x116/0x250 amdgpu_debugfs_regs2_op() and amdgpu_debugfs_process_reg_op() call get_user()/put_user() for every dword while holding grbm_idx_mutex or srbm_mutex, and pm.mutex if requested, so a fault there takes mmap_lock inside those locks. The lock hierarchy in amdgpu_lockdep.c puts srbm_mutex and grbm_idx_mutex below notifier_lock, which amdgpu_cs_submit() takes with the BO reservations held, and dma_resv_lockdep() orders the reservations inside mmap_lock. So mmap_lock is outside grbm_idx_mutex and srbm_mutex, and these two debugfs paths nest it the other way round. The copies under the locks are as old as the bank selection in both files; commit 1d0f5838b126 ("drm/amdgpu: Add lockdep annotations for lock ordering validation") only made them visible to lockdep. Copy the user buffer in before any of the locks is taken and the result out after all of them are dropped, as amdgpu_debugfs_wave_read(), amdgpu_debugfs_gpr_read() and amdgpu_debugfs_gprwave_read() already do. The buffer is sized by the count passed to read() or write(), so an access larger than PAGE_SIZE is now rejected with -EINVAL; umr reads and writes one register at a time. A fault on the user buffer of a write now fails with -EFAULT before any register is written, instead of after a partial write. Fixes: 566281595037 ("drm/amd/amdgpu: Add bank selection for MMIO debugfs (v3)") Fixes: 37df9560cd3e ("drm/amd/amdgpu: New debugfs interface for MMIO registers (v5)") Acked-by: Christian König Acked-by: Tom St Denis Reviewed-by: Vitaly Prosyak Signed-off-by: Mikhail Gavrilov Signed-off-by: Alex Deucher (cherry picked from commit ccf9e35ed6811f2c25ba6368e7dc6481da17159d) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/amdgpu/amdgpu_debugfs.c | 77 ++++++++++++--------- 1 file changed, 46 insertions(+), 31 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_debugfs.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_debugfs.c index aca1a8045afa..ab7ccfaea22a 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_debugfs.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_debugfs.c @@ -121,6 +121,20 @@ static int amdgpu_debugfs_process_reg_op(bool read, struct file *f, *pos &= (1UL << 22) - 1; + if (size > PAGE_SIZE) + return -EINVAL; + + /* + * Access the user buffer only while none of the locks below are held: + * a fault takes mmap_lock, and grbm_idx_mutex and srbm_mutex nest + * inside it. + */ + u32 *data __free(kfree) = kmalloc(size, GFP_KERNEL); + if (!data) + return -ENOMEM; + if (!read && copy_from_user(data, buf, size)) + return -EFAULT; + r = pm_runtime_get_sync(adev_to_drm(adev)->dev); if (r < 0) { pm_runtime_put_autosuspend(adev_to_drm(adev)->dev); @@ -152,28 +166,16 @@ static int amdgpu_debugfs_process_reg_op(bool read, struct file *f, mutex_lock(&adev->pm.mutex); while (size) { - uint32_t value; - - if (read) { - value = RREG32(*pos >> 2); - r = put_user(value, (uint32_t *)buf); - } else { - r = get_user(value, (uint32_t *)buf); - if (!r) - amdgpu_mm_wreg_mmio_rlc(adev, *pos >> 2, value, 0); - } - if (r) { - result = r; - goto end; - } + if (read) + data[result >> 2] = RREG32(*pos >> 2); + else + amdgpu_mm_wreg_mmio_rlc(adev, *pos >> 2, data[result >> 2], 0); result += 4; - buf += 4; *pos += 4; size -= 4; } -end: if (use_bank) { amdgpu_gfx_select_se_sh(adev, 0xffffffff, 0xffffffff, 0xffffffff, 0); mutex_unlock(&adev->grbm_idx_mutex); @@ -188,6 +190,10 @@ static int amdgpu_debugfs_process_reg_op(bool read, struct file *f, pm_runtime_put_autosuspend(adev_to_drm(adev)->dev); amdgpu_virt_disable_access_debugfs(adev); + + if (read && copy_to_user(buf, data, result)) + return -EFAULT; + return result; } @@ -238,11 +244,24 @@ static ssize_t amdgpu_debugfs_regs2_op(struct file *f, char __user *buf, u32 off struct amdgpu_device *adev = rd->adev; ssize_t result = 0; int r; - uint32_t value; if (size & 0x3 || offset & 0x3) return -EINVAL; + if (size > PAGE_SIZE) + return -EINVAL; + + /* + * Access the user buffer only while none of the locks below are held: + * a fault takes mmap_lock, and grbm_idx_mutex and srbm_mutex nest + * inside it. + */ + u32 *data __free(kfree) = kmalloc(size, GFP_KERNEL); + if (!data) + return -ENOMEM; + if (write_en && copy_from_user(data, buf, size)) + return -EFAULT; + r = pm_runtime_get_sync(adev_to_drm(adev)->dev); if (r < 0) { pm_runtime_put_autosuspend(adev_to_drm(adev)->dev); @@ -281,24 +300,16 @@ static ssize_t amdgpu_debugfs_regs2_op(struct file *f, char __user *buf, u32 off mutex_lock(&adev->pm.mutex); while (size) { - if (!write_en) { - value = RREG32(offset >> 2); - r = put_user(value, (uint32_t *)buf); - } else { - r = get_user(value, (uint32_t *)buf); - if (!r) - amdgpu_mm_wreg_mmio_rlc(adev, offset >> 2, value, rd->id.xcc_id); - } - if (r) { - result = r; - goto end; - } + if (!write_en) + data[result >> 2] = RREG32(offset >> 2); + else + amdgpu_mm_wreg_mmio_rlc(adev, offset >> 2, data[result >> 2], + rd->id.xcc_id); offset += 4; size -= 4; result += 4; - buf += 4; } -end: + if (rd->id.use_grbm) { amdgpu_gfx_select_se_sh(adev, 0xffffffff, 0xffffffff, 0xffffffff, rd->id.xcc_id); mutex_unlock(&adev->grbm_idx_mutex); @@ -317,6 +328,10 @@ static ssize_t amdgpu_debugfs_regs2_op(struct file *f, char __user *buf, u32 off pm_runtime_put_autosuspend(adev_to_drm(adev)->dev); amdgpu_virt_disable_access_debugfs(adev); + + if (!write_en && copy_to_user(buf, data, result)) + return -EFAULT; + return result; } From f145d5b3f740de05d85257acdc82f6877180f5c6 Mon Sep 17 00:00:00 2001 From: Saleel Kudchadker Date: Thu, 24 Sep 2026 23:52:19 +0000 Subject: [PATCH 305/367] drm/amdkfd: align SDMA doorbell base for shader doorbell writes On GC 9.4.3, 9.4.4 and 9.5.0, doorbell writes issued from shaders are routed to an SDMA engine at 32-byte (4 doorbell index) granularity. SDMA engine doorbell ranges are 10 indices apart, so the range of every odd engine starts 2 indices into a 4-index block that begins in the previous engine's range. A shader ringing the doorbell of a queue on an odd engine therefore rings the previous engine instead. The intended queue never sees the doorbell, so its work stalls and the application hangs. Align the per-engine doorbell base up to a 4-index boundary on these ASICs. Each engine only uses 4 of its 10 indices, so the aligned base plus the queue offset stays within the engine's range. Even engines are already aligned, so only the doorbell IDs of odd-engine queues change. Only doorbells rung by the device itself are routed this way. Doorbell writes from the host, including the kernel's own SDMA rings, are not affected. The doorbell ID of an SDMA queue is saved in CRIU checkpoints and checked again on restore. A checkpoint taken before this change that contains an SDMA queue on an odd engine will fail to restore with -EINVAL. This is intentional: the restored process still holds the old doorbell address, so accepting the stale ID would bring back the same stall. Tested on MI300X with a shader-rung SDMA doorbell test: queues on odd engines hung before this change and complete with it. v2: use u32 instead of uint32_t for the new locals (checkpatch) v3: name the routing granularity, use dev->node_id, document the layout invariants, describe impact v4: express the granularity in doorbell indices, drop unrelated cleanups, explain the CRIU restore behavior, add test note Fixes: 6df442a03d1a ("drm/amdgpu: add new doorbell assignment table for aqua_vanjaram") Signed-off-by: Saleel Kudchadker Reviewed-by: Felix Kuehling Reviewed-by: Alex Deucher Signed-off-by: Alex Deucher (cherry picked from commit ee1379e6d7da47bdf7a8d5b89c2d1b28473701fb) Cc: stable@vger.kernel.org --- .../drm/amd/amdkfd/kfd_device_queue_manager.c | 32 +++++++++++++++---- drivers/gpu/drm/amd/amdkfd/kfd_priv.h | 7 ++++ 2 files changed, 33 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_device_queue_manager.c b/drivers/gpu/drm/amd/amdkfd/kfd_device_queue_manager.c index 2f78395a0c31..4594824985e1 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_device_queue_manager.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_device_queue_manager.c @@ -572,12 +572,32 @@ static int allocate_doorbell(struct qcm_process_device *qpd, * we need the physical sdma engine id in order to get the * correct doorbell offset. */ - uint32_t valid_id = idx_offset[qpd->dqm->dev->node_id * - get_num_all_sdma_engines(qpd->dqm) + - q->properties.sdma_engine_id] - + (q->properties.sdma_queue_id & 1) - * KFD_QUEUE_DOORBELL_MIRROR_OFFSET - + (q->properties.sdma_queue_id >> 1); + u32 engine_base = idx_offset[dev->node_id * + get_num_all_sdma_engines(qpd->dqm) + + q->properties.sdma_engine_id]; + u32 valid_id; + + /* + * With the aqua_vanjaram doorbell layout, a shader doorbell + * write whose 32-byte block starts in the previous engine's + * range rings that engine instead. Engine ranges are 10 + * indices apart (sdma_doorbell_range = 20 dwords, halved for + * 8-byte doorbells, in aqua_vanjaram_doorbell_index_init()), + * which puts odd engines 2 indices into a shared block. + * Aligning costs at most 2 indices, and 8 queues per engine use + * only 4 of the 10, so the aligned base stays in range. The + * 512-index mirror offset keeps the alignment. + */ + if (KFD_GC_VERSION(dev) == IP_VERSION(9, 4, 3) || + KFD_GC_VERSION(dev) == IP_VERSION(9, 4, 4) || + KFD_GC_VERSION(dev) == IP_VERSION(9, 5, 0)) + engine_base = ALIGN(engine_base, + KFD_SDMA_SHADER_DOORBELL_GRANULARITY); + + valid_id = engine_base + + (q->properties.sdma_queue_id & 1) * + KFD_QUEUE_DOORBELL_MIRROR_OFFSET + + (q->properties.sdma_queue_id >> 1); if (restore_id && *restore_id != valid_id) return -EINVAL; diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_priv.h b/drivers/gpu/drm/amd/amdkfd/kfd_priv.h index d8631847f0eb..435bf2692968 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_priv.h +++ b/drivers/gpu/drm/amd/amdkfd/kfd_priv.h @@ -133,6 +133,13 @@ */ #define KFD_QUEUE_DOORBELL_MIRROR_OFFSET 512 +/* + * On GC 9.4.3, 9.4.4 and 9.5.0, doorbell writes issued from shaders are + * routed to an SDMA engine at 32-byte granularity, i.e. 4 indices with the + * 8-byte SDMA doorbells used on SOC15. + */ +#define KFD_SDMA_SHADER_DOORBELL_GRANULARITY 4 + /** * enum kfd_ioctl_flags - KFD ioctl flags * Various flags that can be set in &amdkfd_ioctl_desc.flags to control how From 54c998c55fc9cbc391b350d67b86e0c2a0748f1a Mon Sep 17 00:00:00 2001 From: Adrian Betschart Date: Fri, 25 Sep 2026 09:48:29 +0200 Subject: [PATCH 306/367] drm/amd/display: Don't replace a sink mode that shares the native totals decide_crtc_timing_for_drm_display_mode() copies the native mode's CRTC timing into the requested mode when clock, htotal and vtotal match. That is meant for the modes amdgpu inserts itself: amdgpu_dm_create_common_mode() duplicates the native mode and changes only hdisplay and vdisplay, so those are sent with the native timing and scaled. A sink can also offer a real mode with the same clock and totals as its native mode. CTA-861 VIC 102 (4096x2160@60) and VIC 97 (3840x2160@60) both use 594 MHz and 4400x2250. With 3840x2160@60 as the preferred mode, a 4096x2160@60 request goes out with the 3840x2160 timing and the framebuffer scaled into it (dst 3840x2025 at y=67), while the AVI InfoFrame still carries VIC 102. A JVC projector then reports 3840x2160 and shows a corrupted right edge. 4096x2160@24, whose totals differ, is sent and displayed correctly. Inserted modes keep the native sync positions; a real mode with its own active width does not. Compare those too, so only inserted modes are patched. Fixes: 4562236b3bc0 ("drm/amd/dc: Add dc display driver (v2)") Signed-off-by: Adrian Betschart Assisted-by: Claude-Code:claude-opus-5-5 Tested-by: Dan Wheeler Reviewed-by: Roman Li Signed-off-by: Alex Deucher (cherry picked from commit 22f9a2da101c37f821cd00954f81dc598754f4d5) Cc: stable@vger.kernel.org --- .../display/amdgpu_dm/amdgpu_dm_connector.c | 7 +++- .../tests/amdgpu_dm_connector_test.c | 42 +++++++++++++++++++ 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c index d386bb1475bb..45a69bf43971 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c @@ -1012,10 +1012,15 @@ decide_crtc_timing_for_drm_display_mode(struct drm_display_mode *drm_mode, const struct drm_display_mode *native_mode, bool scale_enabled) { + /* Inserted modes copy the native sync; a sink mode with equal totals may not */ if (scale_enabled || ( native_mode->clock == drm_mode->clock && native_mode->htotal == drm_mode->htotal && - native_mode->vtotal == drm_mode->vtotal)) { + native_mode->vtotal == drm_mode->vtotal && + native_mode->hsync_start == drm_mode->hsync_start && + native_mode->hsync_end == drm_mode->hsync_end && + native_mode->vsync_start == drm_mode->vsync_start && + native_mode->vsync_end == drm_mode->vsync_end)) { if (native_mode->crtc_clock) copy_crtc_timing_for_drm_display_mode(native_mode, drm_mode); } else { diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c index 59ce682f491b..b61771749ca0 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c @@ -1572,6 +1572,46 @@ static void dm_test_decide_crtc_timing_no_crtc_clock(struct kunit *test) KUNIT_EXPECT_EQ(test, drm_mode.crtc_hdisplay, 0); } +static void dm_test_decide_crtc_timing_sink_mode_same_totals(struct kunit *test) +{ + struct drm_display_mode native_mode = { + DRM_MODE("3840x2160", DRM_MODE_TYPE_DRIVER, 594000, 3840, 4016, 4104, + 4400, 0, 2160, 2168, 2178, 2250, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }; + struct drm_display_mode drm_mode = { + DRM_MODE("4096x2160", DRM_MODE_TYPE_DRIVER, 594000, 4096, 4184, 4272, + 4400, 0, 2160, 2168, 2178, 2250, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }; + + drm_mode_set_crtcinfo(&native_mode, 0); + drm_mode_set_crtcinfo(&drm_mode, 0); + + decide_crtc_timing_for_drm_display_mode(&drm_mode, &native_mode, false); + + KUNIT_EXPECT_EQ(test, drm_mode.crtc_hdisplay, 4096); + KUNIT_EXPECT_EQ(test, drm_mode.crtc_hsync_start, 4184); +} + +static void dm_test_decide_crtc_timing_inserted_mode(struct kunit *test) +{ + struct drm_display_mode native_mode = { + DRM_MODE("3840x2160", DRM_MODE_TYPE_DRIVER, 594000, 3840, 4016, 4104, + 4400, 0, 2160, 2168, 2178, 2250, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }; + struct drm_display_mode drm_mode; + + drm_mode_set_crtcinfo(&native_mode, 0); + drm_mode = native_mode; + drm_mode.hdisplay = 1920; + drm_mode.vdisplay = 1080; + drm_mode_set_crtcinfo(&drm_mode, 0); + + decide_crtc_timing_for_drm_display_mode(&drm_mode, &native_mode, false); + + KUNIT_EXPECT_EQ(test, drm_mode.crtc_hdisplay, 3840); + KUNIT_EXPECT_EQ(test, drm_mode.crtc_vdisplay, 2160); +} + /* Tests for amdgpu_dm_connector_funcs_reset() */ static const struct drm_connector_funcs dm_test_connector_funcs = { @@ -5635,6 +5675,8 @@ static struct kunit_case amdgpu_dm_connector_tests[] = { KUNIT_CASE(dm_test_decide_crtc_timing_matching_mode), KUNIT_CASE(dm_test_decide_crtc_timing_no_copy), KUNIT_CASE(dm_test_decide_crtc_timing_no_crtc_clock), + KUNIT_CASE(dm_test_decide_crtc_timing_sink_mode_same_totals), + KUNIT_CASE(dm_test_decide_crtc_timing_inserted_mode), /* amdgpu_dm_connector_funcs_reset */ KUNIT_CASE(dm_test_funcs_reset_sets_defaults), KUNIT_CASE(dm_test_funcs_reset_edp_abm_level), From e6895021a328594368a2a19406a53ffca1ce51c7 Mon Sep 17 00:00:00 2001 From: Haojie Li Date: Fri, 2 Oct 2026 10:29:27 +0800 Subject: [PATCH 307/367] drm/amdkfd: fix dma_buf reference leak in get_dmabuf_info When kzalloc fails for metadata_buffer in amdgpu_amdkfd_get_dmabuf_info(), the function returns -ENOMEM directly without calling dma_buf_put(), leaking the dma_buf reference obtained earlier via dma_buf_get(). Fix both issues by: - Adding an error check after the first amdgpu_bo_get_metadata() call - Replacing the direct return with r = -ENOMEM and goto out_put to ensure dma_buf_put() is called on all error paths Fixes: f54ce9e8cbd3 ("drm/amdkfd: Let driver decide buffer size at AMDKFD_IOC_GET_DMABUF_INFO ioctl") Signed-off-by: Haojie Li Signed-off-by: Alex Deucher (cherry picked from commit ffa699553332f8caf8688d312279ddcea9e3c141) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd.c index 054870e9078d..ca5860e493a6 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd.c @@ -598,14 +598,18 @@ int amdgpu_amdkfd_get_dmabuf_info(struct amdgpu_device *adev, int dma_buf_fd, /* first get metadata_size by buffer = NULL */ r = amdgpu_bo_get_metadata(bo, NULL, 0, metadata_size, NULL); + if (r) + goto out_put; /* user buf_size is bigger than bo metadata_size * allocate a buf at kernel space and copy */ if (*metadata_size <= buffer_size) { *metadata_buffer = kzalloc(*metadata_size, GFP_KERNEL); - if (!*metadata_buffer) - return -ENOMEM; + if (!*metadata_buffer) { + r = -ENOMEM; + goto out_put; + } r = amdgpu_bo_get_metadata(bo, *metadata_buffer, *metadata_size, NULL, &metadata_flags); From c7f44ec5c4ec760e74351f1843dcb8322f67cbf6 Mon Sep 17 00:00:00 2001 From: Alex Deucher Date: Fri, 25 Sep 2026 09:16:26 -0400 Subject: [PATCH 308/367] drm/amdgpu: make Mac FB workaround generic MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Rather than adding a case of every chip where this is seen just check if the FB is at 0 and the ssid is apple. This should cover all chips which fall into this case. Tested-by: Ayhan Çiçek Signed-off-by: Alex Deucher (cherry picked from commit c54e4d10507722398dc90cf3c532e50b86cf425a) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/amdgpu/amdgpu_gmc.c | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_gmc.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_gmc.c index 3dc8faa091d7..61dc2a6be7e7 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_gmc.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_gmc.c @@ -1080,20 +1080,17 @@ void amdgpu_gmc_init_vga_resv_regions(struct amdgpu_device *adev) case CHIP_RENOIR: adev->mman.keep_stolen_vga_memory = true; break; - case CHIP_POLARIS10: - case CHIP_POLARIS11: - case CHIP_POLARIS12: - /* MacBookPros with switchable graphics put VRAM at 0 when - * the iGPU is enabled which results in cursor issues if - * the cursor ends up at 0. Reserve vram at 0 in that case. - */ - if (adev->gmc.vram_start == 0) - adev->mman.keep_stolen_vga_memory = true; - break; default: adev->mman.keep_stolen_vga_memory = false; break; } + /* MacBookPros with switchable graphics put VRAM at 0 when + * the iGPU is enabled which results in cursor issues if + * the cursor ends up at 0. Reserve vram at 0 in that case. + */ + if ((adev->pdev->subsystem_vendor == PCI_VENDOR_ID_APPLE) && + (adev->gmc.vram_start == 0)) + adev->mman.keep_stolen_vga_memory = true; if (amdgpu_sriov_vf(adev) || !amdgpu_device_has_display_hardware(adev)) { From 9727d1c4e1c971fc42b049b645fdda6852758835 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Wed, 7 Oct 2026 22:42:08 +0200 Subject: [PATCH 309/367] Revert "wifi: libertas: reject short monitor TX frames" This reverts commit 13ff543e0b2c713aedeaadadde686686e949dc78. The 'goto free' just made things worse, causing a spinlock to be handled incorrectly. Revert this for now. Reported-by: Takashi Iwai Cc: stable@vger.kernel.org Fixes: 13ff543e0b2c ("wifi: libertas: reject short monitor TX frames") Signed-off-by: Johannes Berg --- drivers/net/wireless/marvell/libertas/tx.c | 7 ------- 1 file changed, 7 deletions(-) diff --git a/drivers/net/wireless/marvell/libertas/tx.c b/drivers/net/wireless/marvell/libertas/tx.c index 13d08022e414..27304a98787d 100644 --- a/drivers/net/wireless/marvell/libertas/tx.c +++ b/drivers/net/wireless/marvell/libertas/tx.c @@ -117,13 +117,6 @@ netdev_tx_t lbs_hard_start_xmit(struct sk_buff *skb, struct net_device *dev) if (priv->wdev->iftype == NL80211_IFTYPE_MONITOR) { struct tx_radiotap_hdr *rtap_hdr = (void *)skb->data; - if (skb->len < sizeof(*rtap_hdr) + 4 + ETH_ALEN) { - lbs_deb_tx("tx err: short monitor frame %u\n", skb->len); - dev->stats.tx_dropped++; - dev->stats.tx_errors++; - goto free; - } - /* set txpd fields from the radiotap header */ txpd->tx_control = cpu_to_le32(convert_radiotap_rate_to_mv(rtap_hdr->rate)); From 9b6c20f788472d2042717a3c2f1bc60b2b77083e Mon Sep 17 00:00:00 2001 From: Fredrik Nyberg Date: Fri, 2 Oct 2026 14:28:57 +0200 Subject: [PATCH 310/367] net: usb: ax88179_178a: fix rx frame length for non-last packets Commit 4ce62d5b2f7a ("net: usb: ax88179_178a: stop lying about skb->truesize") replaced skb_clone() with a copy into a new skb for every packet of a bulk-in transfer except the last one. The copy skips the 2-byte IP alignment pseudo header but still copies pkt_len bytes, and pkt_len includes that header. Each of these frames is therefore delivered with two extra trailing bytes: the 0xeeee pseudo header of the next slot when the frame ends on an 8-byte boundary, or slot padding otherwise. The last packet is still trimmed to pkt_len - 2. IPv4 and IPv6 trim the extra bytes, so most traffic is unaffected. Anything that relies on the frame length is not. MACsec takes the ICV from the last 16 bytes and drops every such frame as InPktsNotValid. In our setup that was 0.2-1.2% of received MACsec frames, depending on how many frames shared a transfer, including service discovery multicast. Allocate and copy pkt_len - 2 bytes, like the last-packet path. Fixes: 4ce62d5b2f7a ("net: usb: ax88179_178a: stop lying about skb->truesize") Cc: stable@vger.kernel.org Signed-off-by: Fredrik Nyberg Reviewed-by: Simon Horman Link: https://patch.msgid.link/20261002-ax88179-rx-len-v1-1-f01a2a295079@volvocars.com Signed-off-by: Jakub Kicinski --- drivers/net/usb/ax88179_178a.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/net/usb/ax88179_178a.c b/drivers/net/usb/ax88179_178a.c index 81d8412ce8e2..cba1dc95d359 100644 --- a/drivers/net/usb/ax88179_178a.c +++ b/drivers/net/usb/ax88179_178a.c @@ -1457,11 +1457,11 @@ static int ax88179_rx_fixup(struct usbnet *dev, struct sk_buff *skb) return 1; } - ax_skb = netdev_alloc_skb_ip_align(dev->net, pkt_len); + ax_skb = netdev_alloc_skb_ip_align(dev->net, pkt_len - 2); if (!ax_skb) return 0; - skb_put(ax_skb, pkt_len); - memcpy(ax_skb->data, skb->data + 2, pkt_len); + skb_put(ax_skb, pkt_len - 2); + memcpy(ax_skb->data, skb->data + 2, pkt_len - 2); ax88179_rx_checksum(ax_skb, pkt_hdr); usbnet_skb_return(dev, ax_skb); From 8f5d59b9cfa69beab3b52cca621bb301c5af98aa Mon Sep 17 00:00:00 2001 From: Michael Chan Date: Mon, 5 Oct 2026 13:42:44 -0700 Subject: [PATCH 311/367] bnxt_en: Add bnxt_clear_bars() helper In bnxt_io_slot_reset(), we clear the 6 BAR registers. Add a helper function to do that. The helper will be used again in the next patch. Reviewed-by: Kalesh AP Reviewed-by: Somnath Kotur Reviewed-by: Joe Damato Signed-off-by: Pavan Chebbi Signed-off-by: Michael Chan Reviewed-by: Jacob Keller Link: https://patch.msgid.link/20261005204246.3822563-2-michael.chan@broadcom.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/broadcom/bnxt/bnxt.c | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/drivers/net/ethernet/broadcom/bnxt/bnxt.c b/drivers/net/ethernet/broadcom/bnxt/bnxt.c index d7728d0c5b6e..8ce8a82d3453 100644 --- a/drivers/net/ethernet/broadcom/bnxt/bnxt.c +++ b/drivers/net/ethernet/broadcom/bnxt/bnxt.c @@ -17111,6 +17111,14 @@ void bnxt_print_device_info(struct bnxt *bp) pcie_print_link_status(bp->pdev); } +static void bnxt_clear_bars(struct pci_dev *pdev) +{ + int off; + + for (off = PCI_BASE_ADDRESS_0; off <= PCI_BASE_ADDRESS_5; off += 4) + pci_write_config_dword(pdev, off, 0); +} + static int bnxt_init_one(struct pci_dev *pdev, const struct pci_device_id *ent) { struct bnxt_hw_resc *hw_resc; @@ -17598,7 +17606,6 @@ static pci_ers_result_t bnxt_io_slot_reset(struct pci_dev *pdev) struct bnxt *bp = netdev_priv(netdev); int retry = 0; int err = 0; - int off; netdev_info(bp->dev, "PCI Slot Reset\n"); @@ -17627,11 +17634,8 @@ static pci_ers_result_t bnxt_io_slot_reset(struct pci_dev *pdev) * write the BARs to 0 to force restore, in case of fatal error. */ if (test_and_clear_bit(BNXT_STATE_PCI_CHANNEL_IO_FROZEN, - &bp->state)) { - for (off = PCI_BASE_ADDRESS_0; - off <= PCI_BASE_ADDRESS_5; off += 4) - pci_write_config_dword(bp->pdev, off, 0); - } + &bp->state)) + bnxt_clear_bars(pdev); pci_restore_state(pdev); bnxt_inv_fw_health_reg(bp); From 386f887c8290f0747dd0180e98ec7a82811bc506 Mon Sep 17 00:00:00 2001 From: Michael Chan Date: Mon, 5 Oct 2026 13:42:45 -0700 Subject: [PATCH 312/367] bnxt_en: Fix driver init in kdump kernel Fix and strengthen the FLR sequence when initializing in the kdump kernel. If the NIC is behind a PCIe switch in synthetic (smart) mode, the switch may need to see that the BARs have been initialized before it will pass mem read/write TLPs to the NIC. On a Dell system with a PEX89144 PCIe switch, echo c > /proc/sysrq-trigger will trigger fatal AER without this patch: bnxt_en 0000:67:00.0: enabling device (0000 -> 0002) [Hardware Error]: Hardware error from APEI Generic Hardware Error Source: 5 [Hardware Error]: event severity: recoverable [Hardware Error]: Error 0, type: fatal [Hardware Error]: section_type: PCIe error [Hardware Error]: port_type: 5, upstream switch port ... Add a new bnxt_kdump_reset() to do the expanded FLR sequence in the kdump kernel. We now disable bus master and memory, save the PCI state, do the FLR, clear the BARs, and restore the PCI state. The BARs have to be cleared to ensure that they get re-initialized and visible to the PCIe switch. Since it is the kdump kernel, we make every effort to continue in the best possible way even if pci_save_state() or pcie_flr() returns error. After FLR, we poll for an additional 5 seconds before aborting in case the device is not properly returning CRS. This is similar to the 5-second wait in bnxt_io_slot_reset(). Fixes: 8743db4a9acf ("bnxt_en: Issue PCIe FLR in kdump kernel to cleanup pending DMAs.") Reviewed-by: Kalesh AP Signed-off-by: Pavan Chebbi Signed-off-by: Michael Chan Reviewed-by: Jacob Keller Link: https://patch.msgid.link/20261005204246.3822563-3-michael.chan@broadcom.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/broadcom/bnxt/bnxt.c | 45 ++++++++++++++++++++--- 1 file changed, 40 insertions(+), 5 deletions(-) diff --git a/drivers/net/ethernet/broadcom/bnxt/bnxt.c b/drivers/net/ethernet/broadcom/bnxt/bnxt.c index 8ce8a82d3453..9ea7e172787e 100644 --- a/drivers/net/ethernet/broadcom/bnxt/bnxt.c +++ b/drivers/net/ethernet/broadcom/bnxt/bnxt.c @@ -17119,6 +17119,43 @@ static void bnxt_clear_bars(struct pci_dev *pdev) pci_write_config_dword(pdev, off, 0); } +/* Clear any pending DMA transactions from crash kernel while loading driver in + * capture kernel. + */ +static int bnxt_kdump_reset(struct pci_dev *pdev) +{ + int rc, i; + u16 cmd; + + pci_read_config_word(pdev, PCI_COMMAND, &cmd); + cmd &= ~(PCI_COMMAND_MASTER | PCI_COMMAND_MEMORY); + pci_write_config_word(pdev, PCI_COMMAND, cmd); + + if (pci_save_state(pdev)) + dev_warn(&pdev->dev, "Failed to save PCI state, PCI restore may be incomplete\n"); + + rc = pcie_flr(pdev); + if (rc) + dev_warn(&pdev->dev, "pcie_flr() failed (rc: %d), trying to continue\n", + rc); + + /* In case device is not returning CRS, wait 5 seconds longer */ + for (i = 0; i < 50; i++) { + pci_read_config_word(pdev, PCI_COMMAND, &cmd); + if (!PCI_POSSIBLE_ERROR(cmd)) + break; + msleep(100); + } + if (PCI_POSSIBLE_ERROR(cmd)) { + dev_err(&pdev->dev, "PCI config space inaccessible after FLR, aborting\n"); + return -ENODEV; + } + + bnxt_clear_bars(pdev); + pci_restore_state(pdev); + return 0; +} + static int bnxt_init_one(struct pci_dev *pdev, const struct pci_device_id *ent) { struct bnxt_hw_resc *hw_resc; @@ -17134,12 +17171,10 @@ static int bnxt_init_one(struct pci_dev *pdev, const struct pci_device_id *ent) return -ENODEV; } - /* Clear any pending DMA transactions from crash kernel - * while loading driver in capture kernel. - */ if (is_kdump_kernel()) { - pci_clear_master(pdev); - pcie_flr(pdev); + rc = bnxt_kdump_reset(pdev); + if (rc) + return rc; } max_irqs = bnxt_get_max_irq(pdev); From 2e52da27096d9dbc487f303bbc10d44ad837a8c4 Mon Sep 17 00:00:00 2001 From: Pavan Chebbi Date: Mon, 5 Oct 2026 13:42:46 -0700 Subject: [PATCH 313/367] bnxt_en: Re-write the BARs following any type of PCIe errors Currently the driver zeroes the BARs only when fatal PCIe errors are reported so that pci_restore_state() restores it. However firmware handles both fatal and non-fatal errors the same way when it sees the slot reset resulting from the PCI_ERS_RESULT_NEED_RESET return code from the driver. This means that we must re-write the BARs post recovery even during non-fatal errors. Otherwise we will see that every MMIO access returns all-ones and the firmware appears dead. Zero-out the BARs during PCIe error recovery regardless of type of PCIe error, and make the wait after the hot reset unconditional. Disable memory decode and bus mastering before rewriting the BARs so the device doesn't decode a half-updated address, bailing out if config space is still inaccessible. Defer pci_enable_device() until after the BAR rewrite and restore, so the device isn't re-enabled while its BARs are still being rewritten, then re-enable the device and re-assert bus mastering. Guard the same Command register cleanup on the re-enable failure path against an inaccessible device. Skip re-enabling the device in bnxt_io_slot_reset() if it is already enabled, so enable_cnt does not go unbalanced. A concurrent bnxt_fw_reset_task() can also be re-enabling the same device in its ENABLE_DEV state, so guard that call the same way. Fixes: f75d9a0aa967 ("bnxt_en: Re-write PCI BARs after PCI fatal error.") Reviewed-by: Kalesh AP Reviewed-by: Scott Branden Signed-off-by: Pavan Chebbi Signed-off-by: Michael Chan Reviewed-by: Jacob Keller Link: https://patch.msgid.link/20261005204246.3822563-4-michael.chan@broadcom.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/broadcom/bnxt/bnxt.c | 117 ++++++++++++---------- drivers/net/ethernet/broadcom/bnxt/bnxt.h | 1 - 2 files changed, 65 insertions(+), 53 deletions(-) diff --git a/drivers/net/ethernet/broadcom/bnxt/bnxt.c b/drivers/net/ethernet/broadcom/bnxt/bnxt.c index 9ea7e172787e..5bd817479d64 100644 --- a/drivers/net/ethernet/broadcom/bnxt/bnxt.c +++ b/drivers/net/ethernet/broadcom/bnxt/bnxt.c @@ -15547,7 +15547,7 @@ static void bnxt_fw_reset_task(struct work_struct *work) if (test_and_clear_bit(BNXT_STATE_FW_ACTIVATE_RESET, &bp->state) && !test_bit(BNXT_STATE_FW_ACTIVATE, &bp->state)) bnxt_dl_remote_reload(bp); - if (pci_enable_device(bp->pdev)) { + if (!pci_is_enabled(bp->pdev) && pci_enable_device(bp->pdev)) { netdev_err(bp->dev, "Cannot re-enable PCI device\n"); rc = -ENODEV; goto fw_reset_abort; @@ -17608,10 +17608,8 @@ static pci_ers_result_t bnxt_io_error_detected(struct pci_dev *pdev, * so we disable bus master to prevent any potential bad DMAs before * freeing kernel memory. */ - if (state == pci_channel_io_frozen) { - set_bit(BNXT_STATE_PCI_CHANNEL_IO_FROZEN, &bp->state); + if (state == pci_channel_io_frozen) bnxt_fw_fatal_close(bp); - } if (netif_running(netdev)) __bnxt_close_nic(bp, true, true); @@ -17641,64 +17639,79 @@ static pci_ers_result_t bnxt_io_slot_reset(struct pci_dev *pdev) struct bnxt *bp = netdev_priv(netdev); int retry = 0; int err = 0; + u16 cmd; netdev_info(bp->dev, "PCI Slot Reset\n"); - if (test_bit(BNXT_STATE_PCI_CHANNEL_IO_FROZEN, &bp->state)) { - /* After DPC, the chip should return CRS when the vendor ID - * config register is read until it is ready. On all chips, - * this is not happening reliably so add a 5-second delay as a - * workaround. - */ - msleep(5000); - } + /* After a PCIe hot reset, the chip should return CRS when the + * vendor ID config register is read until it is ready. On all + * chips, this is not happening reliably so add a 5-second delay + * as a workaround. + */ + msleep(5000); netdev_lock(netdev); - if (pci_enable_device(pdev)) { + pci_read_config_word(pdev, PCI_COMMAND, &cmd); + if (PCI_POSSIBLE_ERROR(cmd)) { + dev_err(&pdev->dev, + "PCI config space inaccessible after reset\n"); + goto reset_exit; + } + + /* Upon PCIe error, our device internal logic that latches to + * BAR value is getting reset and will restore only upon + * rewriting the BARs. + * + * As pci_restore_state() does not re-write the BARs if the + * value is same as saved value earlier, driver needs to + * write the BARs to 0 to force restore. + */ + pci_clear_master(pdev); + pci_read_config_word(pdev, PCI_COMMAND, &cmd); + cmd &= ~PCI_COMMAND_MEMORY; + pci_write_config_word(pdev, PCI_COMMAND, cmd); + + bnxt_clear_bars(pdev); + pci_restore_state(pdev); + + if (!pci_is_enabled(pdev) && pci_enable_device(pdev)) { dev_err(&pdev->dev, "Cannot re-enable PCI device after reset.\n"); - } else { - pci_set_master(pdev); - /* Upon fatal error, our device internal logic that latches to - * BAR value is getting reset and will restore only upon - * rewriting the BARs. - * - * As pci_restore_state() does not re-write the BARs if the - * value is same as saved value earlier, driver needs to - * write the BARs to 0 to force restore, in case of fatal error. - */ - if (test_and_clear_bit(BNXT_STATE_PCI_CHANNEL_IO_FROZEN, - &bp->state)) - bnxt_clear_bars(pdev); - pci_restore_state(pdev); - - bnxt_inv_fw_health_reg(bp); - bnxt_try_map_fw_health_reg(bp); - - /* In some PCIe AER scenarios, firmware may take up to - * 10 seconds to become ready in the worst case. - */ - do { - err = bnxt_try_recover_fw(bp); - if (!err) - break; - retry++; - } while (retry < BNXT_FW_SLOT_RESET_RETRY); - - if (err) { - dev_err(&pdev->dev, "Firmware not ready\n"); - goto reset_exit; + pci_read_config_word(pdev, PCI_COMMAND, &cmd); + if (!PCI_POSSIBLE_ERROR(cmd)) { + cmd &= ~(PCI_COMMAND_MASTER | PCI_COMMAND_MEMORY); + pci_write_config_word(pdev, PCI_COMMAND, cmd); } - - err = bnxt_hwrm_func_reset(bp); - if (!err) - result = PCI_ERS_RESULT_RECOVERED; - - /* IRQ will be initialized later in bnxt_io_resume */ - bnxt_ulp_irq_stop(bp); - bnxt_clear_int_mode(bp); + goto reset_exit; } + pci_set_master(pdev); + + bnxt_inv_fw_health_reg(bp); + bnxt_try_map_fw_health_reg(bp); + + /* In some PCIe AER scenarios, firmware may take up to + * 10 seconds to become ready in the worst case. + */ + do { + err = bnxt_try_recover_fw(bp); + if (!err) + break; + retry++; + } while (retry < BNXT_FW_SLOT_RESET_RETRY); + + if (err) { + dev_err(&pdev->dev, "Firmware not ready\n"); + goto reset_exit; + } + + err = bnxt_hwrm_func_reset(bp); + if (!err) + result = PCI_ERS_RESULT_RECOVERED; + + /* IRQ will be initialized later in bnxt_io_resume */ + bnxt_ulp_irq_stop(bp); + bnxt_clear_int_mode(bp); reset_exit: clear_bit(BNXT_STATE_IN_FW_RESET, &bp->state); diff --git a/drivers/net/ethernet/broadcom/bnxt/bnxt.h b/drivers/net/ethernet/broadcom/bnxt/bnxt.h index c673b2ce4a0d..41d05efd6307 100644 --- a/drivers/net/ethernet/broadcom/bnxt/bnxt.h +++ b/drivers/net/ethernet/broadcom/bnxt/bnxt.h @@ -2468,7 +2468,6 @@ struct bnxt { #define BNXT_STATE_ABORT_ERR 5 #define BNXT_STATE_FW_FATAL_COND 6 #define BNXT_STATE_DRV_REGISTERED 7 -#define BNXT_STATE_PCI_CHANNEL_IO_FROZEN 8 #define BNXT_STATE_NAPI_DISABLED 9 #define BNXT_STATE_FW_ACTIVATE 11 #define BNXT_STATE_RECOVER 12 From a51d233ccd4889aadb71dace863525cc2b00271e Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Wed, 7 Oct 2026 07:17:47 +0200 Subject: [PATCH 314/367] bnxt_en: fix DMA mapping length for padded small packets Stefan Fleischmann reported Intel IOMMU DMA Read faults on BCM57412 NetXtreme-E NICs when transmitting packets on VLAN/macvlan interfaces: DMAR: [DMA Read NO_PASID] Request device [18:00.0] fault addr 0xfc499000 [fault reason 0x06] PTE Read access is not set bnxt_en 0000:18:00.0 eno1np0: Abandoning msg {0xb4 0x41a} len: 0 due to firmware status: 0x2000001 ... NETDEV WATCHDOG: eno1np0 (bnxt_en): transmit queue 0 timed out The fault address (0xfc499000) is on an exact 4KB page boundary, pointing to a DMA read buffer overrun. In bnxt_start_xmit(), packets smaller than BNXT_MIN_PKT_SIZE (52 bytes), such as 42-byte untagged ARP frames, are padded: if (length < BNXT_MIN_PKT_SIZE) { pad = BNXT_MIN_PKT_SIZE - length; if (skb_pad(skb, pad)) goto tx_kick_pending; length = BNXT_MIN_PKT_SIZE; } mapping = dma_map_single(&pdev->dev, skb->data, len, DMA_TO_DEVICE); ... dma_unmap_len_set(tx_buf, len, len); However, 'len' was initialized earlier to skb_headlen(skb) (e.g. 42 bytes) and is left unadjusted after padding. Consequently, dma_map_single() and dma_unmap_len_set() map and track only 42 bytes. Later, the hardware TX buffer descriptor is programmed with the padded length: txbd->tx_bd_len_flags_type = cpu_to_le32(((len + pad) << TX_BD_LEN_SHIFT) | flags | TX_BD_FLAGS_PACKET_END); The NIC DMA engine is thus instructed to read 52 bytes from a region where only 42 bytes were DMA-mapped. If skb->data ends near the boundary of a 4KB page (within 'pad' bytes of the next page), the hardware DMA read overruns into the unmapped adjacent page, triggering an IOMMU fault. This issue was exposed after commit 447cbe95ebb9 ("vlan: fix skb_under_panic and races when toggling HW VLAN offload") because reserving extra VLAN headroom rounded LL_RESERVED_SPACE from 48 up to 64 bytes, shifting skb->data offsets and potentially causing small frames to land right against page boundaries. Fix this by using skb_put_padto(skb, BNXT_MIN_PKT_SIZE) in bnxt_start_xmit(), before skb_shinfo(skb)->nr_frags is sampled, because padding might linearize the skb. This ensures skb->len and skb_headlen(skb) reflect the padded size so that dma_map_single() maps the full buffer and the descriptor length is consistent. This also removes the temporary 'pad' variable and masking logic. The padding is done after the SW USO branch, otherwise bnxt_sw_udp_gso_xmit() would account the padding as UDP payload and send it in extra segments. Note that SW USO segments are still not padded: with IPv4, a segment carrying less than 10 bytes of UDP payload (small gso_size, or a short last segment) is sent as a frame shorter than BNXT_MIN_PKT_SIZE. This is a separate issue, left for a followup patch. Fixes: c0c050c58d84 ("bnxt_en: New Broadcom ethernet driver.") Cc: stable@vger.kernel.org Reported-by: Stefan Fleischmann Closes: https://lore.kernel.org/netdev/20261004122616.56714cbd@nargothrond/ Signed-off-by: Eric Dumazet Reviewed-by: Michael Chan Link: https://patch.msgid.link/20261007051747.455273-1-edumazet@kernel.org Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/broadcom/bnxt/bnxt.c | 25 +++++++++++------------ 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/drivers/net/ethernet/broadcom/bnxt/bnxt.c b/drivers/net/ethernet/broadcom/bnxt/bnxt.c index 5bd817479d64..15a8349ccf7b 100644 --- a/drivers/net/ethernet/broadcom/bnxt/bnxt.c +++ b/drivers/net/ethernet/broadcom/bnxt/bnxt.c @@ -486,7 +486,7 @@ static netdev_tx_t bnxt_start_xmit(struct sk_buff *skb, struct net_device *dev) struct netdev_queue *txq; int i; dma_addr_t mapping; - unsigned int length, pad = 0; + unsigned int length; u32 len, free_size, vlan_tag_flags, cfa_action, flags; struct bnxt_ptp_cfg *ptp = bp->ptp_cfg; struct pci_dev *pdev = bp->pdev; @@ -536,6 +536,16 @@ static netdev_tx_t bnxt_start_xmit(struct sk_buff *skb, struct net_device *dev) return rc < 0 ? NETDEV_TX_BUSY : NETDEV_TX_OK; } + /* Pad after the SW USO branch: bnxt_sw_udp_gso_xmit() would + * otherwise account the padding as UDP payload. + * Must be done before skb_shinfo(skb)->nr_frags is sampled, + * because skb_put_padto() might linearize the skb. + */ + if (skb_put_padto(skb, BNXT_MIN_PKT_SIZE)) { + /* SKB already freed. */ + goto tx_kick_pending; + } + free_size = bnxt_tx_avail(bp, txr); if (unlikely(free_size < skb_shinfo(skb)->nr_frags + 2)) { /* We must have raced with NAPI cleanup */ @@ -672,14 +682,6 @@ static netdev_tx_t bnxt_start_xmit(struct sk_buff *skb, struct net_device *dev) } normal_tx: - if (length < BNXT_MIN_PKT_SIZE) { - pad = BNXT_MIN_PKT_SIZE - length; - if (skb_pad(skb, pad)) - /* SKB already freed. */ - goto tx_kick_pending; - length = BNXT_MIN_PKT_SIZE; - } - mapping = dma_map_single(&pdev->dev, skb->data, len, DMA_TO_DEVICE); if (unlikely(dma_mapping_error(&pdev->dev, mapping))) @@ -759,10 +761,7 @@ static netdev_tx_t bnxt_start_xmit(struct sk_buff *skb, struct net_device *dev) txbd->tx_bd_len_flags_type = cpu_to_le32(flags); } - flags &= ~TX_BD_LEN; - txbd->tx_bd_len_flags_type = - cpu_to_le32(((len + pad) << TX_BD_LEN_SHIFT) | flags | - TX_BD_FLAGS_PACKET_END); + txbd->tx_bd_len_flags_type |= cpu_to_le32(TX_BD_FLAGS_PACKET_END); netdev_tx_sent_queue(txq, skb->len); From f202578efc738b883cfa04889eb97328914265a7 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Wed, 7 Oct 2026 06:56:15 +0200 Subject: [PATCH 315/367] flow_dissector: avoid u16 truncation of hlen in bpf_flow_dissect() bpf_flow_dissect() sanitizes the offsets returned by the BPF program with clamp_t(u16, ..., hlen). If hlen is bigger than 65535, the upper bound is truncated before the comparison. For instance, with hlen == 65536, both nhoff and thoff are forced to zero, even if the program returned sane values. This is the same class of bug as the one fixed in commit 99bda1ecbd56 ("flow_dissector: avoid u16 truncation of skb->len when computing thoff"). That fix does not cover the BPF path, because __skb_flow_dissect() returns early when a BPF flow dissector handles the packet. struct bpf_flow_keys stores nhoff and thoff as u16, so cap hlen to U16_MAX before clamping. Found by sashiko while reviewing the commit above. Fixes: d58e468b1112 ("flow_dissector: implements flow dissector BPF hook") Signed-off-by: Eric Dumazet Link: https://patch.msgid.link/20261007045616.445596-1-edumazet@kernel.org Signed-off-by: Jakub Kicinski --- net/core/flow_dissector.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/net/core/flow_dissector.c b/net/core/flow_dissector.c index 27d8a01bc923..1be6c739e3c7 100644 --- a/net/core/flow_dissector.c +++ b/net/core/flow_dissector.c @@ -1023,6 +1023,8 @@ u32 bpf_flow_dissect(struct bpf_prog *prog, struct bpf_flow_dissector *ctx, result = bpf_prog_run_pin_on_cpu(prog, ctx); + /* bpf_flow_keys offsets are u16: do not let @hlen be truncated. */ + hlen = min_t(int, hlen, U16_MAX); flow_keys->nhoff = clamp_t(u16, flow_keys->nhoff, nhoff, hlen); flow_keys->thoff = clamp_t(u16, flow_keys->thoff, flow_keys->nhoff, hlen); From 4d0697f192975dab5985e921989be1d85cf580e9 Mon Sep 17 00:00:00 2001 From: Ziyi Guo Date: Thu, 1 Oct 2026 13:57:11 +0000 Subject: [PATCH 316/367] riscv: entry: gate shadow call stack reload on sstatus.SPP, not tp Under CONFIG_SHADOW_CALL_STACK, handle_exception() reloads the kernel shadow call stack pointer (gp) only when the trap came from userspace. It made that decision with scs_load_current_if_task_changed, which compares the trap-time tp (read back from sscratch into s5) against the current tp: beq \prev, tp, _skip_scs scs_load_current _skip_scs: tp is a user-writable register. A user task that sets tp to the linear-map address of its own task_struct (¤t) before trapping makes the comparison succeed (can achieve by brute force with KASLR enable under our test), so the kernel skips scs_load_current and keeps running with the gp that userspace left in place. Under CONFIG_SHADOW_CALL_STACK gp is the shadow call stack pointer and load_global_pointer is a no-op, so the compiler-emitted "sd ra, -8(gp)" / "ld ra, -8(gp)" in every non-leaf function then write to, and return through, an attacker-controlled address -- an unprivileged kernel arbitrary-write and control-flow-hijack primitive. The poisoned gp is saved to task_struct.scs_sp on return, so the reload is skipped on every subsequent entry as well. Decide "came from userspace" from the hardware sstatus.SPP bit (already in s1 at this point) instead of from tp, mirroring the check that ret_from_exception already uses a few instructions later. SPP is written by hardware on trap entry and cannot be forged from U-mode. Legitimate behaviour is unchanged: user entries (SPP=0) reload the task shadow call stack, nested kernel entries (SPP=1) skip it. Fixes: d1584d791a29 ("riscv: Implement Shadow Call Stack") Signed-off-by: Ziyi Guo Reviewed-by: Sami Tolvanen Link: https://patch.msgid.link/20261001135711.934152-1-guoziyi114@gmail.com Cc: stable@vger.kernel.org Signed-off-by: Paul Walmsley --- arch/riscv/include/asm/scs.h | 17 +++++++++++++---- arch/riscv/kernel/entry.S | 2 +- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/arch/riscv/include/asm/scs.h b/arch/riscv/include/asm/scs.h index 023a412fe38d..49e40eef4ed7 100644 --- a/arch/riscv/include/asm/scs.h +++ b/arch/riscv/include/asm/scs.h @@ -4,6 +4,7 @@ #ifdef __ASSEMBLER__ #include +#include #ifdef CONFIG_SHADOW_CALL_STACK @@ -22,9 +23,17 @@ REG_L gp, TASK_TI_SCS_SP(tp) .endm -/* Load task_scs_sp(current) to gp, but only if tp has changed. */ -.macro scs_load_current_if_task_changed prev - beq \prev, tp, _skip_scs +/* + * Load task_scs_sp(current) to gp, but only when the trap came from U-mode. + * The source privilege level is taken from the saved sstatus.SPP bit (written + * by hardware on trap entry), not by comparing tp. tp is a user-writable + * register, so gating the shadow call stack reload on it let a user task that + * set tp == ¤t skip the reload and run the kernel with an attacker + * controlled gp (the shadow call stack pointer). + */ +.macro scs_load_current_if_from_user status, tmp + andi \tmp, \status, SR_SPP + bnez \tmp, _skip_scs scs_load_current _skip_scs: .endm @@ -42,7 +51,7 @@ .endm .macro scs_load_current .endm -.macro scs_load_current_if_task_changed prev +.macro scs_load_current_if_from_user status, tmp .endm .macro scs_save_current .endm diff --git a/arch/riscv/kernel/entry.S b/arch/riscv/kernel/entry.S index d799c4e56f80..61b7a7716697 100644 --- a/arch/riscv/kernel/entry.S +++ b/arch/riscv/kernel/entry.S @@ -208,7 +208,7 @@ SYM_CODE_START(handle_exception) load_global_pointer /* Load the kernel shadow call stack pointer if coming from userspace */ - scs_load_current_if_task_changed s5 + scs_load_current_if_from_user s1, s5 #ifdef CONFIG_RISCV_ISA_V_PREEMPTIVE move a0, sp From 1d6500523b8d5112afe0118173ebc8c1ca23784e Mon Sep 17 00:00:00 2001 From: Tony Nguyen Date: Thu, 1 Oct 2026 15:24:39 -0700 Subject: [PATCH 317/367] e1000e: add system to disable K1 list Javier reports seeing packet loss issues related to the re-enablement of K1; disabling it makes the issues go away. Add the reported system to have K1 disabled by default. Reproduction steps (from the Link): Plug the cable and ping the default gateway on the LAN. No suspend/resume involved, machine freshly booted and on AC power. Nothing e1000e-related in dmesg apart from the link up/down messages; no "Hardware Unit Hang". Default (K1 enabled): $ ping -c 30 192.168.10.1 --- 192.168.10.1 ping statistics --- 30 packets transmitted, 18 received, 40% packet loss, time 29734ms rtt min/avg/max/mdev = 0.211/0.315/0.384/0.046 ms The lost packets are spread over the run (seq 6, 8, 9, 11, 13, 14, 16, 19, 20, 24, 25, 30), not a single burst. DHCP on this link also takes 1-2 minutes to get a lease, consistent with incoming packets being dropped. With K1 disabled at runtime: # ethtool --set-priv-flags enp0s31f6 disable-k1 on $ ethtool --show-priv-flags enp0s31f6 Private flags for enp0s31f6: s0ix-enabled: on disable-k1 : on $ ping -c 30 192.168.10.1 --- 192.168.10.1 ping statistics --- 30 packets transmitted, 30 received, 0% packet loss, time 29713ms rtt min/avg/max/mdev = 0.169/0.251/0.468/0.054 ms $ ping -c 30 192.168.10.1 --- 192.168.10.1 ping statistics --- 30 packets transmitted, 30 received, 0% packet loss, time 29677ms rtt min/avg/max/mdev = 0.101/0.222/0.360/0.064 ms I have only compile tested this, but Javier has tested that disabling K1 on his system (Lenovo ThinkPad P14s Gen 5 (Intel), I219-LM [8086:550a]) resolves his issues. Cc: stable@vger.kernel.org Fixes: 578294b8b60d ("e1000e: Reconfigure PLL clock gate timeout and re-enable K1 on Meteor Lake") Reported-by: Javier Herrera Link: https://lore.kernel.org/intel-wired-lan/CAKRuCQLWEcci9hPgNUmvq44Bd2H30T5_vP=exMjsknXMN5Yh9A@mail.gmail.com/ Reviewed-by: Aleksandr Loktionov Signed-off-by: Tony Nguyen Link: https://patch.msgid.link/20261001222443.3500206-7-anthony.l.nguyen@intel.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/intel/e1000e/netdev.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/net/ethernet/intel/e1000e/netdev.c b/drivers/net/ethernet/intel/e1000e/netdev.c index 844f31ab37ad..c282f65199e4 100644 --- a/drivers/net/ethernet/intel/e1000e/netdev.c +++ b/drivers/net/ethernet/intel/e1000e/netdev.c @@ -67,6 +67,13 @@ static const struct dmi_system_id disable_k1_list[] = { DMI_MATCH(DMI_PRODUCT_NAME, "Dell Pro 16 Plus PB16250"), }, }, + { + .ident = "Lenovo ThinkPad P14s Gen 5", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_VERSION, "ThinkPad P14s Gen 5"), + }, + }, {} }; From c57b2e60d4bf29707c51b39ed3bacbcce2f3ba2e Mon Sep 17 00:00:00 2001 From: Haishuang Yan Date: Fri, 2 Oct 2026 23:30:39 +0800 Subject: [PATCH 318/367] bareudp: set the inner protocol to the protocol of the packet bareudp_xmit_skb() sets the inner protocol of the skb to the configured ethertype, and bareudp6_xmit_skb() does not set it at all. The inner protocol is used by skb_udp_tunnel_segment() to segment the inner packet when GSO has to be done in software, for instance when the lower device does not offload the checksum. With multiproto, an IPv4 device also carries IPv6, so the IPv6 header is parsed as an IPv4 one. With IPv6 underlay, the inner protocol is whatever the skb had before. In both cases segmentation fails and the packets are dropped. With veth tx checksum offload disabled, iperf3 TCP over bareudp gets about 10 Mbit/s with thousands of retransmits instead of about 700 Mbit/s for IPv6 over IPv4 and for both families over IPv6, while IPv4 over IPv4 works. At this point skb->protocol is the protocol of the inner packet, which bareudp_xmit() has already checked against the configuration, so use it on both paths. Fixes: 571912c69f0e ("net: UDP tunnel encapsulation module for tunnelling different protocols like MPLS, IP, NSH etc.") Fixes: 4b5f67232d95 ("net: Special handling for IP & MPLS.") Signed-off-by: Haishuang Yan Reviewed-by: Willem de Bruijn Link: https://patch.msgid.link/20261002153039.462663-1-yanhaishuang@cmss.chinamobile.com Signed-off-by: Jakub Kicinski --- drivers/net/bareudp.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/net/bareudp.c b/drivers/net/bareudp.c index c3b5ed52d877..b9fe54151e1a 100644 --- a/drivers/net/bareudp.c +++ b/drivers/net/bareudp.c @@ -356,7 +356,7 @@ static int bareudp_xmit_skb(struct sk_buff *skb, struct net_device *dev, if (err) goto free_dst; - skb_set_inner_protocol(skb, bareudp->ethertype); + skb_set_inner_protocol(skb, skb->protocol); udp_tunnel_xmit_skb(rt, sk, skb, saddr, info->key.u.ipv4.dst, tos, ttl, df, sport, bareudp->port, !net_eq(bareudp->net, dev_net(bareudp->dev)), @@ -424,6 +424,7 @@ static int bareudp6_xmit_skb(struct sk_buff *skb, struct net_device *dev, if (err) goto free_dst; + skb_set_inner_protocol(skb, skb->protocol); daddr = info->key.u.ipv6.dst; udp_tunnel6_xmit_skb(dst, sk, skb, dev, &saddr, &daddr, prio, ttl, From 6d25ffca055a77787c21a36b66c253f76239411b Mon Sep 17 00:00:00 2001 From: Daehyeon Ko <4ncienth@gmail.com> Date: Tue, 6 Oct 2026 12:51:08 +0900 Subject: [PATCH 319/367] cipso: adjust cached option offsets when removing CIPSO MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit cipso_v4_skbuff_delattr() removes the CIPSO bytes but does not adjust cached offsets for options that follow them. For example, with a valid 10-byte CIPSO option followed by a seven-byte RR option, parsing records rr at offset 30. Removing CIPSO moves RR to offset 20, while the cached offset remains 30. Consumers such as ip_forward_options() and __ip_options_echo() then access the wrong bytes; the latter may interpret packet data as the option length and copy it into fixed-size option storage. Mirror cipso_v4_delopt() and subtract cipso_len from the srr, rr, ts and router_alert offsets when they follow CIPSO. cipso_len is the distance the first memmove() shifts those options. The later header move and network-header reset relocate the bytes and their offset base together. Fixes: 89aa3619d141 ("cipso: make cipso_v4_skbuff_delattr() fully remove the CIPSO options") Cc: stable@vger.kernel.org Reviewed-by: Ondrej Mosnáček Signed-off-by: Daehyeon Ko <4ncienth@gmail.com> Acked-by: Paul Moore Link: https://patch.msgid.link/20261006035108.3101440-1-4ncienth@gmail.com Signed-off-by: Jakub Kicinski --- net/ipv4/cipso_ipv4.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/net/ipv4/cipso_ipv4.c b/net/ipv4/cipso_ipv4.c index a05aa075de1a..1aacbbeffbc6 100644 --- a/net/ipv4/cipso_ipv4.c +++ b/net/ipv4/cipso_ipv4.c @@ -2287,6 +2287,14 @@ int cipso_v4_skbuff_delattr(struct sk_buff *skb) new_hdr_len - new_hdr_len_actual); opt->optlen -= hdr_len_delta; + if (opt->srr > opt->cipso) + opt->srr -= cipso_len; + if (opt->rr > opt->cipso) + opt->rr -= cipso_len; + if (opt->ts > opt->cipso) + opt->ts -= cipso_len; + if (opt->router_alert > opt->cipso) + opt->router_alert -= cipso_len; opt->cipso = 0; opt->is_changed = 1; if (hdr_len_delta != 0) { From d4f25b097e46d82718bb737d4540c99b7ab0a408 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Mon, 7 Sep 2026 21:07:50 +0200 Subject: [PATCH 320/367] riscv: fix load_unaligned_zeropad() fixup for RV32 The fixup for load_unaligned_zeropad() runs when REG_L crosses into an unmapped page. REG_L is lw on RV32 and ld on RV64, but the fixup assumes an 8-byte load: it rounds the address down to 8 bytes and derives the shift from three address bits. On RV32, a load starting in the last three bytes of a mapped page selects the word four bytes earlier and shifts it by 40, 48, or 56 bits, more than the register width. The caller gets earlier bytes instead of the string tail followed by zeroes. This affects RV32 MMU kernels built with RISCV_EFFICIENT_UNALIGNED_ACCESS=y, which selects DCACHE_WORD_ACCESS and lets dcache name hashing and comparison and strscpy() use this path. Derive the alignment and offset masks from sizeof(data) instead of a fixed 8. This makes RV32 use the correct source word. RV64 code is unchanged: the complete extable.o is byte-for-byte identical before and after. Fixes: d0fdc20b0429 ("riscv: select DCACHE_WORD_ACCESS for efficient unaligned access HW") Cc: stable@vger.kernel.org Assisted-by: LLM Signed-off-by: Karl Mehltretter Reviewed-by: Jisheng Zhang Link: https://patch.msgid.link/20260907190750.26303-1-kmehltretter@gmail.com Signed-off-by: Paul Walmsley --- arch/riscv/mm/extable.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/arch/riscv/mm/extable.c b/arch/riscv/mm/extable.c index dd1530af3ef1..e252eba55ede 100644 --- a/arch/riscv/mm/extable.c +++ b/arch/riscv/mm/extable.c @@ -68,8 +68,8 @@ ex_handler_load_unaligned_zeropad(const struct exception_table_entry *ex, addr = regs_get_gpr(regs, reg_addr * sizeof(unsigned long)); - offset = addr & 0x7UL; - addr &= ~0x7UL; + offset = addr & (sizeof(data) - 1); + addr &= ~(sizeof(data) - 1); data = *(unsigned long *)addr >> (offset * 8); From 00765a3467b992d0eb56704a28887256317eb4bd Mon Sep 17 00:00:00 2001 From: Jisheng Zhang Date: Thu, 8 Oct 2026 09:00:58 +0800 Subject: [PATCH 321/367] spi: dt-bindings: snps,dw-apb-ssi: Add Synaptics sl2610 spi Add the compatible for the Synaptics SL2610 SoC SPI controllers, which are compatible with the Synopsys DesignWare APB SSI. Signed-off-by: Jisheng Zhang Acked-by: Conor Dooley Link: https://patch.msgid.link/20261008010058.28382-1-jszhang@kernel.org Signed-off-by: Mark Brown --- Documentation/devicetree/bindings/spi/snps,dw-apb-ssi.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/spi/snps,dw-apb-ssi.yaml b/Documentation/devicetree/bindings/spi/snps,dw-apb-ssi.yaml index e8d6d5d858b6..41ec69f5dceb 100644 --- a/Documentation/devicetree/bindings/spi/snps,dw-apb-ssi.yaml +++ b/Documentation/devicetree/bindings/spi/snps,dw-apb-ssi.yaml @@ -77,6 +77,7 @@ properties: - mscc,ocelot-spi - mscc,jaguar2-spi - sophgo,sg2042-spi + - syna,sl2610-spi - thead,th1520-spi - ultrarisc,dp1000-spi - const: snps,dw-apb-ssi From 196d4d10c1b33ae176685ff42a9994a5952b1d0f Mon Sep 17 00:00:00 2001 From: Gyeyoung Baek Date: Thu, 1 Oct 2026 22:39:12 +0900 Subject: [PATCH 322/367] drm/imagination: Fix reference and vm_bo handling in remap() When a map overlaps part of an existing mapping, pvr_vm_gpuva_remap() splits the mapping into prev/next parts covering what the request did not take, instead of creating something new. It gets two things wrong. - A GEM reference is taken for each part but it's never needed and never dropped, so it leaks one reference per split (remap-next-in-2m in tests/imagination/pvr_vm_map.c): CRITICAL: 33558528 bytes of shmem still held after close - The parts still belong to the original object being split, but pvr_vm_gpuva_remap() links them to ctx->gpuvm_bo, the new object's vm_bo: prev_va --obj--> BO_A BO_B <--obj-- vm_bo (ctx->gpuvm_bo) \___________link___________/ (mismatch: BO_A != BO_B) drm_gpuva_link() catches the mismatch: WARNING: drivers/gpu/drm/drm_gpuvm.c:2108 at drm_gpuva_link+0x2ec/0x310 drm_WARN_ON(obj != vm_bo->obj) Call trace: drm_gpuva_link pvr_vm_gpuva_remap __drm_gpuvm_sm_map pvr_vm_map pvr_ioctl_vm_map Link them to op->remap.unmap->va->vm_bo instead. The locking of the GPUVA lists of the other objects touched by a split is not addressed here; it is handled by switching the GPUVM to immediate mode. Fixes: ff5f643de0bf ("drm/imagination: Add GEM and VM related code") Signed-off-by: Gyeyoung Baek Reviewed-by: Brajesh Gupta Link: https://patch.msgid.link/20261001-pvr-fixes-a-v2-1-f58254e5dfb8@gmail.com Signed-off-by: Alessio Belle --- drivers/gpu/drm/imagination/pvr_vm.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/imagination/pvr_vm.c b/drivers/gpu/drm/imagination/pvr_vm.c index 55cc999f3708..cbdd15ed74fd 100644 --- a/drivers/gpu/drm/imagination/pvr_vm.c +++ b/drivers/gpu/drm/imagination/pvr_vm.c @@ -418,6 +418,8 @@ pvr_vm_gpuva_unmap(struct drm_gpuva_op *op, void *op_ctx) static int pvr_vm_gpuva_remap(struct drm_gpuva_op *op, void *op_ctx) { + /* The split parts belong to the object of the mapping being split. */ + struct drm_gpuvm_bo *vm_bo = op->remap.unmap->va->vm_bo; struct pvr_vm_bind_op *ctx = op_ctx; u64 va_start = 0, va_range = 0; int err; @@ -433,14 +435,12 @@ pvr_vm_gpuva_remap(struct drm_gpuva_op *op, void *op_ctx) drm_gpuva_remap(&ctx->prev_va->base, &ctx->next_va->base, &op->remap); if (op->remap.prev) { - pvr_gem_object_get(gem_to_pvr_gem(ctx->prev_va->base.gem.obj)); - drm_gpuva_link(&ctx->prev_va->base, ctx->gpuvm_bo); + drm_gpuva_link(&ctx->prev_va->base, vm_bo); ctx->prev_va = NULL; } if (op->remap.next) { - pvr_gem_object_get(gem_to_pvr_gem(ctx->next_va->base.gem.obj)); - drm_gpuva_link(&ctx->next_va->base, ctx->gpuvm_bo); + drm_gpuva_link(&ctx->next_va->base, vm_bo); ctx->next_va = NULL; } From 789f290b2e81d42820bddcb4010ffbc2ae2ded54 Mon Sep 17 00:00:00 2001 From: Gyeyoung Baek Date: Thu, 1 Oct 2026 22:39:13 +0900 Subject: [PATCH 323/367] drm/imagination: Size page table preallocation by device address Commit 0a8224058a58 ("drm/imagination: Fix page count for page table for map() interface") passed the device address to pvr_mmu_op_context_create(), but the preallocation is still sized from device_addr + sgt_offset with an exclusive end. The offset into the object's pages has no place in a device-virtual range, and the exclusive end allocates one table too many when the range ends on a table boundary. Count the tables from device_addr and the inclusive end of the range. The new calculation does not take page table indices into account. This fixes another issue with the previous implementation, where the difference between start and end indices could underflow when a mapping crossed a page table boundary. This would lead to the driver preallocating a huge number of MMU pages, exhausting system memory and triggering an out of memory panic. Fixes: 0a8224058a58 ("drm/imagination: Fix page count for page table for map() interface") Fixes: ff5f643de0bf ("drm/imagination: Add GEM and VM related code") Signed-off-by: Gyeyoung Baek Reviewed-by: Brajesh Gupta Reviewed-by: Alessio Belle Cc: stable@vger.kernel.org [Alessio: add paragraph to point out OOM bug being fixed, cc stable] Link: https://patch.msgid.link/20261001-pvr-fixes-a-v2-2-f58254e5dfb8@gmail.com Signed-off-by: Alessio Belle --- drivers/gpu/drm/imagination/pvr_mmu.c | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/drivers/gpu/drm/imagination/pvr_mmu.c b/drivers/gpu/drm/imagination/pvr_mmu.c index 62eae7fcd5a2..67e73d7d5d0e 100644 --- a/drivers/gpu/drm/imagination/pvr_mmu.c +++ b/drivers/gpu/drm/imagination/pvr_mmu.c @@ -2337,7 +2337,7 @@ void pvr_mmu_op_context_destroy(struct pvr_mmu_op_context *op_ctx) * @ctx: MMU context associated with owning VM context. * @sgt: Scatter gather table containing pages pinned for use by this context. * @device_addr: Virtual device address at the start of the requested mapping. - * @sgt_offset: Start offset of the requested device-virtual memory mapping. + * @sgt_offset: Offset into @sgt of the start of the requested mapping. * @size: Size in bytes of the requested device-virtual memory mapping. For an * unmapping, this should be zero so that no page tables are allocated. * @@ -2350,7 +2350,6 @@ struct pvr_mmu_op_context * pvr_mmu_op_context_create(struct pvr_mmu_context *ctx, struct sg_table *sgt, u64 device_addr, u64 sgt_offset, u64 size) { - u64 start_addr = device_addr + sgt_offset; int err; struct pvr_mmu_op_context *op_ctx = kzalloc_obj(*op_ctx); @@ -2365,18 +2364,18 @@ pvr_mmu_op_context_create(struct pvr_mmu_context *ctx, struct sg_table *sgt, if (size) { /* - * The number of page table objects we need to prealloc is - * indicated by the mapping size, start address and the sizes - * of the areas mapped per PT or PD. The range calculation is - * identical to that for the index into a table for a device - * address, so we reuse those functions here. + * The page tables needed are set by the device-virtual range + * being mapped: one level 1 table per 1GiB region and one + * level 0 table per 2MiB region the range touches. Tables that + * already exist are not consumed, so this is an upper bound. */ - const u32 l1_start_idx = pvr_page_table_l2_idx(start_addr); - const u32 l1_end_idx = pvr_page_table_l2_idx(start_addr + size); - const u32 l1_count = l1_end_idx - l1_start_idx + 1; - const u32 l0_start_idx = pvr_page_table_l1_idx(start_addr); - const u32 l0_end_idx = pvr_page_table_l1_idx(start_addr + size); - const u32 l0_count = l0_end_idx - l0_start_idx + 1; + const u64 last_addr = device_addr + size - 1; + const u64 l1_count = + (last_addr >> ROGUE_MMUCTRL_VADDR_PC_INDEX_SHIFT) - + (device_addr >> ROGUE_MMUCTRL_VADDR_PC_INDEX_SHIFT) + 1; + const u64 l0_count = + (last_addr >> ROGUE_MMUCTRL_VADDR_PD_INDEX_SHIFT) - + (device_addr >> ROGUE_MMUCTRL_VADDR_PD_INDEX_SHIFT) + 1; /* * Alloc and push page table entries until we have enough of From 911655ab1dfb9d526978a75df3b210132620d30a Mon Sep 17 00:00:00 2001 From: Marek Szyprowski Date: Wed, 30 Sep 2026 10:50:55 +0200 Subject: [PATCH 324/367] swiotlb: fix default_swiotlb_limit() for non-growable default pool Commit ad96ce3252db ("swiotlb: determine potential physical address limit") changed default_swiotlb_limit() to return the maximum physical address that could ever be used by dynamically allocated pools, so that the value stays constant when CONFIG_SWIOTLB_DYNAMIC=y. However, io_tlb_default_mem.phys_limit is returned even when the default pool cannot grow, e.g. when swiotlb_init_remap() is called with a remap callback. This breaks Xen dom0 on x86: pci_xen_swiotlb_init() passes SWIOTLB_ANY and xen_swiotlb_fixup(), so phys_limit is set to the top of dom0 memory, although xen_swiotlb_fixup() makes the default pool below the 32-bit boundary. xen_swiotlb_dma_supported() then rejects 32-bit DMA masks, even though the bounce buffer is perfectly usable by such devices. Return phys_limit only if the default pool can actually grow, otherwise report the real end of the default pool, like in the !CONFIG_SWIOTLB_DYNAMIC case. Reported-by: Andreas Greve Reported-by: James Dingwall Fixes: ad96ce3252db ("swiotlb: determine potential physical address limit") Closes: https://lore.kernel.org/xen-devel/f74668db-52fd-4575-8372-7bfdf10d62ac@a-greve.de/ Cc: stable@vger.kernel.org Assisted-by: Claude Opus 5.5 (1M context) Tested-by: James Dingwall Link: https://lore.kernel.org/all/20261005070518.1590097-1-m.szyprowski@samsung.com/ Signed-off-by: Marek Szyprowski --- kernel/dma/swiotlb.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/kernel/dma/swiotlb.c b/kernel/dma/swiotlb.c index aa2f1c4588b9..5edea93ec635 100644 --- a/kernel/dma/swiotlb.c +++ b/kernel/dma/swiotlb.c @@ -1844,10 +1844,10 @@ phys_addr_t default_swiotlb_base(void) phys_addr_t default_swiotlb_limit(void) { #ifdef CONFIG_SWIOTLB_DYNAMIC - return io_tlb_default_mem.phys_limit; -#else - return io_tlb_default_mem.defpool.end - 1; + if (io_tlb_default_mem.can_grow) + return io_tlb_default_mem.phys_limit; #endif + return io_tlb_default_mem.defpool.end - 1; } #ifdef CONFIG_DEBUG_FS From 4b1d04693e5ef388d8c6110bc93ee0d0681f9711 Mon Sep 17 00:00:00 2001 From: Matthias Goergens Date: Wed, 23 Sep 2026 18:17:49 +0800 Subject: [PATCH 325/367] MAINTAINERS: name the ext4 dev branch The ext4 T: entry names the repository without a branch, so tools that default to the repository's HEAD select master, which still points at a merge from October 2020 (96485e446260). Development happens on dev, which is also the branch linux-next merges. Name it. The Sashiko review bot, for one, falls back to HEAD this way and has been reviewing ext4 patches against that tree. Its review of an EA inode refcount fix reported a Critical double decrement and an undefined function after applying the patch to that stale baseline; neither holds on dev. Link: https://lore.kernel.org/all/20260916072420.316321F000FF@smtp.kernel.org/ Signed-off-by: Matthias Goergens Acked-by: Jan Kara Link: https://patch.msgid.link/20260923101749.3505886-1-matthias.goergens@gmail.com Signed-off-by: Theodore Ts'o --- MAINTAINERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MAINTAINERS b/MAINTAINERS index cc3cae2e378b..c884c4c62e3f 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -9859,7 +9859,7 @@ L: linux-ext4@vger.kernel.org S: Maintained W: http://ext4.wiki.kernel.org Q: http://patchwork.ozlabs.org/project/linux-ext4/list/ -T: git git://git.kernel.org/pub/scm/linux/kernel/git/tytso/ext4.git +T: git git://git.kernel.org/pub/scm/linux/kernel/git/tytso/ext4.git dev F: Documentation/filesystems/ext4/ F: fs/ext4/ F: include/trace/events/ext4.h From 3e78d7b063230d39ffc42e80ee665b6c32a6adeb Mon Sep 17 00:00:00 2001 From: Theodore Ts'o Date: Thu, 24 Sep 2026 10:58:12 -0400 Subject: [PATCH 326/367] ext4: mark data=journal as deprecated and will be removed in January 2028. Signed-off-by: Theodore Ts'o --- Documentation/admin-guide/ext4.rst | 6 ++++-- fs/ext4/super.c | 1 + 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/Documentation/admin-guide/ext4.rst b/Documentation/admin-guide/ext4.rst index 742a48e6fc0c..8b23b6ff3df1 100644 --- a/Documentation/admin-guide/ext4.rst +++ b/Documentation/admin-guide/ext4.rst @@ -172,7 +172,8 @@ When mounting an ext4 filesystem, the following option are accepted: data=journal All data are committed into the journal prior to being written into the main file system. Enabling this mode will disable delayed allocation - and O_DIRECT support. + and O_DIRECT support. This feature is deprecated and support + will be removed in January 2028. data=ordered (*) All data are forced directly out to the main file system prior to its @@ -418,7 +419,8 @@ There are 3 different data modes: consistent state. This mode is the slowest except when data needs to be read from and written to disk at the same time where it outperforms all others modes. Enabling this mode will disable delayed allocation and O_DIRECT - support. + support. The data=journal feature is deprecated and support will be + removed in January 2028. /proc entries ============= diff --git a/fs/ext4/super.c b/fs/ext4/super.c index bca0dc87d0b7..d636b89090a2 100644 --- a/fs/ext4/super.c +++ b/fs/ext4/super.c @@ -5057,6 +5057,7 @@ static int ext4_load_and_init_journal(struct super_block *sb, static int ext4_check_journal_data_mode(struct super_block *sb) { if (test_opt(sb, DATA_FLAGS) == EXT4_MOUNT_JOURNAL_DATA) { + printk(KERN_WARNING "EXT4-fs: The data=journal mount option is deprecated and support will be removed in January 2028\n"); printk_once(KERN_WARNING "EXT4-fs: Warning: mounting with " "data=journal disables delayed allocation, " "dioread_nolock, O_DIRECT and fast_commit support!\n"); From 259c4a519100e9182f264f8009ee212b32774277 Mon Sep 17 00:00:00 2001 From: Hamza Mahfooz Date: Fri, 2 Oct 2026 21:36:47 -0400 Subject: [PATCH 327/367] net: mana: reserve RX buffer headroom to fix forwarding performance Commit 730ff06d3f5c ("net: mana: Use page pool fragments for RX buffers instead of full pages to improve memory efficiency.") started handing out RX buffers with zero headroom so that two buffers fit into one page at the default MTU. The MANA TX path, however, stores the per scatter-gather entry DMA mappings in `struct mana_skb_head` at skb->head, and mana_start_xmit() therefore calls skb_cow_head(skb, MANA_HEADROOM). The port advertises this requirement as ndev->needed_headroom = MANA_HEADROOM. As a result every packet that is received and then forwarded out of a MANA port fails the skb_cow() in ip_forward() and gets reallocated and copied by pskb_expand_head(). This is invisible to a plain RX or TX workload, but it puts a full skb reallocation plus memcpy on the hot path of every single forwarded packet, which is exactly what a router/NVA workload does. Restore the headroom. Note that reserving MANA_HEADROOM (232) is not enough: ip_forward() asks for LL_RESERVED_SPACE(dev), which rounds hard_header_len + needed_headroom up to HH_DATA_MOD and is 256 bytes on ethernet. Use LL_RESERVED_SPACE() directly so the value keeps tracking both constants. Also, since LL_RESERVED_SPACE() tracks MANA_HEADROOM, it grows with MAX_SKB_FRAGS and for MAX_SKB_FRAGS >= 19 it is greater than 256, so we have to account for that by using the headroom the RX queue actually uses (instead of assuming XDP_PACKET_HEADROOM) and turning MANA_XDP_MTU_MAX into MANA_XDP_MTU_MAX(ndev) (note that at the default CONFIG_MAX_SKB_FRAGS=17 they are equivalent). At the default MTU on a 4K page this means a buffer no longer fits twice into a page (SKB_DATA_ALIGN(1500 + MANA_RXBUF_PAD + 256) = 2112), so the frag-vs-single decision is now made by computing the real buffer size instead of comparing the MTU against PAGE_SIZE / 2. The page_pool fragment path is still used wherever at least two buffers genuinely fit, e.g. on 16K and 64K page sizes. Measured on an Azure VM with a MANA NIC acting as a forwarding NVA (UDP, 1400 byte payload, 4 streams, 8 Gbps offered, only the forwarding node's kernel differs), 8 runs each, median: forwarded pps throughput before 272,830 3.06 Gbps after 390,560 4.37 Gbps (+43%) perf on the forwarding node, same workload: memset_orig __pi_memcpy pskb_expand_head before 10.07% 3.96% present after 0.94% 0.64% gone Cc: stable@vger.kernel.org Fixes: 730ff06d3f5c ("net: mana: Use page pool fragments for RX buffers instead of full pages to improve memory efficiency.") Signed-off-by: Hamza Mahfooz Reviewed-by: Simon Horman Link: https://patch.msgid.link/20261003013647.2051416-1-hamzamahfooz@linux.microsoft.com Signed-off-by: Jakub Kicinski --- .../net/ethernet/microsoft/mana/mana_bpf.c | 8 +-- drivers/net/ethernet/microsoft/mana/mana_en.c | 63 ++++++++++++++----- .../ethernet/microsoft/mana/mana_ethtool.c | 10 +-- include/net/mana/mana.h | 14 ++++- 4 files changed, 68 insertions(+), 27 deletions(-) diff --git a/drivers/net/ethernet/microsoft/mana/mana_bpf.c b/drivers/net/ethernet/microsoft/mana/mana_bpf.c index 5c9961ee9747..b27fd9b8c4f6 100644 --- a/drivers/net/ethernet/microsoft/mana/mana_bpf.c +++ b/drivers/net/ethernet/microsoft/mana/mana_bpf.c @@ -91,7 +91,7 @@ u32 mana_run_xdp(struct net_device *ndev, struct mana_rxq *rxq, goto out; xdp_init_buff(xdp, PAGE_SIZE, &rxq->xdp_rxq); - xdp_prepare_buff(xdp, buf_va, XDP_PACKET_HEADROOM, pkt_len, true); + xdp_prepare_buff(xdp, buf_va, rxq->headroom, pkt_len, true); act = bpf_prog_run_xdp(prog, xdp); @@ -183,9 +183,9 @@ static int mana_xdp_set(struct net_device *ndev, struct bpf_prog *prog, if (!old_prog && !prog) return 0; - if (prog && ndev->mtu > MANA_XDP_MTU_MAX) { + if (prog && ndev->mtu > MANA_XDP_MTU_MAX(ndev)) { netdev_err(ndev, "XDP: mtu:%u too large, mtu_max:%lu\n", - ndev->mtu, MANA_XDP_MTU_MAX); + ndev->mtu, MANA_XDP_MTU_MAX(ndev)); NL_SET_ERR_MSG_MOD(extack, "XDP: mtu too large"); return -EOPNOTSUPP; @@ -238,7 +238,7 @@ static int mana_xdp_set(struct net_device *ndev, struct bpf_prog *prog, bpf_prog_put(old_prog); if (prog) - ndev->max_mtu = min_t(unsigned int, MANA_XDP_MTU_MAX, + ndev->max_mtu = min_t(unsigned int, MANA_XDP_MTU_MAX(ndev), gc->adapter_mtu - ETH_HLEN); else ndev->max_mtu = gc->adapter_mtu - ETH_HLEN; diff --git a/drivers/net/ethernet/microsoft/mana/mana_en.c b/drivers/net/ethernet/microsoft/mana/mana_en.c index 591fb4191d90..a1fb06b23947 100644 --- a/drivers/net/ethernet/microsoft/mana/mana_en.c +++ b/drivers/net/ethernet/microsoft/mana/mana_en.c @@ -758,6 +758,44 @@ static void *mana_get_rxbuf_pre(struct mana_rxq *rxq, dma_addr_t *da) return va; } +/* Reserve enough headroom to satisfy the skb_cow() in ip_forward() and avoid + * reallocation: the TX path keeps the SGE DMA mappings in struct mana_skb_head + * at skb->head, so the port advertises ndev->needed_headroom = MANA_HEADROOM. + */ +static u32 mana_get_rxbuf_headroom(struct mana_port_context *apc) +{ + if (mana_xdp_get(apc)) + return mana_xdp_headroom(apc->ndev); + + return LL_RESERVED_SPACE(apc->ndev); +} + +static u32 mana_get_rxbuf_size(struct mana_port_context *apc, u32 mtu) +{ + u32 len = SKB_DATA_ALIGN(mtu + MANA_RXBUF_PAD + + mana_get_rxbuf_headroom(apc)); + + return ALIGN(len, MANA_RX_FRAG_ALIGNMENT); +} + +/* Returns true when one RX buffer per page is already required by XDP or by + * the buffer size implied by the MTU, i.e. regardless of the + * MANA_PRIV_FLAG_USE_FULL_PAGE_RXBUF private flag. + */ +bool mana_single_rxbuf_per_page_forced(struct mana_port_context *apc, u32 mtu) +{ + /* For xdp make sure only one packet fits per page. */ + if (mana_xdp_get(apc)) + return true; + + /* Only use the page_pool fragment path when at least two buffers, + * including the headroom each of them has to reserve, actually fit + * into one page. Otherwise the fragment path degenerates into one + * buffer per page while still paying the fragment accounting cost. + */ + return PAGE_SIZE / mana_get_rxbuf_size(apc, mtu) < 2; +} + static bool mana_use_single_rxbuf_per_page(struct mana_port_context *apc, u32 mtu) { @@ -770,32 +808,27 @@ mana_use_single_rxbuf_per_page(struct mana_port_context *apc, u32 mtu) if (apc->priv_flags & BIT(MANA_PRIV_FLAG_USE_FULL_PAGE_RXBUF)) return true; - /* For xdp and jumbo frames make sure only one packet fits per page. */ - if (mtu + MANA_RXBUF_PAD > PAGE_SIZE / 2 || mana_xdp_get(apc)) - return true; - - return false; + return mana_single_rxbuf_per_page_forced(apc, mtu); } -/* Get RX buffer's data size, alloc size, XDP headroom based on MTU */ +/* Get RX buffer's data size, alloc size, headroom and frag count based on MTU */ static void mana_get_rxbuf_cfg(struct mana_port_context *apc, int mtu, u32 *datasize, u32 *alloc_size, u32 *headroom, u32 *frag_count) { - u32 len, buf_size; + u32 buf_size; /* Calculate datasize first (consistent across all cases) */ *datasize = mtu + ETH_HLEN; + *headroom = mana_get_rxbuf_headroom(apc); + if (mana_use_single_rxbuf_per_page(apc, mtu)) { - if (mana_xdp_get(apc)) { - *headroom = XDP_PACKET_HEADROOM; + if (mana_xdp_get(apc)) *alloc_size = PAGE_SIZE; - } else { - *headroom = 0; /* no support for XDP */ + else *alloc_size = SKB_DATA_ALIGN(mtu + MANA_RXBUF_PAD + *headroom); - } *frag_count = 1; @@ -809,11 +842,7 @@ static void mana_get_rxbuf_cfg(struct mana_port_context *apc, } /* Standard MTU case - optimize for multiple packets per page */ - *headroom = 0; - - /* Calculate base buffer size needed */ - len = SKB_DATA_ALIGN(mtu + MANA_RXBUF_PAD + *headroom); - buf_size = ALIGN(len, MANA_RX_FRAG_ALIGNMENT); + buf_size = mana_get_rxbuf_size(apc, mtu); /* Calculate how many packets can fit in a page */ *frag_count = PAGE_SIZE / buf_size; diff --git a/drivers/net/ethernet/microsoft/mana/mana_ethtool.c b/drivers/net/ethernet/microsoft/mana/mana_ethtool.c index ece7ff9cc409..33db79569d3b 100644 --- a/drivers/net/ethernet/microsoft/mana/mana_ethtool.c +++ b/drivers/net/ethernet/microsoft/mana/mana_ethtool.c @@ -815,12 +815,12 @@ static int mana_set_priv_flags(struct net_device *ndev, u32 priv_flags) if (!apc->port_is_up) return 0; - /* If XDP is attached or MTU is jumbo, single-buffer-per-page - * is already forced regardless of this flag. Skip the - * expensive detach/attach cycle since nothing changes. + /* If XDP is attached or the MTU already forces one buffer per + * page, single-buffer-per-page is used regardless of this + * flag. Skip the expensive detach/attach cycle since nothing + * changes. */ - if (ndev->mtu + MANA_RXBUF_PAD > PAGE_SIZE / 2 || - mana_xdp_get(apc)) + if (mana_single_rxbuf_per_page_forced(apc, ndev->mtu)) return 0; /* Block RDMA from grabbing the vport during detach/attach */ diff --git a/include/net/mana/mana.h b/include/net/mana/mana.h index 83b7eff4646e..8c3dc9d0b299 100644 --- a/include/net/mana/mana.h +++ b/include/net/mana/mana.h @@ -377,7 +377,18 @@ struct mana_recv_buf_oob { #define MANA_RXBUF_PAD (SKB_DATA_ALIGN(sizeof(struct skb_shared_info)) \ + ETH_HLEN) -#define MANA_XDP_MTU_MAX (PAGE_SIZE - MANA_RXBUF_PAD - XDP_PACKET_HEADROOM) +/* Headroom an RX buffer has to reserve while XDP is attached: the XDP program + * needs XDP_PACKET_HEADROOM, and the TX path needs LL_RESERVED_SPACE() (see + * mana_get_rxbuf_headroom()). LL_RESERVED_SPACE() grows with MAX_SKB_FRAGS and + * exceeds XDP_PACKET_HEADROOM once CONFIG_MAX_SKB_FRAGS is 19 or more. + */ +static inline u32 mana_xdp_headroom(struct net_device *ndev) +{ + return max_t(u32, LL_RESERVED_SPACE(ndev), XDP_PACKET_HEADROOM); +} + +#define MANA_XDP_MTU_MAX(ndev) \ + (PAGE_SIZE - MANA_RXBUF_PAD - mana_xdp_headroom(ndev)) struct mana_rxq { struct gdma_queue *gdma_rq; @@ -691,6 +702,7 @@ int mana_query_link_cfg(struct mana_port_context *apc); int mana_set_bw_clamp(struct mana_port_context *apc, u32 speed, int enable_clamping); void mana_query_phy_stats(struct mana_port_context *apc); +bool mana_single_rxbuf_per_page_forced(struct mana_port_context *apc, u32 mtu); int mana_pre_alloc_rxbufs(struct mana_port_context *apc, int mtu, int num_queues); void mana_pre_dealloc_rxbufs(struct mana_port_context *apc); void mana_unmap_skb(struct sk_buff *skb, struct mana_port_context *apc); From 3f090039995f609a8e236f37980dee5712dded61 Mon Sep 17 00:00:00 2001 From: Tianyi Gao Date: Tue, 6 Oct 2026 18:32:36 +0100 Subject: [PATCH 328/367] veth: fix peer NETDEV_XDP_ACT_NDO_XMIT after GRO is toggled while down MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A veth device advertises NETDEV_XDP_ACT_NDO_XMIT only if its peer has an XDP program attached or GRO enabled, that is, only if the peer will have NAPI to receive the frames. veth_set_features() updates the peer's flag when GRO is toggled, but returns early if the device is down, and veth_open() only refreshes the flags of the device being opened. Toggling GRO while the device is down therefore leaves the peer's flag stale after the device comes up. If GRO was enabled while down, the device comes up with NAPI but the peer does not advertise NDO_XMIT, and devmap rejects redirects to the peer with -EOPNOTSUPP. If GRO was disabled while down, the device comes up without NAPI but the peer still advertises NDO_XMIT, so redirects are accepted and then dropped in veth_xdp_xmit() with -ENXIO. Commit 7a6102aa6df0 ("veth: Update XDP feature set when bringing up device") made veth_open() refresh the device's own flags. Refresh the peer's flags there too. The peer's flag depends on this device's XDP program and GRO setting, not on whether the peer is up, so it is correct to set it even if the peer is down. Fixes: 8267fc71abb2 ("veth: take into account peer device for NETDEV_XDP_ACT_NDO_XMIT xdp_features flag") Signed-off-by: Tianyi Gao Reviewed-by: Toke Høiland-Jørgensen Acked-by: Jesper Dangaard Brouer Acked-by: Lorenzo Bianconi Link: https://patch.msgid.link/20261006173241.65945-2-tianyi@cloudflare.com Signed-off-by: Jakub Kicinski --- drivers/net/veth.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/veth.c b/drivers/net/veth.c index 71227d0389aa..643b97dc5245 100644 --- a/drivers/net/veth.c +++ b/drivers/net/veth.c @@ -1415,6 +1415,7 @@ static int veth_open(struct net_device *dev) } veth_set_xdp_features(dev); + veth_set_xdp_features(peer); return 0; } From c017800af5c71ebb2e6209eebf5c60c19850ffe7 Mon Sep 17 00:00:00 2001 From: Tianyi Gao Date: Tue, 6 Oct 2026 18:32:37 +0100 Subject: [PATCH 329/367] selftests: net: veth: test peer ndo-xmit after GRO toggle while down Test that toggling GRO on a veth device while it is down updates the peer's ndo-xmit XDP feature once the device comes up, for both GRO on and GRO off. xdp-features is only visible through netlink, so read it with the ynl CLI, as double_udp_encap.sh does. Skip the checks if the CLI is not found, as in an installed kselftest tree. Signed-off-by: Tianyi Gao Link: https://patch.msgid.link/20261006173241.65945-3-tianyi@cloudflare.com Signed-off-by: Jakub Kicinski --- tools/testing/selftests/net/veth.sh | 46 +++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/tools/testing/selftests/net/veth.sh b/tools/testing/selftests/net/veth.sh index 9709dd067c72..8b867e0675a7 100755 --- a/tools/testing/selftests/net/veth.sh +++ b/tools/testing/selftests/net/veth.sh @@ -9,6 +9,8 @@ readonly DST=1 readonly DST_NAT=100 readonly NS_SRC=$BASE$SRC readonly NS_DST=$BASE$DST +# shellcheck disable=SC2155 # prefer RO variable over return value from cmd +readonly YNL="$(dirname "$(readlink -f "$0")")/../../../net/ynl/pyynl/cli.py" # "baremetal" network used for raw UDP traffic readonly BM_NET_V4=192.168.1. @@ -74,6 +76,37 @@ chk_tso_flag() { __chk_flag "$1" $2 $3 tcp-segmentation-offload } +chk_xdp_feature() { + local msg="$1" + local target=$2 + local expected=$3 + local feature=$4 + local ifindex + local out + local st + local flag + + ifindex=`ip netns exec $BASE$target cat /sys/class/net/veth$target/ifindex` + out=`ip netns exec $BASE$target "$YNL" --family netdev --do dev-get \ + --output-json --json "{\"ifindex\": $ifindex}" 2>&1` + st=$? + + printf "%-60s" "$msg" + if [ $st -ne 0 ]; then + echo " fail - ynl cli error: $out" + ret=1 + return + fi + + flag=`echo "$out" | grep -c "\"$feature\""` + if [ "$flag" = "$expected" ]; then + echo " ok " + else + echo " fail - expected $expected found $flag" + ret=1 + fi +} + chk_channels() { local msg="$1" local target=$2 @@ -222,6 +255,7 @@ fi [ $CPUS -lt 2 ] && echo "Only one CPU available, some tests will be skipped" [ $STRESS -gt 0 -a $CPUS -lt 3 ] && echo " stress test will be skipped, too" +[ ! -f "$YNL" ] && echo "ynl cli not found, ndo-xmit tests will be skipped" create_ns chk_gro_flag "default - gro flag" $SRC off @@ -283,6 +317,18 @@ chk_gro_flag " - peer gro flag" $SRC off chk_tso_flag " - tso flag" $SRC on chk_tso_flag " - peer tso flag" $DST on ip -n $NS_DST link set dev veth$DST up +if [ -f "$YNL" ]; then + chk_xdp_feature " - peer ndo-xmit" $SRC 1 ndo-xmit + # make sure the peer flag is set before disabling gro while down + ip netns exec $NS_DST ethtool -K veth$DST gro off + ip netns exec $NS_DST ethtool -K veth$DST gro on + chk_xdp_feature " - peer ndo-xmit re-armed" $SRC 1 ndo-xmit + ip -n $NS_DST link set dev veth$DST down + ip netns exec $NS_DST ethtool -K veth$DST gro off + ip -n $NS_DST link set dev veth$DST up + chk_xdp_feature " - peer ndo-xmit cleared" $SRC 0 ndo-xmit + ip netns exec $NS_DST ethtool -K veth$DST gro on +fi ip netns exec $NS_SRC ethtool -K veth$SRC tx-udp-segmentation off ip netns exec $NS_DST ethtool -K veth$DST rx-udp-gro-forwarding on chk_gro " - aggregation with TSO off" 1 From b056ca4d3742d0769f91137bb1ce3215428398f7 Mon Sep 17 00:00:00 2001 From: Li RongQing Date: Tue, 6 Oct 2026 13:58:20 +0300 Subject: [PATCH 330/367] net/mlx5e: Order ICOSQ cc update after CQ doorbell mlx5e_poll_ico_cq() requires sq->cc to be updated only after mlx5_cqwq_update_db_record(), otherwise a CQ overrun may occur. The current implementation updates sq->cc before the CQ doorbell record, violating this ordering requirement. Update the CQ doorbell record first and use dma_wmb() before updating sq->cc. This ensures that the CQ space is released to the device before the corresponding ICOSQ consumer index is updated by software. Fixes: fd9b4be8002c ("net/mlx5e: RX, Support multiple outstanding UMR posts") Signed-off-by: Li RongQing Reviewed-by: Dragos Tatulea Signed-off-by: Tariq Toukan Reviewed-by: Daniel Machon Link: https://patch.msgid.link/20261006105820.257208-1-tariqt@nvidia.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/mellanox/mlx5/core/en_rx.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_rx.c b/drivers/net/ethernet/mellanox/mlx5/core/en_rx.c index 7bd0606a5253..903af3b3e779 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en_rx.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/en_rx.c @@ -908,10 +908,12 @@ int mlx5e_poll_ico_cq(struct mlx5e_cq *cq) } while (!last_wqe); } while ((++i < MLX5E_TX_CQ_POLL_BUDGET) && (cqe = mlx5_cqwq_get_cqe(&cq->wq))); - sq->cc = sqcc; - mlx5_cqwq_update_db_record(&cq->wq); + /* ensure cq space is freed before enabling more cqes */ + dma_wmb(); + + sq->cc = sqcc; return i; } From fc13ba44da197a186f2f4d77c5b7d73d0f99f053 Mon Sep 17 00:00:00 2001 From: Sebastien Royen Date: Wed, 7 Oct 2026 10:05:57 +0200 Subject: [PATCH 331/367] net: dsa: microchip: fix KSZ8765 fiber detection The KSZ8765 is similar to the KSZ8795 but has two fiber ports. KSZ8_PORT_STATUS_0 is used to detect fiber mode. It is currently defined as 0x08, which is the Global Control 6 MIB Control register and has bit 7 defined as Flush Counter. Set KSZ8_PORT_STATUS_0 to 0x18, which is the Port 1 Status 0 register where bit 7 is the Fiber Mode bit. This issue was discovered when upgrading an embedded device from kernel 5.10 to 6.16. The device was using a KSZ8765 device tree configuration and the corresponding hardware, but during boot the kernel incorrectly detected it as a KSZ8795. The relevant boot messages were: ksz-switch spi0.0: found switch: KSZ8795, rev 0 ksz-switch spi0.0: Device tree specifies chip KSZ8765 but found KSZ8795, please fix it! Cc: stable@vger.kernel.org Fixes: 91a98917a883 ("net: dsa: microchip: move switch chip_id detection to ksz_common") Reviewed-by: Nicolai Buchwitz Signed-off-by: Sebastien Royen Link: https://patch.msgid.link/20261007080557.15719-1-sebastien.royen@armadeus.com Signed-off-by: Jakub Kicinski --- drivers/net/dsa/microchip/ksz_common.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/dsa/microchip/ksz_common.h b/drivers/net/dsa/microchip/ksz_common.h index cbe98494578c..f6104f714a8d 100644 --- a/drivers/net/dsa/microchip/ksz_common.h +++ b/drivers/net/dsa/microchip/ksz_common.h @@ -853,7 +853,7 @@ static inline bool ksz_is_sgmii_port(struct ksz_device *dev, int port) #define KSZ88_FAMILY_ID 0x88 #define KSZ8895_FAMILY_ID 0x95 -#define KSZ8_PORT_STATUS_0 0x08 +#define KSZ8_PORT_STATUS_0 0x18 #define KSZ8_PORT_FIBER_MODE BIT(7) #define SW_CHIP_ID_M GENMASK(7, 4) From 7ea07afb230f1342ab0f9365edd0aae137f425d3 Mon Sep 17 00:00:00 2001 From: Petr Machata Date: Tue, 6 Oct 2026 16:21:28 +0200 Subject: [PATCH 332/367] mlxsw: spectrum_flower: Fix port range register leak in tmplt_create() mlxsw_sp_flower_tmplt_create() parses a flow_cls_offload template into a stack-local struct mlxsw_sp_acl_rule_info purely to compute rulei.values.elusage. Parsing can acquire port range registers via mlxsw_sp_flower_parse_ports_range(), but since this rulei never goes through mlxsw_sp_acl_rulei_destroy(), those registers were never released, including through chain template deletion. Factor out of mlxsw_sp_acl_rulei_destroy() the code to actually release the necessary resources and call from mlxsw_sp_flower_tmplt_create() to plug the leak. The issue was found during a review of Wentao Liang's patch referenced below. Fixes: fe22f7410527 ("mlxsw: spectrum_flower: Add ability to match on port ranges") Reported-by: Wentao Liang Closes: https://lore.kernel.org/netdev/20260917113236.2149095-1-vulab@iscas.ac.cn/ Reviewed-by: Ido Schimmel Signed-off-by: Petr Machata Reviewed-by: Jacob Keller Link: https://patch.msgid.link/95339cf970ce78e1a74aa86ffcc104ecf7c7762c.1791294384.git.petrm@nvidia.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/mellanox/mlxsw/spectrum.h | 2 ++ drivers/net/ethernet/mellanox/mlxsw/spectrum_acl.c | 10 ++++++++-- drivers/net/ethernet/mellanox/mlxsw/spectrum_flower.c | 9 +++++++-- 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/drivers/net/ethernet/mellanox/mlxsw/spectrum.h b/drivers/net/ethernet/mellanox/mlxsw/spectrum.h index b03ff9e044f9..10af08ce72ef 100644 --- a/drivers/net/ethernet/mellanox/mlxsw/spectrum.h +++ b/drivers/net/ethernet/mellanox/mlxsw/spectrum.h @@ -989,6 +989,8 @@ void mlxsw_sp_acl_ruleset_prio_get(struct mlxsw_sp_acl_ruleset *ruleset, struct mlxsw_sp_acl_rule_info * mlxsw_sp_acl_rulei_create(struct mlxsw_sp_acl *acl, struct mlxsw_afa_block *afa_block); +void mlxsw_sp_acl_rulei_free(struct mlxsw_sp *mlxsw_sp, + const struct mlxsw_sp_acl_rule_info *rulei); void mlxsw_sp_acl_rulei_destroy(struct mlxsw_sp *mlxsw_sp, struct mlxsw_sp_acl_rule_info *rulei); int mlxsw_sp_acl_rulei_commit(struct mlxsw_sp_acl_rule_info *rulei); diff --git a/drivers/net/ethernet/mellanox/mlxsw/spectrum_acl.c b/drivers/net/ethernet/mellanox/mlxsw/spectrum_acl.c index cb232accb296..f7be4f3a1518 100644 --- a/drivers/net/ethernet/mellanox/mlxsw/spectrum_acl.c +++ b/drivers/net/ethernet/mellanox/mlxsw/spectrum_acl.c @@ -340,8 +340,8 @@ mlxsw_sp_acl_rulei_create(struct mlxsw_sp_acl *acl, return ERR_PTR(err); } -void mlxsw_sp_acl_rulei_destroy(struct mlxsw_sp *mlxsw_sp, - struct mlxsw_sp_acl_rule_info *rulei) +void mlxsw_sp_acl_rulei_free(struct mlxsw_sp *mlxsw_sp, + const struct mlxsw_sp_acl_rule_info *rulei) { if (rulei->action_created) mlxsw_afa_block_destroy(rulei->act_block); @@ -351,6 +351,12 @@ void mlxsw_sp_acl_rulei_destroy(struct mlxsw_sp *mlxsw_sp, if (rulei->dst_port_range_reg_valid) mlxsw_sp_port_range_reg_put(mlxsw_sp, rulei->dst_port_range_reg_index); +} + +void mlxsw_sp_acl_rulei_destroy(struct mlxsw_sp *mlxsw_sp, + struct mlxsw_sp_acl_rule_info *rulei) +{ + mlxsw_sp_acl_rulei_free(mlxsw_sp, rulei); kfree(rulei); } diff --git a/drivers/net/ethernet/mellanox/mlxsw/spectrum_flower.c b/drivers/net/ethernet/mellanox/mlxsw/spectrum_flower.c index 353fd9ca89a6..5e4632cb792d 100644 --- a/drivers/net/ethernet/mellanox/mlxsw/spectrum_flower.c +++ b/drivers/net/ethernet/mellanox/mlxsw/spectrum_flower.c @@ -862,14 +862,19 @@ int mlxsw_sp_flower_tmplt_create(struct mlxsw_sp *mlxsw_sp, memset(&rulei, 0, sizeof(rulei)); err = mlxsw_sp_flower_parse(mlxsw_sp, block, &rulei, f); if (err) - return err; + goto out; + ruleset = mlxsw_sp_acl_ruleset_get(mlxsw_sp, block, f->common.chain_index, MLXSW_SP_ACL_PROFILE_FLOWER, &rulei.values.elusage); + err = PTR_ERR_OR_ZERO(ruleset); + +out: + mlxsw_sp_acl_rulei_free(mlxsw_sp, &rulei); /* keep the reference to the ruleset */ - return PTR_ERR_OR_ZERO(ruleset); + return err; } void mlxsw_sp_flower_tmplt_destroy(struct mlxsw_sp *mlxsw_sp, From 71198b59df56a9a0115115091cc1aad2a4e3c74d Mon Sep 17 00:00:00 2001 From: Petr Machata Date: Tue, 6 Oct 2026 16:21:29 +0200 Subject: [PATCH 333/367] selftests: mlxsw: Test port range occupancy on template create Add a test that creates a tc chain template matching on both source and destination port ranges and verifies via devlink-resource occupancy that this does not leak port range registers, neither while the template exists nor after it is deleted. Reviewed-by: Ido Schimmel Signed-off-by: Petr Machata Reviewed-by: Jacob Keller Link: https://patch.msgid.link/e7b37b80adb7ac8b0ef20a22d7654a6656c1c545.1791294384.git.petrm@nvidia.com Signed-off-by: Jakub Kicinski --- .../drivers/net/mlxsw/port_range_occ.sh | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/tools/testing/selftests/drivers/net/mlxsw/port_range_occ.sh b/tools/testing/selftests/drivers/net/mlxsw/port_range_occ.sh index b1f0781f6b25..c7eba3f15066 100755 --- a/tools/testing/selftests/drivers/net/mlxsw/port_range_occ.sh +++ b/tools/testing/selftests/drivers/net/mlxsw/port_range_occ.sh @@ -9,6 +9,7 @@ lib_dir=$(dirname $0)/../../../net/forwarding ALL_TESTS=" port_range_occ_test + port_range_occ_tmplt_test " NUM_NETIFS=2 source $lib_dir/lib.sh @@ -101,6 +102,31 @@ port_range_occ_test() log_test "port range occupancy" } +port_range_occ_tmplt_test() +{ + local occ + + RET=0 + occ=$(port_range_occ_get) + + # Addition of a chain template should not impact occupancy. + tc chain add dev "$swp1" ingress chain 1 protocol ip \ + flower ip_proto udp src_port 501-600 dst_port 701-800 + check_err $? "Could not add chain template" + + (( occ == $(port_range_occ_get) )) + check_err $? "Got occupancy $(port_range_occ_get), expected $occ" + + # And just for completeness sake, neither should deletion. + tc chain del dev "$swp1" ingress chain 1 + check_err $? "Could not delete chain template" + + (( occ == $(port_range_occ_get) )) + check_err $? "Got occupancy $(port_range_occ_get), expected $occ" + + log_test "port range occupancy after template create/destroy" +} + trap cleanup EXIT setup_prepare From 2b82e16d6084cbc651e3c902198f1945408ee1a5 Mon Sep 17 00:00:00 2001 From: Daniel Machon Date: Wed, 7 Oct 2026 17:03:15 +0200 Subject: [PATCH 334/367] net: sparx5: free the matchall entry on destroy sparx5_tc_matchall_replace() allocates a struct sparx5_mall_entry for every offloaded matchall filter and adds it to sparx5->mall_entries. sparx5_tc_matchall_destroy() removes the entry from the list, but never frees it, so the entry of every deleted mirror and goto matchall filter is leaked. Free the entry after unlinking it. The leak was discovered by an AI code review agent, and reproduced with kmemleak on a lan969x EV board (EV23X71A) by repeatedly adding and deleting matchall mirror and goto filters. With the fix, kmemleak no longer reports the leak. Cc: stable@vger.kernel.org Fixes: 1ede4acf045c ("net: sparx5: add bookkeeping code for matchall rules") Signed-off-by: Daniel Machon Link: https://patch.msgid.link/20261007-sparx5-matchall-kfree-net-v1-1-c8918685b337@microchip.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/microchip/sparx5/sparx5_tc_matchall.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/ethernet/microchip/sparx5/sparx5_tc_matchall.c b/drivers/net/ethernet/microchip/sparx5/sparx5_tc_matchall.c index 702257979462..146d7f28e8f3 100644 --- a/drivers/net/ethernet/microchip/sparx5/sparx5_tc_matchall.c +++ b/drivers/net/ethernet/microchip/sparx5/sparx5_tc_matchall.c @@ -160,6 +160,7 @@ static int sparx5_tc_matchall_destroy(struct net_device *ndev, } list_del(&entry->list); + kfree(entry); return err; } From ab9414ed70bd783e902a85c350620dee269bcb2b Mon Sep 17 00:00:00 2001 From: Josef Bacik Date: Wed, 7 Oct 2026 17:29:34 +0000 Subject: [PATCH 335/367] net: skbuff: don't leave stale bytes in skb_copy_and_csum_bits() When skb_copy_and_csum_bits() reaches unreadable frags it returns 0 after copying only the linear part, and the rest of the caller's buffer is left as it was. The callers copy into a buffer that is about to go out on the wire: an ICMP error quoting the offending packet, or a driver's TX bounce buffer in skb_copy_and_csum_dev(). Neither buffer is zeroed beforehand, so whatever was in memory there gets sent. Zero the part of the buffer we didn't fill. The checksum usually won't match the data any more, so the receiver will usually drop the packet, but either way it no longer carries anything it shouldn't. Only zero for a positive @len, a negative one from a broken caller must not turn into a huge memset(). Fixes: 65249feb6b3d ("net: add support for skbs with unreadable frags") Cc: stable@vger.kernel.org Signed-off-by: Josef Bacik Reviewed-by: Mina Almasry Link: https://patch.msgid.link/20261007-b4-skb-copy-csum-stale-bytes-v1-1-adbbde033fb3@toxicpanda.com Signed-off-by: Jakub Kicinski --- net/core/skbuff.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/net/core/skbuff.c b/net/core/skbuff.c index 4aea06d5167d..41beaf625421 100644 --- a/net/core/skbuff.c +++ b/net/core/skbuff.c @@ -3633,8 +3633,12 @@ __wsum skb_copy_and_csum_bits(const struct sk_buff *skb, int offset, pos = copy; } - if (!skb_frags_readable(skb)) + if (!skb_frags_readable(skb)) { + /* Don't hand the caller a buffer with stale bytes in it. */ + if (len > 0) + memset(to, 0, len); return 0; + } for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) { int end; From 089e58805c452e52179482b1025a8e309a57f801 Mon Sep 17 00:00:00 2001 From: Josef Bacik Date: Wed, 7 Oct 2026 17:55:40 +0000 Subject: [PATCH 336/367] xen/netfront: drop RX packets with a short Ethernet header handle_incoming_queue() pulls pull_to bytes into the head before calling eth_type_trans(). pull_to is the length of the first RX slot, capped at RX_COPY_THRESHOLD, and that length comes from the backend. Nothing checks it against ETH_HLEN. If the first slot is shorter than ETH_HLEN and more slots follow, the head ends up shorter than an Ethernet header while skb->len is longer, and eth_type_trans() BUG()s in __skb_pull(). If the whole packet is shorter than ETH_HLEN, eth_type_trans() reads the header past the end of the data instead. Pull at least ETH_HLEN, and drop the packet if that fails, which also drops packets too short to hold an Ethernet header. This also checks the return value of the pull, which was ignored. Fixes: 0d160211965b ("xen: add virtual network device driver") Cc: stable@vger.kernel.org Signed-off-by: Josef Bacik Link: https://patch.msgid.link/20261007-b4-xen-netfront-short-head-v1-1-12d7113a7e4e@toxicpanda.com Signed-off-by: Jakub Kicinski --- drivers/net/xen-netfront.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/drivers/net/xen-netfront.c b/drivers/net/xen-netfront.c index 2ed673649c48..d269457e839e 100644 --- a/drivers/net/xen-netfront.c +++ b/drivers/net/xen-netfront.c @@ -1234,8 +1234,16 @@ static int handle_incoming_queue(struct netfront_queue *queue, while ((skb = __skb_dequeue(rxq)) != NULL) { int pull_to = NETFRONT_SKB_CB(skb)->pull_to; - if (pull_to > skb_headlen(skb)) - __pskb_pull_tail(skb, pull_to - skb_headlen(skb)); + /* pull_to comes from the first slot's length, which the + * backend controls. Make sure the head holds at least an + * Ethernet header for eth_type_trans(). + */ + if (!pskb_may_pull(skb, max(pull_to, ETH_HLEN))) { + kfree_skb(skb); + packets_dropped++; + queue->info->netdev->stats.rx_errors++; + continue; + } /* Ethernet work: Delayed to here as it peeks the header. */ skb->protocol = eth_type_trans(skb, queue->info->netdev); From 513e23857a3a64fa73a1deb36a385eecd4ad7690 Mon Sep 17 00:00:00 2001 From: Willem de Bruijn Date: Wed, 7 Oct 2026 13:57:30 -0400 Subject: [PATCH 337/367] net/packet: call packet_parse_headers after virtio_net_hdr_to_skb SOCK_RAW packet sockets incorrectly drop VLAN-tagged GSO packets without VIRTIO_NET_HDR_F_NEEDS_CSUM. packet_parse_headers() sets skb->protocol to VLAN but advances skb->network_header past the VLAN tags. virtio_net_hdr_to_skb() then flow dissects these packets, which parses the network header as a VLAN tag, and fails. Call packet_parse_headers() after virtio_net_hdr_to_skb() instead. This matches tun_get_user() and tap_get_user() and thus simplifies overall complexity. Commit 01fdecc0480d ("net: packet: fix wrong transport_header when sending VLAN-tagged frame") fixed the same issue for the transport header probe in packet_parse_headers(). That probe is now skipped if virtio_net_hdr_to_skb() already set the transport header, avoiding a redundant dissection. Implementation details: - Do not set skb->protocol to the RX wildcard ETH_P_ALL. SOCK_RAW then derives it from the link layer header, as before. SOCK_DGRAM now leaves it 0, or derives it from gso_type for GSO with NEEDS_CSUM. - Keep virtio_net_hdr_set_proto() last, so that the link layer protocol takes priority over gso_type. Reported-by: Junnan Zhang Closes: https://lore.kernel.org/netdev/20260821085722.24036-1-zhangjn_dev@163.com/ Fixes: dfed913e8b55 ("net/af_packet: add VLAN support for AF_PACKET SOCK_RAW GSO") Signed-off-by: Willem de Bruijn Acked-by: Michael S. Tsirkin Link: https://patch.msgid.link/20261007175816.3138556-1-willemdebruijn.kernel@gmail.com Signed-off-by: Jakub Kicinski --- net/packet/af_packet.c | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/net/packet/af_packet.c b/net/packet/af_packet.c index 6ff16eef24f4..52ae3d875e97 100644 --- a/net/packet/af_packet.c +++ b/net/packet/af_packet.c @@ -1943,8 +1943,7 @@ static void packet_parse_headers(struct sk_buff *skb, struct socket *sock) /* On TX skb->data is the L2 header; anchor it for all socket types. */ skb_reset_mac_header(skb); - if ((!skb->protocol || skb->protocol == htons(ETH_P_ALL)) && - sock->type == SOCK_RAW) + if (!skb->protocol && sock->type == SOCK_RAW) skb->protocol = dev_parse_header_protocol(skb); skb_probe_transport_header(skb); @@ -2614,7 +2613,8 @@ static int tpacket_fill_skb(struct packet_sock *po, struct sk_buff *skb, struct page *page; int err; - skb->protocol = proto; + if (proto != htons(ETH_P_ALL)) + skb->protocol = proto; skb->dev = dev; skb->priority = sockc->priority; skb->mark = sockc->mark; @@ -2678,8 +2678,6 @@ static int tpacket_fill_skb(struct packet_sock *po, struct sk_buff *skb, if (unlikely(!skb->len)) return -EINVAL; - packet_parse_headers(skb, sock); - return tp_len; } @@ -2919,9 +2917,13 @@ static int tpacket_snd(struct packet_sock *po, struct msghdr *msg) tp_len = -EINVAL; goto tpacket_error; } - virtio_net_hdr_set_proto(skb, &vnet_hdr); } + packet_parse_headers(skb, po->sk.sk_socket); + + if (has_vnet_hdr) + virtio_net_hdr_set_proto(skb, &vnet_hdr); + uarg = kmalloc(sizeof(*uarg), GFP_KERNEL); if (unlikely(!uarg)) { if (likely(len_sum > 0)) @@ -3122,7 +3124,8 @@ static int packet_snd(struct socket *sock, struct msghdr *msg, size_t len) goto out_free; } - skb->protocol = proto; + if (proto != htons(ETH_P_ALL)) + skb->protocol = proto; skb->dev = dev; skb->priority = sockc.priority; skb->mark = sockc.mark; @@ -3131,16 +3134,18 @@ static int packet_snd(struct socket *sock, struct msghdr *msg, size_t len) if (unlikely(extra_len == 4)) skb->no_fcs = 1; - packet_parse_headers(skb, sock); - if (vnet_hdr_sz) { err = virtio_net_hdr_to_skb(skb, &vnet_hdr, vio_le()); if (err) goto out_free; len += vnet_hdr_sz; - virtio_net_hdr_set_proto(skb, &vnet_hdr); } + packet_parse_headers(skb, sock); + + if (vnet_hdr_sz) + virtio_net_hdr_set_proto(skb, &vnet_hdr); + err = packet_xmit(po, skb); if (unlikely(err != 0)) { From 93ccaf1c26e2133396173b76a071e59024daa622 Mon Sep 17 00:00:00 2001 From: Josef Bacik Date: Wed, 7 Oct 2026 17:57:32 +0000 Subject: [PATCH 338/367] xen/netfront: don't leak the skb when xennet_fill_frags() fails When a response chain has more slots than fit in the skb's frags, xennet_fill_frags() returns an error and xennet_poll() jumps to its error path. That path moves what's left on tmpq to errq to be freed, but the skb being filled was already dequeued from tmpq, so it's never freed. Each chain that overflows leaks the skb and the pages attached to it as frags, and the backend decides how many slots it sends. Put the skb back on tmpq before taking the error path, like the xennet_set_skb_gso() failure just above it does. Fixes: ad4f15dc2c70 ("xen/netfront: don't bug in case of too many frags") Cc: stable@vger.kernel.org Signed-off-by: Josef Bacik Reviewed-by: Juergen Gross Link: https://patch.msgid.link/20261007-b4-xen-netfront-fill-frags-leak-v1-1-a8a01ff9cd52@toxicpanda.com Signed-off-by: Jakub Kicinski --- drivers/net/xen-netfront.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/net/xen-netfront.c b/drivers/net/xen-netfront.c index d269457e839e..fdcb91042f29 100644 --- a/drivers/net/xen-netfront.c +++ b/drivers/net/xen-netfront.c @@ -1346,8 +1346,10 @@ static int xennet_poll(struct napi_struct *napi, int budget) skb->data_len = rx->status; skb->len += rx->status; - if (unlikely(xennet_fill_frags(queue, skb, &tmpq))) + if (unlikely(xennet_fill_frags(queue, skb, &tmpq))) { + __skb_queue_head(&tmpq, skb); goto err; + } if (rx->flags & XEN_NETRXF_csum_blank) skb->ip_summed = CHECKSUM_PARTIAL; From f8c8bd159a9bf626dd4921b7746ff481fce8c758 Mon Sep 17 00:00:00 2001 From: Vadim Fedorenko Date: Wed, 7 Oct 2026 20:33:59 +0000 Subject: [PATCH 339/367] ptp: ocp: fix PCIe delay estimation calculation The commit in fixes introduced a high cap for delayas U64_MAX value while ktime_t is actually s64. This is wrong cap as it becomes negative value and any comparison to a real delay will fail to update delay value. Use KTIME_MAX constant as correct max cap for PCIe delay. The issue was hit in production (a negative value is observed): # cat /sys/class/timecard/ocp0/ts_window_adjust -3 Fixes: aa05fe67bcd64 ("ptp: ocp: Improve PCIe delay estimation") Signed-off-by: Vadim Fedorenko Reviewed-by: Daniel Machon Link: https://patch.msgid.link/20261007203359.417270-1-vadim.fedorenko@linux.dev Signed-off-by: Jakub Kicinski --- drivers/ptp/ptp_ocp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/ptp/ptp_ocp.c b/drivers/ptp/ptp_ocp.c index 3d26ec1f7b9e..a8cba2f64b98 100644 --- a/drivers/ptp/ptp_ocp.c +++ b/drivers/ptp/ptp_ocp.c @@ -1825,7 +1825,7 @@ ptp_ocp_watchdog(struct timer_list *t) static void ptp_ocp_estimate_pci_timing(struct ptp_ocp *bp) { - ktime_t start, end, delay = U64_MAX; + ktime_t start, end, delay = KTIME_MAX; u32 ctrl; int i; From 6785011f8b16abf40b1e9d8b2e332232b3e68822 Mon Sep 17 00:00:00 2001 From: "Michael S. Tsirkin" Date: Wed, 7 Oct 2026 18:42:49 -0400 Subject: [PATCH 340/367] ipv4: validate checksum_start before completing checksum If a packet with bad checksum metadata gets into the ipv4 stack, skb_checksum_help can corrupt the network header and cause a bunch of mischief. This was discovered and reported by Paulos, and has been reporoduced by others independently since. We really shouldn't allow such packets in, but as a defence in depth measure, let's also check before we complete the checksum. A more complete validation at input is forthcoming, but needs more work. Cc: stable@vger.kernel.org Fixes: f43798c27684 ("tun: Allow GSO using virtio_net_hdr") Fixes: bfd5f4a3d605 ("packet: Add GSO/csum offload support.") Reported-by: Paulos Yibelo Closes: https://lore.kernel.org/netdev/20260922030310.8684-2-habte.yibelo@gmail.com/ Signed-off-by: Michael S. Tsirkin Reviewed-by: Willem de Bruijn Link: https://patch.msgid.link/0a012b4923e189c4c593ef4f471e5ff0edbe9030.1791412497.git.mst@redhat.com Signed-off-by: Jakub Kicinski --- include/net/ip.h | 7 +++++++ net/bridge/netfilter/nf_conntrack_bridge.c | 10 +++++++--- net/ipv4/ip_output.c | 10 +++++++--- net/netfilter/nfnetlink_queue.c | 7 +++++++ net/netfilter/xt_CHECKSUM.c | 6 +++++- net/xfrm/xfrm_output.c | 16 +++++++++++----- 6 files changed, 44 insertions(+), 12 deletions(-) diff --git a/include/net/ip.h b/include/net/ip.h index 6f602df72ee6..d07c2c573024 100644 --- a/include/net/ip.h +++ b/include/net/ip.h @@ -74,6 +74,13 @@ static inline unsigned int ip_hdrlen(const struct sk_buff *skb) return ip_hdr(skb)->ihl * 4; } +static inline int ip_check_csum_start(const struct sk_buff *skb) +{ + if (unlikely(skb->csum_start < skb->network_header + ip_hdrlen(skb))) + return -EINVAL; + return 0; +} + struct ipcm_cookie { struct sockcm_cookie sockc; __be32 addr; diff --git a/net/bridge/netfilter/nf_conntrack_bridge.c b/net/bridge/netfilter/nf_conntrack_bridge.c index 7ecb8a26bfa3..b5444335b86f 100644 --- a/net/bridge/netfilter/nf_conntrack_bridge.c +++ b/net/bridge/netfilter/nf_conntrack_bridge.c @@ -39,9 +39,13 @@ static int nf_br_ip_fragment(struct net *net, struct sock *sk, int err = 0; /* for offloaded checksums cleanup checksum before fragmentation */ - if (skb->ip_summed == CHECKSUM_PARTIAL && - (err = skb_checksum_help(skb))) - goto blackhole; + if (skb->ip_summed == CHECKSUM_PARTIAL) { + err = ip_check_csum_start(skb); + if (!err) + err = skb_checksum_help(skb); + if (err) + goto blackhole; + } iph = ip_hdr(skb); diff --git a/net/ipv4/ip_output.c b/net/ipv4/ip_output.c index a24cc8ee11d3..eaa6fabda347 100644 --- a/net/ipv4/ip_output.c +++ b/net/ipv4/ip_output.c @@ -771,9 +771,13 @@ int ip_do_fragment(struct net *net, struct sock *sk, struct sk_buff *skb, int err = 0; /* for offloaded checksums cleanup checksum before fragmentation */ - if (skb->ip_summed == CHECKSUM_PARTIAL && - (err = skb_checksum_help(skb))) - goto fail; + if (skb->ip_summed == CHECKSUM_PARTIAL) { + err = ip_check_csum_start(skb); + if (!err) + err = skb_checksum_help(skb); + if (err) + goto fail; + } /* * Point into the IP datagram header. diff --git a/net/netfilter/nfnetlink_queue.c b/net/netfilter/nfnetlink_queue.c index a3bc00280051..477fc632657f 100644 --- a/net/netfilter/nfnetlink_queue.c +++ b/net/netfilter/nfnetlink_queue.c @@ -40,6 +40,7 @@ #include #include #include +#include #include #include #include @@ -674,6 +675,12 @@ static int nfqnl_put_bridge(struct nf_queue_entry *entry, struct sk_buff *skb) static int nf_queue_checksum_help(struct sk_buff *entskb) { + if (entskb->protocol == htons(ETH_P_IP)) { + if (!pskb_network_may_pull(entskb, sizeof(struct iphdr)) || + ip_check_csum_start(entskb)) + return -EINVAL; + } + if (skb_csum_is_sctp(entskb)) return skb_crc32c_csum_help(entskb); diff --git a/net/netfilter/xt_CHECKSUM.c b/net/netfilter/xt_CHECKSUM.c index 9d99f5a3d176..1fb0f8118404 100644 --- a/net/netfilter/xt_CHECKSUM.c +++ b/net/netfilter/xt_CHECKSUM.c @@ -15,6 +15,7 @@ #include #include +#include MODULE_LICENSE("GPL"); MODULE_AUTHOR("Michael S. Tsirkin "); @@ -25,8 +26,11 @@ MODULE_ALIAS("ip6t_CHECKSUM"); static unsigned int checksum_tg(struct sk_buff *skb, const struct xt_action_param *par) { - if (skb->ip_summed == CHECKSUM_PARTIAL && !skb_is_gso(skb)) + if (skb->ip_summed == CHECKSUM_PARTIAL && !skb_is_gso(skb)) { + if (xt_family(par) == NFPROTO_IPV4 && ip_check_csum_start(skb)) + return NF_DROP; skb_checksum_help(skb); + } return XT_CONTINUE; } diff --git a/net/xfrm/xfrm_output.c b/net/xfrm/xfrm_output.c index e305ba32e356..f1f612cd7b43 100644 --- a/net/xfrm/xfrm_output.c +++ b/net/xfrm/xfrm_output.c @@ -823,16 +823,22 @@ int xfrm_output(struct sock *sk, struct sk_buff *skb) } if (skb->ip_summed == CHECKSUM_PARTIAL) { - err = skb_checksum_help(skb); - if (err) { - XFRM_INC_STATS(net, LINUX_MIB_XFRMOUTERROR); - kfree_skb(skb); - return err; + if (skb->protocol == htons(ETH_P_IP)) { + err = ip_check_csum_start(skb); + if (err) + goto error; } + err = skb_checksum_help(skb); + if (err) + goto error; } out: return xfrm_output2(net, sk, skb); +error: + XFRM_INC_STATS(net, LINUX_MIB_XFRMOUTERROR); + kfree_skb(skb); + return err; } EXPORT_SYMBOL_GPL(xfrm_output); From e02a5b6588161673ef75a4bc5cdfb62bd493374d Mon Sep 17 00:00:00 2001 From: Daehyeon Ko <4ncienth@gmail.com> Date: Thu, 8 Oct 2026 14:29:22 +0900 Subject: [PATCH 341/367] ipv4: do not warn on route notification size race Commit 680aea08e78c ("net: ipv4: Emit notification when fib hardware flags are changed") added asynchronous route notifications for hardware flag changes. fib_alias_hw_flags_set() sizes the skb with fib_nlmsg_size() and later fills it with fib_dump_info() while holding only RCU. With nexthop compatibility mode enabled, a concurrent replacement can grow the group between these independent snapshots. fib_dump_info() can then legitimately return -EMSGSIZE, so the warning does not prove a sizing bug. Remove the warning. The existing error path still frees the skb and reports the error to listeners. Fixes: 680aea08e78c ("net: ipv4: Emit notification when fib hardware flags are changed") Reported-by: Ido Schimmel Link: https://lore.kernel.org/netdev/20261004082043.GB92032@shredder/ Suggested-by: Ido Schimmel Cc: stable@vger.kernel.org Signed-off-by: Daehyeon Ko <4ncienth@gmail.com> Link: https://patch.msgid.link/b7e203fc5867651cb67ef84aa03e082f0f76008e.1791423190.git.4ncienth@gmail.com Signed-off-by: Jakub Kicinski --- net/ipv4/fib_trie.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/net/ipv4/fib_trie.c b/net/ipv4/fib_trie.c index acb1e4385914..248514dce0cd 100644 --- a/net/ipv4/fib_trie.c +++ b/net/ipv4/fib_trie.c @@ -1079,8 +1079,6 @@ void fib_alias_hw_flags_set(struct net *net, const struct fib_rt_info *fri) err = fib_dump_info(skb, 0, 0, RTM_NEWROUTE, fri, 0); if (err < 0) { - /* -EMSGSIZE implies BUG in fib_nlmsg_size() */ - WARN_ON(err == -EMSGSIZE); kfree_skb(skb); goto errout; } From c97426ec649d99596d8969e1a052c16b14b7b485 Mon Sep 17 00:00:00 2001 From: Daehyeon Ko <4ncienth@gmail.com> Date: Thu, 8 Oct 2026 14:29:23 +0900 Subject: [PATCH 342/367] ipv6: do not warn on route notification size race fib6_info_hw_flags_set() sizes the skb with rt6_nlmsg_size() and later fills it with rt6_fill_node(). With nexthop compatibility mode enabled, a concurrent replacement can grow the group between these independent snapshots. rt6_fill_node() can then legitimately return -EMSGSIZE, so the warning does not prove a sizing bug. Remove the warning. The existing error path still frees the skb and reports the error to listeners. Fixes: 907eea486888 ("net: ipv6: Emit notification when fib hardware flags are changed") Reported-by: Ido Schimmel Link: https://lore.kernel.org/netdev/20261007114003.GA1011260@shredder/ Suggested-by: Ido Schimmel Cc: stable@vger.kernel.org Signed-off-by: Daehyeon Ko <4ncienth@gmail.com> Link: https://patch.msgid.link/4012604727e5ba80b69ccd4d08796511397ddaef.1791423190.git.4ncienth@gmail.com Signed-off-by: Jakub Kicinski --- net/ipv6/route.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/net/ipv6/route.c b/net/ipv6/route.c index 153ce16628c1..a5e955e56e79 100644 --- a/net/ipv6/route.c +++ b/net/ipv6/route.c @@ -6509,8 +6509,6 @@ void fib6_info_hw_flags_set(struct net *net, struct fib6_info *f6i, err = rt6_fill_node(net, skb, f6i, NULL, NULL, NULL, 0, RTM_NEWROUTE, 0, 0, 0, RT_DEL_REASON_UNSPEC); if (err < 0) { - /* -EMSGSIZE implies BUG in rt6_nlmsg_size() */ - WARN_ON(err == -EMSGSIZE); kfree_skb(skb); goto errout; } From 64a6b36b0e1855288080bc5f89769f27d6dc03c8 Mon Sep 17 00:00:00 2001 From: Chengfeng Ye Date: Thu, 8 Oct 2026 16:44:44 +0800 Subject: [PATCH 343/367] net/smc: protect clcsock lifetime in smc_getname smc_getname() dereferences smc->clcsock without holding clcsock_release_lock. Link-group termination can release the CLC socket through smc_close_active_abort() while the SMC socket is still open, for example after shutdown(SHUT_WR). A getsockname() caller can load smc->clcsock, then the termination worker can clear the pointer and call sock_release() before the caller accesses clcsock->ops or invokes getname(). This causes a use-after-free; if the worker clears the pointer before the load, it causes a NULL dereference. The syscall's file reference keeps the SMC socket alive but does not prevent asynchronous release of its CLC socket. KASAN reported the following with test-only timing instrumentation: BUG: KASAN: slab-use-after-free in smc_getname+0x19e/0x1b0 Read of size 8 at addr ffff888109abb4e0 by task poc/103 Call Trace: smc_getname+0x19e/0x1b0 do_getsockname+0xe5/0x170 __sys_getsockname+0x8c/0x100 Allocated by task 95: sock_alloc_inode+0x1e/0x280 sock_alloc+0x3d/0x240 __sock_create+0x7e/0x430 smc_create+0x121/0x240 Freed by task 0: kmem_cache_free+0xcc/0x340 rcu_core+0x50a/0x1850 Last potentially related work creation: evict+0x446/0x6c0 smc_clcsock_release+0xa8/0xd0 smc_close_active_abort+0x26a/0x3a0 __smc_lgr_terminate.part.0+0x137/0x2e0 Hold clcsock_release_lock across the pointer check and the getname() callback to serialize with smc_clcsock_release(). Return -EBADF if the CLC socket has already been released, preserving the existing peer state check and the callback's return value otherwise. Fixes: b03faa1fafc8 ("net/smc: postpone release of clcsock") Cc: stable@vger.kernel.org Signed-off-by: Chengfeng Ye Reviewed-by: Mahanta Jambigi Reviewed-by: Dust Li Link: https://patch.msgid.link/20261008084444.787449-1-nicoyip.dev@gmail.com Signed-off-by: Jakub Kicinski --- net/smc/af_smc.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/net/smc/af_smc.c b/net/smc/af_smc.c index e9f93b3ab435..8b3ee70f8433 100644 --- a/net/smc/af_smc.c +++ b/net/smc/af_smc.c @@ -2784,6 +2784,7 @@ int smc_getname(struct socket *sock, struct sockaddr *addr, int peer) { struct smc_sock *smc; + int rc = -EBADF; if (peer && (sock->sk->sk_state != SMC_ACTIVE) && (sock->sk->sk_state != SMC_APPCLOSEWAIT1)) @@ -2791,7 +2792,11 @@ int smc_getname(struct socket *sock, struct sockaddr *addr, smc = smc_sk(sock->sk); - return smc->clcsock->ops->getname(smc->clcsock, addr, peer); + mutex_lock(&smc->clcsock_release_lock); + if (smc->clcsock) + rc = smc->clcsock->ops->getname(smc->clcsock, addr, peer); + mutex_unlock(&smc->clcsock_release_lock); + return rc; } int smc_sendmsg(struct socket *sock, struct msghdr *msg, size_t len) From 3c6a4b11330fe424557538a98fca9f0a4c5bf313 Mon Sep 17 00:00:00 2001 From: Fernando Fernandez Mancera Date: Thu, 8 Oct 2026 12:41:35 +0200 Subject: [PATCH 344/367] net: openvswitch: validate transport header presence in set_ipv6_addr When executing IPv6 address rewrite actions on IPv6 fragments, set_ipv6_addr() calls update_ipv6_checksum(). If parse_ipv6hdr() processes a non-first IPv6 fragment, it sets key->ip.proto to NEXTHDR_FRAGMENT and returns early without calling skb_set_transport_header(). update_ipv6_checksum() unconditionally evaluates skb_transport_offset() on entry before checking l4_proto. Because skb->transport_header is uninitialized, this triggers a warning under CONFIG_DEBUG_NET=y although it is completely harmless. Fix this by returning early in update_ipv6_checksum() if l4_proto is NEXTHDR_FRAGMENT. This avoids reading the uninitialized transport offset for fragments while preserving the debug warning for any other protocol where the transport header is unexpectedly missing. See the syzbot trace: !skb_transport_header_was_set(skb) WARNING: ./include/linux/skbuff.h:3075 at skb_transport_header include/linux/skbuff.h:3075 [inline], CPU#1: syz-executor463/5635 WARNING: ./include/linux/skbuff.h:3075 at skb_transport_offset include/linux/skbuff.h:3250 [inline], CPU#1: syz-executor463/5635 WARNING: ./include/linux/skbuff.h:3075 at update_ipv6_checksum net/openvswitch/actions.c:361 [inline], CPU#1: syz-executor463/5635 WARNING: ./include/linux/skbuff.h:3075 at set_ipv6_addr+0x462/0x660 net/openvswitch/actions.c:399, CPU#1: syz-executor463/5635 [...] RIP: 0010:skb_transport_header include/linux/skbuff.h:3075 [inline] RIP: 0010:skb_transport_offset include/linux/skbuff.h:3250 [inline] RIP: 0010:update_ipv6_checksum net/openvswitch/actions.c:361 [inline] RIP: 0010:set_ipv6_addr+0x462/0x660 net/openvswitch/actions.c:399 [...] Call Trace: set_ipv6 net/openvswitch/actions.c:531 [inline] do_execute_actions+0x557e/0x8600 net/openvswitch/actions.c:1366 ovs_execute_actions+0xde/0x520 net/openvswitch/actions.c:1592 ovs_packet_cmd_execute+0xb4f/0xf10 net/openvswitch/datapath.c:705 genl_family_rcv_msg_doit+0x233/0x340 net/netlink/genetlink.c:1114 genl_rcv_msg+0x614/0x7a0 net/netlink/genetlink.c:1209 netlink_rcv_skb+0x226/0x4a0 net/netlink/af_netlink.c:2572 genl_rcv+0x28/0x40 net/netlink/genetlink.c:1218 netlink_unicast+0x7bd/0x940 net/netlink/af_netlink.c:1361 netlink_sendmsg+0x813/0xb40 net/netlink/af_netlink.c:1916 sock_sendmsg_nosec+0x14e/0x190 net/socket.c:800 Reported-by: syzbot+4cc63fcfb3845e149969@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=4cc63fcfb3845e149969 Fixes: 3fdbd1ce11e5 ("openvswitch: add ipv6 'set' action") Signed-off-by: Fernando Fernandez Mancera Reviewed-by: Ilya Maximets Link: https://patch.msgid.link/20261008104135.4722-1-fmancera@suse.de Signed-off-by: Jakub Kicinski --- net/openvswitch/actions.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/net/openvswitch/actions.c b/net/openvswitch/actions.c index dc5ff859f114..27152bca8a8c 100644 --- a/net/openvswitch/actions.c +++ b/net/openvswitch/actions.c @@ -358,7 +358,12 @@ static void set_ip_addr(struct sk_buff *skb, struct iphdr *nh, static void update_ipv6_checksum(struct sk_buff *skb, u8 l4_proto, __be32 addr[4], const __be32 new_addr[4]) { - int transport_len = skb->len - skb_transport_offset(skb); + int transport_len; + + if (l4_proto == NEXTHDR_FRAGMENT) + return; + + transport_len = skb->len - skb_transport_offset(skb); if (l4_proto == NEXTHDR_TCP) { if (likely(transport_len >= sizeof(struct tcphdr))) From 65ab9de4bdd4c05d8277cfc20ae0b9f5aff60897 Mon Sep 17 00:00:00 2001 From: Ramses de Norre Date: Thu, 8 Oct 2026 14:59:15 +0200 Subject: [PATCH 345/367] wireguard: queueing: preserve tstamp_type when encapsulating packet MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Sending traffic through a wireguard tunnel on a host using the fq qdisc fills the log with: fq: likely mono tstamp with tstamp_type 0 An skb carries a timestamp in skb->tstamp and, separately, a skb->tstamp_type field recording which clock that timestamp came from. The two have to agree. When wireguard encapsulates a packet it calls wg_reset_packet(), which clears the fields that must not leak from the inner packet into the tunnel packet. It does so in two steps: skb_scrub_packet(skb, true); memset(&skb->headers, 0, sizeof(skb->headers)); skb_scrub_packet() deliberately keeps skb->tstamp when it holds a monotonic timestamp: that value is the time the packet is scheduled to be sent, and the qdisc still needs it. The memset then zeroes skb->tstamp_type, because that field sits inside the headers group while skb->tstamp does not. The packet therefore leaves wireguard carrying a monotonic timestamp labelled as a realtime one. Nothing noticed until commit c4f796c4f16b ("net_sched: sch_fq: convert skb->tstamp if not monotonic"): fq used to assume every timestamp was monotonic. It now consults tstamp_type, spots the mismatch, warns, and falls back to treating the value as monotonic. Pacing still ends up correct, so the log spam is the actual problem. Save tstamp_type before the memset and restore it when encapsulating, next to the hash fields that are already carried over this way. When decapsulating it stays zeroed, which is right: an incoming packet's timestamp is a realtime receive timestamp. Fixes: d98d58a00261 ("net: Set skb->mono_delivery_time and clear it after sch_handle_ingress()") Signed-off-by: Ramses de Norre Reviewed-by: Toke Høiland-Jørgensen Reviewed-by: Eric Dumazet Cc: stable@vger.kernel.org Signed-off-by: Jason A. Donenfeld Link: https://patch.msgid.link/20261008130124.724119-3-Jason@zx2c4.com Signed-off-by: Jakub Kicinski --- drivers/net/wireguard/queueing.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/net/wireguard/queueing.h b/drivers/net/wireguard/queueing.h index 79b6d70de236..663b19b78395 100644 --- a/drivers/net/wireguard/queueing.h +++ b/drivers/net/wireguard/queueing.h @@ -78,12 +78,14 @@ static inline void wg_reset_packet(struct sk_buff *skb, bool encapsulating) u8 l4_hash = skb->l4_hash; u8 sw_hash = skb->sw_hash; u32 hash = skb->hash; + u8 tstamp_type = skb->tstamp_type; skb_scrub_packet(skb, true); memset(&skb->headers, 0, sizeof(skb->headers)); if (encapsulating) { skb->l4_hash = l4_hash; skb->sw_hash = sw_hash; skb->hash = hash; + skb->tstamp_type = tstamp_type; } skb->queue_mapping = 0; skb->nohdr = 0; From d0305f8d90029556260c6824b61c493c424eba9e Mon Sep 17 00:00:00 2001 From: "Jason A. Donenfeld" Date: Thu, 8 Oct 2026 14:59:16 +0200 Subject: [PATCH 346/367] wireguard: noise: reject response consumption after intermediate initiation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two threads begin processing the identical response message, received twice. The first thread, A, runs. While it's running, the second one, B, gets partway through, and during that slow calculation, or even while blocking on down_write(), A completes and then also a handshake initiation that's already been queued up runs in thread C, which itself takes that same down_write(). The handshake initiation creation succeeds, and sets the state back to waiting-for-response, and calls up_write(), at which point thread B resumes, because either its finished its calculations or was finally allowed to acquire down_write(). Thread B then copies the state back to the peer, and begins a new session, using that state, which is the same session as the one made in thread A. Thread A Thread B Thread C down_read() sA = handshake->state memcpy(cA, handshake->crypto) up_read() if (sA != 1) goto fail slow_crypto(cA) down_read() sB = handshake->state memcpy(cB, handshake->crypto) up_read() if (sB != 1) goto fail slow_crypto(cB) down_write() if (sA != handshake->state) goto fail memcpy(handshake->crypto, cA) handshake->state = 2 up_write() down_write() if (handshake->state != 2) goto fail derive_session(handshake->crypto) up_write() down_write() slow_crypto(handshake->crypto) handshake->state = 1 up_write() down_write() if (sB != handshake->state) goto fail memcpy(handshake->crypto, cB) handshake->state = 2 up_write() down_write() if (handshake->state != 2) goto fail derive_session(handshake->crypto) up_write() This seems basically impossible to hit in a meaningful way in practice, but ensure that it absolutely cannot happen by comparing the ephemeral private key that's on the stack with the latest one that the peer's handshake state has. Cc: stable@vger.kernel.org Fixes: e7096c131e51 ("net: WireGuard secure network tunnel") Reported-by: Jérémy Jean Signed-off-by: Jason A. Donenfeld Link: https://patch.msgid.link/20261008130124.724119-4-Jason@zx2c4.com Signed-off-by: Jakub Kicinski --- drivers/net/wireguard/noise.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/drivers/net/wireguard/noise.c b/drivers/net/wireguard/noise.c index 9c0a09bf6c95..ec2513d6dfab 100644 --- a/drivers/net/wireguard/noise.c +++ b/drivers/net/wireguard/noise.c @@ -784,10 +784,11 @@ wg_noise_handshake_consume_response(struct message_handshake_response *src, /* Success! Copy everything to peer */ down_write(&handshake->lock); - /* It's important to check that the state is still the same, while we - * have an exclusive lock. + /* Check that the state is the same and that this is still the + * initiation we started with, while we have an exclusive lock. */ - if (handshake->state != state) { + if (handshake->state != state || + crypto_memneq(handshake->ephemeral_private, ephemeral_private, NOISE_PUBLIC_KEY_LEN)) { up_write(&handshake->lock); goto fail; } From 7c24a4e87e219072e5e106f6791146cbf3b056b3 Mon Sep 17 00:00:00 2001 From: Ilia Gavrilov Date: Thu, 8 Oct 2026 14:41:17 +0000 Subject: [PATCH 347/367] vsock: Fix memory leak in vmci_transport_recv_dgram_cb() During the closure of a datagram socket, the vmci_transport_recv_dgram_cb() function may be called, which will add the packets to the socket's backlog; then, after the receive queue is cleared, the __release_sock() function will move the packet back from the socket's backlog to the receive queue, which will lead to a memory leak. sock_close __sock_release __vsock_release // take ownership by user-space lock_sock_nested sock_set_flag(sk, SOCK_DEAD) vmci_transport_release vmci_dispatch_dgs vmci_datagram_invoke_guest_handler vmci_transport_recv_dgram_cb sk_receive_skb if (!sock_owned_by_user(sk)) [...] (!) else if sk_add_backlog() [...] (!) skb_queue_purge(&sk->sk_receive_queue) release_sock __release_sock // move all packets from the backlog // to the receive queue (!) sk_backlog_rcv vsock_queue_rcv_skb [...] __sock_queue_rcv_skb if (!sock_flag(sk, SOCK_DEAD)) // Since the socket is "dead", the function will not // be called sk->sk_data_ready(sk) sock_release_ownership(sk); sock_put Add a receive queue cleanup in the socket destructor to fix this. syzkaller report: unreferenced object 0xffff8880117bfb80 (size 240): comm "irq/56-vmw_vmci", pid 205, jiffies 4295064448 (age 73.992s) hex dump (first 32 bytes): 78 dc 26 1e 80 88 ff ff 78 dc 26 1e 80 88 ff ff x.&.....x.&..... 00 00 00 00 00 00 00 00 c0 da 26 1e 80 88 ff ff ..........&..... backtrace: [] __alloc_skb+0x287/0x330 net/core/skbuff.c:505 [] vmci_transport_recv_dgram_cb+0xbd/0x210 [vmw_vsock_vmci_transport] [] vmci_datagram_invoke_guest_handler+0x366/0x450 [vmw_vmci] [] vmci_dispatch_dgs+0x235/0x490 [vmw_vmci] [] vmci_interrupt+0x55/0x290 [vmw_vmci] [] irq_thread_fn+0x8b/0x1a0 kernel/irq/manage.c:1205 [] irq_thread+0x28e/0x530 kernel/irq/manage.c:1314 [] kthread+0x2e6/0x3a0 kernel/kthread.c:376 [] ret_from_fork+0x22/0x30 arch/x86/entry/entry_64.S:295 Found by InfoTeCS on behalf of Linux Verification Center (linuxtesting.org) with Syzkaller. Fixes: d021c344051a ("VSOCK: Introduce VM Sockets") Reviewed-by: Stefano Garzarella Signed-off-by: Ilia Gavrilov Link: https://patch.msgid.link/20261008144046.90853-1-Ilia.Gavrilov@infotecs.ru Signed-off-by: Jakub Kicinski --- net/vmw_vsock/af_vsock.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/net/vmw_vsock/af_vsock.c b/net/vmw_vsock/af_vsock.c index 9b71479a2b29..533c733618df 100644 --- a/net/vmw_vsock/af_vsock.c +++ b/net/vmw_vsock/af_vsock.c @@ -1048,6 +1048,8 @@ static void vsock_sk_destruct(struct sock *sk) { struct vsock_sock *vsk = vsock_sk(sk); + /* Socket buffers may remain in the queue in VMCI datagram mode */ + __skb_queue_purge(&sk->sk_receive_queue); /* Flush MSG_ZEROCOPY leftovers. */ __skb_queue_purge(&sk->sk_error_queue); From 6b48ed85ae0ca37c7403ef5d512be75975844f2f Mon Sep 17 00:00:00 2001 From: Nicolai Buchwitz Date: Tue, 6 Oct 2026 07:41:32 +0200 Subject: [PATCH 348/367] net: macb: check TX ring before modifying skb macb_pad_and_fcs() replaces or extends the skb before the ring space check. On NETDEV_TX_BUSY the stack requeues an skb that is already freed or grown. Check the ring first, using the padded length for the descriptor count. Nonlinear skbs always take the copy path so the count can assume a linear skb. Fixes: 653e92a9175e ("net: macb: add support for padding and fcs computation") Signed-off-by: Nicolai Buchwitz Link: https://patch.msgid.link/20261006-nb-macb-shared-skb-net-v1-1-a80641479041@tipi-net.de Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/cadence/macb_main.c | 76 ++++++++++++++---------- 1 file changed, 46 insertions(+), 30 deletions(-) diff --git a/drivers/net/ethernet/cadence/macb_main.c b/drivers/net/ethernet/cadence/macb_main.c index 8e5c034dc3a4..6082e63009a5 100644 --- a/drivers/net/ethernet/cadence/macb_main.c +++ b/drivers/net/ethernet/cadence/macb_main.c @@ -2421,8 +2421,15 @@ static inline int macb_clear_csum(struct sk_buff *skb) return 0; } +static bool macb_needs_sw_fcs(struct sk_buff *skb, struct net_device *netdev) +{ + return netdev->features & NETIF_F_HW_CSUM && + skb->ip_summed != CHECKSUM_PARTIAL && + !skb_shinfo(skb)->gso_size && !ptp_one_step_sync(skb); +} + /* Returns a negative errno, or the FCS bytes appended (0 or ETH_FCS_LEN). */ -static int macb_pad_and_fcs(struct sk_buff **skb, struct net_device *netdev) +static int macb_pad_and_fcs(struct sk_buff **skb, bool add_fcs) { bool cloned = skb_cloned(*skb) || skb_header_cloned(*skb) || skb_is_nonlinear(*skb); @@ -2431,18 +2438,15 @@ static int macb_pad_and_fcs(struct sk_buff **skb, struct net_device *netdev) struct sk_buff *nskb; u32 fcs; - if (!(netdev->features & NETIF_F_HW_CSUM) || - !((*skb)->ip_summed != CHECKSUM_PARTIAL) || - skb_shinfo(*skb)->gso_size || ptp_one_step_sync(*skb)) + if (!add_fcs) return 0; if (padlen <= 0) { - /* FCS could be appeded to tailroom. */ - if (tailroom >= ETH_FCS_LEN) + /* FCS could be appended to tailroom. */ + if (!skb_is_nonlinear(*skb) && tailroom >= ETH_FCS_LEN) goto add_fcs; - /* No room for FCS, need to reallocate skb. */ - else - padlen = ETH_FCS_LEN; + /* Reallocate with room for the FCS. */ + padlen = ETH_FCS_LEN; } else { /* Add room for FCS. */ padlen += ETH_FCS_LEN; @@ -2481,27 +2485,15 @@ static netdev_tx_t macb_start_xmit(struct sk_buff *skb, unsigned int desc_cnt, nr_frags, frag_size, f; struct macb_queue *queue = &bp->queues[q]; netdev_tx_t ret = NETDEV_TX_OK; - unsigned int hdrlen; + unsigned int hdrlen, tx_len; + bool add_fcs, is_lso; unsigned long flags; int fcs_len; - bool is_lso; - - if (macb_clear_csum(skb)) { - dev_kfree_skb_any(skb); - return ret; - } - - fcs_len = macb_pad_and_fcs(&skb, netdev); - if (fcs_len < 0) { - dev_kfree_skb_any(skb); - return ret; - } - - if (macb_dma_ptp(bp) && - (skb_shinfo(skb)->tx_flags & SKBTX_HW_TSTAMP)) - skb_shinfo(skb)->tx_flags |= SKBTX_IN_PROGRESS; + add_fcs = macb_needs_sw_fcs(skb, netdev); is_lso = (skb_shinfo(skb)->gso_size != 0); + tx_len = add_fcs ? max_t(unsigned int, skb->len, ETH_ZLEN) + + ETH_FCS_LEN : skb->len; if (is_lso) { /* length of headers */ @@ -2515,8 +2507,11 @@ static netdev_tx_t macb_start_xmit(struct sk_buff *skb, /* if this is required, would need to copy to single buffer */ return NETDEV_TX_BUSY; } - } else + } else if (add_fcs) { + hdrlen = umin(tx_len, bp->max_tx_length); + } else { hdrlen = umin(skb_headlen(skb), bp->max_tx_length); + } #if defined(DEBUG) && defined(VERBOSE_DEBUG) netdev_vdbg(bp->netdev, @@ -2531,12 +2526,18 @@ static netdev_tx_t macb_start_xmit(struct sk_buff *skb, * socket buffer: skb fragments of jumbo frames may need to be * split into many buffer descriptors. */ - if (is_lso && (skb_headlen(skb) > hdrlen)) + if (add_fcs) { + /* macb_pad_and_fcs() linearizes the skb before adding the FCS. */ + desc_cnt = DIV_ROUND_UP(tx_len, bp->max_tx_length); + nr_frags = 0; + } else if (is_lso && (skb_headlen(skb) > hdrlen)) { /* extra header descriptor if also payload in first buffer */ desc_cnt = DIV_ROUND_UP((skb_headlen(skb) - hdrlen), bp->max_tx_length) + 1; - else + nr_frags = skb_shinfo(skb)->nr_frags; + } else { desc_cnt = DIV_ROUND_UP(skb_headlen(skb), bp->max_tx_length); - nr_frags = skb_shinfo(skb)->nr_frags; + nr_frags = skb_shinfo(skb)->nr_frags; + } for (f = 0; f < nr_frags; f++) { frag_size = skb_frag_size(&skb_shinfo(skb)->frags[f]); desc_cnt += DIV_ROUND_UP(frag_size, bp->max_tx_length); @@ -2554,6 +2555,21 @@ static netdev_tx_t macb_start_xmit(struct sk_buff *skb, goto unlock; } + if (macb_clear_csum(skb)) { + dev_kfree_skb_any(skb); + goto unlock; + } + + fcs_len = macb_pad_and_fcs(&skb, add_fcs); + if (fcs_len < 0) { + dev_kfree_skb_any(skb); + goto unlock; + } + + if (macb_dma_ptp(bp) && + (skb_shinfo(skb)->tx_flags & SKBTX_HW_TSTAMP)) + skb_shinfo(skb)->tx_flags |= SKBTX_IN_PROGRESS; + /* Map socket buffer for DMA transfer */ if (macb_tx_map(bp, queue, skb, hdrlen, fcs_len)) { dev_kfree_skb_any(skb); From 9151d6c42799105e109c8b54dbaad91ce0e17c47 Mon Sep 17 00:00:00 2001 From: Nicolai Buchwitz Date: Tue, 6 Oct 2026 07:41:33 +0200 Subject: [PATCH 349/367] net: macb: copy shared skbs before appending the FCS macb_pad_and_fcs() appends the FCS in place when the skb has tailroom. A shared skb, as pktgen sends in clone_skb mode, grows by one FCS per transmit. BQL then completes more bytes than were queued and dql_completed() hits its BUG_ON. On a Raspberry Pi CM5 (RP1 GEM) pktgen with clone_skb 1000 burst 32 at 60 bytes kills the box within seconds. Copy shared skbs before appending the FCS. Clearing IFF_TX_SKB_SHARING would also fix it but makes pktgen refuse clone_skb on macb. Fixes: 653e92a9175e ("net: macb: add support for padding and fcs computation") Signed-off-by: Nicolai Buchwitz Link: https://patch.msgid.link/20261006-nb-macb-shared-skb-net-v1-2-a80641479041@tipi-net.de Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/cadence/macb_main.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/net/ethernet/cadence/macb_main.c b/drivers/net/ethernet/cadence/macb_main.c index 6082e63009a5..261a7e87520a 100644 --- a/drivers/net/ethernet/cadence/macb_main.c +++ b/drivers/net/ethernet/cadence/macb_main.c @@ -2435,6 +2435,7 @@ static int macb_pad_and_fcs(struct sk_buff **skb, bool add_fcs) skb_is_nonlinear(*skb); int padlen = ETH_ZLEN - (*skb)->len; int tailroom = skb_tailroom(*skb); + bool shared = skb_shared(*skb); struct sk_buff *nskb; u32 fcs; @@ -2443,7 +2444,8 @@ static int macb_pad_and_fcs(struct sk_buff **skb, bool add_fcs) if (padlen <= 0) { /* FCS could be appended to tailroom. */ - if (!skb_is_nonlinear(*skb) && tailroom >= ETH_FCS_LEN) + if (!shared && !skb_is_nonlinear(*skb) && + tailroom >= ETH_FCS_LEN) goto add_fcs; /* Reallocate with room for the FCS. */ padlen = ETH_FCS_LEN; @@ -2452,7 +2454,7 @@ static int macb_pad_and_fcs(struct sk_buff **skb, bool add_fcs) padlen += ETH_FCS_LEN; } - if (cloned || tailroom < padlen) { + if (shared || cloned || tailroom < padlen) { nskb = skb_copy_expand(*skb, 0, padlen, GFP_ATOMIC); if (!nskb) return -ENOMEM; From b59cabf9b4acb65f3f9968087ca0e18010d85076 Mon Sep 17 00:00:00 2001 From: Hengyu Liang Date: Tue, 6 Oct 2026 08:57:32 -0400 Subject: [PATCH 350/367] io_uring: do not charge the SQ/CQ rings to RLIMIT_MEMLOCK Commit 8078486e1d53 ("io_uring: use region api for SQ") and commit 81a4058e0cd0 ("io_uring: use region api for CQ") made io_uring_setup() allocate the rings with io_create_region(). However, io_create_region() charges the memory to RLIMIT_MEMLOCK, and the rings had been exempt from that limit since commit 26bfa89e25f4 ("io_uring: place ring SQ/CQ arrays under memcg memory limits"). As of now, a user without CAP_IPC_LOCK gets ENOMEM from io_uring_setup() when their rings exceed the limit, which is 8 MiB by default. PostgreSQL developers have already hit this in their io_uring tests [1]. The issue can be reproduced with a simple liburing program, run as an unprivileged user: #include #include int main(void) { static struct io_uring ring[64]; int i; for (i = 0; i < 64; i++) if (io_uring_queue_init(4096, &ring[i], 0) < 0) break; printf("%d rings\n", i); return 0; } Before those commits (v6.13), it prints "64 rings". After those commits (v6.14), it prints "21 rings". This patch makes io_create_region() take the user to charge, and passes no user for the SQ/CQ rings. Link: https://www.postgresql.org/message-id/flat/667f7dcf-404b-4898-a426-57ca6e0617f6@gmail.com [1] Fixes: 8078486e1d53 ("io_uring: use region api for SQ") Fixes: 81a4058e0cd0 ("io_uring: use region api for CQ") Cc: stable@vger.kernel.org Signed-off-by: Hengyu Liang Link: https://lore.kernel.org/io-uring/20261006125732.3425762-1-hengyul@cs.unc.edu/ Signed-off-by: Jens Axboe --- io_uring/io_uring.c | 10 ++++++---- io_uring/kbuf.c | 2 +- io_uring/memmap.c | 10 +++++----- io_uring/memmap.h | 2 +- io_uring/register.c | 12 +++++++----- io_uring/zcrx.c | 2 +- 6 files changed, 21 insertions(+), 17 deletions(-) diff --git a/io_uring/io_uring.c b/io_uring/io_uring.c index c2ce83c7c1f1..79c40e0abf17 100644 --- a/io_uring/io_uring.c +++ b/io_uring/io_uring.c @@ -2070,8 +2070,9 @@ int io_submit_sqes(struct io_ring_ctx *ctx, unsigned int nr) static void io_rings_free(struct io_ring_ctx *ctx) { - io_free_region(ctx->user, &ctx->sq_region); - io_free_region(ctx->user, &ctx->ring_region); + /* ring memory is not charged to RLIMIT_MEMLOCK, hence no user */ + io_free_region(NULL, &ctx->sq_region); + io_free_region(NULL, &ctx->ring_region); ctx->rings = NULL; RCU_INIT_POINTER(ctx->rings_rcu, NULL); ctx->sq_sqes = NULL; @@ -2735,7 +2736,8 @@ static __cold int io_allocate_scq_urings(struct io_ring_ctx *ctx, rd.user_addr = p->cq_off.user_addr; rd.flags |= IORING_MEM_REGION_TYPE_USER; } - ret = io_create_region(ctx, &ctx->ring_region, &rd, IORING_OFF_CQ_RING); + /* ring memory is not charged to RLIMIT_MEMLOCK, hence no user */ + ret = io_create_region(NULL, &ctx->ring_region, &rd, IORING_OFF_CQ_RING); if (ret) return ret; ctx->rings = rings = io_region_get_ptr(&ctx->ring_region); @@ -2749,7 +2751,7 @@ static __cold int io_allocate_scq_urings(struct io_ring_ctx *ctx, rd.user_addr = p->sq_off.user_addr; rd.flags |= IORING_MEM_REGION_TYPE_USER; } - ret = io_create_region(ctx, &ctx->sq_region, &rd, IORING_OFF_SQES); + ret = io_create_region(NULL, &ctx->sq_region, &rd, IORING_OFF_SQES); if (ret) { io_rings_free(ctx); return ret; diff --git a/io_uring/kbuf.c b/io_uring/kbuf.c index 7c309173dd19..7c59ab9cfc8b 100644 --- a/io_uring/kbuf.c +++ b/io_uring/kbuf.c @@ -676,7 +676,7 @@ int io_register_pbuf_ring(struct io_ring_ctx *ctx, void __user *arg) rd.user_addr = reg.ring_addr; rd.flags |= IORING_MEM_REGION_TYPE_USER; } - ret = io_create_region(ctx, &bl->region, &rd, mmap_offset); + ret = io_create_region(ctx->user, &bl->region, &rd, mmap_offset); if (ret) goto fail; br = io_region_get_ptr(&bl->region); diff --git a/io_uring/memmap.c b/io_uring/memmap.c index 48c0eb012412..d4d716946971 100644 --- a/io_uring/memmap.c +++ b/io_uring/memmap.c @@ -204,7 +204,7 @@ static int io_region_allocate_pages(struct io_mapped_region *mr, return 0; } -int io_create_region(struct io_ring_ctx *ctx, struct io_mapped_region *mr, +int io_create_region(struct user_struct *user, struct io_mapped_region *mr, struct io_uring_region_desc *reg, unsigned long mmap_offset) { @@ -230,8 +230,8 @@ int io_create_region(struct io_ring_ctx *ctx, struct io_mapped_region *mr, return -EOVERFLOW; nr_pages = reg->size >> PAGE_SHIFT; - if (ctx->user) { - ret = __io_account_mem(ctx->user, nr_pages); + if (user) { + ret = __io_account_mem(user, nr_pages); if (ret) return ret; } @@ -240,7 +240,7 @@ int io_create_region(struct io_ring_ctx *ctx, struct io_mapped_region *mr, if (reg->flags & IORING_MEM_REGION_TYPE_USER) ret = io_region_pin_pages(mr, reg); else - ret = io_region_allocate_pages(mr, reg, mmap_offset, ctx->user); + ret = io_region_allocate_pages(mr, reg, mmap_offset, user); if (ret) goto out_free; @@ -249,7 +249,7 @@ int io_create_region(struct io_ring_ctx *ctx, struct io_mapped_region *mr, goto out_free; return 0; out_free: - io_free_region(ctx->user, mr); + io_free_region(user, mr); return ret; } diff --git a/io_uring/memmap.h b/io_uring/memmap.h index f4cfbb6b9a1f..0714bb5a9616 100644 --- a/io_uring/memmap.h +++ b/io_uring/memmap.h @@ -18,7 +18,7 @@ unsigned long io_uring_get_unmapped_area(struct file *file, unsigned long addr, int io_uring_mmap(struct file *file, struct vm_area_struct *vma); void io_free_region(struct user_struct *user, struct io_mapped_region *mr); -int io_create_region(struct io_ring_ctx *ctx, struct io_mapped_region *mr, +int io_create_region(struct user_struct *user, struct io_mapped_region *mr, struct io_uring_region_desc *reg, unsigned long mmap_offset); diff --git a/io_uring/register.c b/io_uring/register.c index 02bc103bcc9d..d11a53d68431 100644 --- a/io_uring/register.c +++ b/io_uring/register.c @@ -480,8 +480,9 @@ struct io_ring_ctx_rings { static void io_register_free_rings(struct io_ring_ctx *ctx, struct io_ring_ctx_rings *r) { - io_free_region(ctx->user, &r->sq_region); - io_free_region(ctx->user, &r->ring_region); + /* ring memory is not charged to RLIMIT_MEMLOCK, hence no user */ + io_free_region(NULL, &r->sq_region); + io_free_region(NULL, &r->ring_region); } #define swap_old(ctx, o, n, field) \ @@ -529,7 +530,7 @@ static int io_register_resize_rings(struct io_ring_ctx *ctx, void __user *arg) rd.user_addr = p->cq_off.user_addr; rd.flags |= IORING_MEM_REGION_TYPE_USER; } - ret = io_create_region(ctx, &n.ring_region, &rd, IORING_OFF_CQ_RING); + ret = io_create_region(NULL, &n.ring_region, &rd, IORING_OFF_CQ_RING); if (ret) return ret; @@ -559,7 +560,7 @@ static int io_register_resize_rings(struct io_ring_ctx *ctx, void __user *arg) rd.user_addr = p->sq_off.user_addr; rd.flags |= IORING_MEM_REGION_TYPE_USER; } - ret = io_create_region(ctx, &n.sq_region, &rd, IORING_OFF_SQES); + ret = io_create_region(NULL, &n.sq_region, &rd, IORING_OFF_SQES); if (ret) { io_register_free_rings(ctx, &n); return ret; @@ -730,7 +731,8 @@ static int io_register_mem_region(struct io_ring_ctx *ctx, void __user *uarg) !(ctx->flags & IORING_SETUP_R_DISABLED)) return -EINVAL; - ret = io_create_region(ctx, ®ion, &rd, IORING_MAP_OFF_PARAM_REGION); + ret = io_create_region(ctx->user, ®ion, &rd, + IORING_MAP_OFF_PARAM_REGION); if (ret) return ret; if (copy_to_user(rd_uptr, &rd, sizeof(rd))) { diff --git a/io_uring/zcrx.c b/io_uring/zcrx.c index beb35077f3ce..939885985333 100644 --- a/io_uring/zcrx.c +++ b/io_uring/zcrx.c @@ -432,7 +432,7 @@ static int io_allocate_rbuf_ring(struct io_ring_ctx *ctx, mmap_offset = IORING_MAP_OFF_ZCRX_REGION; mmap_offset += (u64)id << IORING_OFF_ZCRX_SHIFT; - ret = io_create_region(ctx, &ifq->rq_region, rd, mmap_offset); + ret = io_create_region(ctx->user, &ifq->rq_region, rd, mmap_offset); if (ret < 0) return ret; From 6abc8e4dcfbc1d26a94c0ac037dfb3716f686b1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ADra=20Canal?= Date: Fri, 25 Sep 2026 16:10:19 -0300 Subject: [PATCH 351/367] drm/vc4: Disable the V3D interrupt across runtime suspend MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit vc4_irq_disable() masks the V3D interrupt sources and then calls synchronize_irq() before the V3D is powered down. However, by itself, this is not enough to quiesce the interrupt handler. synchronize_irq() waits for handlers that have already set IRQD_IRQ_INPROGRESS, and for irqchips reporting IRQCHIP_STATE_ACTIVE. A GIC interrupt chip is able to mark an interrupt active as soon as a CPU acknowledges it, so there these checks cover the whole dispatch path. On RPi 0-3, however, the interrupt controller is ARMCTRL, which has no active state. A CPU that has read the hwirq out of the pending register but has not yet reached handle_level_irq() stays invisible to synchronize_irq(). During a power transition, vc4_irq() may therefore run after the power domain is off, where every V3D register read will return 0xdeadbeef. 0xdeadbeef has FLDONE, FRDONE and OUTOMEM set. This problem doesn't trigger NULL pointer dereference issues only because the functions vc4_irq_finish_bin_job() and vc4_irq_finish_render_job() return early when there is no job pointer, but OUTOMEM is still able to schedule vc4_overflow_mem_work() after vc4_irq_disable() has already cancelled it. Therefore, fix the spurious interrupts by disabling the interrupt line across the PM transition. To preserve the enable/disable balance, move vc4_irq_install() and request the IRQ line disabled before the first runtime resume. Fixes: 9b6f461582e6 ("drm/vc4: v3d: Stop disabling interrupts") Reviewed-by: Iago Toral Quiroga Link: https://patch.msgid.link/20260925191318.541938-1-mcanal@igalia.com Signed-off-by: Maíra Canal --- drivers/gpu/drm/vc4/vc4_irq.c | 18 +++--------------- drivers/gpu/drm/vc4/vc4_v3d.c | 13 +++++++------ 2 files changed, 10 insertions(+), 21 deletions(-) diff --git a/drivers/gpu/drm/vc4/vc4_irq.c b/drivers/gpu/drm/vc4/vc4_irq.c index 7877d493d80e..98a8db720091 100644 --- a/drivers/gpu/drm/vc4/vc4_irq.c +++ b/drivers/gpu/drm/vc4/vc4_irq.c @@ -276,7 +276,7 @@ vc4_irq_disable(struct drm_device *dev) V3D_WRITE(V3D_INTCTL, V3D_DRIVER_IRQS); /* Finish any interrupt handler still in flight. */ - synchronize_irq(vc4->irq); + disable_irq(vc4->irq); cancel_work_sync(&vc4->overflow_mem_work); } @@ -284,7 +284,6 @@ vc4_irq_disable(struct drm_device *dev) int vc4_irq_install(struct drm_device *dev, int irq) { struct vc4_dev *vc4 = to_vc4_dev(dev); - int ret; if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4)) return -ENODEV; @@ -298,19 +297,8 @@ int vc4_irq_install(struct drm_device *dev, int irq) init_waitqueue_head(&vc4->job_wait_queue); INIT_WORK(&vc4->overflow_mem_work, vc4_overflow_mem_work); - /* Clear any pending interrupts someone might have left around - * for us. - */ - V3D_WRITE(V3D_INTCTL, V3D_DRIVER_IRQS); - - ret = devm_request_irq(dev->dev, irq, vc4_irq, 0, - dev_name(dev->dev), dev); - if (ret) - return ret; - - vc4_irq_enable(dev); - - return 0; + return devm_request_irq(dev->dev, irq, vc4_irq, IRQF_NO_AUTOEN, + dev_name(dev->dev), dev); } void vc4_irq_uninstall(struct drm_device *dev) diff --git a/drivers/gpu/drm/vc4/vc4_v3d.c b/drivers/gpu/drm/vc4/vc4_v3d.c index 0f0c090afa6e..8ca0f4dde04b 100644 --- a/drivers/gpu/drm/vc4/vc4_v3d.c +++ b/drivers/gpu/drm/vc4/vc4_v3d.c @@ -397,6 +397,7 @@ static int vc4_v3d_runtime_resume(struct device *dev) vc4_v3d_init_hw(&vc4->base); vc4_irq_enable(&vc4->base); + enable_irq(vc4->irq); return 0; } @@ -453,6 +454,12 @@ static int vc4_v3d_bind(struct device *dev, struct device *master, void *data) return ret; vc4->irq = ret; + ret = vc4_irq_install(drm, vc4->irq); + if (ret) { + drm_err(drm, "Failed to install IRQ handler\n"); + return ret; + } + ret = devm_pm_runtime_enable(dev); if (ret) return ret; @@ -474,12 +481,6 @@ static int vc4_v3d_bind(struct device *dev, struct device *master, void *data) V3D_WRITE(V3D_BPOA, 0); V3D_WRITE(V3D_BPOS, 0); - ret = vc4_irq_install(drm, vc4->irq); - if (ret) { - drm_err(drm, "Failed to install IRQ handler\n"); - goto err_put_runtime_pm; - } - pm_runtime_use_autosuspend(dev); pm_runtime_set_autosuspend_delay(dev, 40); /* a little over 2 frames. */ pm_runtime_put_autosuspend(dev); From cca01232d94adcb10f7c8c5bb8ce072b045fcb58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ADra=20Canal?= Date: Wed, 30 Sep 2026 15:20:44 -0300 Subject: [PATCH 352/367] drm/vc4: Fix binner slot allocation failing on an idle GPU MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A job's binner slots are returned to bin_alloc_used by vc4_complete_exec(), which runs from the job_done workqueue, but the seqno vc4_v3d_get_bin_slot() waits on is incremented earlier, in the function vc4_irq_finish_render_job(). Therefore, a waiter can wake, retry, and still find the pool full because the worker has not run. By then, the job might have left the render_job_list, so no seqno remains to wait on and the allocation fails returning -ENOMEM. This scenario can be reproduced on a Raspberry Pi 3 by using a burst of small jobs (e.g. Piglit's `quick_gl` suite), with userspace seeing a rejected submit for a pool about to become free. Note that the slots are held from validation until the render completes, so the pool can also be exhausted by jobs still queued on bin_job_list, leaving render_job_list empty and nothing to wait on. Release the slots from the FRDONE handler and wait on the pool instead of on one job's seqno. This bounds the wait to the actual resource. Also, by claiming the slot inside the wait condition, another submit is unable to take the slot between the wakeup and the retry. vc4_complete_exec() keeps clearing the slots for jobs that never completed, and now wakes the waiters too. Note that the wait takes a deadline, since the two callers need different ones. The submit path is a user task and can be interrupted, so it waits without one. On the other hand, vc4_overflow_mem_work() runs on a worker and never sees a signal, so it needs a deadline. During a reset, vc4_irq_disable() masks V3D_DRIVER_IRQS before draining the worker, and with those masked nothing can release a slot. This deadlocks: the worker would wait for slots that only the reset can free, while the reset waits in cancel_work_sync() for the worker. Fixes: 553c942f8b2c ("drm/vc4: Allow using more than 256MB of CMA memory.") Reviewed-by: Iago Toral Quiroga Link: https://patch.msgid.link/20260930182204.1877361-1-mcanal@igalia.com Signed-off-by: Maíra Canal --- drivers/gpu/drm/vc4/vc4_drv.h | 2 +- drivers/gpu/drm/vc4/vc4_gem.c | 8 +++++- drivers/gpu/drm/vc4/vc4_irq.c | 6 +++- drivers/gpu/drm/vc4/vc4_v3d.c | 44 ++++++++++++++---------------- drivers/gpu/drm/vc4/vc4_validate.c | 2 +- 5 files changed, 34 insertions(+), 28 deletions(-) diff --git a/drivers/gpu/drm/vc4/vc4_drv.h b/drivers/gpu/drm/vc4/vc4_drv.h index 0f5958c1e6b6..b0e82c2dc1ba 100644 --- a/drivers/gpu/drm/vc4/vc4_drv.h +++ b/drivers/gpu/drm/vc4/vc4_drv.h @@ -1051,7 +1051,7 @@ void vc4_plane_async_set_fb(struct drm_plane *plane, /* vc4_v3d.c */ extern struct platform_driver vc4_v3d_driver; extern const struct of_device_id vc4_v3d_dt_match[]; -int vc4_v3d_get_bin_slot(struct vc4_dev *vc4); +int vc4_v3d_get_bin_slot(struct vc4_dev *vc4, long timeout); int vc4_v3d_bin_bo_get(struct vc4_dev *vc4, bool *used); void vc4_v3d_bin_bo_put(struct vc4_dev *vc4); int vc4_v3d_pm_get(struct vc4_dev *vc4); diff --git a/drivers/gpu/drm/vc4/vc4_gem.c b/drivers/gpu/drm/vc4/vc4_gem.c index e231c906709c..6ee4bd77d433 100644 --- a/drivers/gpu/drm/vc4/vc4_gem.c +++ b/drivers/gpu/drm/vc4/vc4_gem.c @@ -877,11 +877,17 @@ vc4_complete_exec(struct drm_device *dev, struct vc4_exec_info *exec) drm_gem_object_put(&bo->base.base); } - /* Free up the allocation of any bin slots we used. */ + /* Free up the allocation of any bin slots we used. Jobs that ran to + * completion had their slots released in vc4_irq_finish_render_job(). + * Only jobs that never completed still have slots to be released here. + */ spin_lock_irqsave(&vc4->job_lock, irqflags); vc4->bin_alloc_used &= ~exec->bin_slots; spin_unlock_irqrestore(&vc4->job_lock, irqflags); + /* Let anyone waiting on the binner pool retry. */ + wake_up_all(&vc4->job_wait_queue); + /* Release the reference on the binner BO if needed. */ if (exec->bin_bo_used) vc4_v3d_bin_bo_put(vc4); diff --git a/drivers/gpu/drm/vc4/vc4_irq.c b/drivers/gpu/drm/vc4/vc4_irq.c index 98a8db720091..dc10d3a4d90f 100644 --- a/drivers/gpu/drm/vc4/vc4_irq.c +++ b/drivers/gpu/drm/vc4/vc4_irq.c @@ -74,7 +74,7 @@ vc4_overflow_mem_work(struct work_struct *work) bo = vc4->bin_bo; - bin_bo_slot = vc4_v3d_get_bin_slot(vc4); + bin_bo_slot = vc4_v3d_get_bin_slot(vc4, msecs_to_jiffies(500)); if (bin_bo_slot < 0) { drm_err(&vc4->base, "Couldn't allocate binner overflow mem\n"); goto complete; @@ -165,6 +165,10 @@ vc4_irq_finish_render_job(struct drm_device *dev) trace_vc4_rcl_end_irq(dev, exec->seqno); vc4->finished_seqno++; + + vc4->bin_alloc_used &= ~exec->bin_slots; + exec->bin_slots = 0; + list_move_tail(&exec->head, &vc4->job_done_list); nextbin = vc4_first_bin_job(vc4); diff --git a/drivers/gpu/drm/vc4/vc4_v3d.c b/drivers/gpu/drm/vc4/vc4_v3d.c index 8ca0f4dde04b..6844e8f3948c 100644 --- a/drivers/gpu/drm/vc4/vc4_v3d.c +++ b/drivers/gpu/drm/vc4/vc4_v3d.c @@ -173,46 +173,42 @@ static void vc4_v3d_init_hw(struct drm_device *dev) V3D_WRITE(V3D_VPMBASE, 0); } -int vc4_v3d_get_bin_slot(struct vc4_dev *vc4) +static int bin_slot_try_alloc(struct vc4_dev *vc4) { - struct drm_device *dev = &vc4->base; - unsigned long irqflags; int slot; - uint64_t seqno = 0; - struct vc4_exec_info *exec; - if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4)) - return -ENODEV; + guard(spinlock_irqsave)(&vc4->job_lock); -try_again: - spin_lock_irqsave(&vc4->job_lock, irqflags); slot = ffs(~vc4->bin_alloc_used); if (slot != 0) { /* Switch from ffs() bit index to a 0-based index. */ slot--; vc4->bin_alloc_used |= BIT(slot); - spin_unlock_irqrestore(&vc4->job_lock, irqflags); - return slot; + } else { + slot = -ENOMEM; } - /* Couldn't find an open slot. Wait for render to complete - * and try again. - */ - exec = vc4_last_render_job(vc4); - if (exec) - seqno = exec->seqno; - spin_unlock_irqrestore(&vc4->job_lock, irqflags); + return slot; +} - if (seqno) { - int ret = vc4_wait_for_seqno(dev, seqno, ~0ull, true); +int vc4_v3d_get_bin_slot(struct vc4_dev *vc4, long timeout) +{ + int slot; + long ret; - if (ret == 0) - goto try_again; + if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4)) + return -ENODEV; + /* If the pool is full, wait for a job to release its slots. */ + ret = wait_event_interruptible_timeout(vc4->job_wait_queue, + (slot = bin_slot_try_alloc(vc4)) >= 0, + timeout); + if (ret < 0) return ret; - } + if (ret == 0) + return -ENOMEM; - return -ENOMEM; + return slot; } /* diff --git a/drivers/gpu/drm/vc4/vc4_validate.c b/drivers/gpu/drm/vc4/vc4_validate.c index d2a65c968b1f..565227657b43 100644 --- a/drivers/gpu/drm/vc4/vc4_validate.c +++ b/drivers/gpu/drm/vc4/vc4_validate.c @@ -402,7 +402,7 @@ validate_tile_binning_config(VALIDATE_ARGS) return -EINVAL; } - bin_slot = vc4_v3d_get_bin_slot(vc4); + bin_slot = vc4_v3d_get_bin_slot(vc4, MAX_SCHEDULE_TIMEOUT); if (bin_slot < 0) { if (bin_slot != -EINTR && bin_slot != -ERESTARTSYS) { drm_err(dev, "Failed to allocate binner memory: %d\n", From 79c168e2aa957ed0054c93db17caba653b95e377 Mon Sep 17 00:00:00 2001 From: Lukas Wunner Date: Thu, 8 Oct 2026 14:26:00 +0200 Subject: [PATCH 353/367] PCI/AER: Skip error recovery on false alarms Alex is seeing a probe failure of the amdgpu driver after the Root Port above an AMD Navi10 GPU has been reset. The reset was performed to recover from a Firmware First reported Fatal Error. However all status registers in the Root Port's AER Extended Capability are blank, so apparently the platform firmware raised a false alarm. The issue is only occurring since commit eddba19b8b5f ("PCI/AER: Support Advisory Non-Fatal Errors"). It looks like enabling Advisory Non-Fatal Errors causes code paths to be exercised in platform firmware which were never validated before. Skip error recovery on false alarms, i.e. if no unmasked errors were actually signaled. Note that this will also skip recovery if both the Status and Mask registers are "all ones", as would be the case for inaccessible devices. However that seems justified because it would imply either a hot-unplug event or a Surprise Down Error further up in the hierarchy. Interfering with recovery from that seems uncalled for. Fixes: eddba19b8b5f ("PCI/AER: Support Advisory Non-Fatal Errors") Reported-by: Alex Deucher Closes: https://bugzilla.kernel.org/show_bug.cgi?id=222095 Signed-off-by: Lukas Wunner Signed-off-by: Bjorn Helgaas Tested-by: Alex Deucher Acked-by: Alex Deucher Link: https://patch.msgid.link/0552ed277e40a288e0157af799257ee6ec722534.1791460615.git.lukas@wunner.de --- drivers/pci/pcie/aer.c | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/drivers/pci/pcie/aer.c b/drivers/pci/pcie/aer.c index d8dcd238fda1..922a726a52a5 100644 --- a/drivers/pci/pcie/aer.c +++ b/drivers/pci/pcie/aer.c @@ -1360,8 +1360,10 @@ static DEFINE_KFIFO(aer_recover_ring, struct aer_recover_entry, static void aer_recover_work_func(struct work_struct *work) { + struct aer_capability_regs *regs; struct aer_recover_entry entry; struct pci_dev *pdev; + u32 err; while (kfifo_get(&aer_recover_ring, &entry)) { pdev = pci_get_domain_bus_and_slot(entry.domain, entry.bus, @@ -1375,6 +1377,12 @@ static void aer_recover_work_func(struct work_struct *work) } pci_print_aer(pdev, entry.severity, entry.regs); + regs = entry.regs; + if (entry.severity == AER_CORRECTABLE) + err = regs->cor_status & ~regs->cor_mask; + else + err = regs->uncor_status & ~regs->uncor_mask; + /* * Memory for aer_capability_regs(entry.regs) is being * allocated from the ghes_estatus_pool to protect it from @@ -1385,12 +1393,14 @@ static void aer_recover_work_func(struct work_struct *work) ghes_estatus_pool_region_free((unsigned long)entry.regs, sizeof(struct aer_capability_regs)); - if (entry.severity == AER_NONFATAL) - pcie_do_recovery(pdev, pci_channel_io_normal, - aer_root_reset); - else if (entry.severity == AER_FATAL) - pcie_do_recovery(pdev, pci_channel_io_frozen, - aer_root_reset); + if (err) { + if (entry.severity == AER_NONFATAL) + pcie_do_recovery(pdev, pci_channel_io_normal, + aer_root_reset); + else if (entry.severity == AER_FATAL) + pcie_do_recovery(pdev, pci_channel_io_frozen, + aer_root_reset); + } pci_dev_put(pdev); } } From 6ccf996a0dec1852dab94ad865f27b4904750e12 Mon Sep 17 00:00:00 2001 From: Mary Guillemard Date: Wed, 7 Oct 2026 16:30:15 +0200 Subject: [PATCH 354/367] drm/nouveau: Skip the Turing CE workaround object for non-GR channels This workaround is here for the old Gallium driver and predate async CE and NVDEC. Instead of only skipping it for NVDEC, let's only apply it in case of GR channels. This should make async CE work properly on Turing by stopping the assign of a GRCE. (here CE0) Signed-off-by: Mary Guillemard Reviewed-by: Lyude Paul Reviewed-by: Daniel Almeida Signed-off-by: Lyude Paul Link: https://patch.msgid.link/20261007-turing-async-ce-fix-v1-1-12ee6ccd1a3c@mary.zone --- drivers/gpu/drm/nouveau/nouveau_abi16.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/nouveau/nouveau_abi16.c b/drivers/gpu/drm/nouveau/nouveau_abi16.c index 4542d5f4ded8..b776030460f7 100644 --- a/drivers/gpu/drm/nouveau/nouveau_abi16.c +++ b/drivers/gpu/drm/nouveau/nouveau_abi16.c @@ -488,7 +488,7 @@ nouveau_abi16_ioctl_channel_alloc(ABI16_IOCTL_ARGS) goto done; break; case NV_DEVICE_INFO_V0_TURING: - if (engine != NV_DEVICE_HOST_RUNLIST_ENGINES_NVDEC) { + if (engine == NV_DEVICE_HOST_RUNLIST_ENGINES_GR) { ret = nvif_object_ctor(&chan->chan->user, "abi16CeWar", 0, TURING_DMA_COPY_A, NULL, 0, &chan->ce); From cde56daf4b93f474dc256dbcc5205035223ae6f5 Mon Sep 17 00:00:00 2001 From: Jens Axboe Date: Thu, 8 Oct 2026 13:07:38 -0600 Subject: [PATCH 355/367] io_uring/kbuf: don't charge provided buffer rings to RLIMIT_MEMLOCK Just like with the SQ and CQ rings, the provided buffer rings were also not charged against the memlock limit prior to the below mentioned commit. Restore that behavior. Fixes: ef62de3c4ad5 ("io_uring/kbuf: use region api for pbuf rings") Reported-by: Hengyu Liang Cc: stable@vger.kernel.org Tested-by: Hengyu Liang Signed-off-by: Jens Axboe --- io_uring/kbuf.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/io_uring/kbuf.c b/io_uring/kbuf.c index 7c59ab9cfc8b..6e2facf34bfa 100644 --- a/io_uring/kbuf.c +++ b/io_uring/kbuf.c @@ -459,7 +459,7 @@ static int io_remove_buffers_legacy(struct io_ring_ctx *ctx, static void io_put_bl(struct io_ring_ctx *ctx, struct io_buffer_list *bl) { if (bl->flags & IOBL_BUF_RING) - io_free_region(ctx->user, &bl->region); + io_free_region(NULL, &bl->region); else io_remove_buffers_legacy(ctx, bl, -1U); @@ -676,7 +676,7 @@ int io_register_pbuf_ring(struct io_ring_ctx *ctx, void __user *arg) rd.user_addr = reg.ring_addr; rd.flags |= IORING_MEM_REGION_TYPE_USER; } - ret = io_create_region(ctx->user, &bl->region, &rd, mmap_offset); + ret = io_create_region(NULL, &bl->region, &rd, mmap_offset); if (ret) goto fail; br = io_region_get_ptr(&bl->region); @@ -709,7 +709,7 @@ int io_register_pbuf_ring(struct io_ring_ctx *ctx, void __user *arg) if (!ret) return 0; fail: - io_free_region(ctx->user, &bl->region); + io_free_region(NULL, &bl->region); kfree(bl); return ret; } From 6da8f402e362b2f546e2efb7167fc9597aef6faf Mon Sep 17 00:00:00 2001 From: Eugene Gvozdetsky Date: Fri, 9 Oct 2026 01:08:49 +0200 Subject: [PATCH 356/367] io_uring/register: don't exempt disabled rings from task restrictions A ring created with IORING_SETUP_R_DISABLED is exempt from its register restrictions until it is enabled. This lets the creator register the restrictions and then enable the ring with IORING_REGISTER_ENABLE_RINGS, whether or not that opcode is in the allowed set. Since commit ed82f35b926b ("io_uring: allow registration of per-task restrictions"), a ring also inherits restrictions from the task that creates it, and the same exemption applies to those. A task whose register opcodes are restricted can therefore create a disabled ring, issue any register opcode on it, and then enable it. Mark rings whose restrictions were inherited from the task, and keep checking register opcodes for them while the ring is disabled. Enabling such a ring now requires IORING_REGISTER_ENABLE_RINGS to be in the task's allowed set. Per-ring restrictions registered on a disabled ring work as before. Fixes: ed82f35b926b ("io_uring: allow registration of per-task restrictions") Cc: stable@vger.kernel.org Assisted-by: Claude:claude-opus-5-5 Signed-off-by: Eugene Gvozdetsky Link: https://patch.msgid.link/20261009-iouring-task-restrict-fix-v1-1-a49daf55a12c@gmail.com [axboe: amend with inheritance flag fixup] Signed-off-by: Jens Axboe --- include/linux/io_uring_types.h | 2 ++ io_uring/io_uring.c | 3 ++- io_uring/register.c | 9 ++++++++- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/include/linux/io_uring_types.h b/include/linux/io_uring_types.h index 6b9e9c669bb3..f9aab8d3f861 100644 --- a/include/linux/io_uring_types.h +++ b/include/linux/io_uring_types.h @@ -317,6 +317,8 @@ enum { IO_RING_F_DRAIN_DISABLED = BIT(10), IO_RING_F_COMPAT = BIT(11), IO_RING_F_IOWQ_LIMITS_SET = BIT(12), + /* restrictions were inherited from the task that created the ring */ + IO_RING_F_RESTRICT_INHERITED = BIT(13), }; struct iou_ctx {}; diff --git a/io_uring/io_uring.c b/io_uring/io_uring.c index 79c40e0abf17..c994157173a2 100644 --- a/io_uring/io_uring.c +++ b/io_uring/io_uring.c @@ -2962,7 +2962,8 @@ static void io_ctx_restriction_clone(struct io_ring_ctx *ctx, if (dst->op_registered) ctx->int_flags |= IO_RING_F_OP_RESTRICTED; if (dst->reg_registered) - ctx->int_flags |= IO_RING_F_REG_RESTRICTED; + ctx->int_flags |= IO_RING_F_REG_RESTRICTED | + IO_RING_F_RESTRICT_INHERITED; } static __cold int io_uring_create(struct io_ctx_config *config) diff --git a/io_uring/register.c b/io_uring/register.c index d11a53d68431..dab3eb4258df 100644 --- a/io_uring/register.c +++ b/io_uring/register.c @@ -766,7 +766,14 @@ static int __io_uring_register(struct io_ring_ctx *ctx, unsigned opcode, if (ctx->submitter_task && ctx->submitter_task != current) return -EEXIST; - if ((ctx->int_flags & IO_RING_F_REG_RESTRICTED) && !(ctx->flags & IORING_SETUP_R_DISABLED)) { + /* + * A disabled ring is exempt from its own register restrictions, so + * that the creator can set them up and then enable the ring. That + * does not apply to restrictions inherited from the task. + */ + if ((ctx->int_flags & IO_RING_F_REG_RESTRICTED) && + (!(ctx->flags & IORING_SETUP_R_DISABLED) || + (ctx->int_flags & IO_RING_F_RESTRICT_INHERITED))) { opcode = array_index_nospec(opcode, IORING_REGISTER_LAST); if (!test_bit(opcode, ctx->restrictions.register_op)) return -EACCES; From aa5d5f8e502318e783545ba5d04bb40130b83e20 Mon Sep 17 00:00:00 2001 From: Natasha Klaus Date: Fri, 9 Oct 2026 16:38:07 +0300 Subject: [PATCH 357/367] io_uring/net: populate CONNECT filter fields for 24-byte sockaddr_in6 io_connect_bpf_populate() fills connect.port and connect.v6_addr for AF_INET6 only when addr_len >= sizeof(struct sockaddr_in6), i.e. 28 bytes. The IPv6 connect paths accept SIN6_LEN_RFC2133 (24 bytes), which already holds sin6_port and sin6_addr. io_connect() passes addr_len to the socket layer unchanged. At addr_len 24 the filter sees family AF_INET6 with port and address zero, while the kernel connects to the real destination. A filter that denies an IPv6 port or address is bypassed. Denying connect.port == P rejects [::1]:P with -EACCES at addr_len 28 but connects at addr_len 24. Use SIN6_LEN_RFC2133 as the threshold. sin6_scope_id stays zero when absent. The threshold mismatch was found by an AI coding assistant (Claude Code) comparing this hook against the net/ipv6 length checks on my instructions; it also drafted the reproducer and this patch. I verified the bypass and the fix in a VM on 7.3-rc6 and that 7.2 has the same code. Fixes: 899bea8248ce ("io_uring/net: allow filtering on IORING_OP_CONNECT") Cc: stable@vger.kernel.org # 7.2 Signed-off-by: Natasha Klaus Link: https://patch.msgid.link/20261009133807.730724-1-natalie.klaus@runtimeverification.com Signed-off-by: Jens Axboe --- io_uring/net.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/io_uring/net.c b/io_uring/net.c index 050ed274170a..f5f4f3ce6ef3 100644 --- a/io_uring/net.c +++ b/io_uring/net.c @@ -7,6 +7,7 @@ #include #include #include +#include #include #include @@ -1741,7 +1742,8 @@ void io_connect_bpf_populate(struct io_uring_bpf_ctx *bctx, struct io_kiocb *req case AF_INET6: { struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)ss; - if (conn->addr_len < (int)sizeof(*sin6)) + /* The IPv6 stack accepts SIN6_LEN_RFC2133; port and addr are in it. */ + if (conn->addr_len < SIN6_LEN_RFC2133) break; bctx->connect.port = sin6->sin6_port; memcpy(bctx->connect.v6_addr, &sin6->sin6_addr, From fddcf4c5321b3ea5f72166c3fd36a9d1c34f80c7 Mon Sep 17 00:00:00 2001 From: "Cen Zhang (Microsoft Security FORGE Labs)" Date: Thu, 8 Oct 2026 15:16:02 -0400 Subject: [PATCH 358/367] io_uring/cancel: don't spin on local work the task can't run io_uring_cancel_generic() refuses to sleep while any ring on the exiting task's tctx has local work pending. It assumes the task is that ring's submitter and that the next io_uring_try_cancel_requests() pass will run the work. This does not hold for a ring that was still IORING_SETUP_R_DISABLED when the task got its tctx node from io_uring_create() or io_ringfd_register(). The submitter is set later by IORING_REGISTER_ENABLE_RINGS, possibly from another task, and only the submitter may run local work on an IORING_SETUP_DEFER_TASKRUN ring. To the exiting task the work stays pending forever, the loop never reaches schedule(), and the task trips the WARN_ON_ONCE() once and then spins in do_exit(). An unprivileged user can set this up at will by creating a disabled ring, having a child enable it and stop, posting to the ring with IORING_OP_MSG_RING and exiting with a request in flight that only the child can complete. The exiting thread burns a CPU until the child runs or dies, and with panic_on_warn set the WARN panics the kernel. Fix by skipping the sleep only when io_allowed_defer_tw_run() allows the task to run the work. This is reasonable since work it cannot run cannot complete any of its requests either, so nothing is lost by sleeping. Drop the WARN_ON_ONCE() since handing a disabled ring to another thread is a supported scenario, not a bug state. Fixes: 360cd42c4e95 ("io_uring: optimise io_req_local_work_add") Reported-by: AutonomousCodeSecurity@microsoft.com Signed-off-by: Cen Zhang (Microsoft Security FORGE Labs) Link: https://patch.msgid.link/20261008191602.45101-1-cenzhang@linux.microsoft.com Signed-off-by: Jens Axboe --- io_uring/cancel.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/io_uring/cancel.c b/io_uring/cancel.c index 7d7820eab878..4f706aff14fc 100644 --- a/io_uring/cancel.c +++ b/io_uring/cancel.c @@ -634,12 +634,11 @@ __cold void io_uring_cancel_generic(bool cancel_all, struct io_sq_data *sqd) prepare_to_wait(&tctx->wait, &wait, TASK_INTERRUPTIBLE); io_run_task_work(); io_uring_drop_tctx_refs(current); + /* only skip the sleep for local work this task may run */ xa_for_each(&tctx->xa, index, node) { - if (io_local_work_pending(node->ctx)) { - WARN_ON_ONCE(node->ctx->submitter_task && - node->ctx->submitter_task != current); + if (io_local_work_pending(node->ctx) && + io_allowed_defer_tw_run(node->ctx)) goto end_wait; - } } /* * If we've seen completions, retry without waiting. This From f321cc430c49b6c1be0d53f2ef19d8342d8029cc Mon Sep 17 00:00:00 2001 From: Haotian Zhang Date: Fri, 9 Oct 2026 10:48:25 -0700 Subject: [PATCH 359/367] Input: ati_remote2 - fix type confusion in device attribute handlers Previously, the channel_mask and mode_mask attributes were registered on the parent usb_device via sysfs_create_group(). When commit b20d6bf8014b ("Input: ati-remote2 - use driver core to instantiate device attributes") converted the driver to let the driver core manage them via dev_groups in struct usb_driver, the attributes moved to the bound usb_interface devices, but the show and store callbacks were not updated accordingly. As a result, the callbacks still invoked to_usb_device() on a device pointer belonging to a struct usb_interface, producing an invalid usb_device pointer. Use to_usb_interface() directly in the attribute handlers to obtain the interface and its driver data. Fixes: b20d6bf8014b ("Input: ati-remote2 - use driver core to instantiate device attributes") Assisted-by: DeepSeek-V4.1-Flash Signed-off-by: Haotian Zhang Link: https://patch.msgid.link/20261009040302.3087205-1-vulab@iscas.ac.cn Cc: stable@vger.kernel.org Signed-off-by: Dmitry Torokhov --- drivers/input/misc/ati_remote2.c | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/drivers/input/misc/ati_remote2.c b/drivers/input/misc/ati_remote2.c index 8db2dca84975..3e0f874e9b16 100644 --- a/drivers/input/misc/ati_remote2.c +++ b/drivers/input/misc/ati_remote2.c @@ -672,8 +672,7 @@ static ssize_t ati_remote2_show_channel_mask(struct device *dev, struct device_attribute *attr, char *buf) { - struct usb_device *udev = to_usb_device(dev); - struct usb_interface *intf = usb_ifnum_to_if(udev, 0); + struct usb_interface *intf = to_usb_interface(dev); struct ati_remote2 *ar2 = usb_get_intfdata(intf); return sprintf(buf, "0x%04x\n", ar2->channel_mask); @@ -683,8 +682,7 @@ static ssize_t ati_remote2_store_channel_mask(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { - struct usb_device *udev = to_usb_device(dev); - struct usb_interface *intf = usb_ifnum_to_if(udev, 0); + struct usb_interface *intf = to_usb_interface(dev); struct ati_remote2 *ar2 = usb_get_intfdata(intf); unsigned int mask; int r; @@ -720,8 +718,7 @@ static ssize_t ati_remote2_show_mode_mask(struct device *dev, struct device_attribute *attr, char *buf) { - struct usb_device *udev = to_usb_device(dev); - struct usb_interface *intf = usb_ifnum_to_if(udev, 0); + struct usb_interface *intf = to_usb_interface(dev); struct ati_remote2 *ar2 = usb_get_intfdata(intf); return sprintf(buf, "0x%02x\n", ar2->mode_mask); @@ -731,8 +728,7 @@ static ssize_t ati_remote2_store_mode_mask(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { - struct usb_device *udev = to_usb_device(dev); - struct usb_interface *intf = usb_ifnum_to_if(udev, 0); + struct usb_interface *intf = to_usb_interface(dev); struct ati_remote2 *ar2 = usb_get_intfdata(intf); unsigned int mask; int err; From f9282b8aa85a8c97c50d656db75feb6d76fc7cbe Mon Sep 17 00:00:00 2001 From: Krzysztof Furman Date: Fri, 9 Oct 2026 15:47:10 +0100 Subject: [PATCH 360/367] Input: xpad - add support for Corsair Novablade Pro Add device IDs for the Corsair Novablade Pro leverless fight controller in both wired (0x2b1f) and 2.4 GHz receiver (0x2b2b) modes, and add Corsair's vendor ID to the Xbox 360 vendor match list. Outside of its PS4/PS5 modes the controller uses the Xbox 360 protocol (vendor-specific class, subclass 93, protocol 1) on interface 0, which xpad does not currently bind, so no gamepad device is created. The PS4/PS5 modes present a standard HID gamepad and already work with hid-generic. Tested on 7.2.5 with the 2.4 GHz receiver and wired. Assisted-by: Claude Opus 5.5 Signed-off-by: Krzysztof Furman Link: https://patch.msgid.link/20261009-xpad-corsair-novablade-v1-1-e58746bef99f@proton.me Cc: stable@vger.kernel.org Signed-off-by: Dmitry Torokhov --- drivers/input/joystick/xpad.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/input/joystick/xpad.c b/drivers/input/joystick/xpad.c index 6406ceab988a..9dc350b4321a 100644 --- a/drivers/input/joystick/xpad.c +++ b/drivers/input/joystick/xpad.c @@ -302,6 +302,8 @@ static const struct xpad_device { { 0x17ef, 0x6182, "Lenovo Legion Controller for Windows", 0, XTYPE_XBOX360 }, { 0x1949, 0x041a, "Amazon Game Controller", 0, XTYPE_XBOX360 }, { 0x1a86, 0xe310, "Legion Go S", 0, XTYPE_XBOX360 }, + { 0x1b1c, 0x2b1f, "Corsair Novablade Pro (Wired)", 0, XTYPE_XBOX360 }, + { 0x1b1c, 0x2b2b, "Corsair Novablade Pro (2.4 GHz Receiver)", 0, XTYPE_XBOX360 }, { 0x1bad, 0x0002, "Harmonix Rock Band Guitar", 0, XTYPE_XBOX360 }, { 0x1bad, 0x0003, "Harmonix Rock Band Drumkit", MAP_DPAD_TO_BUTTONS, XTYPE_XBOX360 }, { 0x1bad, 0x0130, "Ion Drum Rocker", MAP_DPAD_TO_BUTTONS, XTYPE_XBOX360 }, @@ -545,6 +547,7 @@ static const struct usb_device_id xpad_table[] = { XPAD_XBOX360_VENDOR(0x17ef), /* Lenovo */ XPAD_XBOX360_VENDOR(0x1949), /* Amazon controllers */ XPAD_XBOX360_VENDOR(0x1a86), /* Nanjing Qinheng Microelectronics (WCH) */ + XPAD_XBOX360_VENDOR(0x1b1c), /* Corsair controllers */ XPAD_XBOX360_VENDOR(0x1bad), /* Harmonix Rock Band guitar and drums */ XPAD_XBOX360_VENDOR(0x1ee9), /* ZOTAC Technology Limited */ XPAD_XBOX360_VENDOR(0x20bc), /* BETOP wireless dongles */ From 41363c27b8509bd4efcba244803edac9ba25d1b5 Mon Sep 17 00:00:00 2001 From: Bihaan Sen Date: Fri, 25 Sep 2026 17:14:38 +0530 Subject: [PATCH 361/367] Input: xpad - add support for 8BitDo Pro 2 Wired Controller for Xbox The 8BitDo Pro 2 Wired Controller for Xbox is already matched by the vendor-wide XPAD_XBOXONE_VENDOR(0x2dc8) entry, but a later hardware revision reports product ID 0x201e, which has no entry in the xpad_device table. The device therefore binds and functions but is exposed to userspace as "Generic X-Box pad". Add the VID/PID pair so the controller is named correctly, and set MAP_SHARE_BUTTON to expose the share button as KEY_RECORD. Signed-off-by: Bihaan Sen Link: https://patch.msgid.link/20260925114459.110253-1-stevewholikesthings@gmail.com Cc: stable@vger.kernel.org Signed-off-by: Dmitry Torokhov --- drivers/input/joystick/xpad.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/input/joystick/xpad.c b/drivers/input/joystick/xpad.c index 9dc350b4321a..810e52782ae8 100644 --- a/drivers/input/joystick/xpad.c +++ b/drivers/input/joystick/xpad.c @@ -381,6 +381,7 @@ static const struct xpad_device { { 0x2993, 0x2001, "TECNO Pocket Go", 0, XTYPE_XBOX360 }, { 0x2dc8, 0x2000, "8BitDo Pro 2 Wired Controller fox Xbox", 0, XTYPE_XBOXONE }, { 0x2dc8, 0x200f, "8BitDo Ultimate 3-mode Controller for Xbox", MAP_SHARE_BUTTON, XTYPE_XBOXONE }, + { 0x2dc8, 0x201e, "8BitDo Pro 2 Wired Controller for Xbox", MAP_SHARE_BUTTON, XTYPE_XBOXONE }, { 0x2dc8, 0x3106, "8BitDo Ultimate Wireless / Pro 2 Wired Controller", 0, XTYPE_XBOX360 }, { 0x2dc8, 0x3109, "8BitDo Ultimate Wireless Bluetooth", 0, XTYPE_XBOX360 }, { 0x2dc8, 0x310a, "8BitDo Ultimate 2C Wireless Controller", 0, XTYPE_XBOX360 }, From d84d6c1a7f1e2de9e601703a326a4c1b7b421943 Mon Sep 17 00:00:00 2001 From: Norbert Szetei Date: Wed, 7 Oct 2026 16:43:22 +0200 Subject: [PATCH 362/367] xfs: stop exchanging reflink flags when exchanging mappings When an exchange covers both files from offset 0 to i_disk_size, xmi_can_exchange_reflink_flags() assumes every mapping moved and hands the reflink flag over to the other file. That assumption is wrong. A file can still own shared blocks past i_disk_size, e.g. after EXCHANGE_RANGE_TO_EOF sets a smaller size without unmapping anything. The flag is cleared anyway, and the next write to such a block happens in place instead of CoW, corrupting the other file that shares it. Commit a23eca88448e ("xfs: fix exchange-range reflink flag clearing issue with INO1_WRITTEN") fixed one instance of this. Rather than add another special case, stop moving the flags. xfs_exchmaps_ensure_reflink() already sets the flag on both inodes before the exchange, so both just keep it. Nothing sets CLEAR_INO{1,2}_REFLINK anymore. Intents logged by older kernels may still carry them; recovery already masks them off, so they stay in the log format mask and are ignored. XFS_IOC_SWAPEXT has the same problem: it moves the reflink flag from one inode to the other along with the extents. Just removing that is not enough here: the exchange path flags both inodes up front in xfs_exchmaps_ensure_reflink(), but swapext has no such step, so the inode receiving the shared extents would be left without the flag. Set it on both inodes instead, and make sure both have a CoW fork, since an inode with the reflink flag is expected to have one. The flag now stays until FALLOC_FL_UNSHARE_RANGE, xfs_scrub, or a truncate to zero removes it. Fixes: 966ceafc7a43 ("xfs: create deferred log items for file mapping exchanges") Fixes: f0bc4d134b46 ("xfs: swap inode reflink flags when swapping inode extents") Cc: stable@vger.kernel.org # v6.10 Assisted-by: LLM Signed-off-by: Norbert Szetei Reviewed-by: Dave Chinner Reviewed-by: Darrick J. Wong Signed-off-by: Carlos Maiolino --- fs/xfs/libxfs/xfs_exchmaps.c | 80 ++-------------------------------- fs/xfs/libxfs/xfs_log_format.h | 4 +- fs/xfs/xfs_bmap_util.c | 15 +++---- 3 files changed, 10 insertions(+), 89 deletions(-) diff --git a/fs/xfs/libxfs/xfs_exchmaps.c b/fs/xfs/libxfs/xfs_exchmaps.c index 6a66b6075e0a..7af8ee35c89d 100644 --- a/fs/xfs/libxfs/xfs_exchmaps.c +++ b/fs/xfs/libxfs/xfs_exchmaps.c @@ -49,7 +49,7 @@ struct xfs_exchmaps_adjacent { .right2 = { .br_startblock = HOLESTARTBLOCK }, \ } -/* Information to reset reflink flag / CoW fork state after an exchange. */ +/* Information to reset CoW fork state after an exchange. */ /* * If the reflink flag is set on either inode, make sure it has an incore CoW @@ -127,9 +127,7 @@ xmi_has_more_exchange_work(const struct xfs_exchmaps_intent *xmi) static inline bool xmi_has_postop_work(const struct xfs_exchmaps_intent *xmi) { - return xmi->xmi_flags & (XFS_EXCHMAPS_CLEAR_INO1_REFLINK | - XFS_EXCHMAPS_CLEAR_INO2_REFLINK | - __XFS_EXCHMAPS_INO2_SHORTFORM); + return xmi->xmi_flags & __XFS_EXCHMAPS_INO2_SHORTFORM; } /* Check all mappings to make sure we can actually exchange them. */ @@ -525,18 +523,6 @@ xfs_exchmaps_link_to_sf( return error; } -/* Clear the reflink flag after an exchange. */ -static inline void -xfs_exchmaps_clear_reflink( - struct xfs_trans *tp, - struct xfs_inode *ip) -{ - trace_xfs_reflink_unset_inode_flag(ip); - - ip->i_diflags2 &= ~XFS_DIFLAG2_REFLINK; - xfs_trans_log_inode(tp, ip, XFS_ILOG_CORE); -} - /* Finish whatever work might come after an exchange operation. */ static int xfs_exchmaps_do_postop_work( @@ -557,16 +543,6 @@ xfs_exchmaps_do_postop_work( return error; } - if (xmi->xmi_flags & XFS_EXCHMAPS_CLEAR_INO1_REFLINK) { - xfs_exchmaps_clear_reflink(tp, xmi->xmi_ip1); - xmi->xmi_flags &= ~XFS_EXCHMAPS_CLEAR_INO1_REFLINK; - } - - if (xmi->xmi_flags & XFS_EXCHMAPS_CLEAR_INO2_REFLINK) { - xfs_exchmaps_clear_reflink(tp, xmi->xmi_ip2); - xmi->xmi_flags &= ~XFS_EXCHMAPS_CLEAR_INO2_REFLINK; - } - return 0; } @@ -948,46 +924,13 @@ xfs_exchmaps_intent_destroy_cache(void) } /* - * Decide if we will exchange the reflink flags between the two files after the - * exchange. The only time we want to do this is if we're exchanging all - * mappings under EOF and the inode reflink flags have different states. + * Allocate and initialize a new incore intent item from a request. */ -static inline bool -xmi_can_exchange_reflink_flags( - const struct xfs_exchmaps_req *req, - unsigned int reflink_state) -{ - 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) - return false; - if (req->blockcount != XFS_B_TO_FSB(mp, req->ip1->i_disk_size)) - return false; - if (req->blockcount != XFS_B_TO_FSB(mp, req->ip2->i_disk_size)) - return false; - return true; -} - - -/* Allocate and initialize a new incore intent item from a request. */ struct xfs_exchmaps_intent * xfs_exchmaps_init_intent( const struct xfs_exchmaps_req *req) { struct xfs_exchmaps_intent *xmi; - unsigned int rs = 0; xmi = kmem_cache_zalloc(xfs_exchmaps_intent_cache, GFP_NOFS | __GFP_NOFAIL); @@ -1011,23 +954,6 @@ xfs_exchmaps_init_intent( xmi->xmi_isize2 = req->ip1->i_disk_size; } - /* Record the state of each inode's reflink flag before the op. */ - if (xfs_is_reflink_inode(req->ip1)) - rs |= 1; - if (xfs_is_reflink_inode(req->ip2)) - rs |= 2; - - /* - * Figure out if we're clearing the reflink flags (which effectively - * exchanges them) after the operation. - */ - if (xmi_can_exchange_reflink_flags(req, rs)) { - if (rs & 1) - xmi->xmi_flags |= XFS_EXCHMAPS_CLEAR_INO1_REFLINK; - if (rs & 2) - xmi->xmi_flags |= XFS_EXCHMAPS_CLEAR_INO2_REFLINK; - } - if (S_ISDIR(VFS_I(xmi->xmi_ip2)->i_mode) || S_ISLNK(VFS_I(xmi->xmi_ip2)->i_mode)) xmi->xmi_flags |= __XFS_EXCHMAPS_INO2_SHORTFORM; diff --git a/fs/xfs/libxfs/xfs_log_format.h b/fs/xfs/libxfs/xfs_log_format.h index a4e1b3eb425c..6e620380d5dc 100644 --- a/fs/xfs/libxfs/xfs_log_format.h +++ b/fs/xfs/libxfs/xfs_log_format.h @@ -927,10 +927,8 @@ struct xfs_xmi_log_format { */ #define XFS_EXCHMAPS_INO1_WRITTEN (1ULL << 2) -/* Clear the reflink flag from inode1 after the operation. */ +/* No longer set, but older intents may carry them; keep them valid. */ #define XFS_EXCHMAPS_CLEAR_INO1_REFLINK (1ULL << 3) - -/* Clear the reflink flag from inode2 after the operation. */ #define XFS_EXCHMAPS_CLEAR_INO2_REFLINK (1ULL << 4) #define XFS_EXCHMAPS_LOGGED_FLAGS (XFS_EXCHMAPS_ATTR_FORK | \ diff --git a/fs/xfs/xfs_bmap_util.c b/fs/xfs/xfs_bmap_util.c index 268d159339d0..865aec3ae5ab 100644 --- a/fs/xfs/xfs_bmap_util.c +++ b/fs/xfs/xfs_bmap_util.c @@ -1544,7 +1544,6 @@ xfs_swap_extents( struct xfs_bstat *sbp = &sxp->sx_stat; int src_log_flags, target_log_flags; int error = 0; - uint64_t f; int resblks = 0; unsigned int flags = 0; struct timespec64 ctime, mtime; @@ -1700,14 +1699,12 @@ xfs_swap_extents( if (error) goto out_trans_cancel; - /* Do we have to swap reflink flags? */ - if ((ip->i_diflags2 & XFS_DIFLAG2_REFLINK) ^ - (tip->i_diflags2 & XFS_DIFLAG2_REFLINK)) { - f = ip->i_diflags2 & XFS_DIFLAG2_REFLINK; - ip->i_diflags2 &= ~XFS_DIFLAG2_REFLINK; - ip->i_diflags2 |= tip->i_diflags2 & XFS_DIFLAG2_REFLINK; - tip->i_diflags2 &= ~XFS_DIFLAG2_REFLINK; - tip->i_diflags2 |= f & XFS_DIFLAG2_REFLINK; + /* Either inode may now own shared blocks; flag both, with CoW forks. */ + if ((ip->i_diflags2 | tip->i_diflags2) & XFS_DIFLAG2_REFLINK) { + ip->i_diflags2 |= XFS_DIFLAG2_REFLINK; + tip->i_diflags2 |= XFS_DIFLAG2_REFLINK; + xfs_ifork_init_cow(ip); + xfs_ifork_init_cow(tip); } /* Swap the cow forks. */ From 7dfb218c829c1acb626e0fe92c5fddb9a26a03e9 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Wed, 7 Oct 2026 10:02:27 -0700 Subject: [PATCH 363/367] xfs: fix exchange-range-to-eof file size exchange Norbert Szetei posted a patch containing an incomplete description of a bug in XFS_IOC_EXCHANGE_RANGE's TO_EOF flag. The bug tricks exchange-range into modifying a file so that it has shared blocks starting beyond EOF, which is never allowed because files never have written data beyond EOF, and you can only share written data blocks. This is key to all the incorrect behavior that follows. Eventually I got from Norbert a description of what's going wrong: > Fair. Here it is with physical block numbers, 4k blocks. peer is the file > that gets rewritten and file1 is the one that ends up with the flag > cleared. > > Start, nothing shared: > > peer size 4096 -> [100] > file1 size 12288 -> [200] [201] [202] > donor1 size 4096 -> [300] > donor2 size 4096 -> [400] > > 1. FICLONERANGE peer's block into file1's middle slot: > > peer size 4096 -> [100] > file1 size 12288 -> [200] [100] [202] reflink flag set > ^^^^^ both files own block 100 > > 2. XFS_IOC_EXCHANGE_RANGE file1 against donor1, file1_offset 0, > file2_offset 8192, length 0, TO_EOF. One block is exchanged, and the sizes > are exchanged with it: > > file1 size 4096 -> [200] [100] [300] reflink flag set > ^^^^^^^^^^^ still owned, now past the size > donor1 size 12288 -> [202] > > file1 says it is one block long while it still owns three. Nothing was > unmapped. file2_offset is block 2, so this exchange cannot clear a flag. > > 3. XFS_IOC_EXCHANGE_RANGE file1 against donor2, both offsets 0, length 4096. > By i_disk_size this is two one-block files exchanging everything, so > xmi_can_exchange_reflink_flags() agrees and the flag is cleared: > > file1 size 4096 -> [400] [100] [300] reflink flag CLEARED > donor2 size 4096 -> [200] > > Block 100 is still shared with peer. > > 4. pwrite(file1, 4096, 4096), which is file1's second slot, block 100. > xfs_is_cow_inode(file1) is false, so no CoW: > > peer size 4096 -> [100] same block, new contents > > Block 100 is the only one that matters here. PoC below, it reads a file > called peer in the current directory. The filesystem needs reflink and > exchange-range, which mkfs.xfs 7.x turns on by default. So yes, TO_EOF is screwing up the file size even if it's actually exchanging the data block correctly. The following is the intended behavior of XFS_EXCHANGE_RANGE_TO_EOF, as described by its manpage: "Ignore the length parameter. All bytes in file1_fd from file1_offset to EOF are moved to file2_fd, and file2's size is set to (file2_offset+(file1_length-file1_offset)). Meanwhile, all bytes in file2 from file2_offset to EOF are moved to file1 and file1's size is set to (file1_offset+(file2_length-file2_offset))." In other words, if we only exchange a portion of the file data, then we should only exchange the portion of the file size that relates to the exchanged data. Unfortunately, the code swaps the ondisk file size and the incore file sizes, which is incorrect if the caller passes in a nonzero offset. Having confused the file sizes, the PoC uses a second exchange-range call on what looks like a non-reflinked single-block file. Because the file size is set incorrectly, exchange-range thinks it's exchanging the full contents of two files and clears the reflink flag on the broken file. That enables an extending write of the broken file to rewrite the shared block that's just past EOF. This has become known colloquially as refluxfs. Once the file sizes are set correctly, the second exchange-range no longer thinks that it's doing a full-contents swap, so it won't clear the reflink flag on either of its file arguments. Norbert followed up to an earlier version of this patch with a second complaint about us not handling the i_disk_size state correctly: > The offsets are bounded against the incore i_size but subtracted from > the ondisk i_disk_size, so a file that has not been written back gives > xmi_isize1 = 0 + (0 - 12288). > > For instance, you can do: > > $ xfs_io -f -c "pwrite -q 0 4096" -c fsync file1 > $ xfs_io -f -c "pwrite -q 0 12288" \ > -c "exchangerange -s 0 -d 12288 file1" dirty > $ stat -c %s file1 > 0 > $ stat -c %i file1 > 131 > $ sudo xfs_io -r -c "bulkstat_single 131" . | grep bs_size > bs_size = 18446744073709539328 That's wrong, and the new file size update logic depends on the ondisk i_disk_size matching the incore i_size, so flush the pagecache as needed to push it upwards. Link: https://lore.kernel.org/linux-xfs/1E196589-DEBE-40AC-AFEA-D420DAAB067F@doyensec.com/ Reported-by: Norbert Szetei Cc: stable@vger.kernel.org # v6.10 Fixes: 966ceafc7a4371 ("xfs: create deferred log items for file mapping exchanges") Signed-off-by: Darrick J. Wong Reviewed-by: Dave Chinner Signed-off-by: Carlos Maiolino --- fs/xfs/libxfs/xfs_exchmaps.c | 13 +++++++-- fs/xfs/xfs_exchrange.c | 51 ++++++++++++++++++++++++++---------- 2 files changed, 48 insertions(+), 16 deletions(-) diff --git a/fs/xfs/libxfs/xfs_exchmaps.c b/fs/xfs/libxfs/xfs_exchmaps.c index 7af8ee35c89d..f527fce46def 100644 --- a/fs/xfs/libxfs/xfs_exchmaps.c +++ b/fs/xfs/libxfs/xfs_exchmaps.c @@ -931,6 +931,7 @@ xfs_exchmaps_init_intent( const struct xfs_exchmaps_req *req) { struct xfs_exchmaps_intent *xmi; + struct xfs_mount *mp = req->ip1->i_mount; xmi = kmem_cache_zalloc(xfs_exchmaps_intent_cache, GFP_NOFS | __GFP_NOFAIL); @@ -948,10 +949,18 @@ xfs_exchmaps_init_intent( return xmi; } + /* + * If the caller wanted, set each file's size to that file's exchange + * offset + length exchanged from the other file because the ranges in + * each file might be different lengths. + */ if (req->flags & XFS_EXCHMAPS_SET_SIZES) { + loff_t off1 = XFS_FSB_TO_B(mp, xmi->xmi_startoff1); + loff_t off2 = XFS_FSB_TO_B(mp, xmi->xmi_startoff2); + xmi->xmi_flags |= XFS_EXCHMAPS_SET_SIZES; - xmi->xmi_isize1 = req->ip2->i_disk_size; - xmi->xmi_isize2 = req->ip1->i_disk_size; + xmi->xmi_isize1 = off1 + (req->ip2->i_disk_size - off2); + xmi->xmi_isize2 = off2 + (req->ip1->i_disk_size - off1); } if (S_ISDIR(VFS_I(xmi->xmi_ip2)->i_mode) || diff --git a/fs/xfs/xfs_exchrange.c b/fs/xfs/xfs_exchrange.c index fafb4e3f065c..935b561dfd3c 100644 --- a/fs/xfs/xfs_exchrange.c +++ b/fs/xfs/xfs_exchrange.c @@ -290,17 +290,20 @@ xfs_exchrange_mappings( goto out_unlock; /* - * If the caller wanted us to exchange the contents of two complete - * files of unequal length, exchange the incore sizes now. This should - * be safe because we flushed both files' page caches, exchanged all - * the mappings, and updated the ondisk sizes. + * If the caller wanted, set each file's size to that file's exchange + * offset + length exchanged from the other file because the ranges in + * each file might be different lengths. This should be safe because + * we flushed both files' page caches, exchanged all the mappings, and + * updated the ondisk sizes. */ if (fxr->flags & XFS_EXCHANGE_RANGE_TO_EOF) { - loff_t temp; + loff_t old_ip1_size = i_size_read(VFS_I(ip1)); + loff_t old_ip2_size = i_size_read(VFS_I(ip2)); - temp = i_size_read(VFS_I(ip2)); - i_size_write(VFS_I(ip2), i_size_read(VFS_I(ip1))); - i_size_write(VFS_I(ip1), temp); + i_size_write(VFS_I(ip2), fxr->file2_offset + + (old_ip1_size - fxr->file1_offset)); + i_size_write(VFS_I(ip1), fxr->file1_offset + + (old_ip2_size - fxr->file2_offset)); } out_unlock: @@ -445,6 +448,30 @@ xfs_exchange_range_checks( return blen == fxr->length ? 0 : -EINVAL; } +/* Flush all relevant dirty pagecache near the range to be exchanged. */ +static inline int +xfs_exchange_range_flush( + struct xfs_inode *ip, + const struct xfs_exchrange *fxr, + loff_t offset) +{ + /* + * If TO_EOF is set, the ondisk file size update logic depends on the + * ondisk file size matching the incore file size. Flush everything. + */ + if (fxr->flags & XFS_EXCHANGE_RANGE_TO_EOF) + return filemap_write_and_wait(VFS_I(ip)->i_mapping); + + /* + * Because we're updating the ondisk mappings, flush all dirty data + * between the ondisk size and the exchange offset to reduce the chance + * of zeroed file contents in that gap after a crash. + */ + return filemap_write_and_wait_range(VFS_I(ip)->i_mapping, + min(offset, ip->i_disk_size), + offset + fxr->length - 1); +} + /* * Check that the two inodes are eligible for range exchanges, the ranges make * sense, and then flush all dirty data. Caller must ensure that the inodes @@ -470,15 +497,11 @@ xfs_exchange_range_prep( if (!same_inode) inode_dio_wait(inode2); - error = filemap_write_and_wait_range(inode1->i_mapping, - fxr->file1_offset, - fxr->file1_offset + fxr->length - 1); + error = xfs_exchange_range_flush(XFS_I(inode1), fxr, fxr->file1_offset); if (error) return error; - error = filemap_write_and_wait_range(inode2->i_mapping, - fxr->file2_offset, - fxr->file2_offset + fxr->length - 1); + error = xfs_exchange_range_flush(XFS_I(inode2), fxr, fxr->file2_offset); if (error) return error; From 3d8cc52db2f86b624308321c58513019f6b21eec Mon Sep 17 00:00:00 2001 From: Nguyen Ngoc Thang Date: Sat, 10 Oct 2026 23:44:49 +0700 Subject: [PATCH 364/367] landlock: Drop __counted_by from landlock_domain.handled_masks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit With clang 22 and CONFIG_FORTIFY_SOURCE, the second landlock_restrict_self() call hits a bogus fortify report in inherit_ruleset(): memcpy: detected buffer overflow: 4 byte write of buffer size 0 Call Trace: __fortify_panic landlock_merge_ruleset __se_sys_landlock_restrict_self handled_masks[] lives in an anonymous struct inside the anonymous union that overlays work_free. For such a flexible array, clang computes the address of the __counted_by counter as the start of the array instead of &num_layers. The bound is then read from handled_masks[0], i.e. from the first layer's access masks, which is not a count. The same layout crashes clang itself in a standalone test. The annotation was added as documentation, so drop it rather than restructure the union. Keep the information in the member comment. Reported-by: syzbot+a0eecb22de4d821a9e5d@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=a0eecb22de4d821a9e5d Fixes: bd3a19800dd1 ("landlock: Add counted_by in landlock_domain") Signed-off-by: Nguyen Ngoc Thang Link: https://lore.kernel.org/r/20261010164450.194439-1-ngocthang2710.1999@gmail.com Reviewed-by: Günther Noack Reported-by: Konrad Dybcio Reported-by: Mike Bommarito Closes: https://lore.kernel.org/r/20261009115108.17819-1-michael.bommarito@gmail.com [mic: Rebased on master, where the array is handled_masks[], updated the fortify report, added the clang bug link and the other reports] Signed-off-by: Mickaël Salaün --- security/landlock/domain.h | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/security/landlock/domain.h b/security/landlock/domain.h index caa3d19d2c43..428c1d9890db 100644 --- a/security/landlock/domain.h +++ b/security/landlock/domain.h @@ -242,9 +242,14 @@ struct landlock_domain { * to properly handle merged rulesets without * overlapping access rights. These layers are set once * and never changed for the lifetime of the domain. + * + * Not annotated with __counted_by(num_layers): clang + * miscomputes the counter address for a FAM nested in + * an anonymous struct within a union, which trips + * FORTIFY: + * https://github.com/llvm/llvm-project/pull/228309 */ - struct access_masks - handled_masks[] __counted_by(num_layers); + struct access_masks handled_masks[]; }; }; }; From 24a19b953e2a3d0ea5a8c4951a0ab8f32720c907 Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Sun, 11 Oct 2026 20:04:55 -0700 Subject: [PATCH 365/367] Linux 7.3-rc7 --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 7c855e6fe544..0dc1a9f4e63e 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ VERSION = 7 PATCHLEVEL = 3 SUBLEVEL = 0 -EXTRAVERSION = -rc6 +EXTRAVERSION = -rc7 NAME = Baby Opossum Posse # *DOCUMENTATION* From 6d222e9806de4bdb68d6624727ba120c4a2c3c08 Mon Sep 17 00:00:00 2001 From: Svyatoslav Ryhel Date: Tue, 13 Oct 2026 21:02:52 +0300 Subject: [PATCH 366/367] ASoC: nvidia,tegra-audio-common: Document nvidia,aux-devs property Add nvidia,aux-devs which can contain a list of phandles pointing to auxiliary devices, such as amplifiers, sound-muxes or dsp, to be added to the sound card. Signed-off-by: Svyatoslav Ryhel Link: https://patch.msgid.link/20261013180253.477605-2-clamor95@gmail.com Signed-off-by: Mark Brown --- .../devicetree/bindings/sound/nvidia,tegra-audio-common.yaml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Documentation/devicetree/bindings/sound/nvidia,tegra-audio-common.yaml b/Documentation/devicetree/bindings/sound/nvidia,tegra-audio-common.yaml index 2588589ad62d..316806fc7346 100644 --- a/Documentation/devicetree/bindings/sound/nvidia,tegra-audio-common.yaml +++ b/Documentation/devicetree/bindings/sound/nvidia,tegra-audio-common.yaml @@ -48,6 +48,11 @@ properties: $ref: /schemas/types.yaml#/definitions/phandle description: The phandle of audio codec + nvidia,aux-devs: + $ref: /schemas/types.yaml#/definitions/phandle-array + description: List of phandles pointing to auxiliary devices, + such as amplifiers or dsp, to be added to the sound card. + nvidia,spkr-en-gpios: maxItems: 1 description: The GPIO that enables the speakers From 3892339e4fedc2d8545fe2b3e8df600179b1a40c Mon Sep 17 00:00:00 2001 From: Svyatoslav Ryhel Date: Tue, 13 Oct 2026 21:02:53 +0300 Subject: [PATCH 367/367] ASoC: tegra: Support auxiliary devices Add support for auxiliary devices, such as amplifiers, sound-muxes or dsp, to be added to the sound card. Signed-off-by: Svyatoslav Ryhel Link: https://patch.msgid.link/20261013180253.477605-3-clamor95@gmail.com Signed-off-by: Mark Brown --- sound/soc/tegra/tegra_asoc_machine.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/sound/soc/tegra/tegra_asoc_machine.c b/sound/soc/tegra/tegra_asoc_machine.c index 50b23bbb637b..e4af049fd7e8 100644 --- a/sound/soc/tegra/tegra_asoc_machine.c +++ b/sound/soc/tegra/tegra_asoc_machine.c @@ -550,6 +550,10 @@ int tegra_asoc_machine_probe(struct platform_device *pdev) if (asoc->add_common_snd_ops) card->dai_link->ops = &tegra_machine_snd_ops; + err = snd_soc_of_parse_aux_devs(card, "nvidia,aux-devs"); + if (err) + return err; + if (!card->owner) card->owner = THIS_MODULE; if (!card->driver_name)