* 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
* 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 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
* [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
* [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 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 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("a_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("a_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("a_release_completed, 1);
+
+ return 0;
+}
+
+char __license[] SEC("license") = "GPL";
--
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 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 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 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 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 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 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] net: hinic: validate firmware image section bounds
From: Pengpeng Hou @ 2026-07-06 9:34 UTC (permalink / raw)
To: Cai Huoqing
Cc: Pengpeng Hou, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, netdev, linux-kernel
The firmware update path copies each section from the image with an offset
and length supplied by the firmware header. The image validator only
checked the sum of section lengths and used fw_len + header_size to check
the file size, but it did not prove that each section offset and length
fits in the firmware payload.
Reject images with a truncated header, avoid the fw_len plus header-size
addition, validate each section type before it is used as a bit index, and
prove every section range with offset <= fw_len and len <= fw_len - offset.
Signed-off-by: Pengpeng Hou <pengpeng@iscas.ac.cn>
---
.../net/ethernet/huawei/hinic/hinic_devlink.c | 57 ++++++++++++++++---
1 file changed, 49 insertions(+), 8 deletions(-)
diff --git a/drivers/net/ethernet/huawei/hinic/hinic_devlink.c b/drivers/net/ethernet/huawei/hinic/hinic_devlink.c
index c977c43e5d5a..0c06fbdadad3 100644
--- a/drivers/net/ethernet/huawei/hinic/hinic_devlink.c
+++ b/drivers/net/ethernet/huawei/hinic/hinic_devlink.c
@@ -20,14 +20,25 @@
#include "hinic_devlink.h"
#include "hinic_hw_dev.h"
+static bool hinic_fw_section_valid(u32 fw_len, u32 offset, u32 len)
+{
+ return offset <= fw_len && len <= fw_len - offset;
+}
+
static bool check_image_valid(struct hinic_devlink_priv *priv, const u8 *buf,
- u32 image_size, struct host_image_st *host_image)
+ size_t image_size, struct host_image_st *host_image)
{
- struct fw_image_st *fw_image = NULL;
+ const struct fw_image_st *fw_image;
u32 len = 0;
u32 i;
- fw_image = (struct fw_image_st *)buf;
+ if (image_size < UPDATEFW_IMAGE_HEAD_SIZE) {
+ dev_err(&priv->hwdev->hwif->pdev->dev,
+ "Wrong image size read from file\n");
+ return false;
+ }
+
+ fw_image = (const struct fw_image_st *)buf;
if (fw_image->fw_magic != HINIC_MAGIC_NUM) {
dev_err(&priv->hwdev->hwif->pdev->dev, "Wrong fw_magic read from file, fw_magic: 0x%x\n",
@@ -41,14 +52,44 @@ static bool check_image_valid(struct hinic_devlink_priv *priv, const u8 *buf,
return false;
}
+ if (fw_image->fw_len != image_size - UPDATEFW_IMAGE_HEAD_SIZE) {
+ dev_err(&priv->hwdev->hwif->pdev->dev,
+ "Wrong data size read from file\n");
+ return false;
+ }
+
for (i = 0; i < fw_image->fw_info.fw_section_cnt; i++) {
- len += fw_image->fw_section_info[i].fw_section_len;
- host_image->image_section_info[i] = fw_image->fw_section_info[i];
+ const struct fw_section_info_st *section =
+ &fw_image->fw_section_info[i];
+
+ if (section->fw_section_type >= FILE_TYPE_TOTAL_NUM) {
+ dev_err(&priv->hwdev->hwif->pdev->dev,
+ "Wrong section type read from file: %u\n",
+ section->fw_section_type);
+ return false;
+ }
+
+ if (!hinic_fw_section_valid(fw_image->fw_len,
+ section->fw_section_offset,
+ section->fw_section_len)) {
+ dev_err(&priv->hwdev->hwif->pdev->dev,
+ "Wrong section size read from file\n");
+ return false;
+ }
+
+ if (section->fw_section_len > fw_image->fw_len - len) {
+ dev_err(&priv->hwdev->hwif->pdev->dev,
+ "Wrong data size read from file\n");
+ return false;
+ }
+
+ len += section->fw_section_len;
+ host_image->image_section_info[i] = *section;
}
- if (len != fw_image->fw_len ||
- (fw_image->fw_len + UPDATEFW_IMAGE_HEAD_SIZE) != image_size) {
- dev_err(&priv->hwdev->hwif->pdev->dev, "Wrong data size read from file\n");
+ if (len != fw_image->fw_len) {
+ dev_err(&priv->hwdev->hwif->pdev->dev,
+ "Wrong data size read from file\n");
return false;
}
--
2.43.0
^ permalink raw reply related
* [RFC net-next] pppoe: fix stale device name shown in procfs
From: Qingfang Deng @ 2026-07-06 9:34 UTC (permalink / raw)
To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Qingfang Deng, Kees Cook, Felix Fietkau,
Eric Woudstra, netdev, linux-kernel
Cc: linux-ppp
In pppoe_seq_show(), the device name is currently read from
po->pppoe_pa.dev, which contains the name at the time the socket was
connected. If the lower network device is renamed afterwards, reading
/proc/net/pppoe will still print the old name.
Fix this by reading the name directly from the bound net_device via the
po->pppoe_dev pointer. Note that the pointer can be cleared when the
socket is being released, so a NULL check is required.
Signed-off-by: Qingfang Deng <qingfang.deng@linux.dev>
---
drivers/net/ppp/pppoe.c | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/drivers/net/ppp/pppoe.c b/drivers/net/ppp/pppoe.c
index 4a018acb5262..5bf526c596b6 100644
--- a/drivers/net/ppp/pppoe.c
+++ b/drivers/net/ppp/pppoe.c
@@ -960,6 +960,7 @@ static int pppoe_recvmsg(struct socket *sock, struct msghdr *m,
#ifdef CONFIG_PROC_FS
static int pppoe_seq_show(struct seq_file *seq, void *v)
{
+ struct net_device *dev;
struct pppox_sock *po;
char *dev_name;
@@ -969,7 +970,10 @@ static int pppoe_seq_show(struct seq_file *seq, void *v)
}
po = v;
- dev_name = po->pppoe_pa.dev;
+ dev = READ_ONCE(po->pppoe_dev);
+ if (!dev)
+ goto out;
+ dev_name = dev->name;
seq_printf(seq, "%08X %pM %8s\n",
po->pppoe_pa.sid, po->pppoe_pa.remote, dev_name);
--
2.43.0
^ permalink raw reply related
* [PATCH] net: bnx2x: validate firmware section bounds without overflow
From: Pengpeng Hou @ 2026-07-06 9:34 UTC (permalink / raw)
To: Sudarsana Kalluru
Cc: Pengpeng Hou, Manish Chopra, Andrew Lunn, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, netdev, linux-kernel
The firmware loader validates each section with offset + length before it
uses section offsets to read data from the firmware image. Both fields are
32-bit values from the firmware header, so the addition can wrap before it
is compared with the firmware size.
Validate sections as offset <= size and length <= size - offset instead.
Also require the firmware version section to contain the four bytes that
bnx2x_check_firmware() reads later.
Signed-off-by: Pengpeng Hou <pengpeng@iscas.ac.cn>
---
drivers/net/ethernet/broadcom/bnx2x/bnx2x_main.c | 12 +++++++++++-
1 file changed, 11 insertions(+), 1 deletion(-)
diff --git a/drivers/net/ethernet/broadcom/bnx2x/bnx2x_main.c b/drivers/net/ethernet/broadcom/bnx2x/bnx2x_main.c
index 208a894d6190..c756a639e6ee 100644
--- a/drivers/net/ethernet/broadcom/bnx2x/bnx2x_main.c
+++ b/drivers/net/ethernet/broadcom/bnx2x/bnx2x_main.c
@@ -13258,6 +13258,11 @@ static int bnx2x_init_dev(struct bnx2x *bp, struct pci_dev *pdev,
return rc;
}
+static bool bnx2x_fw_section_valid(size_t fw_size, u32 offset, u32 len)
+{
+ return offset <= fw_size && len <= fw_size - offset;
+}
+
static int bnx2x_check_firmware(struct bnx2x *bp)
{
const struct firmware *firmware = bp->firmware;
@@ -13281,7 +13286,7 @@ static int bnx2x_check_firmware(struct bnx2x *bp)
for (i = 0; i < sizeof(*fw_hdr) / sizeof(*sections); i++) {
offset = be32_to_cpu(sections[i].offset);
len = be32_to_cpu(sections[i].len);
- if (offset + len > firmware->size) {
+ if (!bnx2x_fw_section_valid(firmware->size, offset, len)) {
BNX2X_ERR("Section %d length is out of bounds\n", i);
return -EINVAL;
}
@@ -13300,6 +13305,11 @@ static int bnx2x_check_firmware(struct bnx2x *bp)
}
/* Check FW version */
+ if (be32_to_cpu(fw_hdr->fw_version.len) < 4) {
+ BNX2X_ERR("FW version section is too small\n");
+ return -EINVAL;
+ }
+
offset = be32_to_cpu(fw_hdr->fw_version.offset);
fw_ver = firmware->data + offset;
if (fw_ver[0] != bp->fw_major || fw_ver[1] != bp->fw_minor ||
--
2.43.0
^ permalink raw reply related
* [PATCH net-next v5 1/2] net: lan743x: add RMII strap status detection for PCI11x1x
From: Thangaraj Samynathan @ 2026-07-06 9:31 UTC (permalink / raw)
To: netdev
Cc: andrew+netdev, davem, edumazet, kuba, pabeni, horms,
bryan.whitehead, UNGLinuxDriver, linux-kernel
In-Reply-To: <20260706093150.9033-1-thangaraj.s@microchip.com>
Extend pci11x1x_strap_get_status() to read the RMII strap bits from
the STRAP_READ register. The is_rmii_en flag is initialized to
false and updated based on the hardware strap only if SGMII is not
already enabled. This ensures correct interface identification during
adapter initialization.
Update the netif_dbg() to report the selected interface as SGMII,
RMII, or RGMII.
Signed-off-by: Thangaraj Samynathan <thangaraj.s@microchip.com>
---
drivers/net/ethernet/microchip/lan743x_main.c | 12 ++++++++++--
drivers/net/ethernet/microchip/lan743x_main.h | 3 +++
2 files changed, 13 insertions(+), 2 deletions(-)
diff --git a/drivers/net/ethernet/microchip/lan743x_main.c b/drivers/net/ethernet/microchip/lan743x_main.c
index e759171bfd76..3a418105cd11 100644
--- a/drivers/net/ethernet/microchip/lan743x_main.c
+++ b/drivers/net/ethernet/microchip/lan743x_main.c
@@ -42,6 +42,7 @@ static void pci11x1x_strap_get_status(struct lan743x_adapter *adapter)
u32 strap;
int ret;
+ adapter->is_rmii_en = false;
/* Timeout = 100 (i.e. 1 sec (10 msce * 100)) */
ret = lan743x_hs_syslock_acquire(adapter, 100);
if (ret < 0) {
@@ -73,8 +74,15 @@ static void pci11x1x_strap_get_status(struct lan743x_adapter *adapter)
adapter->is_sgmii_en = false;
}
}
- netif_dbg(adapter, drv, adapter->netdev,
- "SGMII I/F %sable\n", adapter->is_sgmii_en ? "En" : "Dis");
+
+ if (!adapter->is_sgmii_en && strap & STRAP_READ_USE_RMII_EN_) {
+ if (strap & STRAP_READ_RMII_EN_)
+ adapter->is_rmii_en = true;
+ }
+
+ netif_dbg(adapter, drv, adapter->netdev, "Selected I/F: %s\n",
+ adapter->is_sgmii_en ? "SGMII" :
+ adapter->is_rmii_en ? "RMII" : "RGMII");
}
static bool is_pci11x1x_chip(struct lan743x_adapter *adapter)
diff --git a/drivers/net/ethernet/microchip/lan743x_main.h b/drivers/net/ethernet/microchip/lan743x_main.h
index 1573c8f9c993..1f8d9294a6ef 100644
--- a/drivers/net/ethernet/microchip/lan743x_main.h
+++ b/drivers/net/ethernet/microchip/lan743x_main.h
@@ -36,7 +36,9 @@
#define FPGA_SGMII_OP BIT(24)
#define STRAP_READ (0x0C)
+#define STRAP_READ_USE_RMII_EN_ BIT(23)
#define STRAP_READ_USE_SGMII_EN_ BIT(22)
+#define STRAP_READ_RMII_EN_ BIT(7)
#define STRAP_READ_SGMII_EN_ BIT(6)
#define STRAP_READ_SGMII_REFCLK_ BIT(5)
#define STRAP_READ_SGMII_2_5G_ BIT(4)
@@ -1072,6 +1074,7 @@ struct lan743x_adapter {
struct lan743x_rx rx[LAN743X_USED_RX_CHANNELS];
bool is_pci11x1x;
bool is_sgmii_en;
+ bool is_rmii_en;
/* protect ethernet syslock */
spinlock_t eth_syslock_spinlock;
bool eth_syslock_en;
--
2.34.1
^ permalink raw reply related
* [PATCH net-next v5 0/2] net: lan743x: add RMII support for PCI11x1x
From: Thangaraj Samynathan @ 2026-07-06 9:31 UTC (permalink / raw)
To: netdev
Cc: andrew+netdev, davem, edumazet, kuba, pabeni, horms,
bryan.whitehead, UNGLinuxDriver, linux-kernel
This series adds RMII interface support for the Microchip PCI11x1x
Ethernet controller.
The PCI11x1x device supports RMII as an alternative MAC-PHY interface,
selected via the STRAP_READ software strap register. Patch 1 reads the
RMII strap bits from this register and sets the is_rmii_en flag. Patch 2
uses this flag to configure the PHY interface mode, phylink supported
interfaces, and enables RMII in hardware via the RMII_CTL register.
Change Log:
===========
v4 -> v5:
- Always write RMII_CTL with explicit set/clear of RMII_ENABLE_,
mirroring the SGMII_CTL pattern for a known register state on every
probe [Simon Horman, Paolo Abeni]
v3 -> v4:
- Fix dev_dbg() in lan743x_mdiobus_init() to print "RMII operation"
instead of "RGMII operation" when RMII is selected [Simon Horman]
v2 -> v3:
- Update debug log to report selected interface (SGMII/RMII/RGMII)
instead of only SGMII enable/disable state [patch 1/2]
- Update commit message to document that EEE is disabled by setting
lpi_capabilities = 0 [patch 2/2]
v1 -> v2:
- Remove redundant mac_capabilities &= ~MAC_1000FD; phylink already
handles capability reduction for RMII via phy_caps_from_interface()
[patch 2/2]
Thangaraj Samynathan (2):
net: lan743x: add RMII strap status detection for PCI11x1x
net: lan743x: add support for RMII interface
drivers/net/ethernet/microchip/lan743x_main.c | 35 ++++++++++++++++---
drivers/net/ethernet/microchip/lan743x_main.h | 6 ++++
2 files changed, 37 insertions(+), 4 deletions(-)
--
2.34.1
^ permalink raw reply
* [PATCH net-next v5 2/2] net: lan743x: add support for RMII interface
From: Thangaraj Samynathan @ 2026-07-06 9:31 UTC (permalink / raw)
To: netdev
Cc: andrew+netdev, davem, edumazet, kuba, pabeni, horms,
bryan.whitehead, UNGLinuxDriver, linux-kernel
In-Reply-To: <20260706093150.9033-1-thangaraj.s@microchip.com>
Enable RMII interface in the lan743x driver for PHY and MAC
configuration.
- Select RMII interface in lan743x_phy_interface_select().
- Update phylink supported_interfaces and MAC capabilities.
- Enable RMII via RMII_CTL in lan743x_hardware_init().
- Define RMII_CTL register and enable bit in lan743x_main.h.
EEE is not supported with RMII on PCI11x1x: the hardware does not
implement LPI signaling over RMII. Clear RMII from lpi_interfaces to
prevent phylink from enabling EEE on this interface.
Signed-off-by: Thangaraj Samynathan <thangaraj.s@microchip.com>
---
drivers/net/ethernet/microchip/lan743x_main.c | 23 +++++++++++++++++--
drivers/net/ethernet/microchip/lan743x_main.h | 3 +++
2 files changed, 24 insertions(+), 2 deletions(-)
diff --git a/drivers/net/ethernet/microchip/lan743x_main.c b/drivers/net/ethernet/microchip/lan743x_main.c
index 3a418105cd11..24ae56a3c9ed 100644
--- a/drivers/net/ethernet/microchip/lan743x_main.c
+++ b/drivers/net/ethernet/microchip/lan743x_main.c
@@ -1402,6 +1402,8 @@ static void lan743x_phy_interface_select(struct lan743x_adapter *adapter)
if (adapter->is_pci11x1x && adapter->is_sgmii_en)
adapter->phy_interface = PHY_INTERFACE_MODE_SGMII;
+ else if (adapter->is_pci11x1x && adapter->is_rmii_en)
+ adapter->phy_interface = PHY_INTERFACE_MODE_RMII;
else if (id_rev == ID_REV_ID_LAN7430_)
adapter->phy_interface = PHY_INTERFACE_MODE_GMII;
else if ((id_rev == ID_REV_ID_LAN7431_) && (data & MAC_CR_MII_EN_))
@@ -3190,6 +3192,12 @@ static int lan743x_phylink_create(struct lan743x_adapter *adapter)
__set_bit(PHY_INTERFACE_MODE_MII,
adapter->phylink_config.supported_interfaces);
break;
+ case PHY_INTERFACE_MODE_RMII:
+ __set_bit(PHY_INTERFACE_MODE_RMII,
+ adapter->phylink_config.supported_interfaces);
+ adapter->phylink_config.lpi_capabilities = 0;
+ break;
+
default:
phy_interface_set_rgmii(adapter->phylink_config.supported_interfaces);
}
@@ -3197,6 +3205,9 @@ static int lan743x_phylink_create(struct lan743x_adapter *adapter)
memcpy(adapter->phylink_config.lpi_interfaces,
adapter->phylink_config.supported_interfaces,
sizeof(adapter->phylink_config.lpi_interfaces));
+ if (adapter->phy_interface == PHY_INTERFACE_MODE_RMII)
+ __clear_bit(PHY_INTERFACE_MODE_RMII,
+ adapter->phylink_config.lpi_interfaces);
pl = phylink_create(&adapter->phylink_config, NULL,
adapter->phy_interface, &lan743x_phylink_mac_ops);
@@ -3541,6 +3552,7 @@ static int lan743x_hardware_init(struct lan743x_adapter *adapter,
{
struct lan743x_tx *tx;
u32 sgmii_ctl;
+ u32 rmii_ctl;
int index;
int ret;
@@ -3562,6 +3574,12 @@ static int lan743x_hardware_init(struct lan743x_adapter *adapter,
sgmii_ctl |= SGMII_CTL_SGMII_POWER_DN_;
}
lan743x_csr_write(adapter, SGMII_CTL, sgmii_ctl);
+ rmii_ctl = lan743x_csr_read(adapter, RMII_CTL);
+ if (adapter->is_rmii_en)
+ rmii_ctl |= RMII_CTL_RMII_ENABLE_;
+ else
+ rmii_ctl &= ~RMII_CTL_RMII_ENABLE_;
+ lan743x_csr_write(adapter, RMII_CTL, rmii_ctl);
} else {
adapter->max_tx_channels = LAN743X_MAX_TX_CHANNELS;
adapter->used_tx_channels = LAN743X_USED_TX_CHANNELS;
@@ -3628,8 +3646,9 @@ static int lan743x_mdiobus_init(struct lan743x_adapter *adapter)
adapter->mdiobus->name = "lan743x-mdiobus-c45";
dev_dbg(&adapter->pdev->dev, "lan743x-mdiobus-c45\n");
} else {
- dev_dbg(&adapter->pdev->dev, "RGMII operation\n");
- // Only C22 support when RGMII I/F
+ dev_dbg(&adapter->pdev->dev, "%s operation\n",
+ adapter->is_rmii_en ? "RMII" : "RGMII");
+ // Only C22 support when RGMII/RMII I/F
adapter->mdiobus->read = lan743x_mdiobus_read_c22;
adapter->mdiobus->write = lan743x_mdiobus_write_c22;
adapter->mdiobus->name = "lan743x-mdiobus";
diff --git a/drivers/net/ethernet/microchip/lan743x_main.h b/drivers/net/ethernet/microchip/lan743x_main.h
index 1f8d9294a6ef..d9495cf96b41 100644
--- a/drivers/net/ethernet/microchip/lan743x_main.h
+++ b/drivers/net/ethernet/microchip/lan743x_main.h
@@ -325,6 +325,9 @@
#define MAC_WUCSR2_IPV6_TCPSYN_RCD_ BIT(5)
#define MAC_WUCSR2_IPV4_TCPSYN_RCD_ BIT(4)
+#define RMII_CTL (0x710)
+#define RMII_CTL_RMII_ENABLE_ BIT(0)
+
#define SGMII_ACC (0x720)
#define SGMII_ACC_SGMII_BZY_ BIT(31)
#define SGMII_ACC_SGMII_WR_ BIT(30)
--
2.34.1
^ permalink raw reply related
* Re: [PATCH net-next v6 2/5] dpll: add DPLL_PIN_TYPE_INT_NCO pin type
From: Paolo Abeni @ 2026-07-06 9:31 UTC (permalink / raw)
To: Ivan Vecera, netdev, Vadim Fedorenko
Cc: Jiri Pirko, David S. Miller, Donald Hunter, Eric Dumazet,
Jakub Kicinski, Jiri Pirko, Michal Schmidt, Pasi Vaananen,
Arkadiusz Kubalewski, Petr Oros, Prathosh Satish, Simon Horman,
linux-kernel
In-Reply-To: <20260630125536.720717-3-ivecera@redhat.com>
On 6/30/26 2:55 PM, Ivan Vecera wrote:
> Add DPLL_PIN_TYPE_INT_NCO pin type for virtual pins representing
> the NCO mode of a DPLL. When connected as a DPLL input, the DPLL
> enters NCO mode where the output frequency is adjusted by the host
> via the PTP clock interface.
>
> Update the fractional-frequency-offset and fractional-frequency-
> offset-ppt attribute documentation to note that for INT_NCO pins
> these attributes represent the DPLL's current output frequency
> offset from its nominal frequency.
>
> Reviewed-by: Jiri Pirko <jiri@nvidia.com>
> Signed-off-by: Ivan Vecera <ivecera@redhat.com>
@Vadim: it's not clear to me if your doubts over the design choice
raised in previous iterations are still there. My understanding is there
is reasonable agreement vs the new pin type, if you have very strong
objections please raise them soonish.
Thanks,
Paolo
^ permalink raw reply
* Re: [PATCH net v2] ppp: defer channel free to an RCU grace period to fix pppol2tp RX UAF
From: Sebastian Andrzej Siewior @ 2026-07-06 9:29 UTC (permalink / raw)
To: Qingfang Deng
Cc: Breno Leitao, Norbert Szetei, Andrew Lunn, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Taegu Ha, Kees Cook,
linux-ppp, linux-kernel, Guillaume Nault, netdev,
Luis Chamberlain, Petr Pavlu, Daniel Gomez, Sami Tolvanen,
Aaron Tomlin, linux-modules
In-Reply-To: <87111f02-5b7a-4185-8364-2faba650578b@linux.dev>
+ MODULE maintainer
On 2026-07-05 10:57:44 [+0800], Qingfang Deng wrote:
> On 7/4/2026 at 12:32 AM, Breno Leitao wrote:
> > On Fri, Jul 03, 2026 at 03:27:00PM +0800, Qingfang Deng wrote:
> > > AI-review found an issue: https://sashiko.dev/#/patchset/D9C0245B-608B-4884-8A09-F55BA4A9F948%40doyensec.com
> > >
> > > An rcu_barrier() call is needed at the end of ppp_cleanup().
> >
> > I was initially unclear why rcu_barrier() would be necessary on a kfree path,
> > but it appears to be required during module unload to ensure that
> > ppp_release_channel_free() completes before the module's struct rcu_head is
> > destroyed. Is that the correct understanding?
>
> It's required to ensure that all ppp_release_channel_free() callback
> complete before the text segment of the module is unloaded.
So either a rcu_barrier() in ppp's module_exit() callback or a
synchronize_rcu() instead of the call_rcu(). And all this because the
module RCU callbacks pending which can be invoked after the module has
been removed. There is a synchronize_rcu() during module exit but this
is after the module code is gone.
I'm curious how many modules have a call_rcu() within their code but
don't have anything to enforce its completion before module removal is
complete? Wouldn't something like
diff --git a/kernel/module/main.c b/kernel/module/main.c
index 46dd8d25a6058..8eae1ea2d6eb4 100644
--- a/kernel/module/main.c
+++ b/kernel/module/main.c
@@ -858,6 +858,9 @@ SYSCALL_DEFINE2(delete_module, const char __user *, name_user,
goto out;
mutex_unlock(&module_mutex);
+
+ /* Ensure all rcu callbacks issued by the module have completed */
+ rcu_barrier();
/* Final destruction now no one is using it. */
if (mod->exit != NULL)
mod->exit();
make sense?
Sebastian
^ permalink raw reply related
* [PATCH v1 net-next] net: phy: Drop #inclusion of <linux/mod_devicetable.h> from <linux/mdio.h>
From: Uwe Kleine-König (The Capable Hub) @ 2026-07-06 9:29 UTC (permalink / raw)
To: Andrew Lunn, Heiner Kallweit; +Cc: Russell King, netdev, linux-kernel
<linux/mdio.h> itself doesn't use any of the device id structures. The
files #including <linux/mdio.h> use a variety of them:
$ git grep -l '<linux/mdio.h>' | xargs grep --color -E '\<(acpi_device_id|amba_id|ap_device_id|apr_device_id|auxiliary_device_id|bcma_device_id|ccw_device_id|cdx_device_id|coreboot_device_id|css_device_id|dfl_device_id|dmi_(device|system)_id|eisa_device_id|fsl_mc_device_id|hda_device_id|hid_device_id|hv_vmbus_device_id|i2c_device_id|i3c_device_id|ieee1394_device_id|input_device_id|ipack_device_id|isapnp_device_id|ishtp_device_id|mcb_device_id|mdio_device_id|mei_cl_device_id|mhi_device_id|mips_cdmm_device_id|of_device_id|parisc_device_id|pci_device_id|pci_epf_device_id|pcmcia_device_id|platform_device_id|pnp_(card_)?device_id|rio_device_id|rpmsg_device_id|sdio_device_id|sdw_device_id|serio_device_id|slim_device_id|spi_device_id|spmi_device_id|ssam_device_id|ssb_device_id|tb_service_id|tee_client_device_id|typec_device_id|ulpi_device_id|usb_device_id|vchiq_device_id|virtio_device_id|wmi_device_id|x86_(cpu|device)_id|zorro_device_id|cpu_feature)\>'
...
but none of them relies on <linux/mdio.h>'s #include.
<linux/mod_devicetable.h> is a bad header mixing many different device
id structures and thus is an unfortunate dependency because if e.g.
struct wmi_device_id is modified all users of <linux/mdio.h> need to be
recompiled despite none of them using struct wmi_device_id.
So drop the unused header.
Signed-off-by: Uwe Kleine-König (The Capable Hub) <u.kleine-koenig@baylibre.com>
---
Hello,
the check that <linux/mod_devicetable.h> wasn't needed by all the users
of <linux/mdio.h> was done on top of v7.2-rc2.
Best regards
Uwe
include/linux/mdio.h | 1 -
1 file changed, 1 deletion(-)
diff --git a/include/linux/mdio.h b/include/linux/mdio.h
index f4f9d9609448..300805e66592 100644
--- a/include/linux/mdio.h
+++ b/include/linux/mdio.h
@@ -8,7 +8,6 @@
#include <uapi/linux/mdio.h>
#include <linux/bitfield.h>
-#include <linux/mod_devicetable.h>
struct gpio_desc;
struct mii_bus;
base-commit: 8cdeaa50eae8dad34885515f62559ee83e7e8dda
--
2.55.0.11.g153666a7d9bb
^ permalink raw reply related
* [PATCH] net: prestera: validate firmware header length
From: Pengpeng Hou @ 2026-07-06 9:26 UTC (permalink / raw)
To: Elad Nachman
Cc: Pengpeng Hou, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, netdev, linux-kernel
prestera_fw_hdr_parse() reads the firmware header before checking
that the firmware image contains that header.
Reject images shorter than struct prestera_fw_header before decoding the
magic and version fields.
Signed-off-by: Pengpeng Hou <pengpeng@iscas.ac.cn>
---
drivers/net/ethernet/marvell/prestera/prestera_pci.c | 3 +++
1 file changed, 3 insertions(+)
diff --git a/drivers/net/ethernet/marvell/prestera/prestera_pci.c b/drivers/net/ethernet/marvell/prestera/prestera_pci.c
index 2989a77e3b42..1ad0e62a8433 100644
--- a/drivers/net/ethernet/marvell/prestera/prestera_pci.c
+++ b/drivers/net/ethernet/marvell/prestera/prestera_pci.c
@@ -684,6 +684,9 @@ static int prestera_fw_hdr_parse(struct prestera_fw *fw)
struct prestera_fw_header *hdr;
u32 magic;
+ if (fw->bin->size < sizeof(*hdr))
+ return -EINVAL;
+
hdr = (struct prestera_fw_header *)fw->bin->data;
magic = be32_to_cpu(hdr->magic_number);
--
2.43.0
^ permalink raw reply related
* [PATCH] ixgbe: validate E610 PFA TLV bounds
From: Pengpeng Hou @ 2026-07-06 9:25 UTC (permalink / raw)
To: Tony Nguyen
Cc: Pengpeng Hou, Przemek Kitszel, Andrew Lunn, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, intel-wired-lan,
netdev, linux-kernel
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;
+
/* 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;
}
/* Module does not exist */
--
2.43.0
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox