netfilter pull request 26-06-10

-----BEGIN PGP SIGNATURE-----
 
 iQIzBAABCgAdFiEEjF9xRqF1emXiQiqU1w0aZmrPKyEFAmopiokACgkQ1w0aZmrP
 KyGBoA//W6EXg8MCgQGojXZ4+41fPpJ6opw7t65e26/CIwTBgtOiX/no4WOJcOk/
 J6WBk2sHIg+RgPPzzOLfzAeyVf7MfbnXvBnEMselFGOtd1o5LeikdcskuMIQ2GP/
 v7a3C49Xueyp5ChjdArTbYcaREnAFVnmPGdsaE90D64SHNlmuiyIsRtEut+XUfuz
 Tl+JNCV2RCdSAl36MqnwAF8sBpj9ndnekNz8E5UONwgA7sowPM6KsGbYqCJrQXxa
 qDqbj9fme8BmLWc8gwQ37yD5iBGxc7LdVlyavIJ9uSi2Gp/jnDSsuyUm5WDo84wm
 R65jYmkXh9ZHr8OU/qYg5Mb0LBsSfNRzcE4WZIqb71WQ45IqA53kXam/2W0CuflN
 g9tPfAqboI9A11Mq3LSwwFWfa3L2xJIi1LFpzGThNTZa9vFoqhXZG0PCqXBKiDdo
 KsEmU4vtMFqga8tOFDJMgKHAxr/jPWhEZp1ZgIy3xAM2s9xC56Qe1g8mwcBFeZR7
 n/YEFGpWFa83EnXGow1gdhUQHu+PYbiwQQy1tSVaZ+kg5WhA3ahnJfIopS2qIrHq
 NN1RP8m85YjSZGzF/gvq+BEuuYZsebeaFxYLDha0zrhqXA3vlRZvVxoySlJtW/bk
 m/05GsIb7ySpAyOjejJ9fokymzjD1op+0o69AnEqvg1IXUmE0oM=
 =Rg2q
 -----END PGP SIGNATURE-----

Merge tag 'nf-26-06-10' of git://git.kernel.org/pub/scm/linux/kernel/git/netfilter/nf

Pablo Neira Ayuso says:

====================
Netfilter fixes for net

The following patchset contains Netfilter fixes for net:

1) Revalidate bridge ports, add missing NULL checks to fetch the bridge
   device by the port. From Florian Westphal.

2) Fix netdevice refcount leak in the error path of nft_fwd hardware
   offload function, also from Florian.

3) Unregister helper expectfn callback on conntrack helper module
   removal, otherwise dangling pointer remains in place,
   from Weiming Shi.

4) Fix possible pointer infoleak in getsockopt() IPT_SO_GET_ENTRIES,
   From Kyle Zeng.

5) Validate that device MAC header is present before nf_syslog
   accesses it. From Xiang Mei.

6-8) Three patches to address a possible infoleak of stale stack
     data in three nf_tables expressions, due to mismatch in the
     _init() and _eval() function which is possible since 14fb07130c.
     From Davide Ornaghi and Florian Westphal.

netfilter pull request 26-06-10

* tag 'nf-26-06-10' of git://git.kernel.org/pub/scm/linux/kernel/git/netfilter/nf:
  netfilter: nft_meta_bridge: fix stale stack leak via IIFHWADDR register
  netfilter: nft_fib: fix stale stack leak via the OIFNAME register
  netfilter: nft_exthdr: fix register tracking for F_PRESENT flag
  netfilter: nf_log: validate MAC header was set before dumping it
  netfilter: x_tables: avoid leaking percpu counter pointers
  netfilter: nf_conntrack: destroy stale expectfn expectations on unregister
  netfilter: nf_tables_offload: drop device refcount on error
  netfilter: revalidate bridge ports
====================

Link: https://patch.msgid.link/20260610161629.214092-1-pablo@netfilter.org
Signed-off-by: Paolo Abeni <pabeni@redhat.com>
This commit is contained in:
Paolo Abeni 2026-06-11 12:29:59 +02:00
commit 64ced6c088
19 changed files with 151 additions and 51 deletions

View file

@ -39,7 +39,9 @@ ebt_dnat_tg(struct sk_buff *skb, const struct xt_action_param *par)
dev = xt_in(par);
break;
case NF_BR_PRE_ROUTING:
dev = br_port_get_rcu(xt_in(par))->br->dev;
dev = netdev_master_upper_dev_get_rcu(xt_in(par));
if (!dev) /* bridge port removed? */
return EBT_DROP;
break;
default:
dev = NULL;

View file

@ -24,12 +24,18 @@ ebt_redirect_tg(struct sk_buff *skb, const struct xt_action_param *par)
if (skb_ensure_writable(skb, 0))
return EBT_DROP;
if (xt_hooknum(par) != NF_BR_BROUTING)
/* rcu_read_lock()ed by nf_hook_thresh */
ether_addr_copy(eth_hdr(skb)->h_dest,
br_port_get_rcu(xt_in(par))->br->dev->dev_addr);
else
if (xt_hooknum(par) != NF_BR_BROUTING) {
const struct net_device *dev;
dev = netdev_master_upper_dev_get_rcu(xt_in(par));
if (!dev)
return EBT_DROP;
ether_addr_copy(eth_hdr(skb)->h_dest, dev->dev_addr);
} else {
ether_addr_copy(eth_hdr(skb)->h_dest, xt_in(par)->dev_addr);
}
skb->pkt_type = PACKET_HOST;
return info->target;
}

View file

@ -64,6 +64,8 @@ static void nft_meta_bridge_get_eval(const struct nft_expr *expr,
if (!br_dev)
goto err;
/* ETH_ALEN (6) is shorter than the destination register span (8) */
dest[1] = 0;
memcpy(dest, br_dev->dev_addr, ETH_ALEN);
return;
default:

View file

@ -702,14 +702,12 @@ static int copy_entries_to_user(unsigned int total_size,
const struct xt_entry_target *t;
e = loc_cpu_entry + off;
if (copy_to_user(userptr + off, e, sizeof(*e))) {
ret = -EFAULT;
goto free_counters;
}
if (copy_to_user(userptr + off
if (copy_to_user(userptr + off, e,
offsetof(struct arpt_entry, counters)) ||
copy_to_user(userptr + off
+ offsetof(struct arpt_entry, counters),
&counters[num],
sizeof(counters[num])) != 0) {
sizeof(counters[num]))) {
ret = -EFAULT;
goto free_counters;
}
@ -1327,9 +1325,8 @@ static int compat_copy_entry_to_user(struct arpt_entry *e, void __user **dstptr,
origsize = *size;
ce = *dstptr;
if (copy_to_user(ce, e, sizeof(struct arpt_entry)) != 0 ||
copy_to_user(&ce->counters, &counters[i],
sizeof(counters[i])) != 0)
if (copy_to_user(ce, e, offsetof(struct compat_arpt_entry, counters)) ||
copy_to_user(&ce->counters, &counters[i], sizeof(counters[i])))
return -EFAULT;
*dstptr += sizeof(struct compat_arpt_entry);

View file

@ -832,14 +832,12 @@ copy_entries_to_user(unsigned int total_size,
const struct xt_entry_target *t;
e = loc_cpu_entry + off;
if (copy_to_user(userptr + off, e, sizeof(*e))) {
ret = -EFAULT;
goto free_counters;
}
if (copy_to_user(userptr + off
if (copy_to_user(userptr + off, e,
offsetof(struct ipt_entry, counters)) ||
copy_to_user(userptr + off
+ offsetof(struct ipt_entry, counters),
&counters[num],
sizeof(counters[num])) != 0) {
sizeof(counters[num]))) {
ret = -EFAULT;
goto free_counters;
}
@ -1228,9 +1226,8 @@ compat_copy_entry_to_user(struct ipt_entry *e, void __user **dstptr,
origsize = *size;
ce = *dstptr;
if (copy_to_user(ce, e, sizeof(struct ipt_entry)) != 0 ||
copy_to_user(&ce->counters, &counters[i],
sizeof(counters[i])) != 0)
if (copy_to_user(ce, e, offsetof(struct compat_ipt_entry, counters)) ||
copy_to_user(&ce->counters, &counters[i], sizeof(counters[i])))
return -EFAULT;
*dstptr += sizeof(struct compat_ipt_entry);

View file

@ -555,6 +555,8 @@ static void __exit nf_nat_h323_fini(void)
nf_ct_helper_expectfn_unregister(&q931_nat);
nf_ct_helper_expectfn_unregister(&callforwarding_nat);
synchronize_rcu();
nf_ct_helper_expectfn_destroy(&q931_nat);
nf_ct_helper_expectfn_destroy(&callforwarding_nat);
}
/****************************************************************************/

View file

@ -128,7 +128,7 @@ void nft_fib4_eval(const struct nft_expr *expr, struct nft_regs *regs,
fl4.saddr = get_saddr(iph->daddr);
}
*dest = 0;
nft_fib_store_result(dest, priv, NULL);
if (fib_lookup(nft_net(pkt), &fl4, &res, FIB_LOOKUP_IGNORE_LINKSTATE))
return;

View file

@ -848,14 +848,12 @@ copy_entries_to_user(unsigned int total_size,
const struct xt_entry_target *t;
e = loc_cpu_entry + off;
if (copy_to_user(userptr + off, e, sizeof(*e))) {
ret = -EFAULT;
goto free_counters;
}
if (copy_to_user(userptr + off
if (copy_to_user(userptr + off, e,
offsetof(struct ip6t_entry, counters)) ||
copy_to_user(userptr + off
+ offsetof(struct ip6t_entry, counters),
&counters[num],
sizeof(counters[num])) != 0) {
sizeof(counters[num]))) {
ret = -EFAULT;
goto free_counters;
}
@ -1244,9 +1242,8 @@ compat_copy_entry_to_user(struct ip6t_entry *e, void __user **dstptr,
origsize = *size;
ce = *dstptr;
if (copy_to_user(ce, e, sizeof(struct ip6t_entry)) != 0 ||
copy_to_user(&ce->counters, &counters[i],
sizeof(counters[i])) != 0)
if (copy_to_user(ce, e, offsetof(struct compat_ip6t_entry, counters)) ||
copy_to_user(&ce->counters, &counters[i], sizeof(counters[i])))
return -EFAULT;
*dstptr += sizeof(struct compat_ip6t_entry);

View file

@ -239,7 +239,7 @@ void nft_fib6_eval(const struct nft_expr *expr, struct nft_regs *regs,
lookup_flags = nft_fib6_flowi_init(&fl6, priv, pkt, oif, iph);
*dest = 0;
nft_fib_store_result(dest, priv, NULL);
ret = nft_fib6_lookup(nft_net(pkt), &fl6, &res, lookup_flags);
if (ret || res.fib6_flags & (RTF_REJECT | RTF_ANYCAST | RTF_LOCAL))
return;

View file

@ -283,6 +283,25 @@ void nf_ct_helper_expectfn_unregister(struct nf_ct_helper_expectfn *n)
}
EXPORT_SYMBOL_GPL(nf_ct_helper_expectfn_unregister);
static bool expect_iter_expectfn(struct nf_conntrack_expect *exp, void *data)
{
const struct nf_ct_helper_expectfn *n = data;
/* Relies on registered expectfn descriptors having unique ->expectfn
* pointers, which holds for the in-tree NAT helpers.
*/
return exp->expectfn == n->expectfn;
}
/* Destroy expectations still pointing at @n->expectfn; call after the
* caller's RCU grace period so none outlives the (often modular) callback.
*/
void nf_ct_helper_expectfn_destroy(const struct nf_ct_helper_expectfn *n)
{
nf_ct_expect_iterate_destroy(expect_iter_expectfn, (void *)n);
}
EXPORT_SYMBOL_GPL(nf_ct_helper_expectfn_destroy);
/* Caller should hold the rcu lock */
struct nf_ct_helper_expectfn *
nf_ct_helper_expectfn_find_by_name(const char *name)

View file

@ -74,16 +74,18 @@ int nft_fwd_dup_netdev_offload(struct nft_offload_ctx *ctx,
struct flow_action_entry *entry;
struct net_device *dev;
/* nft_flow_rule_destroy() releases the reference on this device. */
dev = dev_get_by_index(ctx->net, oif);
if (!dev)
return -EOPNOTSUPP;
entry = nft_flow_action_entry_next(ctx, flow);
if (!entry)
if (!entry) {
dev_put(dev);
return -E2BIG;
}
entry->id = id;
/* nft_flow_rule_destroy() releases the reference on this device. */
entry->dev = dev;
return 0;

View file

@ -815,8 +815,8 @@ static void dump_mac_header(struct nf_log_buf *m,
fallback:
nf_log_buf_add(m, "MAC=");
if (dev->hard_header_len &&
skb->mac_header != skb->network_header) {
if (dev->hard_header_len && skb_mac_header_was_set(skb) &&
skb_mac_header_len(skb) != 0) {
const unsigned char *p = skb_mac_header(skb);
unsigned int i;

View file

@ -1341,6 +1341,7 @@ static int __init nf_nat_init(void)
RCU_INIT_POINTER(nf_nat_hook, NULL);
nf_ct_helper_expectfn_unregister(&follow_master_nat);
synchronize_net();
nf_ct_helper_expectfn_destroy(&follow_master_nat);
unregister_pernet_subsys(&nat_net_ops);
kvfree(nf_nat_bysource);
}
@ -1358,6 +1359,7 @@ static void __exit nf_nat_cleanup(void)
RCU_INIT_POINTER(nf_nat_hook, NULL);
synchronize_net();
nf_ct_helper_expectfn_destroy(&follow_master_nat);
kvfree(nf_nat_bysource);
unregister_pernet_subsys(&nat_net_ops);
}

View file

@ -655,6 +655,7 @@ static void __exit nf_nat_sip_fini(void)
RCU_INIT_POINTER(nf_nat_sip_hooks, NULL);
nf_ct_helper_expectfn_unregister(&sip_nat);
synchronize_rcu();
nf_ct_helper_expectfn_destroy(&sip_nat);
}
static const struct nf_nat_sip_hooks sip_hooks = {

View file

@ -451,6 +451,23 @@ nla_put_failure:
return -1;
}
#if IS_ENABLED(CONFIG_BRIDGE_NETFILTER)
static int nflog_put_master_ifindex(struct sk_buff *nlskb, int attr,
const struct net_device *dev)
{
const struct net_device *upper;
if (dev && !netif_is_bridge_port(dev))
return 0;
upper = netdev_master_upper_dev_get_rcu((struct net_device *)dev);
if (upper && nla_put_be32(nlskb, attr, htonl(upper->ifindex)))
return -EMSGSIZE;
return 0;
}
#endif
/* This is an inline function, we don't really care about a long
* list of arguments */
static inline int
@ -505,8 +522,7 @@ __build_packet_message(struct nfnl_log_net *log,
/* rcu_read_lock()ed by nf_hook_thresh or
* nf_log_packet.
*/
nla_put_be32(inst->skb, NFULA_IFINDEX_INDEV,
htonl(br_port_get_rcu(indev)->br->dev->ifindex)))
nflog_put_master_ifindex(inst->skb, NFULA_IFINDEX_INDEV, indev))
goto nla_put_failure;
} else {
int physinif;
@ -542,8 +558,7 @@ __build_packet_message(struct nfnl_log_net *log,
/* rcu_read_lock()ed by nf_hook_thresh or
* nf_log_packet.
*/
nla_put_be32(inst->skb, NFULA_IFINDEX_OUTDEV,
htonl(br_port_get_rcu(outdev)->br->dev->ifindex)))
nflog_put_master_ifindex(inst->skb, NFULA_IFINDEX_OUTDEV, outdev))
goto nla_put_failure;
} else {
struct net_device *physoutdev;

View file

@ -440,10 +440,47 @@ static bool nf_ct_drop_unconfirmed(const struct nf_queue_entry *entry, bool *is_
return false;
}
static bool nf_bridge_port_valid(const struct net_device *dev)
{
if (!dev)
return true;
return netif_is_bridge_port(dev);
}
/* queued skbs leave rcu protection. We bump device refcount so that
* the device cannot go away. However, while packet was out the port
* could have been removed from the bridge.
*
* Ensure in+outdev are still part of a bridge at reinject time.
*
* The device rx_handler_data could even be pointing at data that is
* not a net_bridge_port structure.
*/
static bool nf_bridge_ports_valid(const struct nf_queue_entry *entry)
{
#if IS_ENABLED(CONFIG_BRIDGE_NETFILTER)
if (!nf_bridge_port_valid(entry->physin) ||
!nf_bridge_port_valid(entry->physout))
return false;
#endif
if (entry->state.pf != PF_BRIDGE)
return true;
if (!nf_bridge_port_valid(entry->state.in) ||
!nf_bridge_port_valid(entry->state.out))
return false;
return true;
}
static void nfqnl_reinject(struct nf_queue_entry *entry, unsigned int verdict)
{
const struct nf_ct_hook *ct_hook;
if (!nf_bridge_ports_valid(entry))
verdict = NF_DROP;
if (verdict == NF_ACCEPT ||
verdict == NF_REPEAT ||
verdict == NF_STOP) {
@ -636,6 +673,23 @@ static int nf_queue_checksum_help(struct sk_buff *entskb)
return skb_checksum_help(entskb);
}
#if IS_ENABLED(CONFIG_BRIDGE_NETFILTER)
static int nfqnl_put_master_ifindex(struct sk_buff *nlskb, int attr,
const struct net_device *dev)
{
const struct net_device *upper;
if (dev && !netif_is_bridge_port(dev))
return 0;
upper = netdev_master_upper_dev_get_rcu((struct net_device *)dev);
if (upper && nla_put_be32(nlskb, attr, htonl(upper->ifindex)))
return -EMSGSIZE;
return 0;
}
#endif
static struct sk_buff *
nfqnl_build_packet_message(struct net *net, struct nfqnl_instance *queue,
struct nf_queue_entry *entry,
@ -771,10 +825,7 @@ nfqnl_build_packet_message(struct net *net, struct nfqnl_instance *queue,
* netfilter_bridge) */
if (nla_put_be32(skb, NFQA_IFINDEX_PHYSINDEV,
htonl(indev->ifindex)) ||
/* this is the bridge group "brX" */
/* rcu_read_lock()ed by __nf_queue */
nla_put_be32(skb, NFQA_IFINDEX_INDEV,
htonl(br_port_get_rcu(indev)->br->dev->ifindex)))
nfqnl_put_master_ifindex(skb, NFQA_IFINDEX_INDEV, indev))
goto nla_put_failure;
} else {
int physinif;
@ -805,10 +856,7 @@ nfqnl_build_packet_message(struct net *net, struct nfqnl_instance *queue,
* netfilter_bridge) */
if (nla_put_be32(skb, NFQA_IFINDEX_PHYSOUTDEV,
htonl(outdev->ifindex)) ||
/* this is the bridge group "brX" */
/* rcu_read_lock()ed by __nf_queue */
nla_put_be32(skb, NFQA_IFINDEX_OUTDEV,
htonl(br_port_get_rcu(outdev)->br->dev->ifindex)))
nfqnl_put_master_ifindex(skb, NFQA_IFINDEX_OUTDEV, outdev))
goto nla_put_failure;
} else {
int physoutif;

View file

@ -532,6 +532,9 @@ static int nft_exthdr_init(const struct nft_ctx *ctx,
return err;
}
if ((flags & NFT_EXTHDR_F_PRESENT) && len != 1)
return -EINVAL;
priv->type = nla_get_u8(tb[NFTA_EXTHDR_TYPE]);
priv->offset = offset;
priv->len = len;

View file

@ -107,6 +107,12 @@ int nft_fib_init(const struct nft_ctx *ctx, const struct nft_expr *expr,
return -EINVAL;
}
if (priv->flags & NFTA_FIB_F_PRESENT) {
if (priv->result != NFT_FIB_RESULT_OIF)
return -EINVAL;
len = sizeof(u8);
}
err = nft_parse_register_store(ctx, tb[NFTA_FIB_DREG], &priv->dreg,
NULL, NFT_DATA_VALUE, len);
if (err < 0)