From: Simon Horman <horms@kernel.org>
To: Pengpeng Hou <pengpeng@iscas.ac.cn>
Cc: Shahed Shaikh <shshaikh@marvell.com>,
Manish Chopra <manishc@marvell.com>,
GR-Linux-NIC-Dev@marvell.com, Andrew Lunn <andrew+netdev@lunn.ch>,
"David S. Miller" <davem@davemloft.net>,
Eric Dumazet <edumazet@google.com>,
Jakub Kicinski <kuba@kernel.org>, Paolo Abeni <pabeni@redhat.com>,
netdev@vger.kernel.org, linux-kernel@vger.kernel.org
Subject: Re: [PATCH] net: qlcnic: validate unified ROM directory bounds
Date: Sat, 11 Jul 2026 16:13:15 +0100 [thread overview]
Message-ID: <20260711151315.GA1364329@horms.kernel.org> (raw)
In-Reply-To: <20260706093601.81535-1-pengpeng@iscas.ac.cn>
On Mon, Jul 06, 2026 at 05:36:01PM +0800, Pengpeng Hou wrote:
> 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)
...
> +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;
I realise that this isn't the existing style of this code, but where
possible please try to move the arrangement of local variable declarations
to follow reverse xmas tree order - longest line to shortest (seems trivial
in this case). If necessary declarations and split should be split (with
all declarations coming first, then a blank line (probably not necessary
here).
>
> - 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;
I might have declared i as a size_t to avoid this cast.
> + 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];
It seems that the only change above is an update to white space.
Which is a cleanup separate from the intention of this patch.
Please drop such cleanups from this patch as the add noise.
> }
>
> return NULL;
...
> @@ -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;
This is one of two issues flagged by AI-generated of this patch at
sashiko.dev that I plan to include in this email. There are other issues,
which you may wish to look into in the context of possible follow-up. But I
feel that these two warrant consideration in the context of this patch as
they are closely related to it's intent.
This is a pre-existing issue, but does this validation need to enforce a
minimum length for the bootloader section?
The firmware loader unconditionally calculates a hardcoded size for the
bootloader section and iteratively reads this amount:
qlcnic_load_firmware() {
...
size = (QLCNIC_IMAGE_START - QLCNIC_BOOTLD_START) / 8;
...
for (i = 0; i < size; i++) {
data = le64_to_cpu(ptr64[i]);
...
}
If an attacker provides a firmware image with a valid offset near the end
of the file and a small declared size, couldn't the loop in
qlcnic_load_firmware() read past the end of the adapter->fw->data buffer?
>
> 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;
This is the other portion of the AI-generated review of this patch on
sashiko.dev that I'd like to as you to consider in the context of this patch.
This is a pre-existing issue, but does this validation need to verify that
data_len is at least 17 bytes?
In qlcnic_get_fw_version(), the version string pointer is calculated like
this:
qlcnic_get_fw_version() {
...
ver_str = fw->data + le32_to_cpu(fw_data_desc->findex) +
le32_to_cpu(fw_data_desc->size) - 17;
...
}
If the firmware image specifies a size less than 17, won't
le32_to_cpu(fw_data_desc->size) - 17 wrap around due to 32-bit unsigned
arithmetic, leading to an out-of-bounds pointer well outside the fw->data
buffer?
...
--
pw-bot: changes-requested
prev parent reply other threads:[~2026-07-11 15:13 UTC|newest]
Thread overview: 2+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-07-06 9:36 [PATCH] net: qlcnic: validate unified ROM directory bounds Pengpeng Hou
2026-07-11 15:13 ` Simon Horman [this message]
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260711151315.GA1364329@horms.kernel.org \
--to=horms@kernel.org \
--cc=GR-Linux-NIC-Dev@marvell.com \
--cc=andrew+netdev@lunn.ch \
--cc=davem@davemloft.net \
--cc=edumazet@google.com \
--cc=kuba@kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=manishc@marvell.com \
--cc=netdev@vger.kernel.org \
--cc=pabeni@redhat.com \
--cc=pengpeng@iscas.ac.cn \
--cc=shshaikh@marvell.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox