Netdev List
 help / color / mirror / Atom feed
* [PATCH 1/2] arm64: dts: amd: seattle: Remove useless clocks DTSI include
From: Krzysztof Kozlowski @ 2026-07-06  9:35 UTC (permalink / raw)
  To: Suravee Suthikulpanit, Tom Lendacky, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Raju Rangoju,
	Prashanth Kumar K R, Richard Cochran, devicetree, linux-kernel,
	netdev
  Cc: Krzysztof Kozlowski

The "amd-seattle-clks.dtsi" file is included exactly once, so paste the
contents directly in proper DTSI.  No functional impact.

Signed-off-by: Krzysztof Kozlowski <krzysztof.kozlowski@oss.qualcomm.com>
---
 arch/arm64/boot/dts/amd/amd-seattle-clks.dtsi | 43 -------------------
 arch/arm64/boot/dts/amd/amd-seattle-soc.dtsi  | 36 +++++++++++++++-
 2 files changed, 35 insertions(+), 44 deletions(-)
 delete mode 100644 arch/arm64/boot/dts/amd/amd-seattle-clks.dtsi

diff --git a/arch/arm64/boot/dts/amd/amd-seattle-clks.dtsi b/arch/arm64/boot/dts/amd/amd-seattle-clks.dtsi
deleted file mode 100644
index 73f687773ce6..000000000000
--- a/arch/arm64/boot/dts/amd/amd-seattle-clks.dtsi
+++ /dev/null
@@ -1,43 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0
-/*
- * DTS file for AMD Seattle Clocks
- *
- * Copyright (C) 2014 Advanced Micro Devices, Inc.
- */
-
-	adl3clk_100mhz: uartspiclk_100mhz: clock-100000000 {
-		compatible = "fixed-clock";
-		#clock-cells = <0>;
-		clock-frequency = <100000000>;
-		clock-output-names = "adl3clk_100mhz";
-	};
-
-	ccpclk_375mhz: clock-375000000 {
-		compatible = "fixed-clock";
-		#clock-cells = <0>;
-		clock-frequency = <375000000>;
-		clock-output-names = "ccpclk_375mhz";
-	};
-
-	sataclk_333mhz: clock-333000000 {
-		compatible = "fixed-clock";
-		#clock-cells = <0>;
-		clock-frequency = <333000000>;
-		clock-output-names = "sataclk_333mhz";
-	};
-
-	dmaclk_500mhz: pcieclk_500mhz: clock-500000000 {
-		compatible = "fixed-clock";
-		#clock-cells = <0>;
-		clock-frequency = <500000000>;
-		clock-output-names = "pcieclk_500mhz";
-	};
-
-	xgmacclk0_dma_250mhz: xgmacclk0_ptp_250mhz: xgmacclk1_dma_250mhz: xgmacclk1_ptp_250mhz:
-	miscclk_250mhz: clock-250000000 {
-		compatible = "fixed-clock";
-		#clock-cells = <0>;
-		clock-frequency = <250000000>;
-		clock-output-names = "miscclk_250mhz";
-	};
-
diff --git a/arch/arm64/boot/dts/amd/amd-seattle-soc.dtsi b/arch/arm64/boot/dts/amd/amd-seattle-soc.dtsi
index a611f8288b3e..c5b9dbfdf014 100644
--- a/arch/arm64/boot/dts/amd/amd-seattle-soc.dtsi
+++ b/arch/arm64/boot/dts/amd/amd-seattle-soc.dtsi
@@ -11,7 +11,41 @@ / {
 	#address-cells = <2>;
 	#size-cells = <2>;
 
-	/include/ "amd-seattle-clks.dtsi"
+	adl3clk_100mhz: uartspiclk_100mhz: clock-100000000 {
+		compatible = "fixed-clock";
+		#clock-cells = <0>;
+		clock-frequency = <100000000>;
+		clock-output-names = "adl3clk_100mhz";
+	};
+
+	ccpclk_375mhz: clock-375000000 {
+		compatible = "fixed-clock";
+		#clock-cells = <0>;
+		clock-frequency = <375000000>;
+		clock-output-names = "ccpclk_375mhz";
+	};
+
+	sataclk_333mhz: clock-333000000 {
+		compatible = "fixed-clock";
+		#clock-cells = <0>;
+		clock-frequency = <333000000>;
+		clock-output-names = "sataclk_333mhz";
+	};
+
+	dmaclk_500mhz: pcieclk_500mhz: clock-500000000 {
+		compatible = "fixed-clock";
+		#clock-cells = <0>;
+		clock-frequency = <500000000>;
+		clock-output-names = "pcieclk_500mhz";
+	};
+
+	xgmacclk0_dma_250mhz: xgmacclk0_ptp_250mhz: xgmacclk1_dma_250mhz: xgmacclk1_ptp_250mhz:
+	miscclk_250mhz: clock-250000000 {
+		compatible = "fixed-clock";
+		#clock-cells = <0>;
+		clock-frequency = <250000000>;
+		clock-output-names = "miscclk_250mhz";
+	};
 
 	gic0: interrupt-controller@e1101000 {
 		compatible = "arm,gic-400", "arm,cortex-a15-gic";
-- 
2.53.0


^ permalink raw reply related

* [PATCH 2/2] arm64: dts: amd: seattle: Remove useless xgbe DTSI include
From: Krzysztof Kozlowski @ 2026-07-06  9:35 UTC (permalink / raw)
  To: Suravee Suthikulpanit, Tom Lendacky, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Raju Rangoju,
	Prashanth Kumar K R, Richard Cochran, devicetree, linux-kernel,
	netdev
  Cc: Krzysztof Kozlowski
In-Reply-To: <20260706093523.274093-3-krzysztof.kozlowski@oss.qualcomm.com>

The "amd-seattle-xgbe-b.dtsi" file is included exactly once, so paste the
contents directly in proper DTSI.  No functional impact.

Signed-off-by: Krzysztof Kozlowski <krzysztof.kozlowski@oss.qualcomm.com>
---
 .../boot/dts/amd/amd-overdrive-rev-b0.dts     | 78 ++++++++++++++++-
 .../boot/dts/amd/amd-seattle-xgbe-b.dtsi      | 84 -------------------
 2 files changed, 77 insertions(+), 85 deletions(-)
 delete mode 100644 arch/arm64/boot/dts/amd/amd-seattle-xgbe-b.dtsi

diff --git a/arch/arm64/boot/dts/amd/amd-overdrive-rev-b0.dts b/arch/arm64/boot/dts/amd/amd-overdrive-rev-b0.dts
index 8862adae44e9..0860fc7a6927 100644
--- a/arch/arm64/boot/dts/amd/amd-overdrive-rev-b0.dts
+++ b/arch/arm64/boot/dts/amd/amd-overdrive-rev-b0.dts
@@ -71,5 +71,81 @@ sdcard0: mmc@0 {
 };
 
 &smb0 {
-	/include/ "amd-seattle-xgbe-b.dtsi"
+	xgmac0: ethernet@e0700000 {
+		compatible = "amd,xgbe-seattle-v1a";
+		reg = <0 0xe0700000 0 0x80000>,
+		      <0 0xe0780000 0 0x80000>,
+		      <0 0xe1240800 0 0x00400>, /* SERDES RX/TX0 */
+		      <0 0xe1250000 0 0x00060>, /* SERDES IR 1/2 */
+		      <0 0xe12500f8 0 0x00004>; /* SERDES IR 2/2 */
+		interrupts = <0 325 4>,
+			     <0 346 1>, <0 347 1>, <0 348 1>, <0 349 1>,
+			     <0 323 4>;
+		amd,per-channel-interrupt;
+		amd,speed-set = <0>;
+		amd,serdes-blwc = <1>, <1>, <0>;
+		amd,serdes-cdr-rate = <2>, <2>, <7>;
+		amd,serdes-pq-skew = <10>, <10>, <18>;
+		amd,serdes-tx-amp = <0>, <0>, <0>;
+		amd,serdes-dfe-tap-config = <3>, <3>, <3>;
+		amd,serdes-dfe-tap-enable = <0>, <0>, <7>;
+		mac-address = [ 02 A1 A2 A3 A4 A5 ];
+		clocks = <&xgmacclk0_dma_250mhz>, <&xgmacclk0_ptp_250mhz>;
+		clock-names = "dma_clk", "ptp_clk";
+		phy-mode = "xgmii";
+		iommus = <&xgmac0_smmu 0x00 0x17>; /* 0-7, 16-23 */
+		dma-coherent;
+	};
+
+	xgmac1: ethernet@e0900000 {
+		compatible = "amd,xgbe-seattle-v1a";
+		reg = <0 0xe0900000 0 0x80000>,
+		      <0 0xe0980000 0 0x80000>,
+		      <0 0xe1240c00 0 0x00400>, /* SERDES RX/TX1 */
+		      <0 0xe1250080 0 0x00060>, /* SERDES IR 1/2 */
+		      <0 0xe12500fc 0 0x00004>; /* SERDES IR 2/2 */
+		interrupts = <0 324 4>,
+			     <0 341 1>, <0 342 1>, <0 343 1>, <0 344 1>,
+			     <0 322 4>;
+		amd,per-channel-interrupt;
+		amd,speed-set = <0>;
+		amd,serdes-blwc = <1>, <1>, <0>;
+		amd,serdes-cdr-rate = <2>, <2>, <7>;
+		amd,serdes-pq-skew = <10>, <10>, <18>;
+		amd,serdes-tx-amp = <0>, <0>, <0>;
+		amd,serdes-dfe-tap-config = <3>, <3>, <3>;
+		amd,serdes-dfe-tap-enable = <0>, <0>, <7>;
+		mac-address = [ 02 B1 B2 B3 B4 B5 ];
+		clocks = <&xgmacclk1_dma_250mhz>, <&xgmacclk1_ptp_250mhz>;
+		clock-names = "dma_clk", "ptp_clk";
+		phy-mode = "xgmii";
+		iommus = <&xgmac1_smmu 0x00 0x17>; /* 0-7, 16-23 */
+		dma-coherent;
+	};
+
+	xgmac0_smmu: iommu@e0600000 {
+		 compatible = "arm,mmu-401";
+		 reg = <0 0xe0600000 0 0x10000>;
+		 #global-interrupts = <1>;
+		 interrupts = /* Uses combined intr for both
+			       * global and context
+			       */
+			      <0 336 4>,
+			      <0 336 4>;
+		#iommu-cells = <2>;
+		dma-coherent;
+	 };
+
+	 xgmac1_smmu: iommu@e0800000 {
+		 compatible = "arm,mmu-401";
+		 reg = <0 0xe0800000 0 0x10000>;
+		 #global-interrupts = <1>;
+		 interrupts = /* Uses combined intr for both
+			       * global and context
+			       */
+			      <0 335 4>,
+			      <0 335 4>;
+		#iommu-cells = <2>;
+		dma-coherent;
+	 };
 };
diff --git a/arch/arm64/boot/dts/amd/amd-seattle-xgbe-b.dtsi b/arch/arm64/boot/dts/amd/amd-seattle-xgbe-b.dtsi
deleted file mode 100644
index 18b0c2dd1b2d..000000000000
--- a/arch/arm64/boot/dts/amd/amd-seattle-xgbe-b.dtsi
+++ /dev/null
@@ -1,84 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0
-/*
- * DTS file for AMD Seattle XGBE (RevB)
- *
- * Copyright (C) 2015 Advanced Micro Devices, Inc.
- */
-
-	xgmac0: ethernet@e0700000 {
-		compatible = "amd,xgbe-seattle-v1a";
-		reg = <0 0xe0700000 0 0x80000>,
-		      <0 0xe0780000 0 0x80000>,
-		      <0 0xe1240800 0 0x00400>, /* SERDES RX/TX0 */
-		      <0 0xe1250000 0 0x00060>, /* SERDES IR 1/2 */
-		      <0 0xe12500f8 0 0x00004>; /* SERDES IR 2/2 */
-		interrupts = <0 325 4>,
-			     <0 346 1>, <0 347 1>, <0 348 1>, <0 349 1>,
-			     <0 323 4>;
-		amd,per-channel-interrupt;
-		amd,speed-set = <0>;
-		amd,serdes-blwc = <1>, <1>, <0>;
-		amd,serdes-cdr-rate = <2>, <2>, <7>;
-		amd,serdes-pq-skew = <10>, <10>, <18>;
-		amd,serdes-tx-amp = <0>, <0>, <0>;
-		amd,serdes-dfe-tap-config = <3>, <3>, <3>;
-		amd,serdes-dfe-tap-enable = <0>, <0>, <7>;
-		mac-address = [ 02 A1 A2 A3 A4 A5 ];
-		clocks = <&xgmacclk0_dma_250mhz>, <&xgmacclk0_ptp_250mhz>;
-		clock-names = "dma_clk", "ptp_clk";
-		phy-mode = "xgmii";
-		iommus = <&xgmac0_smmu 0x00 0x17>; /* 0-7, 16-23 */
-		dma-coherent;
-	};
-
-	xgmac1: ethernet@e0900000 {
-		compatible = "amd,xgbe-seattle-v1a";
-		reg = <0 0xe0900000 0 0x80000>,
-		      <0 0xe0980000 0 0x80000>,
-		      <0 0xe1240c00 0 0x00400>, /* SERDES RX/TX1 */
-		      <0 0xe1250080 0 0x00060>, /* SERDES IR 1/2 */
-		      <0 0xe12500fc 0 0x00004>; /* SERDES IR 2/2 */
-		interrupts = <0 324 4>,
-			     <0 341 1>, <0 342 1>, <0 343 1>, <0 344 1>,
-			     <0 322 4>;
-		amd,per-channel-interrupt;
-		amd,speed-set = <0>;
-		amd,serdes-blwc = <1>, <1>, <0>;
-		amd,serdes-cdr-rate = <2>, <2>, <7>;
-		amd,serdes-pq-skew = <10>, <10>, <18>;
-		amd,serdes-tx-amp = <0>, <0>, <0>;
-		amd,serdes-dfe-tap-config = <3>, <3>, <3>;
-		amd,serdes-dfe-tap-enable = <0>, <0>, <7>;
-		mac-address = [ 02 B1 B2 B3 B4 B5 ];
-		clocks = <&xgmacclk1_dma_250mhz>, <&xgmacclk1_ptp_250mhz>;
-		clock-names = "dma_clk", "ptp_clk";
-		phy-mode = "xgmii";
-		iommus = <&xgmac1_smmu 0x00 0x17>; /* 0-7, 16-23 */
-		dma-coherent;
-	};
-
-	xgmac0_smmu: iommu@e0600000 {
-		 compatible = "arm,mmu-401";
-		 reg = <0 0xe0600000 0 0x10000>;
-		 #global-interrupts = <1>;
-		 interrupts = /* Uses combined intr for both
-			       * global and context
-			       */
-			      <0 336 4>,
-			      <0 336 4>;
-		#iommu-cells = <2>;
-		dma-coherent;
-	 };
-
-	 xgmac1_smmu: iommu@e0800000 {
-		 compatible = "arm,mmu-401";
-		 reg = <0 0xe0800000 0 0x10000>;
-		 #global-interrupts = <1>;
-		 interrupts = /* Uses combined intr for both
-			       * global and context
-			       */
-			      <0 335 4>,
-			      <0 335 4>;
-		#iommu-cells = <2>;
-		dma-coherent;
-	 };
-- 
2.53.0


^ permalink raw reply related

* [PATCH bpf-next 0/6] Introduce bpf_ksock
From: Mahe Tardy @ 2026-07-06  9:35 UTC (permalink / raw)
  To: bpf
  Cc: andrew+netdev, andrii, ast, daniel, davem, eddyz87, edumazet,
	john.fastabend, kuba, liamwisehart, martin.lau, pabeni, song,
	netdev, Mahe Tardy

This patch series introduces bpf_ksock, a set of BPF kfuncs to allow BPF
programs to create UDP sockets and send data. This provides a mechanism
for BPF LSM progs to emit telemetry over UDP independently of userspace.

The main use case is to be able to completely dispense with
agents/daemons for BPF programs after startup. In the case of
Isovalent's Tetragon, the idea would be to be able to emit security
alerts or export data from BPF even when the agent is down. For meta,
according to Liam presentation[^2], this could replace logging via
ringbuffers which created cross-binary versioning issues.

The implementation follows the established kfunc lifecycle pattern
(create/acquire/release with refcounting, kptr map storage, dtor
registration), for example used by the network bpf_crypto kfuncs.

For reference, this was discussed at LSF/MM/BPF 2025[^1] in Montreal,
again at Plumbers 2025 in Tokyo. Liam Wisehart mentioned this work
during his presentation of BpfJailer[^2]. Then it was also discussed
during LSF/MM/BPF 2026 in Zagreb.

A first version of it, called bpf_netpoll was submitted to the mailing
list but eventually NACKED by Jakub Kicinski[^3]. The discussion
eventually reached an agreement that we should use regular kernel
sockets if we want to do network from BPF programs[^4]. This was
fundamentally more complex to implement but here is a first proposition
of how it could look like after several automated reviews using sashiko.

For more details, here are some of the main adjustements I had to make
during the preparation of these patches:

Initially, the goal was to register the ksock_kfunc_set with
BPF_PROG_TYPE_UNSPEC to allow send to be called from any programs. This
introduces significant challenges (but might be doable). The limitation
is still that the programs should be able to sleep but combining this
with bpf workqueue allows to send from virtually anywhere. However, not
by-passing LSM socket hooks make it impossible to be called from the
workqueue context as the credential of the initial caller would not be
preserved. Also, it would be easy for users to shoot themselves in the
foot and attach a program that sends asynchronously over the network on
a network hook. So the idea for now is to restrict the ksock_kfunc_set
(which is acquire, release and send) to SYSCALL and LSM to make it
simpler. Also to make the patch set easier to start with, the sockets
are restricted to UDP.

v0 updates (from local sashiko iterations):
- do not bypass the LSM and thus add send re-enter protection;
- limit the number of socket creation through the kfunc per ns;
- copy the arg values to avoid TOCTOU race since kfunc can sleep;
- prevent calling bpf_ksock_create from workqueue with improper creds.

[^1]: https://lwn.net/Articles/1022034/
[^2]: https://lpc.events/event/19/contributions/2159/
[^3]: https://lore.kernel.org/bpf/20260511182019.69ebc7c6@kernel.org/
[^4]: https://lore.kernel.org/bpf/CAPhsuW71P58XqsXrLbqsShgnozg66TA=T_c=fYrqSSzvL1tTWA@mail.gmail.com/

Mahe Tardy (6):
  net: Add __sys_connect_socket() helper
  bpf: Add ksock kfuncs
  selftests/bpf: Add ksock kfunc test
  selftests/bpf: Add ksock LSM recursion test
  selftests/bpf: Add ksock net ns quota tests
  selftests/bpf: Add ksock test for async callback guard

 Documentation/admin-guide/sysctl/net.rst      |  12 +
 include/linux/bpf_ksock.h                     |  50 ++
 include/linux/socket.h                        |   2 +
 kernel/bpf/verifier.c                         |   3 +
 net/core/Makefile                             |   3 +
 net/core/bpf_ksock.c                          | 516 ++++++++++++++++++
 net/core/sysctl_net_core.c                    |  11 +
 net/socket.c                                  |  32 +-
 .../testing/selftests/bpf/prog_tests/ksock.c  | 239 ++++++++
 .../selftests/bpf/prog_tests/ksock_quota.c    | 139 +++++
 .../selftests/bpf/prog_tests/ksock_wq.c       |  34 ++
 .../testing/selftests/bpf/progs/ksock_basic.c |  37 ++
 .../selftests/bpf/progs/ksock_common.h        | 110 ++++
 .../testing/selftests/bpf/progs/ksock_quota.c | 167 ++++++
 .../selftests/bpf/progs/ksock_recursion.c     |  69 +++
 tools/testing/selftests/bpf/progs/ksock_wq.c  |  62 +++
 16 files changed, 1472 insertions(+), 14 deletions(-)
 create mode 100644 include/linux/bpf_ksock.h
 create mode 100644 net/core/bpf_ksock.c
 create mode 100644 tools/testing/selftests/bpf/prog_tests/ksock.c
 create mode 100644 tools/testing/selftests/bpf/prog_tests/ksock_quota.c
 create mode 100644 tools/testing/selftests/bpf/prog_tests/ksock_wq.c
 create mode 100644 tools/testing/selftests/bpf/progs/ksock_basic.c
 create mode 100644 tools/testing/selftests/bpf/progs/ksock_common.h
 create mode 100644 tools/testing/selftests/bpf/progs/ksock_quota.c
 create mode 100644 tools/testing/selftests/bpf/progs/ksock_recursion.c
 create mode 100644 tools/testing/selftests/bpf/progs/ksock_wq.c

--
2.34.1


^ permalink raw reply

* [PATCH bpf-next 1/6] net: Add __sys_connect_socket() helper
From: Mahe Tardy @ 2026-07-06  9:35 UTC (permalink / raw)
  To: bpf
  Cc: andrew+netdev, andrii, ast, daniel, davem, eddyz87, edumazet,
	john.fastabend, kuba, liamwisehart, martin.lau, pabeni, song,
	netdev, Mahe Tardy
In-Reply-To: <20260706093525.13030-1-mahe.tardy@gmail.com>

Add a helper that connects an existing socket while invoking the LSM
hook. Reuse it in __sys_connect_file() to avoid duplicating the connect
logic. Other socket operations have equivalent helpers that trigger the
appropriate LSM hooks that can be reused, this one was the only one
missing.

Signed-off-by: Mahe Tardy <mahe.tardy@gmail.com>
---
 include/linux/socket.h |  2 ++
 net/socket.c           | 32 ++++++++++++++++++--------------
 2 files changed, 20 insertions(+), 14 deletions(-)

diff --git a/include/linux/socket.h b/include/linux/socket.h
index 2a8d7b14f1d1..48f1eb8193de 100644
--- a/include/linux/socket.h
+++ b/include/linux/socket.h
@@ -461,6 +461,8 @@ extern struct file *__sys_socket_file(int family, int type, int protocol);
 extern int __sys_bind(int fd, struct sockaddr __user *umyaddr, int addrlen);
 extern int __sys_bind_socket(struct socket *sock, struct sockaddr_storage *address,
 			     int addrlen);
+extern int __sys_connect_socket(struct socket *sock, struct sockaddr_storage *addr,
+				int addrlen, int flags);
 extern int __sys_connect_file(struct file *file, struct sockaddr_storage *addr,
 			      int addrlen, int file_flags);
 extern int __sys_connect(int fd, struct sockaddr __user *uservaddr,
diff --git a/net/socket.c b/net/socket.c
index 63c69a0fa74e..c7427dd4dd52 100644
--- a/net/socket.c
+++ b/net/socket.c
@@ -2115,27 +2115,31 @@ SYSCALL_DEFINE3(accept, int, fd, struct sockaddr __user *, upeer_sockaddr,
  *	include the -EINPROGRESS status for such sockets.
  */

+int __sys_connect_socket(struct socket *sock, struct sockaddr_storage *address,
+			 int addrlen, int flags)
+{
+	int err;
+
+	err = security_socket_connect(sock, (struct sockaddr *)address, addrlen);
+	if (err)
+		return err;
+
+	return READ_ONCE(sock->ops)->connect(sock,
+				    (struct sockaddr_unsized *)address,
+				    addrlen, flags);
+}
+
 int __sys_connect_file(struct file *file, struct sockaddr_storage *address,
 		       int addrlen, int file_flags)
 {
 	struct socket *sock;
-	int err;

 	sock = sock_from_file(file);
-	if (!sock) {
-		err = -ENOTSOCK;
-		goto out;
-	}
-
-	err =
-	    security_socket_connect(sock, (struct sockaddr *)address, addrlen);
-	if (err)
-		goto out;
+	if (!sock)
+		return -ENOTSOCK;

-	err = READ_ONCE(sock->ops)->connect(sock, (struct sockaddr_unsized *)address,
-					    addrlen, sock->file->f_flags | file_flags);
-out:
-	return err;
+	return __sys_connect_socket(sock, address, addrlen,
+				    sock->file->f_flags | file_flags);
 }

 int __sys_connect(int fd, struct sockaddr __user *uservaddr, int addrlen)
--
2.34.1


^ permalink raw reply related

* [PATCH bpf-next 4/6] selftests/bpf: Add ksock LSM recursion test
From: Mahe Tardy @ 2026-07-06  9:35 UTC (permalink / raw)
  To: bpf
  Cc: andrew+netdev, andrii, ast, daniel, davem, eddyz87, edumazet,
	john.fastabend, kuba, liamwisehart, martin.lau, pabeni, song,
	netdev, Mahe Tardy
In-Reply-To: <20260706093525.13030-1-mahe.tardy@gmail.com>

The bpf_ksock_send() kfunc triggers the security_socket_sendmsg() LSM
hook via kernel_sendmsg(), thus attaching an LSM program triggering that
kfunc on the same hook would provoke recursion. This test exercises that
path and make sure that recursion is guarded and prevented.

Signed-off-by: Mahe Tardy <mahe.tardy@gmail.com>
---
 .../testing/selftests/bpf/prog_tests/ksock.c  | 84 +++++++++++++++++--
 .../testing/selftests/bpf/progs/ksock_basic.c | 33 +-------
 .../selftests/bpf/progs/ksock_common.h        | 37 ++++++++
 .../selftests/bpf/progs/ksock_recursion.c     | 69 +++++++++++++++
 4 files changed, 186 insertions(+), 37 deletions(-)
 create mode 100644 tools/testing/selftests/bpf/progs/ksock_recursion.c

diff --git a/tools/testing/selftests/bpf/prog_tests/ksock.c b/tools/testing/selftests/bpf/prog_tests/ksock.c
index 085ddb59067e..8b14fd98d410 100644
--- a/tools/testing/selftests/bpf/prog_tests/ksock.c
+++ b/tools/testing/selftests/bpf/prog_tests/ksock.c
@@ -6,8 +6,10 @@
 #include "test_progs.h"
 #include "network_helpers.h"
 #include "ksock_basic.skel.h"
+#include "ksock_recursion.skel.h"

 #define NS_TEST "ksock_basic_ns"
+#define NS_LSM_RECURSION_TEST "ksock_lsm_recursion_ns"
 #define LOOPBACK_IP "127.0.0.1"
 #define RECV_PORT 7777
 #define RECV_TIMEOUT_SEC 5
@@ -98,15 +100,16 @@ static void ksock_assert_recv(struct ksock_test_env *env, const char *data,
 	ASSERT_MEMEQ(env->buf, data, data_sz, "payload match");
 }

-static bool ksock_setup_ctx(struct ksock_basic *skel)
+static bool ksock_setup_ctx(struct bpf_program *prog, __be32 *ipv4_remote,
+			    __u16 *remote_port)
 {
 	LIBBPF_OPTS(bpf_test_run_opts, opts);
 	int err, pfd;

-	skel->bss->ipv4_remote = inet_addr(LOOPBACK_IP);
-	skel->bss->remote_port = RECV_PORT;
+	*ipv4_remote = inet_addr(LOOPBACK_IP);
+	*remote_port = RECV_PORT;

-	pfd = bpf_program__fd(skel->progs.ksock_setup);
+	pfd = bpf_program__fd(prog);
 	if (!ASSERT_OK_FD(pfd, "ksock_setup fd"))
 		return false;

@@ -140,7 +143,9 @@ void test_ksock_basic(void)
 		goto fail;

 	/* Step 1: Run the setup SYSCALL prog to create ksock */
-	if (!ksock_setup_ctx(skel))
+	if (!ksock_setup_ctx(skel->progs.ksock_setup,
+			     &skel->bss->ipv4_remote,
+			     &skel->bss->remote_port))
 		goto fail;

 	/* Step 2: Run the send SYSCALL prog */
@@ -163,3 +168,72 @@ void test_ksock_basic(void)
 	ksock_test_env_cleanup(&env);
 	ksock_basic__destroy(skel);
 }
+
+void test_ksock_lsm_recursion(void)
+{
+	struct ksock_test_env env;
+	struct ksock_recursion *skel;
+	char trigger = 'x';
+	int tfd = -1;
+	int err;
+	ssize_t n;
+
+	skel = ksock_recursion__open();
+	if (!ASSERT_OK_PTR(skel, "skel open"))
+		return;
+
+	err = ksock_recursion__load(skel);
+	if (!ASSERT_OK(err, "skel load")) {
+		ksock_recursion__destroy(skel);
+		return;
+	}
+
+	if (!ksock_test_env_setup(&env, NS_LSM_RECURSION_TEST))
+		goto fail;
+
+	/* Step 1: Run the setup SYSCALL prog to create the ksock */
+	if (!ksock_setup_ctx(skel->progs.ksock_setup,
+			     &skel->bss->ipv4_remote,
+			     &skel->bss->remote_port))
+		goto fail;
+
+	/* Step 2: Attach LSM prog and trigger socket_sendmsg from userspace */
+	skel->links.ksock_socket_sendmsg =
+		bpf_program__attach_lsm(skel->progs.ksock_socket_sendmsg);
+	if (!ASSERT_OK_PTR(skel->links.ksock_socket_sendmsg,
+			   "attach socket_sendmsg lsm"))
+		goto fail;
+
+	tfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
+	if (!ASSERT_OK_FD(tfd, "trigger socket"))
+		goto fail;
+
+	skel->bss->target_pid = getpid();
+	skel->bss->trigger_send = 1;
+	n = sendto(tfd, &trigger, sizeof(trigger), 0,
+		   (struct sockaddr *)&env.addr, sizeof(env.addr));
+	skel->bss->target_pid = 0;
+	skel->bss->trigger_send = 0;
+	if (!ASSERT_EQ(n, sizeof(trigger), "trigger sendto"))
+		goto fail;
+
+	/* Step 3: The nested bpf_ksock_send() must hit the recursion guard */
+	if (!ASSERT_EQ(skel->bss->rec_count, 2,
+		       "socket_sendmsg recursion count"))
+		goto fail;
+	if (!ASSERT_EQ(skel->data->rec_kfunc_rets[0], -EBUSY,
+		       "recursive send status"))
+		goto fail;
+	if (!ASSERT_EQ(skel->data->rec_kfunc_rets[1],
+		       sizeof(skel->data->send_data), "outer send bytes"))
+		goto fail;
+
+	ksock_assert_recv(&env, skel->data->send_data,
+			  sizeof(skel->data->send_data));
+
+fail:
+	if (tfd >= 0)
+		close(tfd);
+	ksock_test_env_cleanup(&env);
+	ksock_recursion__destroy(skel);
+}
diff --git a/tools/testing/selftests/bpf/progs/ksock_basic.c b/tools/testing/selftests/bpf/progs/ksock_basic.c
index e8131932d97b..189b0fc15770 100644
--- a/tools/testing/selftests/bpf/progs/ksock_basic.c
+++ b/tools/testing/selftests/bpf/progs/ksock_basic.c
@@ -8,41 +8,10 @@
 #include "bpf_tracing_net.h"
 #include "ksock_common.h"

-__be32 ipv4_remote;
-__u16 remote_port;
-
-char send_data[32] = "hello from bpf ksock";
-
 SEC("syscall")
 int ksock_setup(void *ctx)
 {
-	struct bpf_ksock_create_opts create_opts = {};
-	struct bpf_ksock_addr_opts addr_opts = {};
-	struct bpf_ksock *ks;
-	int err = 0;
-
-	create_opts.family = AF_INET;
-	create_opts.type = SOCK_DGRAM;
-	create_opts.protocol = IPPROTO_UDP;
-
-	ks = bpf_ksock_create(&create_opts, sizeof(create_opts), &err);
-	if (!ks)
-		return err;
-
-	addr_opts.family = AF_INET;
-	addr_opts.port = remote_port;
-	addr_opts.ipv4_addr = ipv4_remote;
-
-	err = bpf_ksock_connect(ks, &addr_opts, sizeof(addr_opts));
-	if (err) {
-		bpf_ksock_release(ks);
-		return err;
-	}
-
-	err = ksock_ctx_insert(ks);
-	if (err && err != -EEXIST)
-		return err;
-	return 0;
+	return do_ksock_setup();
 }

 SEC("syscall")
diff --git a/tools/testing/selftests/bpf/progs/ksock_common.h b/tools/testing/selftests/bpf/progs/ksock_common.h
index 87d92372d28d..7cf64319b901 100644
--- a/tools/testing/selftests/bpf/progs/ksock_common.h
+++ b/tools/testing/selftests/bpf/progs/ksock_common.h
@@ -70,4 +70,41 @@ static inline int ksock_ctx_insert(struct bpf_ksock *ctx)
 	return 0;
 }

+/* Globals for passing config from userspace */
+__be32 ipv4_remote;
+__u16 remote_port;
+
+char send_data[32] = "hello from bpf ksock";
+
+static inline int do_ksock_setup(void)
+{
+	struct bpf_ksock_create_opts create_opts = {};
+	struct bpf_ksock_addr_opts addr_opts = {};
+	struct bpf_ksock *ks;
+	int err = 0;
+
+	create_opts.family = AF_INET;
+	create_opts.type = SOCK_DGRAM;
+	create_opts.protocol = IPPROTO_UDP;
+
+	ks = bpf_ksock_create(&create_opts, sizeof(create_opts), &err);
+	if (!ks)
+		return err;
+
+	addr_opts.family = AF_INET;
+	addr_opts.port = remote_port;
+	addr_opts.ipv4_addr = ipv4_remote;
+
+	err = bpf_ksock_connect(ks, &addr_opts, sizeof(addr_opts));
+	if (err) {
+		bpf_ksock_release(ks);
+		return err;
+	}
+
+	err = ksock_ctx_insert(ks);
+	if (err && err != -EEXIST)
+		return err;
+	return 0;
+}
+
 #endif /* _KSOCK_COMMON_H */
diff --git a/tools/testing/selftests/bpf/progs/ksock_recursion.c b/tools/testing/selftests/bpf/progs/ksock_recursion.c
new file mode 100644
index 000000000000..52f5200943e3
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/ksock_recursion.c
@@ -0,0 +1,69 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2026 Isovalent */
+
+#include "vmlinux.h"
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_tracing.h>
+#include <bpf/bpf_endian.h>
+#include "bpf_tracing_net.h"
+#include "ksock_common.h"
+
+void bpf_rcu_read_lock(void) __ksym;
+void bpf_rcu_read_unlock(void) __ksym;
+
+int target_pid;
+int trigger_send;
+
+unsigned int rec_count;
+int rec_kfunc_rets[] = { -1, -1 };
+
+SEC("syscall")
+int ksock_setup(void *ctx)
+{
+	return do_ksock_setup();
+}
+
+SEC("lsm.s/socket_sendmsg")
+int BPF_PROG(ksock_socket_sendmsg, struct socket *sock, struct msghdr *msg,
+	     int size, int ret)
+{
+	struct __ksock_ctx_value *v;
+	struct bpf_ksock *ks, *tmp;
+	u32 pid = bpf_get_current_pid_tgid() >> 32;
+	int kfunc_ret;
+
+	if (ret || !trigger_send || pid != target_pid)
+		return ret;
+
+	v = ksock_ctx_value_lookup();
+	if (!v) {
+		kfunc_ret = -ENOENT;
+		goto out;
+	}
+
+	ks = NULL;
+	bpf_rcu_read_lock();
+	tmp = v->ctx;
+	if (tmp)
+		ks = bpf_ksock_acquire(tmp);
+	bpf_rcu_read_unlock();
+
+	if (!ks) {
+		kfunc_ret = -ENOENT;
+		goto out;
+	}
+
+	kfunc_ret = bpf_ksock_send(ks, send_data, sizeof(send_data));
+	bpf_ksock_release(ks);
+
+out:
+	rec_kfunc_rets[rec_count & 1] = kfunc_ret;
+	__sync_fetch_and_add(&rec_count, 1);
+
+	if (kfunc_ret != -EBUSY)
+		trigger_send = 0;
+
+	return ret;
+}
+
+char __license[] SEC("license") = "GPL";
--
2.34.1


^ permalink raw reply related

* [PATCH bpf-next 3/6] selftests/bpf: Add ksock kfunc test
From: Mahe Tardy @ 2026-07-06  9:35 UTC (permalink / raw)
  To: bpf
  Cc: andrew+netdev, andrii, ast, daniel, davem, eddyz87, edumazet,
	john.fastabend, kuba, liamwisehart, martin.lau, pabeni, song,
	netdev, Mahe Tardy
In-Reply-To: <20260706093525.13030-1-mahe.tardy@gmail.com>

Add a selftest that exercises the ksock kfuncs end-to-end. One sycall
bpf setup program creates a ksock context and connect the socket.
Another syscall bpf program lookup the context and send test data.
The userspace harness create a network namespace and a new socket on
loopback, run the setup and send syscall bpf progs then check that the
userspace socket received the data from bpf.

Signed-off-by: Mahe Tardy <mahe.tardy@gmail.com>
---
 .../testing/selftests/bpf/prog_tests/ksock.c  | 165 ++++++++++++++++++
 .../testing/selftests/bpf/progs/ksock_basic.c |  68 ++++++++
 .../selftests/bpf/progs/ksock_common.h        |  73 ++++++++
 3 files changed, 306 insertions(+)
 create mode 100644 tools/testing/selftests/bpf/prog_tests/ksock.c
 create mode 100644 tools/testing/selftests/bpf/progs/ksock_basic.c
 create mode 100644 tools/testing/selftests/bpf/progs/ksock_common.h

diff --git a/tools/testing/selftests/bpf/prog_tests/ksock.c b/tools/testing/selftests/bpf/prog_tests/ksock.c
new file mode 100644
index 000000000000..085ddb59067e
--- /dev/null
+++ b/tools/testing/selftests/bpf/prog_tests/ksock.c
@@ -0,0 +1,165 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2026 Isovalent */
+
+#include <arpa/inet.h>
+
+#include "test_progs.h"
+#include "network_helpers.h"
+#include "ksock_basic.skel.h"
+
+#define NS_TEST "ksock_basic_ns"
+#define LOOPBACK_IP "127.0.0.1"
+#define RECV_PORT 7777
+#define RECV_TIMEOUT_SEC 5
+
+struct ksock_test_env {
+	const char *netns;
+	bool netns_created;
+	struct nstoken *nstoken;
+	struct sockaddr_in addr;
+	int rfd;
+	char buf[32];
+};
+
+static bool ksock_test_env_setup(struct ksock_test_env *env, const char *netns)
+{
+	struct timeval tv;
+	int err;
+
+	memset(env, 0, sizeof(*env));
+	env->netns = netns;
+	env->rfd = -1;
+
+	SYS(fail, "ip netns add %s", netns);
+	env->netns_created = true;
+	SYS(fail, "ip -net %s link set lo up", netns);
+
+	env->nstoken = open_netns(netns);
+	if (!ASSERT_OK_PTR(env->nstoken, "open_netns"))
+		goto fail;
+
+	env->rfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
+	if (!ASSERT_OK_FD(env->rfd, "receiver socket"))
+		goto fail;
+
+	env->addr.sin_family = AF_INET;
+	env->addr.sin_addr.s_addr = inet_addr(LOOPBACK_IP);
+	env->addr.sin_port = htons(RECV_PORT);
+
+	err = bind(env->rfd, (struct sockaddr *)&env->addr, sizeof(env->addr));
+	if (!ASSERT_OK(err, "bind receiver"))
+		goto fail;
+
+	tv.tv_sec = RECV_TIMEOUT_SEC;
+	tv.tv_usec = 0;
+	err = setsockopt(env->rfd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
+	if (!ASSERT_OK(err, "set rcvtimeo"))
+		goto fail;
+
+	return true;
+
+fail:
+	if (env->rfd >= 0) {
+		close(env->rfd);
+		env->rfd = -1;
+	}
+	if (env->nstoken) {
+		close_netns(env->nstoken);
+		env->nstoken = NULL;
+	}
+	if (env->netns_created) {
+		SYS_NOFAIL("ip netns del %s >/dev/null 2>&1", netns);
+		env->netns_created = false;
+	}
+	return false;
+}
+
+static void ksock_test_env_cleanup(struct ksock_test_env *env)
+{
+	if (env->rfd >= 0)
+		close(env->rfd);
+	if (env->nstoken)
+		close_netns(env->nstoken);
+	if (env->netns_created) {
+		SYS_NOFAIL("ip netns del %s >/dev/null 2>&1", env->netns);
+		env->netns_created = false;
+	}
+}
+
+static void ksock_assert_recv(struct ksock_test_env *env, const char *data,
+			      size_t data_sz)
+{
+	ssize_t n;
+
+	memset(env->buf, 0, sizeof(env->buf));
+	n = recvfrom(env->rfd, env->buf, sizeof(env->buf), 0, NULL, NULL);
+	if (!ASSERT_EQ(n, data_sz, "recvfrom len"))
+		return;
+	ASSERT_MEMEQ(env->buf, data, data_sz, "payload match");
+}
+
+static bool ksock_setup_ctx(struct ksock_basic *skel)
+{
+	LIBBPF_OPTS(bpf_test_run_opts, opts);
+	int err, pfd;
+
+	skel->bss->ipv4_remote = inet_addr(LOOPBACK_IP);
+	skel->bss->remote_port = RECV_PORT;
+
+	pfd = bpf_program__fd(skel->progs.ksock_setup);
+	if (!ASSERT_OK_FD(pfd, "ksock_setup fd"))
+		return false;
+
+	err = bpf_prog_test_run_opts(pfd, &opts);
+	if (!ASSERT_OK(err, "ksock_setup run"))
+		return false;
+	if (!ASSERT_OK(opts.retval, "ksock_setup retval"))
+		return false;
+
+	return true;
+}
+
+void test_ksock_basic(void)
+{
+	LIBBPF_OPTS(bpf_test_run_opts, opts);
+	struct ksock_test_env env;
+	struct ksock_basic *skel;
+	int err, pfd;
+
+	skel = ksock_basic__open();
+	if (!ASSERT_OK_PTR(skel, "skel open"))
+		return;
+
+	err = ksock_basic__load(skel);
+	if (!ASSERT_OK(err, "skel load")) {
+		ksock_basic__destroy(skel);
+		return;
+	}
+
+	if (!ksock_test_env_setup(&env, NS_TEST))
+		goto fail;
+
+	/* Step 1: Run the setup SYSCALL prog to create ksock */
+	if (!ksock_setup_ctx(skel))
+		goto fail;
+
+	/* Step 2: Run the send SYSCALL prog */
+	pfd = bpf_program__fd(skel->progs.ksock_send);
+	if (!ASSERT_OK_FD(pfd, "ksock_send fd"))
+		goto fail;
+
+	err = bpf_prog_test_run_opts(pfd, &opts);
+	if (!ASSERT_OK(err, "ksock_send run"))
+		goto fail;
+	if (!ASSERT_EQ(opts.retval,
+		       sizeof(skel->data->send_data), "sendmsg bytes"))
+		goto fail;
+
+	/* Step 3: Receive and verify the data */
+	ksock_assert_recv(&env, skel->data->send_data,
+			  sizeof(skel->data->send_data));
+
+fail:
+	ksock_test_env_cleanup(&env);
+	ksock_basic__destroy(skel);
+}
diff --git a/tools/testing/selftests/bpf/progs/ksock_basic.c b/tools/testing/selftests/bpf/progs/ksock_basic.c
new file mode 100644
index 000000000000..e8131932d97b
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/ksock_basic.c
@@ -0,0 +1,68 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2026 Isovalent */
+
+#include "vmlinux.h"
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_tracing.h>
+#include <bpf/bpf_endian.h>
+#include "bpf_tracing_net.h"
+#include "ksock_common.h"
+
+__be32 ipv4_remote;
+__u16 remote_port;
+
+char send_data[32] = "hello from bpf ksock";
+
+SEC("syscall")
+int ksock_setup(void *ctx)
+{
+	struct bpf_ksock_create_opts create_opts = {};
+	struct bpf_ksock_addr_opts addr_opts = {};
+	struct bpf_ksock *ks;
+	int err = 0;
+
+	create_opts.family = AF_INET;
+	create_opts.type = SOCK_DGRAM;
+	create_opts.protocol = IPPROTO_UDP;
+
+	ks = bpf_ksock_create(&create_opts, sizeof(create_opts), &err);
+	if (!ks)
+		return err;
+
+	addr_opts.family = AF_INET;
+	addr_opts.port = remote_port;
+	addr_opts.ipv4_addr = ipv4_remote;
+
+	err = bpf_ksock_connect(ks, &addr_opts, sizeof(addr_opts));
+	if (err) {
+		bpf_ksock_release(ks);
+		return err;
+	}
+
+	err = ksock_ctx_insert(ks);
+	if (err && err != -EEXIST)
+		return err;
+	return 0;
+}
+
+SEC("syscall")
+int ksock_send(void *ctx)
+{
+	struct __ksock_ctx_value *v;
+	struct bpf_ksock *ks;
+	int send = -1;
+
+	v = ksock_ctx_value_lookup();
+	if (!v)
+		return -ENOENT;
+
+	ks = bpf_kptr_xchg(&v->ctx, NULL);
+	if (!ks)
+		return -ENOENT;
+
+	send = bpf_ksock_send(ks, send_data, sizeof(send_data));
+	bpf_ksock_release(ks);
+	return send;
+}
+
+char __license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/ksock_common.h b/tools/testing/selftests/bpf/progs/ksock_common.h
new file mode 100644
index 000000000000..87d92372d28d
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/ksock_common.h
@@ -0,0 +1,73 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Copyright (c) 2026 Isovalent */
+
+#ifndef _KSOCK_COMMON_H
+#define _KSOCK_COMMON_H
+
+#include "errno.h"
+#include <stdbool.h>
+
+#define SOCK_STREAM	1
+#define SOCK_DGRAM	2
+#define IPPROTO_TCP	6
+#define IPPROTO_UDP	17
+
+struct bpf_ksock *bpf_ksock_create(const struct bpf_ksock_create_opts *opts,
+				   u32 opts__sz, int *err__uninit) __ksym;
+int bpf_ksock_bind(struct bpf_ksock *ks,
+		   const struct bpf_ksock_addr_opts *opts, u32 opts__sz) __ksym;
+int bpf_ksock_connect(struct bpf_ksock *ks,
+		      const struct bpf_ksock_addr_opts *opts, u32 opts__sz) __ksym;
+struct bpf_ksock *bpf_ksock_acquire(struct bpf_ksock *ks) __ksym;
+void bpf_ksock_release(struct bpf_ksock *ks) __ksym;
+int bpf_ksock_send(struct bpf_ksock *ks,
+		   const void *data, u32 data__sz) __ksym;
+
+struct __ksock_ctx_value {
+	struct bpf_ksock __kptr * ctx;
+};
+
+struct {
+	__uint(type, BPF_MAP_TYPE_ARRAY);
+	__type(key, int);
+	__type(value, struct __ksock_ctx_value);
+	__uint(max_entries, 1);
+} __ksock_ctx_map SEC(".maps");
+
+static inline struct __ksock_ctx_value *ksock_ctx_value_lookup(void)
+{
+	u32 key = 0;
+
+	return bpf_map_lookup_elem(&__ksock_ctx_map, &key);
+}
+
+static inline int ksock_ctx_insert(struct bpf_ksock *ctx)
+{
+	struct __ksock_ctx_value local, *v;
+	struct bpf_ksock *old;
+	u32 key = 0;
+	int err;
+
+	local.ctx = NULL;
+	err = bpf_map_update_elem(&__ksock_ctx_map, &key, &local, 0);
+	if (err) {
+		bpf_ksock_release(ctx);
+		return err;
+	}
+
+	v = bpf_map_lookup_elem(&__ksock_ctx_map, &key);
+	if (!v) {
+		bpf_ksock_release(ctx);
+		return -ENOENT;
+	}
+
+	old = bpf_kptr_xchg(&v->ctx, ctx);
+	if (old) {
+		bpf_ksock_release(old);
+		return -EEXIST;
+	}
+
+	return 0;
+}
+
+#endif /* _KSOCK_COMMON_H */
--
2.34.1


^ permalink raw reply related

* [PATCH bpf-next 2/6] bpf: Add ksock kfuncs
From: Mahe Tardy @ 2026-07-06  9:35 UTC (permalink / raw)
  To: bpf
  Cc: andrew+netdev, andrii, ast, daniel, davem, eddyz87, edumazet,
	john.fastabend, kuba, liamwisehart, martin.lau, pabeni, song,
	netdev, Mahe Tardy
In-Reply-To: <20260706093525.13030-1-mahe.tardy@gmail.com>

Add BPF kfuncs that allow BPF LSM programs to create and use sockets for
sending data. This provides a mechanism for BPF programs to emit
telemetry. For this first patch set, it's restricted to SOCK_DGRAM
socket types with IPPROTO_UDP protocol but could be easily extended to
SOCK_STREAM and IPPROTO_TCP in the future.

The API consists of six kfuncs:

  bpf_ksock_create()   - Create a socket (sleepable)
  bpf_ksock_bind()     - Bind socket to local address (sleepable)
  bpf_ksock_connect()  - Connect socket to remote address (sleepable)
  bpf_ksock_send()     - Send data through the socket (sleepable)
  bpf_ksock_acquire()  - Acquire a reference to a socket context
  bpf_ksock_release()  - Release a reference (cleanup via
                         queue_rcu_work since sock_release sleeps)

The setup kfuncs bpf_ksock_create, bpf_ksock_bind, bpf_ksock_connect,
can be called from SYSCALL programs only. While bpf_ksock_acquire,
bpf_ksock_release and bpf_ksock_send can be called from SYSCALL and LSM
programs.

The implementation follows the established kfunc lifecycle pattern
(create/acquire/release with refcounting, kptr map storage, dtor
registration). The kernel socket is wrapped in a refcounted bpf_ksock
struct. Cleanup is deferred via queue_rcu_work() because sock_release()
may sleep.

The kfuncs are only compiled when CONFIG_INET is enabled, as they
specifically support AF_INET and AF_INET6 sockets.

The socket operations go through the expected LSM hooks instead of
by-passing them like many kernel sockets since those are created by BPF
programs and thus system users. Thus bpf_ksock_send() kfunc, which is
exposed to LSM progs, has a re-entering protection to avoid recursion.
Also, because of the LSM checks, we prevent the use of the kfuncs from
asynchronous workqueue as the current value would then be invalid.

A bpf_ksock_max sysctl is added to limit the maximum number of BPF
kernel sockets that may exist in each network namespace. Out of
simplicity for now, the settings is host wide but the counters are per
network namespace.

In bpf_ksock_create(), we copy the arg values to avoid TOCTOU races
since the kfunc can sleep and the arg values could be stored in a map
that could be re-written by BPF progs or even userspace programs if the
map is mmaped.

Signed-off-by: Mahe Tardy <mahe.tardy@gmail.com>
---
 Documentation/admin-guide/sysctl/net.rst |  12 +
 include/linux/bpf_ksock.h                |  50 +++
 kernel/bpf/verifier.c                    |   3 +
 net/core/Makefile                        |   3 +
 net/core/bpf_ksock.c                     | 516 +++++++++++++++++++++++
 net/core/sysctl_net_core.c               |  11 +
 6 files changed, 595 insertions(+)
 create mode 100644 include/linux/bpf_ksock.h
 create mode 100644 net/core/bpf_ksock.c

diff --git a/Documentation/admin-guide/sysctl/net.rst b/Documentation/admin-guide/sysctl/net.rst
index e586e17fc7a5..8bcdc1bfd711 100644
--- a/Documentation/admin-guide/sysctl/net.rst
+++ b/Documentation/admin-guide/sysctl/net.rst
@@ -128,6 +128,18 @@ compiler in order to reject unprivileged JIT requests once it has
 been surpassed. bpf_jit_limit contains the value of the global limit
 in bytes.

+bpf_ksock_max
+-------------
+
+Maximum number of BPF kernel sockets that may exist in each network namespace.
+This host-wide setting is exposed in the initial network namespace and applies
+the same limit independently to every network namespace. Sockets awaiting
+deferred RCU and workqueue cleanup remain counted until the underlying socket
+has been released. A value of 0 disables creation of BPF kernel sockets in
+every network namespace.
+
+Default: 1024
+
 dev_weight
 ----------

diff --git a/include/linux/bpf_ksock.h b/include/linux/bpf_ksock.h
new file mode 100644
index 000000000000..0485b8eddc1f
--- /dev/null
+++ b/include/linux/bpf_ksock.h
@@ -0,0 +1,50 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/* Copyright (c) 2026 Isovalent */
+
+#ifndef _BPF_KSOCK_H
+#define _BPF_KSOCK_H
+
+#include <linux/types.h>
+
+#define BPF_KSOCK_MAX_DEFAULT 1024
+
+extern int sysctl_bpf_ksock_max;
+
+/**
+ * struct bpf_ksock_create_opts - BPF kernel socket creation parameters
+ * @family:	Address family: AF_INET or AF_INET6.
+ * @type:	Socket type: only SOCK_DGRAM supported for now.
+ * @protocol:	Protocol number (e.g. IPPROTO_UDP), or 0 for the default protocol
+ *		of the given type.
+ * @reserved:	Must be zero. Reserved for future use.
+ */
+struct bpf_ksock_create_opts {
+	__u8 family;
+	__u8 type;
+	__u8 protocol;
+	__u8 reserved;
+};
+
+/**
+ * struct bpf_ksock_addr_opts - BPF kernel socket address parameters
+ * @family:	Address family: AF_INET or AF_INET6.
+ * @reserved:	Must be zero. Reserved for future use.
+ * @port:	Port in host byte order.
+ * @scope_id:	IPv6 scope ID for scoped AF_INET6 addresses, or zero.
+ *		Must be zero when family=AF_INET.
+ * @ipv4_addr:	IPv4 address in network byte order. Used when family=AF_INET.
+ * @ipv6_addr:	IPv6 address (16 bytes, network byte order). Used when family=AF_INET6.
+ */
+struct bpf_ksock_addr_opts {
+	__u8 family;
+	__u8 reserved;
+	__u16 port;
+	__u32 scope_id;
+
+	union {
+		__be32 ipv4_addr;
+		__u32 ipv6_addr[4]; /* in6_addr; network order */
+	};
+};
+
+#endif /* _BPF_KSOCK_H */
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index 25aea4271cd0..52f6a89546e4 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -4424,6 +4424,9 @@ BTF_ID(struct, task_struct)
 #ifdef CONFIG_CRYPTO
 BTF_ID(struct, bpf_crypto_ctx)
 #endif
+#ifdef CONFIG_INET
+BTF_ID(struct, bpf_ksock)
+#endif
 BTF_SET_END(rcu_protected_types)

 static bool rcu_protected_object(const struct btf *btf, u32 btf_id)
diff --git a/net/core/Makefile b/net/core/Makefile
index b3fdcb4e355f..a9295b785901 100644
--- a/net/core/Makefile
+++ b/net/core/Makefile
@@ -44,6 +44,9 @@ obj-$(CONFIG_FAILOVER) += failover.o
 obj-$(CONFIG_NET_SOCK_MSG) += skmsg.o
 obj-$(CONFIG_BPF_SYSCALL) += sock_map.o
 obj-$(CONFIG_BPF_SYSCALL) += bpf_sk_storage.o
+ifneq ($(CONFIG_INET),)
+obj-$(CONFIG_BPF_SYSCALL) += bpf_ksock.o
+endif
 obj-$(CONFIG_OF)	+= of_net.o
 obj-$(CONFIG_NET_TEST) += net_test.o
 obj-$(CONFIG_NET_DEVMEM) += devmem.o
diff --git a/net/core/bpf_ksock.c b/net/core/bpf_ksock.c
new file mode 100644
index 000000000000..8bde734bc917
--- /dev/null
+++ b/net/core/bpf_ksock.c
@@ -0,0 +1,516 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Copyright (c) 2026 Isovalent */
+
+#include <linux/bpf.h>
+#include <linux/bpf_ksock.h>
+#include <linux/btf.h>
+#include <linux/btf_ids.h>
+#include <linux/cache.h>
+#include <linux/hash.h>
+#include <linux/in.h>
+#include <linux/in6.h>
+#include <linux/list_bl.h>
+#include <linux/net.h>
+#include <linux/refcount.h>
+#include <linux/rcupdate.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/socket.h>
+#include <linux/unaligned.h>
+#include <linux/workqueue.h>
+#include <linux/ip.h>
+#include <net/ipv6.h>
+#include <net/net_namespace.h>
+#include <net/netns/generic.h>
+#include <net/sock.h>
+
+/**
+ * struct bpf_ksock - refcounted BPF kernel socket context
+ * @sock:	The underlying kernel socket.
+ * @usage:	Reference counter.
+ * @rwork:	RCU work for deferred cleanup (sock_release may sleep).
+ */
+struct bpf_ksock {
+	struct socket *sock;
+	refcount_t usage;
+	struct rcu_work rwork;
+};
+
+struct bpf_ksock_send_guard {
+	struct hlist_bl_node node;
+	struct task_struct *task;
+};
+
+#define BPF_KSOCK_SEND_GUARD_HASH_BITS 6
+
+struct bpf_ksock_send_bucket {
+	struct hlist_bl_head head;
+} ____cacheline_aligned_in_smp;
+
+static struct bpf_ksock_send_bucket
+	bpf_ksock_send_buckets[1 << BPF_KSOCK_SEND_GUARD_HASH_BITS];
+
+static struct bpf_ksock_send_bucket *
+bpf_ksock_send_bucket(const struct task_struct *task)
+{
+	u32 bucket_idx = hash_ptr(task, BPF_KSOCK_SEND_GUARD_HASH_BITS);
+
+	return &bpf_ksock_send_buckets[bucket_idx];
+}
+
+static bool bpf_ksock_send_enter(struct bpf_ksock_send_guard *guard)
+{
+	struct bpf_ksock_send_guard *entry;
+	struct hlist_bl_node *pos;
+	struct bpf_ksock_send_bucket *bucket;
+
+	bucket = bpf_ksock_send_bucket(current);
+	hlist_bl_lock(&bucket->head);
+	hlist_bl_for_each_entry(entry, pos, &bucket->head, node) {
+		if (entry->task == current) {
+			hlist_bl_unlock(&bucket->head);
+			return false;
+		}
+	}
+
+	guard->task = current;
+	INIT_HLIST_BL_NODE(&guard->node);
+	hlist_bl_add_head(&guard->node, &bucket->head);
+	hlist_bl_unlock(&bucket->head);
+	return true;
+}
+
+static void bpf_ksock_send_exit(struct bpf_ksock_send_guard *guard)
+{
+	struct bpf_ksock_send_bucket *bucket;
+
+	bucket = bpf_ksock_send_bucket(guard->task);
+	hlist_bl_lock(&bucket->head);
+	hlist_bl_del(&guard->node);
+	hlist_bl_unlock(&bucket->head);
+}
+
+struct bpf_ksock_net {
+	atomic_t count;
+};
+
+static unsigned int bpf_ksock_net_id;
+int sysctl_bpf_ksock_max __read_mostly = BPF_KSOCK_MAX_DEFAULT;
+
+static struct bpf_ksock_net *bpf_ksock_pernet(const struct net *net)
+{
+	return net_generic(net, bpf_ksock_net_id);
+}
+
+static struct pernet_operations bpf_ksock_net_ops = {
+	.id = &bpf_ksock_net_id,
+	.size = sizeof(struct bpf_ksock_net),
+};
+
+static bool bpf_ksock_net_try_charge(struct net *net)
+{
+	struct bpf_ksock_net *kn = bpf_ksock_pernet(net);
+	int count = atomic_read(&kn->count);
+	int max;
+
+	do {
+		max = READ_ONCE(sysctl_bpf_ksock_max);
+		if (count >= max)
+			return false;
+	} while (!atomic_try_cmpxchg(&kn->count, &count, count + 1));
+
+	return true;
+}
+
+static void bpf_ksock_net_uncharge(struct net *net)
+{
+	struct bpf_ksock_net *kn = bpf_ksock_pernet(net);
+
+	WARN_ON_ONCE(atomic_dec_return(&kn->count) < 0);
+}
+
+static void ksock_release_work_fn(struct work_struct *work)
+{
+	struct bpf_ksock *ks =
+		container_of(to_rcu_work(work), struct bpf_ksock, rwork);
+	struct net *net = get_net(sock_net(ks->sock->sk));
+
+	sock_release(ks->sock);
+	bpf_ksock_net_uncharge(net);
+	put_net(net);
+	kfree(ks);
+}
+
+static int bpf_ksock_get_addr(const struct bpf_ksock_addr_opts *opts,
+			      u32 opts__sz, struct sockaddr_storage *addr)
+{
+	struct bpf_ksock_addr_opts opts_copy;
+
+	if (!opts || opts__sz != sizeof(*opts))
+		return -EINVAL;
+
+	/* Kfunc memory arguments are not guaranteed to be naturally aligned. */
+	memcpy(&opts_copy, opts, sizeof(opts_copy));
+
+	if (opts_copy.reserved)
+		return -EINVAL;
+
+	switch (opts_copy.family) {
+	case AF_INET: {
+		struct sockaddr_in *addr4 = (struct sockaddr_in *)addr;
+
+		if (opts_copy.scope_id)
+			return -EINVAL;
+
+		*addr4 = (struct sockaddr_in){
+			.sin_family = AF_INET,
+			.sin_port = htons(opts_copy.port),
+			.sin_addr.s_addr = opts_copy.ipv4_addr,
+		};
+		return sizeof(*addr4);
+	}
+#if IS_ENABLED(CONFIG_IPV6)
+	case AF_INET6: {
+		struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *)addr;
+
+		*addr6 = (struct sockaddr_in6){
+			.sin6_family = AF_INET6,
+			.sin6_port = htons(opts_copy.port),
+			.sin6_scope_id = opts_copy.scope_id,
+		};
+		memcpy(&addr6->sin6_addr, opts_copy.ipv6_addr,
+		       sizeof(struct in6_addr));
+		return sizeof(*addr6);
+	}
+#endif
+	default:
+		return -EAFNOSUPPORT;
+	}
+}
+
+static bool bpf_ksock_has_user_task_context(void)
+{
+	/*
+	 * Task work can run from do_exit() after exit_nsproxy_namespaces()
+	 * cleared current->nsproxy, while current is still not a kthread.
+	 */
+	return !(current->flags & PF_KTHREAD) && current->nsproxy;
+}
+
+__bpf_kfunc_start_defs();
+
+/**
+ * bpf_ksock_create() - Create a BPF kernel socket.
+ *
+ * Allocates and creates a kernel socket.
+ * The socket is charged against the active network namespace's BPF kernel
+ * socket quota.
+ *
+ * The returned context must either be stored in a map as a kptr, or
+ * freed with bpf_ksock_release().
+ *
+ * This function may sleep (sock_create), so it can only be used
+ * in sleepable BPF programs (SYSCALL).
+ * It cannot be called from a BPF workqueue callback because that callback
+ * does not retain the invoking task's namespace or security context.
+ *
+ * @opts:	Pointer to struct bpf_ksock_create_opts with socket parameters.
+ * @opts__sz:	Size of the opts struct.
+ * @err__uninit:	Integer to store error code when NULL is returned.
+ */
+__bpf_kfunc struct bpf_ksock *
+bpf_ksock_create(const struct bpf_ksock_create_opts *opts, u32 opts__sz,
+		 int *err__uninit)
+{
+	struct bpf_ksock_create_opts opts_copy;
+	struct bpf_ksock *ks;
+	struct net *net;
+	int err;
+
+	/*
+	 * sock_create() derives the network namespace, credentials, and cgroup
+	 * from current. Kernel threads, including BPF workqueue callbacks, do
+	 * not carry the context of the task that invoked the BPF program.
+	 */
+	if (!bpf_ksock_has_user_task_context()) {
+		err = -EOPNOTSUPP;
+		goto err_out;
+	}
+
+	if (!opts || opts__sz != sizeof(struct bpf_ksock_create_opts)) {
+		err = -EINVAL;
+		goto err_out;
+	}
+
+	opts_copy = (struct bpf_ksock_create_opts){
+		.family = READ_ONCE(opts->family),
+		.type = READ_ONCE(opts->type),
+		.protocol = READ_ONCE(opts->protocol),
+		.reserved = READ_ONCE(opts->reserved),
+	};
+
+	if (opts_copy.reserved) {
+		err = -EINVAL;
+		goto err_out;
+	}
+
+	if (opts_copy.family != AF_INET && opts_copy.family != AF_INET6) {
+		err = -EAFNOSUPPORT;
+		goto err_out;
+	}
+
+	if (opts_copy.type != SOCK_DGRAM) {
+		err = -EPROTONOSUPPORT;
+		goto err_out;
+	}
+
+	if (opts_copy.protocol != IPPROTO_UDP && opts_copy.protocol != 0) {
+		err = -EPROTONOSUPPORT;
+		goto err_out;
+	}
+
+	ks = kzalloc_obj(*ks);
+	if (!ks) {
+		err = -ENOMEM;
+		goto err_out;
+	}
+
+	net = current->nsproxy->net_ns;
+	if (!bpf_ksock_net_try_charge(net)) {
+		err = -ENOSPC;
+		goto err_free;
+	}
+
+	/*
+	 * Use the normal current-task socket path so LSM/cgroup policy,
+	 * socket labels, and the active netns reference match a socket(2)
+	 * created by the BPF program's caller.
+	 */
+	err = sock_create(opts_copy.family, opts_copy.type, opts_copy.protocol,
+			  &ks->sock);
+	if (err)
+		goto err_uncharge;
+
+	ks->sock->sk->sk_rcvbuf = SOCK_MIN_RCVBUF;
+	ks->sock->sk->sk_userlocks |= SOCK_RCVBUF_LOCK;
+
+	refcount_set(&ks->usage, 1);
+	put_unaligned(0, err__uninit);
+	return ks;
+
+err_uncharge:
+	bpf_ksock_net_uncharge(net);
+err_free:
+	kfree(ks);
+err_out:
+	put_unaligned(err, err__uninit);
+	return NULL;
+}
+
+/**
+ * bpf_ksock_bind() - Bind a BPF kernel socket to a local address.
+ * @ks:		The BPF kernel socket context.
+ * @opts:	Pointer to struct bpf_ksock_addr_opts with local address.
+ * @opts__sz:	Size of the opts struct.
+ *
+ * Binds the socket to the specified local address and port.
+ * This is optional; if not called, the kernel will auto-assign.
+ *
+ * This function may sleep while binding the socket, so it can only be used in
+ * sleepable BPF programs (SYSCALL).
+ *
+ * Return: 0 on success, negative errno on error.
+ */
+__bpf_kfunc int bpf_ksock_bind(struct bpf_ksock *ks,
+			       const struct bpf_ksock_addr_opts *opts,
+			       u32 opts__sz)
+{
+	struct sockaddr_storage addr = {};
+	int addrlen;
+
+	if (!bpf_ksock_has_user_task_context())
+		return -EOPNOTSUPP;
+
+	addrlen = bpf_ksock_get_addr(opts, opts__sz, &addr);
+	if (addrlen < 0)
+		return addrlen;
+
+	return __sys_bind_socket(ks->sock, &addr, addrlen);
+}
+
+/**
+ * bpf_ksock_connect() - Connect a BPF kernel socket to a remote address.
+ * @ks:		The BPF kernel socket context.
+ * @opts:	Pointer to struct bpf_ksock_addr_opts with remote address.
+ * @opts__sz:	Size of the opts struct.
+ *
+ * Connects the socket to the specified remote address and port.
+ *
+ * This function may sleep while connecting the socket, so it can only be used
+ * in sleepable BPF programs (SYSCALL).
+ *
+ * Return: 0 on success, negative errno on error.
+ */
+__bpf_kfunc int bpf_ksock_connect(struct bpf_ksock *ks,
+				  const struct bpf_ksock_addr_opts *opts,
+				  u32 opts__sz)
+{
+	struct sockaddr_storage addr = {};
+	int addrlen;
+
+	if (!bpf_ksock_has_user_task_context())
+		return -EOPNOTSUPP;
+
+	addrlen = bpf_ksock_get_addr(opts, opts__sz, &addr);
+	if (addrlen < 0)
+		return addrlen;
+
+	return __sys_connect_socket(ks->sock, &addr, addrlen, 0);
+}
+
+/**
+ * bpf_ksock_acquire() - Acquire a reference to a BPF kernel socket.
+ * @ks:	The BPF kernel socket context to acquire. Must be a
+ *	trusted pointer (e.g. RCU-protected kptr from a map).
+ *
+ * The acquired context must either be stored in a map as a kptr, or
+ * freed with bpf_ksock_release().
+ */
+__bpf_kfunc struct bpf_ksock *bpf_ksock_acquire(struct bpf_ksock *ks)
+{
+	if (!refcount_inc_not_zero(&ks->usage))
+		return NULL;
+	return ks;
+}
+
+/**
+ * bpf_ksock_release() - Release a BPF kernel socket.
+ * @ks:	The BPF kernel socket context to release.
+ *
+ * When the final reference is released, the socket is cleaned up via
+ * queue_rcu_work() (since sock_release may sleep).
+ */
+__bpf_kfunc void bpf_ksock_release(struct bpf_ksock *ks)
+{
+	if (refcount_dec_and_test(&ks->usage)) {
+		INIT_RCU_WORK(&ks->rwork, ksock_release_work_fn);
+		queue_rcu_work(system_dfl_wq, &ks->rwork);
+	}
+}
+
+__bpf_kfunc void bpf_ksock_release_dtor(void *ks)
+{
+	bpf_ksock_release(ks);
+}
+CFI_NOSEAL(bpf_ksock_release_dtor);
+
+/**
+ * bpf_ksock_send() - Send data through a BPF kernel socket.
+ * @ks:		The BPF kernel socket context. Must be an acquired reference.
+ * @data:	Pointer to the data to send.
+ * @data__sz:	Size of the data to send (max 65535 bytes).
+ *
+ * Sends data on a connected socket, best-effort and nonblocking. This may sleep
+ * (kernel_sendmsg), so it can only be called from sleepable BPF programs.
+ *
+ * Return: Number of bytes sent on success, negative errno on error.
+ */
+__bpf_kfunc int bpf_ksock_send(struct bpf_ksock *ks, const void *data,
+			       u32 data__sz)
+{
+	struct bpf_ksock_send_guard guard;
+	struct msghdr msg = {
+		.msg_flags = MSG_DONTWAIT,
+	};
+	struct kvec iov = {
+		.iov_base = (void *)data,
+		.iov_len = data__sz,
+	};
+	int ret;
+
+	if (!bpf_ksock_has_user_task_context())
+		return -EOPNOTSUPP;
+
+	/* Early check for UDP. Exact limits enforced by kernel_sendmsg(). */
+	if (data__sz > IP_MAX_MTU)
+		return -EMSGSIZE;
+
+	if (!bpf_ksock_send_enter(&guard))
+		return -EBUSY;
+
+	ret = kernel_sendmsg(ks->sock, &msg, &iov, 1, data__sz);
+	bpf_ksock_send_exit(&guard);
+
+	return ret;
+}
+
+__bpf_kfunc_end_defs();
+
+BTF_KFUNCS_START(ksock_init_kfunc_btf_ids)
+BTF_ID_FLAGS(func, bpf_ksock_create, KF_ACQUIRE | KF_RET_NULL | KF_SLEEPABLE)
+BTF_ID_FLAGS(func, bpf_ksock_bind, KF_SLEEPABLE)
+BTF_ID_FLAGS(func, bpf_ksock_connect, KF_SLEEPABLE)
+BTF_KFUNCS_END(ksock_init_kfunc_btf_ids)
+
+static const struct btf_kfunc_id_set ksock_init_kfunc_set = {
+	.owner = THIS_MODULE,
+	.set = &ksock_init_kfunc_btf_ids,
+};
+
+BTF_KFUNCS_START(ksock_kfunc_btf_ids)
+BTF_ID_FLAGS(func, bpf_ksock_release, KF_RELEASE)
+BTF_ID_FLAGS(func, bpf_ksock_acquire, KF_ACQUIRE | KF_RCU | KF_RET_NULL)
+BTF_ID_FLAGS(func, bpf_ksock_send, KF_SLEEPABLE)
+BTF_KFUNCS_END(ksock_kfunc_btf_ids)
+
+static int bpf_ksock_kfunc_filter(const struct bpf_prog *prog, u32 kfunc_id)
+{
+	if (!btf_id_set8_contains(&ksock_kfunc_btf_ids, kfunc_id) ||
+	    prog->type == BPF_PROG_TYPE_SYSCALL ||
+	    prog->type == BPF_PROG_TYPE_LSM)
+		return 0;
+
+	return -EACCES;
+}
+
+static const struct btf_kfunc_id_set ksock_kfunc_set = {
+	.owner = THIS_MODULE,
+	.set = &ksock_kfunc_btf_ids,
+	.filter = bpf_ksock_kfunc_filter,
+};
+
+BTF_ID_LIST(bpf_ksock_dtor_ids)
+BTF_ID(struct, bpf_ksock)
+BTF_ID(func, bpf_ksock_release_dtor)
+
+static int __init bpf_ksock_kfunc_init(void)
+{
+	int ret;
+	const struct btf_id_dtor_kfunc bpf_ksock_dtors[] = {
+		{
+			.btf_id = bpf_ksock_dtor_ids[0],
+			.kfunc_btf_id = bpf_ksock_dtor_ids[1],
+		},
+	};
+
+	ret = register_pernet_subsys(&bpf_ksock_net_ops);
+	if (ret)
+		return ret;
+
+	ret = register_btf_kfunc_id_set(BPF_PROG_TYPE_SYSCALL,
+					&ksock_init_kfunc_set);
+	if (ret) {
+		unregister_pernet_subsys(&bpf_ksock_net_ops);
+		return ret;
+	}
+
+	ret = register_btf_kfunc_id_set(BPF_PROG_TYPE_SYSCALL,
+					&ksock_kfunc_set);
+	ret = ret ?: register_btf_kfunc_id_set(BPF_PROG_TYPE_LSM,
+					       &ksock_kfunc_set);
+	return ret = ret ?: register_btf_id_dtor_kfuncs(bpf_ksock_dtors,
+						 ARRAY_SIZE(bpf_ksock_dtors),
+						 THIS_MODULE);
+}
+
+late_initcall(bpf_ksock_kfunc_init);
diff --git a/net/core/sysctl_net_core.c b/net/core/sysctl_net_core.c
index b508618bfc12..d221a180c877 100644
--- a/net/core/sysctl_net_core.c
+++ b/net/core/sysctl_net_core.c
@@ -7,6 +7,7 @@
  */

 #include <linux/filter.h>
+#include <linux/bpf_ksock.h>
 #include <linux/mm.h>
 #include <linux/sysctl.h>
 #include <linux/module.h>
@@ -525,6 +526,16 @@ static struct ctl_table net_core_table[] = {
 		.extra1		= SYSCTL_LONG_ONE,
 		.extra2		= &bpf_jit_limit_max,
 	},
+#endif
+#if IS_ENABLED(CONFIG_BPF_SYSCALL) && IS_ENABLED(CONFIG_INET)
+	{
+		.procname	= "bpf_ksock_max",
+		.data		= &sysctl_bpf_ksock_max,
+		.maxlen		= sizeof(int),
+		.mode		= 0644,
+		.proc_handler	= proc_dointvec_minmax,
+		.extra1		= SYSCTL_ZERO,
+	},
 #endif
 	{
 		.procname	= "netdev_tstamp_prequeue",
--
2.34.1


^ permalink raw reply related

* [PATCH bpf-next 5/6] selftests/bpf: Add ksock net ns quota tests
From: Mahe Tardy @ 2026-07-06  9:35 UTC (permalink / raw)
  To: bpf
  Cc: andrew+netdev, andrii, ast, daniel, davem, eddyz87, edumazet,
	john.fastabend, kuba, liamwisehart, martin.lau, pabeni, song,
	netdev, Mahe Tardy
In-Reply-To: <20260706093525.13030-1-mahe.tardy@gmail.com>

The new bpf_ksock_create() kfunc is limited by quota enforced at the
network namespace layer. This test verifies that the quota are enforced
and the acquire/release patch increase/reduce the counters.

Signed-off-by: Mahe Tardy <mahe.tardy@gmail.com>
---
 .../selftests/bpf/prog_tests/ksock_quota.c    | 139 +++++++++++++++
 .../testing/selftests/bpf/progs/ksock_quota.c | 167 ++++++++++++++++++
 2 files changed, 306 insertions(+)
 create mode 100644 tools/testing/selftests/bpf/prog_tests/ksock_quota.c
 create mode 100644 tools/testing/selftests/bpf/progs/ksock_quota.c

diff --git a/tools/testing/selftests/bpf/prog_tests/ksock_quota.c b/tools/testing/selftests/bpf/prog_tests/ksock_quota.c
new file mode 100644
index 000000000000..c86d9b0a9513
--- /dev/null
+++ b/tools/testing/selftests/bpf/prog_tests/ksock_quota.c
@@ -0,0 +1,139 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2026 Isovalent */
+
+#include <sys/stat.h>
+
+#include "test_progs.h"
+#include "network_helpers.h"
+#include "sysctl_helpers.h"
+#include "ksock_quota.skel.h"
+
+#define NS_NET_QUOTA_TEST1 "ksock_net_quota_ns1"
+#define NS_NET_QUOTA_TEST2 "ksock_net_quota_ns2"
+#define KSOCK_MAX_SYSCTL "/proc/sys/net/core/bpf_ksock_max"
+#define KSOCK_QUOTA_NS_SLOTS 3
+#define KSOCK_QUOTA_NS1_OFFSET 0
+#define KSOCK_QUOTA_NS2_OFFSET KSOCK_QUOTA_NS_SLOTS
+
+static int ksock_run_prog(struct bpf_program *prog)
+{
+	LIBBPF_OPTS(bpf_test_run_opts, opts);
+	int err;
+
+	err = bpf_prog_test_run_opts(bpf_program__fd(prog), &opts);
+	if (err)
+		return err;
+	return opts.retval;
+}
+
+static __u64 ksock_quota_release_completed(struct ksock_quota *skel)
+{
+	return __atomic_load_n(&skel->bss->quota_release_completed,
+			       __ATOMIC_ACQUIRE);
+}
+
+static int ksock_release_quota_slots(struct ksock_quota *skel, int slot_offset)
+{
+	__u64 completed;
+	int err;
+
+	completed = ksock_quota_release_completed(skel);
+	skel->bss->quota_slot_offset = slot_offset;
+	err = ksock_run_prog(skel->progs.ksock_quota_release);
+	if (!ASSERT_OK(err, "ksock_quota_release"))
+		return -1;
+
+	while (ksock_quota_release_completed(skel) - completed <
+	       skel->bss->quota_released)
+		usleep(1000);
+
+	return skel->bss->quota_released;
+}
+
+static bool ksock_expect_quota(struct ksock_quota *skel, int slot_offset,
+			       const char *name)
+{
+	int err;
+
+	skel->bss->quota_slot_offset = slot_offset;
+	err = ksock_run_prog(skel->progs.ksock_quota_create);
+	if (!ASSERT_OK(err, name))
+		return false;
+	if (!ASSERT_EQ(skel->bss->quota_created, 2, name))
+		return false;
+	return ASSERT_EQ(skel->bss->quota_err, -ENOSPC, name);
+}
+
+static bool record_current_ns(struct ksock_quota *skel, int target)
+{
+	struct stat st;
+	int err;
+
+	err = stat("/proc/self/ns/net", &st);
+	if (!ASSERT_OK(err, "stat netns"))
+		return false;
+	skel->bss->target_netns_inum[target] = (__u32)st.st_ino;
+	return true;
+}
+
+void serial_test_ksock_net_quota(void)
+{
+	char old_max[16] = {};
+	struct netns_obj *netns1 = NULL, *netns2 = NULL;
+	struct ksock_quota *skel;
+	int released;
+
+	skel = ksock_quota__open_and_load();
+	if (!ASSERT_OK_PTR(skel, "ksock quota skeleton"))
+		return;
+	skel->links.ksock_release_work_enter =
+		bpf_program__attach_trace(skel->progs.ksock_release_work_enter);
+	if (!ASSERT_OK_PTR(skel->links.ksock_release_work_enter,
+			   "attach ksock release work entry"))
+		goto out;
+	skel->links.ksock_release_work_exit =
+		bpf_program__attach_trace(skel->progs.ksock_release_work_exit);
+	if (!ASSERT_OK_PTR(skel->links.ksock_release_work_exit,
+			   "attach ksock release work exit"))
+		goto out;
+
+	if (sysctl_set_or_fail(KSOCK_MAX_SYSCTL, old_max, "2"))
+		goto out;
+
+	netns1 = netns_new(NS_NET_QUOTA_TEST1, true);
+	if (!ASSERT_OK_PTR(netns1, "create first netns"))
+		goto out;
+	if (!record_current_ns(skel, 0))
+		goto out;
+
+	if (!ksock_expect_quota(skel, KSOCK_QUOTA_NS1_OFFSET,
+				"first netns quota"))
+		goto out;
+
+	/* The host-wide setting grants the full allowance to each netns. */
+	netns2 = netns_new(NS_NET_QUOTA_TEST2, true);
+	if (!ASSERT_OK_PTR(netns2, "create second netns"))
+		goto out;
+	if (!record_current_ns(skel, 1))
+		goto out;
+
+	if (!ksock_expect_quota(skel, KSOCK_QUOTA_NS2_OFFSET,
+				"second netns quota"))
+		goto out;
+
+	released = ksock_release_quota_slots(skel, KSOCK_QUOTA_NS2_OFFSET);
+	if (!ASSERT_EQ(released, 2, "second netns quota released"))
+		goto out;
+
+	/* Both released slots must become available again. */
+	ksock_expect_quota(skel, KSOCK_QUOTA_NS2_OFFSET, "recovered quota");
+
+out:
+	ksock_release_quota_slots(skel, KSOCK_QUOTA_NS2_OFFSET);
+	ksock_release_quota_slots(skel, KSOCK_QUOTA_NS1_OFFSET);
+	netns_free(netns2);
+	netns_free(netns1);
+	if (old_max[0])
+		sysctl_set_or_fail(KSOCK_MAX_SYSCTL, NULL, old_max);
+	ksock_quota__destroy(skel);
+}
diff --git a/tools/testing/selftests/bpf/progs/ksock_quota.c b/tools/testing/selftests/bpf/progs/ksock_quota.c
new file mode 100644
index 000000000000..91854ea333c1
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/ksock_quota.c
@@ -0,0 +1,167 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2026 Isovalent */
+
+#include "vmlinux.h"
+#include <bpf/bpf_core_read.h>
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_tracing.h>
+#include "bpf_tracing_net.h"
+#include "ksock_common.h"
+
+#define KSOCK_QUOTA_NS_SLOTS 3
+#define KSOCK_QUOTA_NETNS 2
+#define KSOCK_QUOTA_SLOTS (KSOCK_QUOTA_NS_SLOTS * KSOCK_QUOTA_NETNS)
+
+struct ksock_quota_value {
+	struct bpf_ksock __kptr * ctx;
+};
+
+struct {
+	__uint(type, BPF_MAP_TYPE_ARRAY);
+	__type(key, u32);
+	__type(value, struct ksock_quota_value);
+	__uint(max_entries, KSOCK_QUOTA_SLOTS);
+} quota_map SEC(".maps");
+
+struct {
+	__uint(type, BPF_MAP_TYPE_HASH);
+	__type(key, u64);
+	__type(value, u8);
+	__uint(max_entries, KSOCK_QUOTA_SLOTS);
+} release_workers SEC(".maps");
+
+u32 quota_created;
+u32 quota_released;
+u32 quota_slot_offset;
+u32 target_netns_inum[KSOCK_QUOTA_NETNS];
+u64 quota_release_completed;
+int quota_err;
+
+static __always_inline bool
+ksock_quota_create_one(u32 i,
+		       const struct bpf_ksock_create_opts *create_opts)
+{
+	struct ksock_quota_value *v;
+	struct bpf_ksock *ks, *old;
+	int err = 0;
+
+	i += quota_slot_offset;
+	if (i >= KSOCK_QUOTA_SLOTS) {
+		quota_err = -ERANGE;
+		return true;
+	}
+
+	barrier_var(err);
+	ks = bpf_ksock_create(create_opts, sizeof(*create_opts), &err);
+	if (!ks) {
+		quota_err = err;
+		return true;
+	}
+
+	v = bpf_map_lookup_elem(&quota_map, &i);
+	if (!v) {
+		bpf_ksock_release(ks);
+		quota_err = -ENOENT;
+		return true;
+	}
+
+	old = bpf_kptr_xchg(&v->ctx, ks);
+	if (old)
+		bpf_ksock_release(old);
+	quota_created++;
+	return false;
+}
+
+static __always_inline void ksock_quota_release_one(u32 i)
+{
+	struct ksock_quota_value *v;
+	struct bpf_ksock *ks;
+
+	i += quota_slot_offset;
+	if (i >= KSOCK_QUOTA_SLOTS)
+		return;
+
+	v = bpf_map_lookup_elem(&quota_map, &i);
+	if (!v)
+		return;
+
+	ks = bpf_kptr_xchg(&v->ctx, NULL);
+	if (ks) {
+		bpf_ksock_release(ks);
+		quota_released++;
+	}
+}
+
+SEC("syscall")
+int ksock_quota_create(void *ctx)
+{
+	struct bpf_ksock_create_opts create_opts = {};
+
+	create_opts.family = AF_INET;
+	create_opts.type = SOCK_DGRAM;
+	create_opts.protocol = IPPROTO_UDP;
+	quota_created = 0;
+	quota_err = 0;
+
+	if (ksock_quota_create_one(0, &create_opts))
+		return 0;
+	if (ksock_quota_create_one(1, &create_opts))
+		return 0;
+	ksock_quota_create_one(2, &create_opts);
+
+	return 0;
+}
+
+SEC("syscall")
+int ksock_quota_release(void *ctx)
+{
+	quota_released = 0;
+	ksock_quota_release_one(0);
+	ksock_quota_release_one(1);
+	ksock_quota_release_one(2);
+
+	return 0;
+}
+
+SEC("fentry/ksock_release_work_fn")
+int BPF_PROG(ksock_release_work_enter, struct work_struct *work)
+{
+	struct rcu_work *rwork = container_of(work, struct rcu_work, work);
+	struct bpf_ksock *ks = container_of(rwork, struct bpf_ksock, rwork);
+	u64 pid_tgid = bpf_get_current_pid_tgid();
+	struct net *net;
+	u32 inum;
+	u8 tracked = 1;
+
+	/* ksock_release_work_fn() frees ks before the fexit program runs. */
+	net = BPF_CORE_READ(ks, sock, sk, __sk_common.skc_net.net);
+	if (!net)
+		return 0;
+
+	inum = BPF_CORE_READ(net, ns.inum);
+	if (inum != target_netns_inum[0] &&
+	    inum != target_netns_inum[1])
+		return 0;
+
+	bpf_map_update_elem(&release_workers, &pid_tgid, &tracked, BPF_ANY);
+	return 0;
+}
+
+SEC("fexit/ksock_release_work_fn")
+int BPF_PROG(ksock_release_work_exit, struct work_struct *work)
+{
+	u64 pid_tgid = bpf_get_current_pid_tgid();
+	u8 *tracked;
+
+	tracked = bpf_map_lookup_elem(&release_workers, &pid_tgid);
+	if (!tracked)
+		return 0;
+	bpf_map_delete_elem(&release_workers, &pid_tgid);
+
+	/* The worker has released the socket and returned its quota charge. */
+	__sync_fetch_and_add(&quota_release_completed, 1);
+
+	return 0;
+}
+
+char __license[] SEC("license") = "GPL";
--
2.34.1


^ permalink raw reply related

* [PATCH bpf-next 6/6] selftests/bpf: Add ksock test for async callback guard
From: Mahe Tardy @ 2026-07-06  9:35 UTC (permalink / raw)
  To: bpf
  Cc: andrew+netdev, andrii, ast, daniel, davem, eddyz87, edumazet,
	john.fastabend, kuba, liamwisehart, martin.lau, pabeni, song,
	netdev, Mahe Tardy
In-Reply-To: <20260706093525.13030-1-mahe.tardy@gmail.com>

Because the kfuncs are going through LSM hooks, allowing their use via
workqueue callbacks would expose the wrong credentials. This test
ensures the kfunc are preventing any use from these contexts.

Signed-off-by: Mahe Tardy <mahe.tardy@gmail.com>
---
 .../selftests/bpf/prog_tests/ksock_wq.c       | 34 ++++++++++
 tools/testing/selftests/bpf/progs/ksock_wq.c  | 62 +++++++++++++++++++
 2 files changed, 96 insertions(+)
 create mode 100644 tools/testing/selftests/bpf/prog_tests/ksock_wq.c
 create mode 100644 tools/testing/selftests/bpf/progs/ksock_wq.c

diff --git a/tools/testing/selftests/bpf/prog_tests/ksock_wq.c b/tools/testing/selftests/bpf/prog_tests/ksock_wq.c
new file mode 100644
index 000000000000..184b134f3823
--- /dev/null
+++ b/tools/testing/selftests/bpf/prog_tests/ksock_wq.c
@@ -0,0 +1,34 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2026 Isovalent */
+
+#include <unistd.h>
+
+#include "test_progs.h"
+#include "ksock_wq.skel.h"
+
+void test_ksock_wq(void)
+{
+	LIBBPF_OPTS(bpf_test_run_opts, opts);
+	struct ksock_wq *skel;
+	int err;
+
+	skel = ksock_wq__open_and_load();
+	if (!ASSERT_OK_PTR(skel, "ksock_wq open and load"))
+		return;
+
+	err = bpf_prog_test_run_opts(bpf_program__fd(skel->progs.ksock_wq_start),
+				     &opts);
+	if (!ASSERT_OK(err, "run ksock_wq_start"))
+		goto out;
+	if (!ASSERT_OK(opts.retval, "ksock_wq_start retval"))
+		goto out;
+
+	while (!__atomic_load_n(&skel->bss->callback_done, __ATOMIC_ACQUIRE))
+		usleep(1000);
+
+	ASSERT_EQ(skel->bss->create_err, -EOPNOTSUPP,
+		  "workqueue create rejected");
+
+out:
+	ksock_wq__destroy(skel);
+}
diff --git a/tools/testing/selftests/bpf/progs/ksock_wq.c b/tools/testing/selftests/bpf/progs/ksock_wq.c
new file mode 100644
index 000000000000..16a1873d132e
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/ksock_wq.c
@@ -0,0 +1,62 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2026 Isovalent */
+
+#include "vmlinux.h"
+#include <bpf/bpf_helpers.h>
+#include "bpf_experimental.h"
+#include "bpf_tracing_net.h"
+#include "errno.h"
+#include "ksock_common.h"
+
+struct ksock_wq_value {
+	struct bpf_wq work;
+};
+
+struct {
+	__uint(type, BPF_MAP_TYPE_ARRAY);
+	__uint(max_entries, 1);
+	__type(key, u32);
+	__type(value, struct ksock_wq_value);
+} work_map SEC(".maps");
+
+int create_err;
+u32 callback_done;
+
+static int ksock_wq_callback(void *map, int *key, void *value)
+{
+	struct bpf_ksock_create_opts opts = {
+		.family = AF_INET,
+		.type = SOCK_DGRAM,
+		.protocol = IPPROTO_UDP,
+	};
+	struct bpf_ksock *ks;
+	int err = 0;
+
+	ks = bpf_ksock_create(&opts, sizeof(opts), &err);
+	if (ks)
+		bpf_ksock_release(ks);
+	create_err = err;
+	__sync_fetch_and_add(&callback_done, 1);
+	return 0;
+}
+
+SEC("syscall")
+int ksock_wq_start(void *ctx)
+{
+	struct ksock_wq_value *value;
+	u32 key = 0;
+	int err;
+
+	value = bpf_map_lookup_elem(&work_map, &key);
+	if (!value)
+		return -ENOENT;
+	err = bpf_wq_init(&value->work, &work_map, 0);
+	if (err)
+		return err;
+	err = bpf_wq_set_callback(&value->work, ksock_wq_callback, 0);
+	if (err)
+		return err;
+	return bpf_wq_start(&value->work, 0);
+}
+
+char __license[] SEC("license") = "GPL";
--
2.34.1


^ permalink raw reply related

* [PATCH] net: qlcnic: validate unified ROM directory bounds
From: Pengpeng Hou @ 2026-07-06  9:36 UTC (permalink / raw)
  To: Shahed Shaikh
  Cc: Pengpeng Hou, Manish Chopra, GR-Linux-NIC-Dev, Andrew Lunn,
	David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
	netdev, linux-kernel

The unified ROM parser walks directory and data descriptor tables from the
firmware file. The existing checks compute table and data limits with
base + count * size or base + size before comparing with the firmware
size. Those calculations use fields from the firmware image and can wrap
before the comparison.

Add range helpers that validate tables and entries with division and
subtraction instead of overflowing additions. Pass the firmware size into
the directory lookup helper, validate each directory entry before reading
its type field, and validate bootloader, firmware and product-table
entries before their descriptor fields are used.

Signed-off-by: Pengpeng Hou <pengpeng@iscas.ac.cn>
---
 .../net/ethernet/qlogic/qlcnic/qlcnic_init.c  | 135 +++++++++++-------
 1 file changed, 86 insertions(+), 49 deletions(-)

diff --git a/drivers/net/ethernet/qlogic/qlcnic/qlcnic_init.c b/drivers/net/ethernet/qlogic/qlcnic/qlcnic_init.c
index 9192c5ad5a16..56620aed804b 100644
--- a/drivers/net/ethernet/qlogic/qlcnic/qlcnic_init.c
+++ b/drivers/net/ethernet/qlogic/qlcnic/qlcnic_init.c
@@ -740,21 +740,55 @@ qlcnic_has_mn(struct qlcnic_adapter *adapter)
 	return 0;
 }
 
-static
-struct uni_table_desc *qlcnic_get_table_desc(const u8 *unirom, int section)
+#define QLCNIC_UNI_DIR_TYPE_OFF		8
+#define QLCNIC_UNI_DIR_ENTRY_MIN_SIZE \
+	((QLCNIC_UNI_DIR_TYPE_OFF + 1) * sizeof(__le32))
+#define QLCNIC_UNI_PRODUCT_ENTRY_MIN_SIZE \
+	((QLCNIC_UNI_FIRMWARE_IDX_OFF + 1) * sizeof(__le32))
+
+static bool qlcnic_rom_range_valid(size_t fw_size, u32 offset, u32 len)
+{
+	return offset <= fw_size && len <= fw_size - offset;
+}
+
+static bool qlcnic_rom_table_valid(size_t size, u32 off, u32 n, u32 esz)
+{
+	if (off > size)
+		return false;
+	if (!esz)
+		return n == 0;
+
+	return n <= (size - off) / esz;
+}
+
+static bool qlcnic_rom_entry_valid(size_t size, u32 off, u32 esz, u32 idx)
+{
+	if (!esz || off > size)
+		return false;
+
+	return idx < (size - off) / esz;
+}
+
+static struct uni_table_desc *qlcnic_get_table_desc(const u8 *unirom,
+						    size_t fw_size, int section)
 {
-	u32 i, entries;
 	struct uni_table_desc *directory = (struct uni_table_desc *) &unirom[0];
-	entries = le32_to_cpu(directory->num_entries);
+	u32 entries = le32_to_cpu(directory->num_entries);
+	u32 entry_size = le32_to_cpu(directory->entry_size);
+	u32 findex = le32_to_cpu(directory->findex);
+	u32 i;
 
-	for (i = 0; i < entries; i++) {
+	if (entry_size < QLCNIC_UNI_DIR_ENTRY_MIN_SIZE ||
+	    !qlcnic_rom_table_valid(fw_size, findex, entries, entry_size))
+		return NULL;
 
-		u32 offs = le32_to_cpu(directory->findex) +
-			   i * le32_to_cpu(directory->entry_size);
-		u32 tab_type = le32_to_cpu(*((__le32 *)&unirom[offs] + 8));
+	for (i = 0; i < entries; i++) {
+		size_t offs = findex + (size_t)i * entry_size;
+		u32 tab_type = le32_to_cpu(*((__le32 *)&unirom[offs] +
+						QLCNIC_UNI_DIR_TYPE_OFF));
 
 		if (tab_type == section)
-			return (struct uni_table_desc *) &unirom[offs];
+			return (struct uni_table_desc *)&unirom[offs];
 	}
 
 	return NULL;
@@ -766,19 +800,18 @@ static int
 qlcnic_validate_header(struct qlcnic_adapter *adapter)
 {
 	const u8 *unirom = adapter->fw->data;
-	struct uni_table_desc *directory = (struct uni_table_desc *) &unirom[0];
-	u32 entries, entry_size, tab_size, fw_file_size;
-
-	fw_file_size = adapter->fw->size;
+	struct uni_table_desc *directory = (struct uni_table_desc *)&unirom[0];
+	u32 fw_file_size = adapter->fw->size;
+	u32 entries, entry_size, findex;
 
 	if (fw_file_size < FILEHEADER_SIZE)
 		return -EINVAL;
 
 	entries = le32_to_cpu(directory->num_entries);
 	entry_size = le32_to_cpu(directory->entry_size);
-	tab_size = le32_to_cpu(directory->findex) + (entries * entry_size);
+	findex = le32_to_cpu(directory->findex);
 
-	if (fw_file_size < tab_size)
+	if (!qlcnic_rom_table_valid(fw_file_size, findex, entries, entry_size))
 		return -EINVAL;
 
 	return 0;
@@ -789,31 +822,33 @@ qlcnic_validate_bootld(struct qlcnic_adapter *adapter)
 {
 	struct uni_table_desc *tab_desc;
 	struct uni_data_desc *descr;
-	u32 offs, tab_size, data_size, idx;
 	const u8 *unirom = adapter->fw->data;
+	size_t offs;
+	u32 data_len, data_off, entry_size, findex, idx;
 	__le32 temp;
 
 	temp = *((__le32 *)&unirom[adapter->file_prd_off] +
 		 QLCNIC_UNI_BOOTLD_IDX_OFF);
 	idx = le32_to_cpu(temp);
-	tab_desc = qlcnic_get_table_desc(unirom, QLCNIC_UNI_DIR_SECT_BOOTLD);
+	tab_desc = qlcnic_get_table_desc(unirom, adapter->fw->size,
+					 QLCNIC_UNI_DIR_SECT_BOOTLD);
 
 	if (!tab_desc)
 		return -EINVAL;
 
-	tab_size = le32_to_cpu(tab_desc->findex) +
-		   le32_to_cpu(tab_desc->entry_size) * (idx + 1);
-
-	if (adapter->fw->size < tab_size)
+	entry_size = le32_to_cpu(tab_desc->entry_size);
+	findex = le32_to_cpu(tab_desc->findex);
+	if (entry_size < sizeof(*descr) ||
+	    !qlcnic_rom_entry_valid(adapter->fw->size, findex, entry_size,
+				     idx))
 		return -EINVAL;
 
-	offs = le32_to_cpu(tab_desc->findex) +
-	       le32_to_cpu(tab_desc->entry_size) * idx;
+	offs = findex + (size_t)entry_size * idx;
 	descr = (struct uni_data_desc *)&unirom[offs];
+	data_off = le32_to_cpu(descr->findex);
+	data_len = le32_to_cpu(descr->size);
 
-	data_size = le32_to_cpu(descr->findex) + le32_to_cpu(descr->size);
-
-	if (adapter->fw->size < data_size)
+	if (!qlcnic_rom_range_valid(adapter->fw->size, data_off, data_len))
 		return -EINVAL;
 
 	return 0;
@@ -825,29 +860,31 @@ qlcnic_validate_fw(struct qlcnic_adapter *adapter)
 	struct uni_table_desc *tab_desc;
 	struct uni_data_desc *descr;
 	const u8 *unirom = adapter->fw->data;
-	u32 offs, tab_size, data_size, idx;
+	size_t offs;
+	u32 data_len, data_off, entry_size, findex, idx;
 	__le32 temp;
 
 	temp = *((__le32 *)&unirom[adapter->file_prd_off] +
 		 QLCNIC_UNI_FIRMWARE_IDX_OFF);
 	idx = le32_to_cpu(temp);
-	tab_desc = qlcnic_get_table_desc(unirom, QLCNIC_UNI_DIR_SECT_FW);
+	tab_desc = qlcnic_get_table_desc(unirom, adapter->fw->size,
+					 QLCNIC_UNI_DIR_SECT_FW);
 
 	if (!tab_desc)
 		return -EINVAL;
 
-	tab_size = le32_to_cpu(tab_desc->findex) +
-		   le32_to_cpu(tab_desc->entry_size) * (idx + 1);
-
-	if (adapter->fw->size < tab_size)
+	entry_size = le32_to_cpu(tab_desc->entry_size);
+	findex = le32_to_cpu(tab_desc->findex);
+	if (entry_size < sizeof(*descr) ||
+	    !qlcnic_rom_entry_valid(adapter->fw->size, findex, entry_size,
+				     idx))
 		return -EINVAL;
 
-	offs = le32_to_cpu(tab_desc->findex) +
-	       le32_to_cpu(tab_desc->entry_size) * idx;
+	offs = findex + (size_t)entry_size * idx;
 	descr = (struct uni_data_desc *)&unirom[offs];
-	data_size = le32_to_cpu(descr->findex) + le32_to_cpu(descr->size);
-
-	if (adapter->fw->size < data_size)
+	data_off = le32_to_cpu(descr->findex);
+	data_len = le32_to_cpu(descr->size);
+	if (!qlcnic_rom_range_valid(adapter->fw->size, data_off, data_len))
 		return -EINVAL;
 
 	return 0;
@@ -859,30 +896,30 @@ qlcnic_validate_product_offs(struct qlcnic_adapter *adapter)
 	struct uni_table_desc *ptab_descr;
 	const u8 *unirom = adapter->fw->data;
 	int mn_present = qlcnic_has_mn(adapter);
-	u32 entries, entry_size, tab_size, i;
+	u32 entries, entry_size, findex, i;
+	u32 section = QLCNIC_UNI_DIR_SECT_PRODUCT_TBL;
 	__le32 temp;
 
-	ptab_descr = qlcnic_get_table_desc(unirom,
-				QLCNIC_UNI_DIR_SECT_PRODUCT_TBL);
+	ptab_descr = qlcnic_get_table_desc(unirom, adapter->fw->size, section);
 	if (!ptab_descr)
 		return -EINVAL;
 
 	entries = le32_to_cpu(ptab_descr->num_entries);
 	entry_size = le32_to_cpu(ptab_descr->entry_size);
-	tab_size = le32_to_cpu(ptab_descr->findex) + (entries * entry_size);
-
-	if (adapter->fw->size < tab_size)
+	findex = le32_to_cpu(ptab_descr->findex);
+	if (entry_size < QLCNIC_UNI_PRODUCT_ENTRY_MIN_SIZE ||
+	    !qlcnic_rom_table_valid(adapter->fw->size, findex, entries,
+				     entry_size))
 		return -EINVAL;
 
 nomn:
 	for (i = 0; i < entries; i++) {
-
-		u32 flags, file_chiprev, offs;
+		size_t offs;
+		u32 flags, file_chiprev;
 		u8 chiprev = adapter->ahw->revision_id;
 		u32 flagbit;
 
-		offs = le32_to_cpu(ptab_descr->findex) +
-		       i * le32_to_cpu(ptab_descr->entry_size);
+		offs = findex + (size_t)i * entry_size;
 		temp = *((__le32 *)&unirom[offs] + QLCNIC_UNI_FLAGS_OFF);
 		flags = le32_to_cpu(temp);
 		temp = *((__le32 *)&unirom[offs] + QLCNIC_UNI_CHIP_REV_OFF);
@@ -891,7 +928,7 @@ qlcnic_validate_product_offs(struct qlcnic_adapter *adapter)
 		flagbit = mn_present ? 1 : 2;
 
 		if ((chiprev == file_chiprev) &&
-					((1ULL << flagbit) & flags)) {
+		    ((1ULL << flagbit) & flags)) {
 			adapter->file_prd_off = offs;
 			return 0;
 		}
@@ -945,7 +982,7 @@ struct uni_data_desc *qlcnic_get_data_desc(struct qlcnic_adapter *adapter,
 	temp = *((__le32 *)&unirom[adapter->file_prd_off] + idx_offset);
 	idx = le32_to_cpu(temp);
 
-	tab_desc = qlcnic_get_table_desc(unirom, section);
+	tab_desc = qlcnic_get_table_desc(unirom, adapter->fw->size, section);
 
 	if (tab_desc == NULL)
 		return NULL;
-- 
2.43.0


^ permalink raw reply related

* [PATCH] netxen: validate unified ROM directory bounds
From: Pengpeng Hou @ 2026-07-06  9:37 UTC (permalink / raw)
  To: Manish Chopra
  Cc: Pengpeng Hou, Rahul Verma, GR-Linux-NIC-Dev, Andrew Lunn,
	David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
	netdev, linux-kernel

The unified ROM parser walks directory and data descriptor tables from
the firmware file. The existing checks compute table and data limits with
base + count * size or base + size before comparing with the firmware
size. Those calculations use fields from the firmware image and can wrap
before the comparison.

Add range helpers that validate tables and entries with division and
subtraction instead of overflowing additions. Pass the firmware size into
the directory lookup helper, validate each directory entry before reading
its type field, and validate bootloader, firmware and product-table
entries before their descriptor fields are used.

Signed-off-by: Pengpeng Hou <pengpeng@iscas.ac.cn>
---
 .../ethernet/qlogic/netxen/netxen_nic_init.c  | 148 +++++++++++-------
 1 file changed, 90 insertions(+), 58 deletions(-)

diff --git a/drivers/net/ethernet/qlogic/netxen/netxen_nic_init.c b/drivers/net/ethernet/qlogic/netxen/netxen_nic_init.c
index 8bc4e2b69569..5722c55fa0cc 100644
--- a/drivers/net/ethernet/qlogic/netxen/netxen_nic_init.c
+++ b/drivers/net/ethernet/qlogic/netxen/netxen_nic_init.c
@@ -561,20 +561,55 @@ int netxen_pinit_from_rom(struct netxen_adapter *adapter)
 	return 0;
 }
 
-static struct uni_table_desc *nx_get_table_desc(const u8 *unirom, int section)
+#define NX_UNI_DIR_TYPE_OFF		8
+#define NX_UNI_DIR_ENTRY_MIN_SIZE \
+	((NX_UNI_DIR_TYPE_OFF + 1) * sizeof(u32))
+#define NX_UNI_PRODUCT_ENTRY_MIN_SIZE \
+	((NX_UNI_FIRMWARE_IDX_OFF + 1) * sizeof(u32))
+
+static bool netxen_rom_range_valid(size_t size, u32 off, u32 len)
+{
+	return off <= size && len <= size - off;
+}
+
+static bool netxen_rom_table_valid(size_t size, u32 off, u32 n, u32 esz)
+{
+	if (off > size)
+		return false;
+	if (!esz)
+		return n == 0;
+
+	return n <= (size - off) / esz;
+}
+
+static bool netxen_rom_entry_valid(size_t size, u32 off, u32 esz, u32 idx)
+{
+	if (!esz || off > size)
+		return false;
+
+	return idx < (size - off) / esz;
+}
+
+static struct uni_table_desc *
+nx_get_table_desc(const u8 *unirom, size_t fw_size, int section)
 {
-	uint32_t i;
 	struct uni_table_desc *directory = (struct uni_table_desc *) &unirom[0];
-	__le32 entries = cpu_to_le32(directory->num_entries);
+	u32 entries = cpu_to_le32(directory->num_entries);
+	u32 entry_size = cpu_to_le32(directory->entry_size);
+	u32 findex = cpu_to_le32(directory->findex);
+	u32 i;
 
-	for (i = 0; i < entries; i++) {
+	if (entry_size < NX_UNI_DIR_ENTRY_MIN_SIZE ||
+	    !netxen_rom_table_valid(fw_size, findex, entries, entry_size))
+		return NULL;
 
-		__le32 offs = cpu_to_le32(directory->findex) +
-				(i * cpu_to_le32(directory->entry_size));
-		__le32 tab_type = cpu_to_le32(*((u32 *)&unirom[offs] + 8));
+	for (i = 0; i < entries; i++) {
+		size_t offs = findex + (size_t)i * entry_size;
+		u32 tab_type = cpu_to_le32(*((u32 *)&unirom[offs] +
+						NX_UNI_DIR_TYPE_OFF));
 
 		if (tab_type == section)
-			return (struct uni_table_desc *) &unirom[offs];
+			return (struct uni_table_desc *)&unirom[offs];
 	}
 
 	return NULL;
@@ -586,20 +621,18 @@ static int
 netxen_nic_validate_header(struct netxen_adapter *adapter)
 {
 	const u8 *unirom = adapter->fw->data;
-	struct uni_table_desc *directory = (struct uni_table_desc *) &unirom[0];
+	struct uni_table_desc *directory = (struct uni_table_desc *)&unirom[0];
 	u32 fw_file_size = adapter->fw->size;
-	u32 tab_size;
-	__le32 entries;
-	__le32 entry_size;
+	u32 entries, entry_size, findex;
 
 	if (fw_file_size < QLCNIC_FILEHEADER_SIZE)
 		return -EINVAL;
 
 	entries = cpu_to_le32(directory->num_entries);
 	entry_size = cpu_to_le32(directory->entry_size);
-	tab_size = cpu_to_le32(directory->findex) + (entries * entry_size);
+	findex = cpu_to_le32(directory->findex);
 
-	if (fw_file_size < tab_size)
+	if (!netxen_rom_table_valid(fw_file_size, findex, entries, entry_size))
 		return -EINVAL;
 
 	return 0;
@@ -611,30 +644,30 @@ netxen_nic_validate_bootld(struct netxen_adapter *adapter)
 	struct uni_table_desc *tab_desc;
 	struct uni_data_desc *descr;
 	const u8 *unirom = adapter->fw->data;
-	__le32 idx = cpu_to_le32(*((int *)&unirom[adapter->file_prd_off] +
-				NX_UNI_BOOTLD_IDX_OFF));
-	u32 offs;
-	u32 tab_size;
-	u32 data_size;
+	u32 data_len, data_off, entry_size, findex, idx;
+	u32 section = NX_UNI_DIR_SECT_BOOTLD;
+	size_t offs;
 
-	tab_desc = nx_get_table_desc(unirom, NX_UNI_DIR_SECT_BOOTLD);
+	idx = cpu_to_le32(*((int *)&unirom[adapter->file_prd_off] +
+			     NX_UNI_BOOTLD_IDX_OFF));
+	tab_desc = nx_get_table_desc(unirom, adapter->fw->size, section);
 
 	if (!tab_desc)
 		return -EINVAL;
 
-	tab_size = cpu_to_le32(tab_desc->findex) +
-			(cpu_to_le32(tab_desc->entry_size) * (idx + 1));
-
-	if (adapter->fw->size < tab_size)
+	entry_size = cpu_to_le32(tab_desc->entry_size);
+	findex = cpu_to_le32(tab_desc->findex);
+	if (entry_size < sizeof(*descr) ||
+	    !netxen_rom_entry_valid(adapter->fw->size, findex, entry_size,
+				     idx))
 		return -EINVAL;
 
-	offs = cpu_to_le32(tab_desc->findex) +
-		(cpu_to_le32(tab_desc->entry_size) * (idx));
+	offs = findex + (size_t)entry_size * idx;
 	descr = (struct uni_data_desc *)&unirom[offs];
+	data_off = cpu_to_le32(descr->findex);
+	data_len = cpu_to_le32(descr->size);
 
-	data_size = cpu_to_le32(descr->findex) + cpu_to_le32(descr->size);
-
-	if (adapter->fw->size < data_size)
+	if (!netxen_rom_range_valid(adapter->fw->size, data_off, data_len))
 		return -EINVAL;
 
 	return 0;
@@ -646,29 +679,30 @@ netxen_nic_validate_fw(struct netxen_adapter *adapter)
 	struct uni_table_desc *tab_desc;
 	struct uni_data_desc *descr;
 	const u8 *unirom = adapter->fw->data;
-	__le32 idx = cpu_to_le32(*((int *)&unirom[adapter->file_prd_off] +
-				NX_UNI_FIRMWARE_IDX_OFF));
-	u32 offs;
-	u32 tab_size;
-	u32 data_size;
+	u32 data_len, data_off, entry_size, findex, idx;
+	u32 section = NX_UNI_DIR_SECT_FW;
+	size_t offs;
 
-	tab_desc = nx_get_table_desc(unirom, NX_UNI_DIR_SECT_FW);
+	idx = cpu_to_le32(*((int *)&unirom[adapter->file_prd_off] +
+			     NX_UNI_FIRMWARE_IDX_OFF));
+	tab_desc = nx_get_table_desc(unirom, adapter->fw->size, section);
 
 	if (!tab_desc)
 		return -EINVAL;
 
-	tab_size = cpu_to_le32(tab_desc->findex) +
-			(cpu_to_le32(tab_desc->entry_size) * (idx + 1));
-
-	if (adapter->fw->size < tab_size)
+	entry_size = cpu_to_le32(tab_desc->entry_size);
+	findex = cpu_to_le32(tab_desc->findex);
+	if (entry_size < sizeof(*descr) ||
+	    !netxen_rom_entry_valid(adapter->fw->size, findex, entry_size,
+				     idx))
 		return -EINVAL;
 
-	offs = cpu_to_le32(tab_desc->findex) +
-		(cpu_to_le32(tab_desc->entry_size) * (idx));
+	offs = findex + (size_t)entry_size * idx;
 	descr = (struct uni_data_desc *)&unirom[offs];
-	data_size = cpu_to_le32(descr->findex) + cpu_to_le32(descr->size);
+	data_off = cpu_to_le32(descr->findex);
+	data_len = cpu_to_le32(descr->size);
 
-	if (adapter->fw->size < data_size)
+	if (!netxen_rom_range_valid(adapter->fw->size, data_off, data_len))
 		return -EINVAL;
 
 	return 0;
@@ -682,39 +716,37 @@ netxen_nic_validate_product_offs(struct netxen_adapter *adapter)
 	const u8 *unirom = adapter->fw->data;
 	int mn_present = (NX_IS_REVISION_P2(adapter->ahw.revision_id)) ?
 			1 : netxen_p3_has_mn(adapter);
-	__le32 entries;
-	__le32 entry_size;
-	u32 tab_size;
-	u32 i;
+	u32 entries, entry_size, findex, i;
+	u32 section = NX_UNI_DIR_SECT_PRODUCT_TBL;
 
-	ptab_descr = nx_get_table_desc(unirom, NX_UNI_DIR_SECT_PRODUCT_TBL);
+	ptab_descr = nx_get_table_desc(unirom, adapter->fw->size, section);
 	if (ptab_descr == NULL)
 		return -EINVAL;
 
 	entries = cpu_to_le32(ptab_descr->num_entries);
 	entry_size = cpu_to_le32(ptab_descr->entry_size);
-	tab_size = cpu_to_le32(ptab_descr->findex) + (entries * entry_size);
-
-	if (adapter->fw->size < tab_size)
+	findex = cpu_to_le32(ptab_descr->findex);
+	if (entry_size < NX_UNI_PRODUCT_ENTRY_MIN_SIZE ||
+	    !netxen_rom_table_valid(adapter->fw->size, findex, entries,
+				     entry_size))
 		return -EINVAL;
 
 nomn:
 	for (i = 0; i < entries; i++) {
-
-		__le32 flags, file_chiprev, offs;
+		size_t offs;
+		__le32 flags, file_chiprev;
 		u8 chiprev = adapter->ahw.revision_id;
 		uint32_t flagbit;
 
-		offs = cpu_to_le32(ptab_descr->findex) +
-				(i * cpu_to_le32(ptab_descr->entry_size));
+		offs = findex + (size_t)i * entry_size;
 		flags = cpu_to_le32(*((int *)&unirom[offs] + NX_UNI_FLAGS_OFF));
 		file_chiprev = cpu_to_le32(*((int *)&unirom[offs] +
-							NX_UNI_CHIP_REV_OFF));
+						    NX_UNI_CHIP_REV_OFF));
 
 		flagbit = mn_present ? 1 : 2;
 
 		if ((chiprev == file_chiprev) &&
-					((1ULL << flagbit) & flags)) {
+		    ((1ULL << flagbit) & flags)) {
 			adapter->file_prd_off = offs;
 			return 0;
 		}
@@ -767,7 +799,7 @@ static struct uni_data_desc *nx_get_data_desc(struct netxen_adapter *adapter,
 	struct uni_table_desc *tab_desc;
 	__le32 offs;
 
-	tab_desc = nx_get_table_desc(unirom, section);
+	tab_desc = nx_get_table_desc(unirom, adapter->fw->size, section);
 
 	if (tab_desc == NULL)
 		return NULL;
-- 
2.43.0


^ permalink raw reply related

* Re: [PATCH RESEND net-next] net: hns3: add support to query/set TX pfc_prevention_tout for ethtool with RX prevention disabled
From: patchwork-bot+netdevbpf @ 2026-07-06  9:40 UTC (permalink / raw)
  To: Jijie Shao
  Cc: davem, edumazet, kuba, pabeni, andrew+netdev, horms, shenjian15,
	liuyonglong, chenhao418, yangshuaisong, netdev, linux-kernel
In-Reply-To: <20260630134043.1532431-1-shaojijie@huawei.com>

Hello:

This patch was applied to netdev/net-next.git (main)
by Paolo Abeni <pabeni@redhat.com>:

On Tue, 30 Jun 2026 21:40:43 +0800 you wrote:
> From: Hao Chen <chenhao418@huawei.com>
> 
> Add ethtool support to query and configure the PFC (Priority Flow Control)
> storm prevention timeout. When TX continuously sends PFC frames, the peer
> end is suppressed from sending packets. If this persists, a PFC frame storm
> may occur.
> 
> [...]

Here is the summary with links:
  - [RESEND,net-next] net: hns3: add support to query/set TX pfc_prevention_tout for ethtool with RX prevention disabled
    https://git.kernel.org/netdev/net-next/c/d7261cdb9550

You are awesome, thank you!
-- 
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/patchwork/pwbot.html



^ permalink raw reply

* Re: [PATCH net v3 2/2] octeon_ep_vf: fix skb frags overflow in the RX path
From: Maciej Fijalkowski @ 2026-07-06  9:40 UTC (permalink / raw)
  To: Maoyi Xie
  Cc: Veerasenareddy Burru, Sathesh Edara, Andrew Lunn,
	David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
	netdev, linux-kernel
In-Reply-To: <20260704061511.2350737-3-maoyixie.tju@gmail.com>

On Sat, Jul 04, 2026 at 02:15:11PM +0800, Maoyi Xie wrote:
> __octep_vf_oq_process_rx() has the same unbounded fragment loop as the PF
> driver. buff_info->len comes from the device response header, and one
> fragment is added per buffer_size chunk with no check against
> MAX_SKB_FRAGS. A long packet yields about 18 fragments, one past the
> default MAX_SKB_FRAGS of 17, so skb_add_rx_frag() writes past
> shinfo->frags[].
> 
> The fragment count is now checked before napi_build_skb(). A packet that
> needs more fragments than the skb can hold is dropped.
> octep_vf_oq_drop_rx() drains its descriptors. The napi_build_skb()
> failure path now uses the same helper.
> 
> Fixes: 1cd3b407977c ("octeon_ep_vf: add Tx/Rx processing and interrupt support")
> Co-developed-by: Kaixuan Li <kaixuan.li@ntu.edu.sg>
> Signed-off-by: Kaixuan Li <kaixuan.li@ntu.edu.sg>
> Signed-off-by: Maoyi Xie <maoyixie.tju@gmail.com>

Reviewed-by: Maciej Fijalkowski <maciej.fijalkowski@intel.com>

> ---
>  .../marvell/octeon_ep_vf/octep_vf_rx.c        | 46 ++++++++++++-------
>  1 file changed, 30 insertions(+), 16 deletions(-)
> 
> diff --git a/drivers/net/ethernet/marvell/octeon_ep_vf/octep_vf_rx.c b/drivers/net/ethernet/marvell/octeon_ep_vf/octep_vf_rx.c
> index d982474082..aa77b673ae 100644
> --- a/drivers/net/ethernet/marvell/octeon_ep_vf/octep_vf_rx.c
> +++ b/drivers/net/ethernet/marvell/octeon_ep_vf/octep_vf_rx.c
> @@ -357,6 +357,29 @@ static inline u32 octep_vf_oq_next_idx(struct octep_vf_oq *oq, u32 idx)
>  	return (idx + 1 == oq->max_count) ? 0 : idx + 1;
>  }
>  
> +static void octep_vf_oq_drop_rx(struct octep_vf_oq *oq,
> +				struct octep_vf_rx_buffer *buff_info,
> +				u32 *read_idx, u32 *desc_used)
> +{
> +	u16 data_len = buff_info->len - oq->max_single_buffer_size;
> +
> +	(*desc_used)++;
> +	*read_idx = octep_vf_oq_next_idx(oq, *read_idx);
> +	while (data_len) {
> +		dma_unmap_page(oq->dev, oq->desc_ring[*read_idx].buffer_ptr,
> +			       PAGE_SIZE, DMA_FROM_DEVICE);
> +		buff_info = (struct octep_vf_rx_buffer *)
> +			    &oq->buff_info[*read_idx];
> +		buff_info->page = NULL;
> +		if (data_len < oq->buffer_size)
> +			data_len = 0;
> +		else
> +			data_len -= oq->buffer_size;
> +		(*desc_used)++;
> +		*read_idx = octep_vf_oq_next_idx(oq, *read_idx);
> +	}
> +}
> +
>  /**
>   * __octep_vf_oq_process_rx() - Process hardware Rx queue and push to stack.
>   *
> @@ -431,25 +454,16 @@ static int __octep_vf_oq_process_rx(struct octep_vf_device *oct,
>  			struct skb_shared_info *shinfo;
>  			u16 data_len;
>  
> +			data_len = buff_info->len - oq->max_single_buffer_size;
> +			if (DIV_ROUND_UP(data_len, oq->buffer_size) > MAX_SKB_FRAGS) {
> +				octep_vf_oq_drop_rx(oq, buff_info, &read_idx, &desc_used);
> +				continue;
> +			}
> +
>  			skb = napi_build_skb((void *)resp_hw, PAGE_SIZE);
>  			if (!skb) {
>  				oq->stats->alloc_failures++;
> -				desc_used++;
> -				read_idx = octep_vf_oq_next_idx(oq, read_idx);
> -				data_len = buff_info->len - oq->max_single_buffer_size;
> -				while (data_len) {
> -					dma_unmap_page(oq->dev, oq->desc_ring[read_idx].buffer_ptr,
> -						       PAGE_SIZE, DMA_FROM_DEVICE);
> -					buff_info = (struct octep_vf_rx_buffer *)
> -						    &oq->buff_info[read_idx];
> -					buff_info->page = NULL;
> -					if (data_len < oq->buffer_size)
> -						data_len = 0;
> -					else
> -						data_len -= oq->buffer_size;
> -					desc_used++;
> -					read_idx = octep_vf_oq_next_idx(oq, read_idx);
> -				}
> +				octep_vf_oq_drop_rx(oq, buff_info, &read_idx, &desc_used);
>  				continue;
>  			}
>  			rx_bytes += buff_info->len;
> -- 
> 2.34.1
> 

^ permalink raw reply

* Re: [PATCH net-next 11/17] net: dsa: mv88e6xxx: Move available stats into info structure
From: Luke Howard @ 2026-07-06  9:42 UTC (permalink / raw)
  To: Jagielski, Jedrzej
  Cc: Andrew Lunn, Vladimir Oltean, David S. Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Simon Horman, Russell King,
	Richard Cochran, Florian Fainelli, Cedric Jehasse, Max Holtmann,
	Max Hunter, Tyrrell, Kieran, Ryan Wilkins, Mattias Forsblad,
	netdev@vger.kernel.org, linux-kernel@vger.kernel.org
In-Reply-To: <PH0PR11MB5902B0E9FF059E56C5D6D32BF0F12@PH0PR11MB5902.namprd11.prod.outlook.com>


> Is everything fine with this patch?
> There's nothing beside adding checks to some of the functions
> (which is reverted in the upcoming patch) what does not
> seem to be corresponding to the commit msg

This (Marvell RMU) patch series has been deferred, I’ll note your comments and return to them once the other patch series I have submitted have been processed.

Cheers,
Luke

^ permalink raw reply

* [PATCH net] net: openvswitch: reject oversized nested action attrs
From: Asim Viladi Oglu Manizada @ 2026-07-06  9:44 UTC (permalink / raw)
  To: netdev
  Cc: dev, aconole, echaudro, i.maximets, davem, edumazet, kuba, pabeni,
	horms, Asim Viladi Oglu Manizada, stable

Open vSwitch stores generated flow actions as nlattrs, whose nla_len
field is u16. Commit a1e64addf3ff ("net: openvswitch: remove
misbehaving actions length check") allowed the total sw_flow_actions
stream to grow beyond 64 KiB, which is valid, but also removed the last
guard preventing a generated nested action attribute from exceeding
U16_MAX.

An oversized generated container can thus be closed with a truncated
nla_len. A later dump or teardown then walks a structurally different
stream than the one that was validated. In particular, an oversized
nested CLONE/CT action may cause subsequent bytes in the generated
stream to be interpreted as independent actions.

Keep the larger total-action-stream behavior, but make nested action
close reject generated containers that do not fit in nla_len, and return
the error through all callers. For recursive SAMPLE, CLONE, DEC_TTL, and
CHECK_PKT_LEN builders, trim resource-owning action-list tails in reverse
construction order before discarding failed wrappers, so resources copied
into the rejected tails are released before the wrappers are removed.

Most failed outer wrappers are discarded by truncating actions_len after
child resources have been released. CHECK_PKT_LEN also trims its parent
after branch resources are gone. SET/TUNNEL close failures unwind their
known tun_dst ownership directly, and SET_TO_MASKED has no external
ownership and truncates on close failure.

Fixes: a1e64addf3ff ("net: openvswitch: remove misbehaving actions length check")
Cc: stable@vger.kernel.org
Assisted-by: avom-custom-harness:gpt-5.5-qwen3.6-mod-mix
Signed-off-by: Asim Viladi Oglu Manizada <manizada@pm.me>
---
 net/openvswitch/flow_netlink.c | 201 +++++++++++++++++++++++++--------
 1 file changed, 157 insertions(+), 44 deletions(-)

diff --git a/net/openvswitch/flow_netlink.c b/net/openvswitch/flow_netlink.c
index 13052408a132..d8079dee700e 100644
--- a/net/openvswitch/flow_netlink.c
+++ b/net/openvswitch/flow_netlink.c
@@ -2496,13 +2496,56 @@ static inline int add_nested_action_start(struct sw_flow_actions **sfa,
 	return used;
 }
 
-static inline void add_nested_action_end(struct sw_flow_actions *sfa,
-					 int st_offset)
+static inline int add_nested_action_end(struct sw_flow_actions *sfa,
+					int st_offset)
 {
-	struct nlattr *a = (struct nlattr *) ((unsigned char *)sfa->actions +
-							       st_offset);
+	struct nlattr *a;
+	u32 attr_len;
+
+	if (WARN_ON_ONCE(st_offset < 0 ||
+			 (u32)st_offset > sfa->actions_len))
+		return -EINVAL;
+
+	attr_len = sfa->actions_len - (u32)st_offset;
+	if (WARN_ON_ONCE(attr_len < NLA_HDRLEN))
+		return -EINVAL;
 
-	a->nla_len = sfa->actions_len - st_offset;
+	if (attr_len > U16_MAX)
+		return -EMSGSIZE;
+
+	a = (struct nlattr *)((u8 *)sfa->actions + st_offset);
+	a->nla_len = attr_len;
+	return 0;
+}
+
+/* Free the generated action-list tail at @start and truncate it.
+ * If @nested, @start points to its containing nlattr header.
+ */
+static void ovs_nla_trim(struct sw_flow_actions *sfa, int start, bool nested)
+{
+	const struct nlattr *actions;
+	u32 len;
+
+	if (start < 0)
+		return;
+
+	if (WARN_ON_ONCE((u32)start > sfa->actions_len))
+		return;
+
+	actions = (const struct nlattr *)((u8 *)sfa->actions + start);
+	len = sfa->actions_len - (u32)start;
+
+	if (nested) {
+		if (len < NLA_HDRLEN)
+			goto out;
+
+		actions = (const struct nlattr *)((u8 *)actions + NLA_HDRLEN);
+		len -= NLA_HDRLEN;
+	}
+
+	ovs_nla_free_nested_actions(actions, len);
+out:
+	sfa->actions_len = start;
 }
 
 static int __ovs_nla_copy_actions(struct net *net, const struct nlattr *attr,
@@ -2522,6 +2565,7 @@ static int validate_and_copy_sample(struct net *net, const struct nlattr *attr,
 	const struct nlattr *attrs[OVS_SAMPLE_ATTR_MAX + 1];
 	const struct nlattr *probability, *actions;
 	const struct nlattr *a;
+	int actions_start;
 	int rem, start, err;
 	struct sample_arg arg;
 
@@ -2565,18 +2609,27 @@ static int validate_and_copy_sample(struct net *net, const struct nlattr *attr,
 	err = ovs_nla_add_action(sfa, OVS_SAMPLE_ATTR_ARG, &arg, sizeof(arg),
 				 log);
 	if (err)
-		return err;
+		goto err;
 
+	actions_start = (*sfa)->actions_len;
 	err = __ovs_nla_copy_actions(net, actions, key, sfa,
 				     eth_type, vlan_tci, mpls_label_count, log,
 				     depth + 1);
 
 	if (err)
-		return err;
+		goto err_free;
 
-	add_nested_action_end(*sfa, start);
+	err = add_nested_action_end(*sfa, start);
+	if (err)
+		goto err_free;
 
 	return 0;
+
+err_free:
+	ovs_nla_trim(*sfa, actions_start, false);
+err:
+	(*sfa)->actions_len = start;
+	return err;
 }
 
 static int validate_and_copy_dec_ttl(struct net *net,
@@ -2624,18 +2677,31 @@ static int validate_and_copy_dec_ttl(struct net *net,
 		return start;
 
 	action_start = add_nested_action_start(sfa, OVS_DEC_TTL_ATTR_ACTION, log);
-	if (action_start < 0)
-		return action_start;
+	if (action_start < 0) {
+		err = action_start;
+		goto err;
+	}
 
 	err = __ovs_nla_copy_actions(net, actions, key, sfa, eth_type,
 				     vlan_tci, mpls_label_count, log,
 				     depth + 1);
 	if (err)
-		return err;
+		goto err_free;
+
+	err = add_nested_action_end(*sfa, action_start);
+	if (err)
+		goto err_free;
 
-	add_nested_action_end(*sfa, action_start);
-	add_nested_action_end(*sfa, start);
+	err = add_nested_action_end(*sfa, start);
+	if (err)
+		goto err_free;
 	return 0;
+
+err_free:
+	ovs_nla_trim(*sfa, action_start, true);
+err:
+	(*sfa)->actions_len = start;
+	return err;
 }
 
 static int validate_and_copy_clone(struct net *net,
@@ -2646,6 +2712,7 @@ static int validate_and_copy_clone(struct net *net,
 				   u32 mpls_label_count, bool log, bool last,
 				   u32 depth)
 {
+	int actions_start;
 	int start, err;
 	u32 exec;
 
@@ -2661,17 +2728,26 @@ static int validate_and_copy_clone(struct net *net,
 	err = ovs_nla_add_action(sfa, OVS_CLONE_ATTR_EXEC, &exec,
 				 sizeof(exec), log);
 	if (err)
-		return err;
+		goto err;
 
+	actions_start = (*sfa)->actions_len;
 	err = __ovs_nla_copy_actions(net, attr, key, sfa,
 				     eth_type, vlan_tci, mpls_label_count, log,
 				     depth + 1);
 	if (err)
-		return err;
+		goto err_free;
 
-	add_nested_action_end(*sfa, start);
+	err = add_nested_action_end(*sfa, start);
+	if (err)
+		goto err_free;
 
 	return 0;
+
+err_free:
+	ovs_nla_trim(*sfa, actions_start, false);
+err:
+	(*sfa)->actions_len = start;
+	return err;
 }
 
 void ovs_match_init(struct sw_flow_match *match,
@@ -2763,20 +2839,20 @@ static int validate_and_copy_set_tun(const struct nlattr *attr,
 	tun_dst = metadata_dst_alloc(key.tun_opts_len, METADATA_IP_TUNNEL,
 				     GFP_KERNEL);
 
-	if (!tun_dst)
-		return -ENOMEM;
+	if (!tun_dst) {
+		err = -ENOMEM;
+		goto err;
+	}
 
 	err = dst_cache_init(&tun_dst->u.tun_info.dst_cache, GFP_KERNEL);
-	if (err) {
-		dst_release((struct dst_entry *)tun_dst);
-		return err;
-	}
+	if (err)
+		goto err_free_tun_dst;
 
 	a = __add_action(sfa, OVS_KEY_ATTR_TUNNEL_INFO, NULL,
 			 sizeof(*ovs_tun), log);
 	if (IS_ERR(a)) {
-		dst_release((struct dst_entry *)tun_dst);
-		return PTR_ERR(a);
+		err = PTR_ERR(a);
+		goto err_free_tun_dst;
 	}
 
 	ovs_tun = nla_data(a);
@@ -2797,8 +2873,16 @@ static int validate_and_copy_set_tun(const struct nlattr *attr,
 	ip_tunnel_info_opts_set(tun_info,
 				TUN_METADATA_OPTS(&key, key.tun_opts_len),
 				key.tun_opts_len, dst_opt_type);
-	add_nested_action_end(*sfa, start);
+	err = add_nested_action_end(*sfa, start);
+	if (WARN_ON_ONCE(err))
+		goto err_free_tun_dst;
+
+	return 0;
 
+err_free_tun_dst:
+	dst_release((struct dst_entry *)tun_dst);
+err:
+	(*sfa)->actions_len = start;
 	return err;
 }
 
@@ -2971,7 +3055,7 @@ static int validate_set(const struct nlattr *a,
 
 	/* Convert non-masked non-tunnel set actions to masked set actions. */
 	if (!masked && key_type != OVS_KEY_ATTR_TUNNEL) {
-		int start, len = key_len * 2;
+		int err, start, len = key_len * 2;
 		struct nlattr *at;
 
 		*skip_copy = true;
@@ -2983,8 +3067,11 @@ static int validate_set(const struct nlattr *a,
 			return start;
 
 		at = __add_action(sfa, key_type, NULL, len, log);
-		if (IS_ERR(at))
-			return PTR_ERR(at);
+		if (IS_ERR(at)) {
+			err = PTR_ERR(at);
+			(*sfa)->actions_len = start;
+			return err;
+		}
 
 		memcpy(nla_data(at), nla_data(ovs_key), key_len); /* Key. */
 		memset(nla_data(at) + key_len, 0xff, key_len);    /* Mask. */
@@ -2994,7 +3081,11 @@ static int validate_set(const struct nlattr *a,
 
 			mask->ipv6_label &= htonl(0x000FFFFF);
 		}
-		add_nested_action_end(*sfa, start);
+		err = add_nested_action_end(*sfa, start);
+		if (WARN_ON_ONCE(err)) {
+			(*sfa)->actions_len = start;
+			return err;
+		}
 	}
 
 	return 0;
@@ -3040,7 +3131,8 @@ static int validate_and_copy_check_pkt_len(struct net *net,
 	const struct nlattr *acts_if_greater, *acts_if_lesser_eq;
 	struct nlattr *a[OVS_CHECK_PKT_LEN_ATTR_MAX + 1];
 	struct check_pkt_len_arg arg;
-	int nested_acts_start;
+	int greater_acts_start = -1;
+	int lesser_acts_start = -1;
 	int start, err;
 
 	err = nla_parse_deprecated_strict(a, OVS_CHECK_PKT_LEN_ATTR_MAX,
@@ -3075,37 +3167,58 @@ static int validate_and_copy_check_pkt_len(struct net *net,
 	err = ovs_nla_add_action(sfa, OVS_CHECK_PKT_LEN_ATTR_ARG, &arg,
 				 sizeof(arg), log);
 	if (err)
-		return err;
+		goto err_free;
 
-	nested_acts_start = add_nested_action_start(sfa,
-		OVS_CHECK_PKT_LEN_ATTR_ACTIONS_IF_LESS_EQUAL, log);
-	if (nested_acts_start < 0)
-		return nested_acts_start;
+	lesser_acts_start =
+		add_nested_action_start(sfa,
+					OVS_CHECK_PKT_LEN_ATTR_ACTIONS_IF_LESS_EQUAL,
+					log);
+	if (lesser_acts_start < 0) {
+		err = lesser_acts_start;
+		goto err_free;
+	}
 
 	err = __ovs_nla_copy_actions(net, acts_if_lesser_eq, key, sfa,
 				     eth_type, vlan_tci, mpls_label_count, log,
 				     depth + 1);
 
 	if (err)
-		return err;
+		goto err_free;
 
-	add_nested_action_end(*sfa, nested_acts_start);
+	err = add_nested_action_end(*sfa, lesser_acts_start);
+	if (err)
+		goto err_free;
 
-	nested_acts_start = add_nested_action_start(sfa,
-		OVS_CHECK_PKT_LEN_ATTR_ACTIONS_IF_GREATER, log);
-	if (nested_acts_start < 0)
-		return nested_acts_start;
+	greater_acts_start =
+		add_nested_action_start(sfa,
+					OVS_CHECK_PKT_LEN_ATTR_ACTIONS_IF_GREATER,
+					log);
+	if (greater_acts_start < 0) {
+		err = greater_acts_start;
+		goto err_free;
+	}
 
 	err = __ovs_nla_copy_actions(net, acts_if_greater, key, sfa,
 				     eth_type, vlan_tci, mpls_label_count, log,
 				     depth + 1);
 
 	if (err)
-		return err;
+		goto err_free;
+
+	err = add_nested_action_end(*sfa, greater_acts_start);
+	if (err)
+		goto err_free;
 
-	add_nested_action_end(*sfa, nested_acts_start);
-	add_nested_action_end(*sfa, start);
+	err = add_nested_action_end(*sfa, start);
+	if (err)
+		goto err_free;
 	return 0;
+
+err_free:
+	ovs_nla_trim(*sfa, greater_acts_start, true);
+	ovs_nla_trim(*sfa, lesser_acts_start, true);
+	ovs_nla_trim(*sfa, start, false);
+	return err;
 }
 
 static int validate_psample(const struct nlattr *attr)
-- 
2.53.0


^ permalink raw reply related

* Re: [PATCH net-next] tun: no longer rely on RTNL in tun_fill_info()
From: Paolo Abeni @ 2026-07-06  9:45 UTC (permalink / raw)
  To: Eric Dumazet, David S . Miller, Jakub Kicinski
  Cc: Simon Horman, Kuniyuki Iwashima, Andrew Lunn, netdev,
	eric.dumazet
In-Reply-To: <20260701125112.3652880-1-edumazet@google.com>

On 7/1/26 2:51 PM, Eric Dumazet wrote:
> Update tun_fill_info() to read device configuration fields (flags, owner,
> group, numqueues, numdisabled) locklessly using READ_ONCE().
> 
> Annotate all writes to these fields in the control paths with WRITE_ONCE()
> to prevent data races, as these fields can be modified concurrently via
> ioctls (TUNSETPERSIST, TUNSETOWNER, TUNSETGROUP, TUNSETIFF) or queue
> attaching/detaching.

Sashiko-gemini notes that:

__tun_chr_ioctl() -> tun_vnet_ioctl

can still update tun->flags with no annotation for TUN_VNET_LE. I'm not
sure if a follow-up or a v2 is preferred.

/P


^ permalink raw reply

* [PATCH net v2] tun/tap & vhost-net: make qdisc backpressure opt-in via IFF_BACKPRESSURE
From: Simon Schippers @ 2026-07-06  9:42 UTC (permalink / raw)
  To: Willem de Bruijn, Jason Wang, David S . Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Michael S . Tsirkin, netdev
  Cc: Simon Horman, Jonathan Corbet, Shuah Khan, Andrew Lunn,
	Tim Gebauer, Brett Sheffield, linux-doc, linux-kernel,
	Simon Schippers

Commit 1d6e569b7d0c ("tun/tap & vhost-net: avoid ptr_ring tail-drop
when a qdisc is present") did not show a relevant performance regression
in my testing but on Brett Sheffield's librecast testbed it shows a
significant performance drop in a IPv6 multicast testcase. The regression
can be pinpointed when multiple iperf3 UDP threads are sending. For 8
threads the performance dropped from 13.5 Gbit/s to 9.13 Gbit/s. This is
the reason why this patch makes the qdisc backpressure behavior opt-in.

One option to accomplish the opt-in would be to set the default qdisc to
noqueue at init. However this may also break userspace as users might
have chosen a custom qdisc even though most of the qdiscs did nothing
for tun/tap in the past due to missing backpressure...

This is the reason why in this patch, the flag IFF_BACKPRESSURE is
introduced instead which is required to enable the backpressure logic.
This means the stopping logic in tun_net_xmit() and the waking logic in
__tun_wake_queue() are skipped if the flag is disabled.

To avoid a possible stall due to disabling IFF_BACKPRESSURE, the new
helper tun_force_wake_queue() is implemented. The helper safely wakes the
respective netdev queue and resets cons_cnt while the consumer_lock and
the producer_lock of the ring are held. The helper is run in tun_attach()
when a queue (re)attaches, in tun_set_iff() for attached tfiles, and
in tun_queue_resize().

The documentation in tuntap.rst is updated accordingly.

Fixes: 1d6e569b7d0c ("tun/tap & vhost-net: avoid ptr_ring tail-drop when a qdisc is present")
Reported-by: Brett Sheffield <brett@librecast.net>
Closes: https://lore.kernel.org/netdev/akVnoOYQOrt8k-Gu@karahi.librecast.net/T/#u
Signed-off-by: Simon Schippers <simon.schippers@tu-dortmund.de>
---
V1 -> V2:
- Sashiko: Ensure detached queues are woken on re-attach by calling the
  new tun_force_wake_queue() helper from tun_attach(), and reuse it
  across the existing wake paths.
- Specify the failing test case in the commit message.
---
 Documentation/networking/tuntap.rst | 17 ++++++++++
 drivers/net/tun.c                   | 49 ++++++++++++++++++-----------
 include/uapi/linux/if_tun.h         |  1 +
 tools/include/uapi/linux/if_tun.h   |  1 +
 4 files changed, 50 insertions(+), 18 deletions(-)

diff --git a/Documentation/networking/tuntap.rst b/Documentation/networking/tuntap.rst
index 4d7087f727be..599264825dd2 100644
--- a/Documentation/networking/tuntap.rst
+++ b/Documentation/networking/tuntap.rst
@@ -206,6 +206,23 @@ enable is true we enable it, otherwise we disable it::
       return ioctl(fd, TUNSETQUEUE, (void *)&ifr);
   }
 
+3.4 qdisc backpressure
+----------------------
+
+Starting with Linux 7.2, IFF_BACKPRESSURE can be set to enable qdisc
+backpressure. Without it, TX drops occur when the internal ring buffer is
+full. With it, the kernel stops the TX queue instead, letting the qdisc
+hold packets. Drops only occur as a rare race. This can benefit protocols
+like TCP that react to drops. Backpressure requires a qdisc to be
+attached and has no effect with noqueue.
+
+The TUN/TAP ring buffer size can be reduced alongside this flag to
+further shift buffering into the qdisc and reduce bufferbloat, but comes
+at possible performance cost.
+
+When running multiple network streams in parallel, the flag may reduce
+performance due to the extra overhead of the backpressure mechanism.
+
 Universal TUN/TAP device driver Frequently Asked Question
 =========================================================
 
diff --git a/drivers/net/tun.c b/drivers/net/tun.c
index ffbe6f13fb1f..62e5eab4e345 100644
--- a/drivers/net/tun.c
+++ b/drivers/net/tun.c
@@ -98,7 +98,8 @@ static void tun_default_link_ksettings(struct net_device *dev,
 #define TUN_FASYNC	IFF_ATTACH_QUEUE
 
 #define TUN_FEATURES (IFF_NO_PI | IFF_ONE_QUEUE | IFF_VNET_HDR | \
-		      IFF_MULTI_QUEUE | IFF_NAPI | IFF_NAPI_FRAGS)
+		      IFF_MULTI_QUEUE | IFF_NAPI | IFF_NAPI_FRAGS | \
+		      IFF_BACKPRESSURE)
 
 #define GOODCOPY_LEN 128
 
@@ -694,6 +695,20 @@ static void tun_detach_all(struct net_device *dev)
 		module_put(THIS_MODULE);
 }
 
+static void tun_force_wake_queue(struct tun_struct *tun,
+				 struct tun_file *tfile)
+{
+	/* Ensure that the producer can not stop the
+	 * queue concurrently by taking locks.
+	 */
+	spin_lock_bh(&tfile->tx_ring.consumer_lock);
+	spin_lock(&tfile->tx_ring.producer_lock);
+	netif_wake_subqueue(tun->dev, tfile->queue_index);
+	tfile->cons_cnt = 0;
+	spin_unlock(&tfile->tx_ring.producer_lock);
+	spin_unlock_bh(&tfile->tx_ring.consumer_lock);
+}
+
 static int tun_attach(struct tun_struct *tun, struct file *file,
 		      bool skip_filter, bool napi, bool napi_frags,
 		      bool publish_tun)
@@ -737,11 +752,9 @@ static int tun_attach(struct tun_struct *tun, struct file *file,
 		goto out;
 	}
 
-	spin_lock(&tfile->tx_ring.consumer_lock);
-	tfile->cons_cnt = 0;
-	spin_unlock(&tfile->tx_ring.consumer_lock);
 	tfile->queue_index = tun->numqueues;
 	tfile->socket.sk->sk_shutdown &= ~RCV_SHUTDOWN;
+	tun_force_wake_queue(tun, tfile);
 
 	if (tfile->detached) {
 		/* Re-attach detached tfile, updating XDP queue_index */
@@ -1077,7 +1090,8 @@ static netdev_tx_t tun_net_xmit(struct sk_buff *skb, struct net_device *dev)
 
 	spin_lock(&tfile->tx_ring.producer_lock);
 	ret = __ptr_ring_produce(&tfile->tx_ring, skb);
-	if (!qdisc_txq_has_no_queue(queue) &&
+	if ((tun->flags & IFF_BACKPRESSURE) &&
+	    !qdisc_txq_has_no_queue(queue) &&
 	    __ptr_ring_check_produce(&tfile->tx_ring) == -ENOSPC) {
 		netif_tx_stop_queue(queue);
 		/* Paired with smp_mb() in __tun_wake_queue() */
@@ -2151,8 +2165,12 @@ static ssize_t tun_put_user(struct tun_struct *tun,
 static void __tun_wake_queue(struct tun_struct *tun,
 			     struct tun_file *tfile, int consumed)
 {
-	struct netdev_queue *txq = netdev_get_tx_queue(tun->dev,
-						tfile->queue_index);
+	struct netdev_queue *txq;
+
+	if (!(tun->flags & IFF_BACKPRESSURE))
+		return;
+
+	txq = netdev_get_tx_queue(tun->dev, tfile->queue_index);
 
 	/* Paired with smp_mb__after_atomic() in tun_net_xmit() */
 	smp_mb();
@@ -2764,7 +2782,7 @@ static int tun_set_iff(struct net *net, struct file *file, struct ifreq *ifr)
 	struct tun_struct *tun;
 	struct tun_file *tfile = file->private_data;
 	struct net_device *dev;
-	int err;
+	int err, i;
 
 	if (tfile->detached)
 		return -EINVAL;
@@ -2894,7 +2912,8 @@ static int tun_set_iff(struct net *net, struct file *file, struct ifreq *ifr)
 	 * xoff state.
 	 */
 	if (netif_running(tun->dev))
-		netif_tx_wake_all_queues(tun->dev);
+		for (i = 0; i < tun->numqueues; i++)
+			tun_force_wake_queue(tun, rtnl_dereference(tun->tfiles[i]));
 
 	strscpy(ifr->ifr_name, tun->dev->name);
 	return 0;
@@ -3690,15 +3709,9 @@ static int tun_queue_resize(struct tun_struct *tun)
 					  dev->tx_queue_len, GFP_KERNEL,
 					  tun_ptr_free);
 
-	if (!ret) {
-		for (i = 0; i < tun->numqueues; i++) {
-			tfile = rtnl_dereference(tun->tfiles[i]);
-			spin_lock(&tfile->tx_ring.consumer_lock);
-			netif_wake_subqueue(tun->dev, tfile->queue_index);
-			tfile->cons_cnt = 0;
-			spin_unlock(&tfile->tx_ring.consumer_lock);
-		}
-	}
+	if (!ret)
+		for (i = 0; i < tun->numqueues; i++)
+			tun_force_wake_queue(tun, rtnl_dereference(tun->tfiles[i]));
 
 	kfree(rings);
 	return ret;
diff --git a/include/uapi/linux/if_tun.h b/include/uapi/linux/if_tun.h
index 79d53c7a1ebd..73a77141315c 100644
--- a/include/uapi/linux/if_tun.h
+++ b/include/uapi/linux/if_tun.h
@@ -69,6 +69,7 @@
 #define IFF_NAPI_FRAGS	0x0020
 /* Used in TUNSETIFF to bring up tun/tap without carrier */
 #define IFF_NO_CARRIER	0x0040
+#define IFF_BACKPRESSURE	0x0080
 #define IFF_NO_PI	0x1000
 /* This flag has no real effect */
 #define IFF_ONE_QUEUE	0x2000
diff --git a/tools/include/uapi/linux/if_tun.h b/tools/include/uapi/linux/if_tun.h
index 2ec07de1d73b..97b670f5bc0a 100644
--- a/tools/include/uapi/linux/if_tun.h
+++ b/tools/include/uapi/linux/if_tun.h
@@ -67,6 +67,7 @@
 #define IFF_TAP		0x0002
 #define IFF_NAPI	0x0010
 #define IFF_NAPI_FRAGS	0x0020
+#define IFF_BACKPRESSURE	0x0080
 #define IFF_NO_PI	0x1000
 /* This flag has no real effect */
 #define IFF_ONE_QUEUE	0x2000
-- 
2.43.0


^ permalink raw reply related

* Re: [Intel-wired-lan] [PATCH iwl-net v1] idpf: fix lan_regs leak on core init failure
From: Marcin Szycik @ 2026-07-06  9:51 UTC (permalink / raw)
  To: xuanqiang.luo, intel-wired-lan
  Cc: netdev, linux-kernel, Tony Nguyen, Przemek Kitszel, Andrew Lunn,
	Joshua Hay, Tatyana Nikolova, Xuanqiang Luo
In-Reply-To: <20260703104132.47419-1-xuanqiang.luo@linux.dev>



On 03/07/2026 12:41, xuanqiang.luo@linux.dev wrote:
> From: Xuanqiang Luo <luoxuanqiang@kylinos.cn>
> 
> idpf_vc_core_init() gets the LAN memory region layout before mapping the
> regions and allocating vport resources. Both layout paths allocate
> hw->lan_regs, but later error paths return without freeing it.
> 
> idpf_vc_core_deinit() does not cover these paths because it returns unless
> IDPF_VC_CORE_INIT is set, and that bit is set only after core init
> succeeds.
> 
> Free hw->lan_regs on the post-allocation error paths and clear the
> pointer and region count.
> 
> Fixes: 6aa53e861c1a ("idpf: implement get LAN MMIO memory regions")
> Signed-off-by: Xuanqiang Luo <luoxuanqiang@kylinos.cn>
> ---
>  drivers/net/ethernet/intel/idpf/idpf_virtchnl.c | 14 +++++++++++---
>  1 file changed, 11 insertions(+), 3 deletions(-)
> 
> diff --git a/drivers/net/ethernet/intel/idpf/idpf_virtchnl.c b/drivers/net/ethernet/intel/idpf/idpf_virtchnl.c
> index be66f9b2e101c..da49bb7b7e671 100644
> --- a/drivers/net/ethernet/intel/idpf/idpf_virtchnl.c
> +++ b/drivers/net/ethernet/intel/idpf/idpf_virtchnl.c
> @@ -3479,6 +3479,7 @@ static int idpf_vport_params_buf_alloc(struct idpf_adapter *adapter)
>   */
>  int idpf_vc_core_init(struct idpf_adapter *adapter)
>  {
> +	struct idpf_hw *hw = &adapter->hw;
>  	int task_delay = 30;
>  	u16 num_max_vports;
>  	int err = 0;
> @@ -3550,15 +3551,18 @@ int idpf_vc_core_init(struct idpf_adapter *adapter)
>  	if (err) {
>  		dev_err(&adapter->pdev->dev, "Failed to map BAR0 region(s): %d\n",
>  			err);
> -		return -ENOMEM;
> +		err = -ENOMEM;
> +		goto err_lan_regs;
>  	}
>  
>  	pci_sriov_set_totalvfs(adapter->pdev, idpf_get_max_vfs(adapter));
>  	num_max_vports = idpf_get_max_vports(adapter);
>  	adapter->max_vports = num_max_vports;
>  	adapter->vports = kzalloc_objs(*adapter->vports, num_max_vports);
> -	if (!adapter->vports)
> -		return -ENOMEM;
> +	if (!adapter->vports) {
> +		err = -ENOMEM;
> +		goto err_lan_regs;
> +	}
>  
>  	if (!adapter->netdevs) {
>  		adapter->netdevs = kzalloc_objs(struct net_device *,
> @@ -3624,6 +3628,10 @@ int idpf_vc_core_init(struct idpf_adapter *adapter)
>  err_netdev_alloc:
>  	kfree(adapter->vports);
>  	adapter->vports = NULL;
> +err_lan_regs:
> +	kfree(hw->lan_regs);
> +	hw->lan_regs = NULL;
> +	hw->num_lan_regs = 0;
>  	return err;
>  
>  init_failed:

Does this apply? struct idpf_hw was removed in 9f4334ac4a5a ("idpf: refactor
idpf to use libie control queues") [1].

[1] https://lore.kernel.org/intel-wired-lan/20260608144127.2751230-10-larysa.zaremba@intel.com/

Thanks,
Marcin

^ permalink raw reply

* Re: [PATCH net-next] amt: no longer rely on RTNL in amt_fill_info()
From: patchwork-bot+netdevbpf @ 2026-07-06 10:00 UTC (permalink / raw)
  To: Eric Dumazet
  Cc: davem, kuba, pabeni, horms, kuniyu, andrew+netdev, netdev,
	eric.dumazet
In-Reply-To: <20260701125016.3650708-1-edumazet@google.com>

Hello:

This patch was applied to netdev/net-next.git (main)
by Paolo Abeni <pabeni@redhat.com>:

On Wed,  1 Jul 2026 12:50:16 +0000 you wrote:
> Update amt_fill_info() to run under RCU read lock instead of RTNL.
> 
> The AMT device configuration fields (mode, relay_port, gw_port, local_ip,
> discovery_ip, max_tunnels) and stream_dev pointer are initialized during
> device creation (amt_newlink) and are immutable. Accessing them locklessly
> is safe. The stream_dev net_device structure is protected from being freed
> by RCU.
> 
> [...]

Here is the summary with links:
  - [net-next] amt: no longer rely on RTNL in amt_fill_info()
    https://git.kernel.org/netdev/net-next/c/586c4dcf28eb

You are awesome, thank you!
-- 
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/patchwork/pwbot.html



^ permalink raw reply

* Re: [Intel-wired-lan] [PATCH] ixgbe: validate E610 PFA TLV bounds
From: Marcin Szycik @ 2026-07-06 10:02 UTC (permalink / raw)
  To: Pengpeng Hou, Tony Nguyen
  Cc: Przemek Kitszel, Andrew Lunn, David S. Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, intel-wired-lan, netdev,
	linux-kernel
In-Reply-To: <20260706092500.79044-1-pengpeng@iscas.ac.cn>



On 06/07/2026 11:25, Pengpeng Hou wrote:
> ixgbe_get_pfa_module_tlv() walks E610 PFA TLV records stored in
> EEPROM.
> 
> Stop parsing malformed TLVs whose header or declared value length would
> exceed the PFA boundary.
> 
> Signed-off-by: Pengpeng Hou <pengpeng@iscas.ac.cn>
> ---
>  drivers/net/ethernet/intel/ixgbe/ixgbe_e610.c | 6 ++++++
>  1 file changed, 6 insertions(+)
> 
> diff --git a/drivers/net/ethernet/intel/ixgbe/ixgbe_e610.c b/drivers/net/ethernet/intel/ixgbe/ixgbe_e610.c
> index 4d8ae5b56145..03e88bdf5a43 100644
> --- a/drivers/net/ethernet/intel/ixgbe/ixgbe_e610.c
> +++ b/drivers/net/ethernet/intel/ixgbe/ixgbe_e610.c
> @@ -3895,6 +3895,9 @@ static int ixgbe_get_pfa_module_tlv(struct ixgbe_hw *hw, u16 *module_tlv,
>  	while (next_tlv < pfa_end_ptr) {
>  		u16 tlv_sub_module_type, tlv_len;
>  
> +		if (pfa_end_ptr - next_tlv < 2)
> +			break;

This check could go in the while condition above.

> +
>  		/* Read TLV type */
>  		err = ixgbe_read_ee_aci_e610(hw, next_tlv,
>  					     &tlv_sub_module_type);
> @@ -3917,6 +3920,9 @@ static int ixgbe_get_pfa_module_tlv(struct ixgbe_hw *hw, u16 *module_tlv,
>  		/* Check next TLV, i.e. current TLV pointer + length + 2 words
>  		 * (for current TLV's type and length).
>  		 */
> +		if (tlv_len > pfa_end_ptr - next_tlv - 2)
> +			break;
> +
>  		next_tlv = next_tlv + tlv_len + 2;

Would be nice to define the magic number (2), since we're reusing it now.

>  	}
>  	/* Module does not exist */

Thanks,
Marcin

^ permalink raw reply

* Re: [PATCH net] qede: fix off-by-one in BD ring consumption on build_skb failure
From: Paolo Abeni @ 2026-07-06 10:02 UTC (permalink / raw)
  To: Shigeru Yoshida, Jamie Bainbridge
  Cc: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Matvey Kovalev, Pavel Zhigulin, netdev, linux-kernel
In-Reply-To: <875x2te8l3.fsf@redhat.com>

On 7/5/26 5:53 PM, Shigeru Yoshida wrote:
> Jamie Bainbridge <jamie.bainbridge@gmail.com> writes:
> 
>> On Fri, 3 Jul 2026 at 09:52, Jamie Bainbridge
>> <jamie.bainbridge@gmail.com> wrote:
>>>
>>> On Wed, 1 Jul 2026 at 02:47, Shigeru Yoshida <syoshida@redhat.com> wrote:
>>>>
>>>> qede_rx_build_skb() and qede_tpa_rx_build_skb() do not check for a
>>>> NULL return from qede_build_skb(). When it returns NULL under memory
>>>> pressure, the functions still consume a BD from the ring before
>>>> returning NULL. The callers then recycle additional BDs, resulting in
>>>> one extra BD being consumed (off-by-one). This desynchronizes the BD
>>>> ring, which can corrupt DMA page reference counts and lead to SLUB
>>>> freelist corruption.
>>>
>>> Good catch.
>>>
>>> Reviewed-by: Jamie Bainbridge <jamie.bainbridge@gmail.com>
>>
>> Sorry for the double mail.
>>
>> I believe the Fixes: should be against the problematic original code:
>>
>> Fixes: 8a8633978b842 ("qede: Add build_skb() support.")
>>
>> because that is what you are fixing.
> 
> Thank you for your review.
> 
> The problematic code was introduced in commit 8a8633978b84 ("qede: Add
> build_skb() support."), so putting this in the Fixes tag would be
> correct.
> 
> I'll send the v2 patch with the modified Fixes tag.

No need for a v2: I'll update the fixes tag while applying the patch.
The PW is already quite significant and I think it's currently better to
avoid repost if possible.

Thanks!

Paolo


^ permalink raw reply

* [PATCH iwl-next v2] ixgbe: E610: force phy link to get down when interface is down
From: Jedrzej Jagielski @ 2026-07-06  9:43 UTC (permalink / raw)
  To: intel-wired-lan
  Cc: anthony.l.nguyen, netdev, Jedrzej Jagielski, Aleksandr Loktionov

For the E610 family, similarly to the E8xx adapters, the default behavior
is for the PHY link to remain up even when the corresponding OS interface
is down.

Add function setting down the PHY config IXGBE_ACI_PHY_ENA_LINK bit
what leads to disabling PHY link.

Now ixgbe_close() needs to share some of the ixgbe_watchdog_link_is_down
code so move the common part into the separate function.

Align functionality with the implementation of the ice driver.

Let user to configure link-down-on-close enablement through ethtool.

Reviewed-by: Aleksandr Loktionov <aleksandr.loktionov@intel.com>
Signed-off-by: Jedrzej Jagielski <jedrzej.jagielski@intel.com>
---
v2: apply Paul's notes
---
 drivers/net/ethernet/intel/ixgbe/ixgbe.h      |  1 +
 drivers/net/ethernet/intel/ixgbe/ixgbe_e610.c | 35 ++++++++++++++++++-
 drivers/net/ethernet/intel/ixgbe/ixgbe_e610.h |  1 +
 .../net/ethernet/intel/ixgbe/ixgbe_ethtool.c  | 15 ++++++++
 drivers/net/ethernet/intel/ixgbe/ixgbe_main.c | 27 +++++++++++---
 5 files changed, 73 insertions(+), 6 deletions(-)

diff --git a/drivers/net/ethernet/intel/ixgbe/ixgbe.h b/drivers/net/ethernet/intel/ixgbe/ixgbe.h
index 30f62174acf2..7bbb82dd962c 100644
--- a/drivers/net/ethernet/intel/ixgbe/ixgbe.h
+++ b/drivers/net/ethernet/intel/ixgbe/ixgbe.h
@@ -685,6 +685,7 @@ struct ixgbe_adapter {
 #define IXGBE_FLAG2_MOD_POWER_UNSUPPORTED	BIT(22)
 #define IXGBE_FLAG2_API_MISMATCH		BIT(23)
 #define IXGBE_FLAG2_FW_ROLLBACK			BIT(24)
+#define IXGBE_FLAG2_LINK_DOWN_ON_CLOSE		BIT(25)
 
 	/* Tx fast path data */
 	int num_tx_queues;
diff --git a/drivers/net/ethernet/intel/ixgbe/ixgbe_e610.c b/drivers/net/ethernet/intel/ixgbe/ixgbe_e610.c
index 831cfe9a4697..02bc0dac5123 100644
--- a/drivers/net/ethernet/intel/ixgbe/ixgbe_e610.c
+++ b/drivers/net/ethernet/intel/ixgbe/ixgbe_e610.c
@@ -1923,6 +1923,33 @@ void ixgbe_fc_autoneg_e610(struct ixgbe_hw *hw)
 	hw->fc.current_mode = hw->fc.requested_mode;
 }
 
+/**
+ * ixgbe_disable_phy_link - force phy link to get down
+ * @hw: pointer to hardware structure
+ *
+ * Send 0x0601 with the IXGBE_ACI_PHY_ENA_LINK bit set down.
+ *
+ * Return: the exit code of the operation.
+ */
+int ixgbe_disable_phy_link(struct ixgbe_hw *hw)
+{
+	struct ixgbe_aci_cmd_get_phy_caps_data pcaps = {};
+	struct ixgbe_aci_cmd_set_phy_cfg_data pcfg = {};
+	int err;
+
+	err = ixgbe_aci_get_phy_caps(hw, false, IXGBE_ACI_REPORT_ACTIVE_CFG,
+				     &pcaps);
+	if (err)
+		return err;
+
+	ixgbe_copy_phy_caps_to_cfg(&pcaps, &pcfg);
+
+	pcfg.caps &= ~IXGBE_ACI_PHY_ENA_LINK;
+	pcfg.caps |= IXGBE_ACI_PHY_ENA_AUTO_LINK_UPDT;
+
+	return ixgbe_aci_set_phy_cfg(hw, &pcfg);
+}
+
 /**
  * ixgbe_disable_rx_e610 - Disable RX unit
  * @hw: pointer to hardware structure
@@ -2207,6 +2234,7 @@ int ixgbe_setup_phy_link_e610(struct ixgbe_hw *hw)
 	u8 rmode = IXGBE_ACI_REPORT_TOPO_CAP_MEDIA;
 	u64 sup_phy_type_low, sup_phy_type_high;
 	u64 phy_type_low = 0, phy_type_high = 0;
+	bool force_on_required;
 	int err;
 
 	err = ixgbe_aci_get_link_info(hw, false, NULL);
@@ -2272,6 +2300,11 @@ int ixgbe_setup_phy_link_e610(struct ixgbe_hw *hw)
 		phy_type_high |= IXGBE_PHY_TYPE_HIGH_10G_USXGMII;
 	}
 
+	/* If IXGBE_ACI_PHY_ENA_LINK has been explicitly disabled that means
+	 * we need to force PHY link UP state during PHY link setup
+	 */
+	force_on_required = !(pcfg.caps & IXGBE_ACI_PHY_ENA_LINK);
+
 	/* Mask the set values to avoid requesting unsupported link types. */
 	phy_type_low &= sup_phy_type_low;
 	pcfg.phy_type_low = cpu_to_le64(phy_type_low);
@@ -2280,7 +2313,7 @@ int ixgbe_setup_phy_link_e610(struct ixgbe_hw *hw)
 
 	if (pcfg.phy_type_high != pcaps.phy_type_high ||
 	    pcfg.phy_type_low != pcaps.phy_type_low ||
-	    pcfg.caps != pcaps.caps) {
+	    pcfg.caps != pcaps.caps || force_on_required) {
 		pcfg.caps |= IXGBE_ACI_PHY_ENA_LINK;
 		pcfg.caps |= IXGBE_ACI_PHY_ENA_AUTO_LINK_UPDT;
 
diff --git a/drivers/net/ethernet/intel/ixgbe/ixgbe_e610.h b/drivers/net/ethernet/intel/ixgbe/ixgbe_e610.h
index 2cb76a3d30ae..59044d67ebeb 100644
--- a/drivers/net/ethernet/intel/ixgbe/ixgbe_e610.h
+++ b/drivers/net/ethernet/intel/ixgbe/ixgbe_e610.h
@@ -50,6 +50,7 @@ int ixgbe_cfg_phy_fc(struct ixgbe_hw *hw,
 		     enum ixgbe_fc_mode req_mode);
 int ixgbe_setup_fc_e610(struct ixgbe_hw *hw);
 void ixgbe_fc_autoneg_e610(struct ixgbe_hw *hw);
+int ixgbe_disable_phy_link(struct ixgbe_hw *hw);
 void ixgbe_disable_rx_e610(struct ixgbe_hw *hw);
 int ixgbe_init_phy_ops_e610(struct ixgbe_hw *hw);
 int ixgbe_identify_phy_e610(struct ixgbe_hw *hw);
diff --git a/drivers/net/ethernet/intel/ixgbe/ixgbe_ethtool.c b/drivers/net/ethernet/intel/ixgbe/ixgbe_ethtool.c
index b8e85bc91a27..16e26d54f3be 100644
--- a/drivers/net/ethernet/intel/ixgbe/ixgbe_ethtool.c
+++ b/drivers/net/ethernet/intel/ixgbe/ixgbe_ethtool.c
@@ -139,6 +139,8 @@ static const char ixgbe_priv_flags_strings[][ETH_GSTRING_LEN] = {
 	"vf-ipsec",
 #define IXGBE_PRIV_FLAGS_AUTO_DISABLE_VF	BIT(2)
 	"mdd-disable-vf",
+#define IXGBE_PRIV_LINK_DOWN_ON_CLOSE	BIT(3)
+	"link-down-on-close",
 };
 
 #define IXGBE_PRIV_FLAGS_STR_LEN ARRAY_SIZE(ixgbe_priv_flags_strings)
@@ -3822,6 +3824,9 @@ static u32 ixgbe_get_priv_flags(struct net_device *netdev)
 	if (adapter->flags2 & IXGBE_FLAG2_AUTO_DISABLE_VF)
 		priv_flags |= IXGBE_PRIV_FLAGS_AUTO_DISABLE_VF;
 
+	if (adapter->flags2 & IXGBE_FLAG2_LINK_DOWN_ON_CLOSE)
+		priv_flags |= IXGBE_PRIV_LINK_DOWN_ON_CLOSE;
+
 	return priv_flags;
 }
 
@@ -3859,6 +3864,16 @@ static int ixgbe_set_priv_flags(struct net_device *netdev, u32 priv_flags)
 		}
 	}
 
+	flags2 &= ~IXGBE_FLAG2_LINK_DOWN_ON_CLOSE;
+	if (priv_flags & IXGBE_PRIV_LINK_DOWN_ON_CLOSE) {
+		if (adapter->hw.mac.type == ixgbe_mac_e610) {
+			flags2 |= IXGBE_FLAG2_LINK_DOWN_ON_CLOSE;
+		} else {
+			e_info(probe, "Cannot set private flags: Feature supported only for E610 devices\n");
+			return -EOPNOTSUPP;
+		}
+	}
+
 	if (flags2 != adapter->flags2) {
 		adapter->flags2 = flags2;
 
diff --git a/drivers/net/ethernet/intel/ixgbe/ixgbe_main.c b/drivers/net/ethernet/intel/ixgbe/ixgbe_main.c
index 7a0783c1abc1..f650fa45b9ef 100644
--- a/drivers/net/ethernet/intel/ixgbe/ixgbe_main.c
+++ b/drivers/net/ethernet/intel/ixgbe/ixgbe_main.c
@@ -7551,6 +7551,17 @@ static void ixgbe_close_suspend(struct ixgbe_adapter *adapter)
 	ixgbe_free_all_rx_resources(adapter);
 }
 
+static void ixgbe_handle_link_down(struct ixgbe_adapter *adapter)
+{
+	struct net_device *netdev = adapter->netdev;
+
+	if (test_bit(__IXGBE_PTP_RUNNING, &adapter->state))
+		ixgbe_ptp_start_cyclecounter(adapter);
+
+	e_info(drv, "NIC Link is Down\n");
+	netif_carrier_off(netdev);
+}
+
 /**
  * ixgbe_close - Disables a network interface
  * @netdev: network interface device structure
@@ -7573,6 +7584,16 @@ int ixgbe_close(struct net_device *netdev)
 
 	ixgbe_fdir_filter_exit(adapter);
 
+	if (adapter->flags2 & IXGBE_FLAG2_LINK_DOWN_ON_CLOSE) {
+		int err;
+
+		err = ixgbe_disable_phy_link(&adapter->hw);
+		if (err)
+			e_error(drv, "Cannot set PHY link down\n");
+
+		ixgbe_handle_link_down(adapter);
+	}
+
 	ixgbe_release_hw_control(adapter);
 
 	return 0;
@@ -8251,11 +8272,7 @@ static void ixgbe_watchdog_link_is_down(struct ixgbe_adapter *adapter)
 	if (ixgbe_is_sfp(hw) && hw->mac.type == ixgbe_mac_82598EB)
 		adapter->flags2 |= IXGBE_FLAG2_SEARCH_FOR_SFP;
 
-	if (test_bit(__IXGBE_PTP_RUNNING, &adapter->state))
-		ixgbe_ptp_start_cyclecounter(adapter);
-
-	e_info(drv, "NIC Link is Down\n");
-	netif_carrier_off(netdev);
+	ixgbe_handle_link_down(adapter);
 }
 
 static bool ixgbe_ring_tx_pending(struct ixgbe_adapter *adapter)
-- 
2.31.1


^ permalink raw reply related

* [PATCH net-next] net: skbuff: use net_zcopy_get() instead of refcount_inc()
From: Yun Lu @ 2026-07-06 10:02 UTC (permalink / raw)
  To: davem, edumazet, kuba, pabeni, horms, kerneljasonxing, kuniyu
  Cc: mhal, bjorn, jiayuan.chen, netdev

From: Yun Lu <luyun@kylinos.cn>

The net_zcopy_get() increments the uarg->refcnt, which is called both
in pskb_carve_inside_header() and pskb_carve_inside_nonlinear().
Also use net_zcopy_get() in pskb_expand_head for code consistency.

No functional change intended.

Signed-off-by: Yun Lu <luyun@kylinos.cn>
---
 net/core/skbuff.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/net/core/skbuff.c b/net/core/skbuff.c
index 18dabb4e9cfa..bcf3b2c65fb9 100644
--- a/net/core/skbuff.c
+++ b/net/core/skbuff.c
@@ -2326,7 +2326,7 @@ int pskb_expand_head(struct sk_buff *skb, int nhead, int ntail,
 		if (skb_orphan_frags(skb, gfp_mask))
 			goto nofrags;
 		if (skb_zcopy(skb))
-			refcount_inc(&skb_uarg(skb)->refcnt);
+			net_zcopy_get(skb_zcopy(skb));
 		for (i = 0; i < skb_shinfo(skb)->nr_frags; i++)
 			skb_frag_ref(skb, i);
 
-- 
2.43.0


^ permalink raw reply related

* Re: [PATCH v2 2/2] bonding: reuse neigh_setup from slave neigh_parms
From: Paritosh Potukuchi @ 2026-07-06 10:07 UTC (permalink / raw)
  To: Kuniyuki Iwashima
  Cc: netdev, linux-kernel, kuba, edumazet, andrew+netdev, jv, davem,
	pabeni, paritosh.potukuchi
In-Reply-To: <CAAVpQUChEhWDxVPXOOQQcPMBLNocCACKNiX6AZwy5O-3+wgbtw@mail.gmail.com>

Hi Kuniyuki,

>The real user is qeth_l3_main.c only and it's not compiled
>on 99% host.

>We usually bail out at if (!slave_ops->ndo_neigh_setup),
>which is called after your neigh_parms_lookup_dev(), and
>there is no need to do O(n) traversal.

>With 2K netns, it could incur unnecessary 4K traversal (lo
>+ another dev) for each neigh creation, which is done under
>RTNL or from interrupt context.

>So, it will be a problem.

That makes sense. Thanks for the insight.

>It does not help.  Just populating fields does not change the
>loop detection logic.

True. Just populating fields would not suffice. Do you think
it would a good solution to modify the ndo_neigh_setup 
function?

Currently it takes a (netdev, parms) pair as an argument.

The way the ndo_neigh_setup is being used , both in stacked/
virtual devices and slave devices, is to expose the underlying
netdev's local neigh_setup function, through the parms argument.

There is a TODO in the bond_main.c file that suggests the 
following:

 /* TODO: find another way [1] to implement this.
  * Passing a zeroed structure is fragile,
  * but at least we do not pass garbage.
  *
  * [1] One way would be that ndo_neigh_setup() never touch
  *     struct neigh_parms, but propagate the new neigh_setup()
  *     back to ___neigh_create() / neigh_parms_alloc()
  */

As the TODO suggests, would it be a good idea to modify the 
ndo_neigh_setup function to be able to return the netdev's
local neigh_setup function.
This would solve the problem of passing a dummy parms structure.

typedef int (*neigh_setup_fn_t)(struct neighbour *);

static neigh_setup_fn_t ndo_neigh_setup(struct net_device *dev)
{
    return qeth_l3_neigh_setup_noarp;
}


Though the problem with this approach is that ndo_neigh_setup 
loses the flexibility to modify other fields in the parms 
structure.

What is your opinion on this? 

^ permalink raw reply

* Re: [PATCH net] qede: fix off-by-one in BD ring consumption on build_skb failure
From: patchwork-bot+netdevbpf @ 2026-07-06 10:10 UTC (permalink / raw)
  To: Shigeru Yoshida
  Cc: andrew+netdev, davem, edumazet, kuba, pabeni, matvey.kovalev,
	Pavel.Zhigulin, jamie.bainbridge, netdev, linux-kernel
In-Reply-To: <20260630164623.3152625-1-syoshida@redhat.com>

Hello:

This patch was applied to netdev/net.git (main)
by Paolo Abeni <pabeni@redhat.com>:

On Wed,  1 Jul 2026 01:46:20 +0900 you wrote:
> qede_rx_build_skb() and qede_tpa_rx_build_skb() do not check for a
> NULL return from qede_build_skb(). When it returns NULL under memory
> pressure, the functions still consume a BD from the ring before
> returning NULL. The callers then recycle additional BDs, resulting in
> one extra BD being consumed (off-by-one). This desynchronizes the BD
> ring, which can corrupt DMA page reference counts and lead to SLUB
> freelist corruption.
> 
> [...]

Here is the summary with links:
  - [net] qede: fix off-by-one in BD ring consumption on build_skb failure
    https://git.kernel.org/netdev/net/c/a0a558ca7e75

You are awesome, thank you!
-- 
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/patchwork/pwbot.html



^ permalink raw reply


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox