From cdb719f4b8596d9ccee2d56d204c2c4dce982f46 Mon Sep 17 00:00:00 2001 From: Jakub Kicinski Date: Wed, 2 Sep 2026 20:26:07 -0700 Subject: [PATCH 1/5] net: dsa: bcm_sf2: bound the CFP rule dump by the caller's buffer size bcm_sf2_cfp_rule_get_all() walks the whole cfp.unique bitmap into rule_locs[] without consulting nfc->rule_cnt, which is how many entries the caller had room for. ETHTOOL_GRXCLSRLALL requires no CAP_NET_ADMIN and the ioctl sizes the buffer from the rule_cnt userspace passes in, so once an admin has installed CFP rules any user can ask for fewer slots than there are rules and run off the end of the allocation. A rule_cnt of 0 leaves the buffer pointer NULL and the walk dereferences it. Fixes: 7318166cacad ("net: dsa: bcm_sf2: Add support for ethtool::rxnfc") Reviewed-by: Jonas Gorski Reviewed-by: Florian Fainelli Reviewed-by: Joe Damato Link: https://patch.msgid.link/20260903032611.3000029-2-kuba@kernel.org Signed-off-by: Jakub Kicinski --- drivers/net/dsa/bcm_sf2_cfp.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/net/dsa/bcm_sf2_cfp.c b/drivers/net/dsa/bcm_sf2_cfp.c index 50d3a818eb1b..84a086c3e99b 100644 --- a/drivers/net/dsa/bcm_sf2_cfp.c +++ b/drivers/net/dsa/bcm_sf2_cfp.c @@ -1088,6 +1088,8 @@ static int bcm_sf2_cfp_rule_get_all(struct bcm_sf2_priv *priv, unsigned int index = 1, rules_cnt = 0; for_each_set_bit_from(index, priv->cfp.unique, priv->num_cfp_rules) { + if (rules_cnt == nfc->rule_cnt) + return -EMSGSIZE; rule_locs[rules_cnt] = index; rules_cnt++; } From f1986bf87b0709c95126fe196cf39e5b8c8453a1 Mon Sep 17 00:00:00 2001 From: Jakub Kicinski Date: Wed, 2 Sep 2026 20:26:08 -0700 Subject: [PATCH 2/5] eth: nfp: bound the ntuple rule dump by the caller's buffer size nfp_net_get_fs_loc() dumps every entry of nn->fs.list into rule_locs[] without consulting cmd->rule_cnt, which is how many entries the caller had room for. ETHTOOL_GRXCLSRLALL requires no CAP_NET_ADMIN and the ioctl sizes the buffer from the rule_cnt userspace passes in, so once an admin has installed flow steering rules any user can ask for fewer slots than there are rules and run off the end of the allocation. A rule_cnt of 0 leaves the buffer pointer NULL and the walk dereferences it. Bail out with -EMSGSIZE when the buffer fills up, the way the other ntuple capable drivers do, and report how many locations were filled so a shrinking rule list does not leave the caller reading stale slots. Reported-by: VEGA Fixes: 9eb03bb1c035 ("nfp: add ethtool flow steering callbacks") Reviewed-by: Joe Damato Link: https://patch.msgid.link/20260903032611.3000029-3-kuba@kernel.org Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/netronome/nfp/nfp_net_ethtool.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/drivers/net/ethernet/netronome/nfp/nfp_net_ethtool.c b/drivers/net/ethernet/netronome/nfp/nfp_net_ethtool.c index a2a89d48e3ca..9419e1ed8466 100644 --- a/drivers/net/ethernet/netronome/nfp/nfp_net_ethtool.c +++ b/drivers/net/ethernet/netronome/nfp/nfp_net_ethtool.c @@ -1421,7 +1421,8 @@ static int nfp_net_get_fs_rule(struct nfp_net *nn, struct ethtool_rxnfc *cmd) return -ENOENT; } -static int nfp_net_get_fs_loc(struct nfp_net *nn, u32 *rule_locs) +static int nfp_net_get_fs_loc(struct nfp_net *nn, struct ethtool_rxnfc *cmd, + u32 *rule_locs) { struct nfp_fs_entry *entry; u32 count = 0; @@ -1429,8 +1430,12 @@ static int nfp_net_get_fs_loc(struct nfp_net *nn, u32 *rule_locs) if (!(nn->cap_w1 & NFP_NET_CFG_CTRL_FLOW_STEER)) return -EOPNOTSUPP; - list_for_each_entry(entry, &nn->fs.list, node) + list_for_each_entry(entry, &nn->fs.list, node) { + if (count == cmd->rule_cnt) + return -EMSGSIZE; rule_locs[count++] = entry->loc; + } + cmd->rule_cnt = count; return 0; } @@ -1455,7 +1460,7 @@ static int nfp_net_get_rxnfc(struct net_device *netdev, return nfp_net_get_fs_rule(nn, cmd); case ETHTOOL_GRXCLSRLALL: cmd->data = NFP_FS_MAX_ENTRY; - return nfp_net_get_fs_loc(nn, rule_locs); + return nfp_net_get_fs_loc(nn, cmd, rule_locs); default: return -EOPNOTSUPP; } From 108bb2142e3a12c9ad625ad662973127a113ddc6 Mon Sep 17 00:00:00 2001 From: Jakub Kicinski Date: Wed, 2 Sep 2026 20:26:09 -0700 Subject: [PATCH 3/5] eth: nfp: drop the replaced rule from the list when reprogramming fails nfp_net_fs_add() replaces an existing rule by deleting it from the hardware, decrementing nn->fs.count and programming the new one. If nfp_net_fs_add_hw() fails the old entry stays on nn->fs.list - only the success path reaches list_replace() - so the list is one longer than nn->fs.count, and it advertises a rule whose hardware entry has already been torn down. nn->fs.count is what ETHTOOL_GRXCLSRLCNT reports, so userspace then sizes its buffer one entry short of what the GRXCLSRLALL walk wants to write. That used to overwrite one u32 past the allocation; since the walk is bounded it is a permanent -EMSGSIZE instead, as nothing ever resyncs the counter. Fixes: 9eb03bb1c035 ("nfp: add ethtool flow steering callbacks") Reviewed-by: Joe Damato Link: https://patch.msgid.link/20260903032611.3000029-4-kuba@kernel.org Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/netronome/nfp/nfp_net_ethtool.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/drivers/net/ethernet/netronome/nfp/nfp_net_ethtool.c b/drivers/net/ethernet/netronome/nfp/nfp_net_ethtool.c index 9419e1ed8466..4e83637715e0 100644 --- a/drivers/net/ethernet/netronome/nfp/nfp_net_ethtool.c +++ b/drivers/net/ethernet/netronome/nfp/nfp_net_ethtool.c @@ -1703,8 +1703,14 @@ static int nfp_net_fs_add(struct nfp_net *nn, struct ethtool_rxnfc *cmd) nn->fs.count--; err = nfp_net_fs_add_hw(nn, new); - if (err) + if (err) { + /* mbox broken, adding the old rule back will + * likely also fail. + */ + list_del(&entry->node); + kfree(entry); goto err; + } nn->fs.count++; list_replace(&entry->node, &new->node); From b1fffc273112e7284c5b705e186b43b5770cd3d5 Mon Sep 17 00:00:00 2001 From: Jakub Kicinski Date: Wed, 2 Sep 2026 20:26:10 -0700 Subject: [PATCH 4/5] net: dsa: mv88e6xxx: bound the policy rule dump by the caller's buffer size mv88e6xxx_get_rxnfc() uses rxnfc->rule_cnt as the write index while dumping the policy IDR, clobbering the input value before it has been looked at. That input is the number of entries the caller had room for. ETHTOOL_GRXCLSRLALL requires no CAP_NET_ADMIN and the ioctl sizes the buffer from the rule_cnt userspace passes in, so once an admin has installed policy rules any user can ask for fewer slots than there are rules and run off the end of the allocation. A rule_cnt of 0 leaves the buffer pointer NULL and the walk dereferences it. Count into a local so the caller's limit survives the walk, and stop with -EMSGSIZE once it is reached. Fixes: da7dc8755304 ("net: dsa: mv88e6xxx: add RXNFC support") Reviewed-by: Joe Damato Link: https://patch.msgid.link/20260903032611.3000029-5-kuba@kernel.org Signed-off-by: Jakub Kicinski --- drivers/net/dsa/mv88e6xxx/chip.c | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/drivers/net/dsa/mv88e6xxx/chip.c b/drivers/net/dsa/mv88e6xxx/chip.c index 80b877c74513..7f68a0c55802 100644 --- a/drivers/net/dsa/mv88e6xxx/chip.c +++ b/drivers/net/dsa/mv88e6xxx/chip.c @@ -2438,6 +2438,7 @@ static int mv88e6xxx_get_rxnfc(struct dsa_switch *ds, int port, struct ethtool_rx_flow_spec *fs = &rxnfc->fs; struct mv88e6xxx_chip *chip = ds->priv; struct mv88e6xxx_policy *policy; + u32 cnt = 0; int err; int id; @@ -2463,11 +2464,18 @@ static int mv88e6xxx_get_rxnfc(struct dsa_switch *ds, int port, break; case ETHTOOL_GRXCLSRLALL: rxnfc->data = 0; - rxnfc->rule_cnt = 0; - idr_for_each_entry(&chip->policies, policy, id) - if (policy->port == port) - rule_locs[rxnfc->rule_cnt++] = id; err = 0; + idr_for_each_entry(&chip->policies, policy, id) { + if (policy->port != port) + continue; + if (cnt == rxnfc->rule_cnt) { + err = -EMSGSIZE; + break; + } + rule_locs[cnt++] = id; + } + if (!err) + rxnfc->rule_cnt = cnt; break; default: err = -EOPNOTSUPP; From 47a582b2b0e7bb5753e4803988e150a405b57f51 Mon Sep 17 00:00:00 2001 From: Jakub Kicinski Date: Wed, 2 Sep 2026 20:26:11 -0700 Subject: [PATCH 5/5] ethtool: document that GRXCLSRLALL rule_cnt is a caller-provided limit Three drivers have shipped a get_rxnfc() which dumps its entire rule table into rule_locs, reading rule_cnt as "how many rules do I have" rather than "how many entries did the caller allocate". Nothing in the callback's documentation contradicted that reading. The distinction only matters because the ioctl lets an unprivileged caller pick rule_cnt directly, so getting it wrong is a heap overflow rather than a truncated dump. Reviewed-by: Joe Damato Link: https://patch.msgid.link/20260903032611.3000029-6-kuba@kernel.org Signed-off-by: Jakub Kicinski --- include/linux/ethtool.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/include/linux/ethtool.h b/include/linux/ethtool.h index 12683b5d125e..253600c0eccd 100644 --- a/include/linux/ethtool.h +++ b/include/linux/ethtool.h @@ -1057,6 +1057,12 @@ struct kernel_ethtool_ts_info { * @get_sset_count: Get number of strings that @get_strings will write. * @get_rxnfc: Get RX flow classification rules. Returns a negative * error code or zero. + * Note that for %ETHTOOL_GRXCLSRLALL rule_cnt and size of the arrays + * is user-provided, and not guaranteed to match what driver would + * have reported via %ETHTOOL_GRXCLSRLCNT. Drivers must return -%EMSGSIZE + * when rule_cnt is too small. rule_locs is %NULL when rule_cnt is zero. + * On success drivers must set rule_cnt to the number of locations they + * filled in, the core copies out exactly that many. * @set_rxnfc: Set RX flow classification rules. Returns a negative * error code or zero. * @flash_device: Write a firmware image to device's flash memory.