[PATCH iptables] nft: fix out-of-bounds read listing an old-revision match
Omkhar Arasaratnam <[email protected]> Fri, 17 Jul 2026 18:59:27 +0000
| Newsgroups | gmane.comp.security.firewalls.netfilter.devel |
|---|---|
| Message-ID | <LV0SPRMB0026AFC6E85D19E90A2C3CBAA1C62@LV0SPRMB0026.namprd21.prod.outlook.com> |
nft_parse_match() sizes the xt_entry_match buffer from the wire blob
length reported by the kernel:
m = xtables_calloc(1, sizeof(struct xt_entry_match) + mt_len);
memcpy(&m->data, mt_info, mt_len);
but selects the print/save/compare extension purely by name via
xtables_find_match(), which returns the highest supported revision. When
the kernel stored an older, smaller revision of the match, mt_len is
smaller than the resolved extension's userspacesize, and the print
callback -- and compare_matches(), which memcmp()s userspacesize bytes
-- read past the mt_len-sized allocation.
For example a conntrack match stored as xt_conntrack_mtinfo1 (152 bytes)
is printed by the rev3 callback conntrack_dump(), which reads
xt_conntrack_mtinfo3 fields (info->origsrc_port_high at offset 154), two
bytes past the buffer. Listing such a ruleset with iptables-nft -L reads
out of bounds.
Size the allocation to the resolved extension when its blob is larger,
just as nft_create_match() and __nft_create_target() already do, so the
print and compare paths stay in bounds. Matches whose stored blob is at
least as large as the extension are unaffected.
Fixes: cdc78b1d6bd7 ("nft: convert rule into a command state structure")
Signed-off-by: Omkhar Arasaratnam <[email protected]>
---
iptables/nft-ruleparse.c | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/iptables/nft-ruleparse.c b/iptables/nft-ruleparse.c
index 26a605cf..2c5aa9ce 100644
--- a/iptables/nft-ruleparse.c
+++ b/iptables/nft-ruleparse.c
@@ -638,7 +638,10 @@ static void nft_parse_match(struct nft_xt_ctx *ctx, struct nftnl_expr *e)
return;
}
- m = xtables_calloc(1, sizeof(struct xt_entry_match) + mt_len);
+ /* Kernel blob may be smaller than the resolved extension (older
+ * revision); size to the extension so print/compare stay in bounds. */
+ m = xtables_calloc(1, sizeof(struct xt_entry_match) +
+ (mt_len < match->size ? match->size : mt_len));
memcpy(&m->data, mt_info, mt_len);
m->u.match_size = mt_len + XT_ALIGN(sizeof(struct xt_entry_match));
m->u.user.revision = nftnl_expr_get_u32(e, NFTNL_EXPR_TG_REV);
--
2.34.1