Merge tag 'nf-next-26-08-18' of git://git.kernel.org/pub/scm/linux/kernel/git/netfilter/nf-next

Pablo Neira Ayuso says:

====================
Netfilter/IPVS fixes for net-next

This contains fixes for nf_tables, revisit issues with expectation
infra updates reported by sashiko, an ipset fix for deletions in the
hash:net type and tne fix for the IPVS FTP helper.

1) Validate layer 4 header mangling done via nfnetlink_queue and
   nft_payload, this is a follow up to recent similar validation
   at layer 3. From Zhiling Zou.

2) Do not allocate memory on delete operations in ipset hash:net
   type, delete operation must always succeed. From Florian Westphal.

3) Deliver nft_obj overquota packet path notification directly via
   nfnetlink, do not use the control plane batch logic.
   From Fourie Zhang.

4) Follow up to controlidate check for reinserted dead expectations,
   to cover the nf_conntrack_expect_related_pair() function too.

5) Do not expose expectation dead flag to userspace via ctnetlink.

6) Make commit set_update_list per-netns to prepare to publish
   set clone earlier.

7) Publish the set clone earlier from commit path to address set
   lookup failures during table re-creation, this is targetting
   the rbtree and pipapo set backends.

8) Fix an integer overflow in the IPVS FTP helper. A similar fix
   was already proposed for the conntrack FTP helper months ago.
   From Joas Antonio dos Santos.

* tag 'nf-next-26-08-18' of git://git.kernel.org/pub/scm/linux/kernel/git/netfilter/nf-next:
  ipvs: fix integer overflow in ftp helper port/address parsing
  netfilter: nf_tables: call set ops .commit when building new ruleset blob
  netfilter: nf_tables: move set_update_list to nftables per-netns
  netfilter: ctnetlink: do not expose expectation DEAD flag
  netfilter: nf_conntrack_expect: consolidate check for insertion of dead expectation
  netfilter: nf_tables: don't queue packet path object notifications
  netfilter: ipset: remove need to allocate memory on delete operations
  netfilter: validate L4 headers after userspace packet writes
====================

Link: https://patch.msgid.link/20260817232957.1281637-1-pablo@netfilter.org
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
Jakub Kicinski
2026-08-20 11:36:56 -07:00
10 changed files with 308 additions and 120 deletions

View File

@@ -1949,6 +1949,7 @@ struct nftables_pernet {
struct list_head binding_list;
struct list_head module_list;
struct list_head notify_list;
struct list_head set_update_list;
struct mutex commit_mutex;
u64 table_handle;
u64 tstamp;

View File

@@ -8,6 +8,7 @@
#include <linux/rcupdate_wait.h>
#include <linux/jhash.h>
#include <linux/types.h>
#include <linux/seqlock.h>
#include <linux/netfilter/nfnetlink.h>
#include <linux/netfilter/ipset/ip_set.h>
@@ -98,14 +99,34 @@ struct htable {
#define IPSET_NET_COUNT 1
#endif
/* Book-keeping of the prefixes added to the set */
/**
* struct net_prefix - Representation of a network prefix.
* @cidr: The CIDR prefix length.
* @count: Number of occurrences.
*/
struct net_prefix {
u8 cidr; /* the cidr value */
u32 count; /* number of elements of this cidr */
u32 cidr:8;
u32 count:24;
};
#define CIDR_MAX_COUNT ((1 << 24) - 1)
/**
* struct net_prefixes - A collection of network prefixes.
* @rcu: RCU head
* @seq: Sequence counter guarding in-place reordering of @nets
* @len: Number of entries in the array.
* @nets: Array of net_prefix structures (sorted by CIDR descending).
*
* @nets entries are updated in place under @set's lock. A single entry's
* cidr/count pair is always updated atomically via READ_ONCE()/WRITE_ONCE(),
* but removing an entry also shifts every following entry down by one slot.
* Lockless readers that scan the whole array (i.e. more than a single
* indexed slot) must use @seq to detect and retry across such a shift.
*/
struct net_prefixes {
struct rcu_head rcu;
seqcount_spinlock_t seq;
u8 len;
struct net_prefix nets[] __counted_by(len);
};
@@ -143,8 +164,11 @@ htable_size(u8 hbits)
#endif
#define INIT_CIDR(n, host_mask) ({ \
const struct net_prefixes *__n = rcu_dereference(n); \
DCIDR_PUT((__n)->len ? (__n)->nets[0].cidr : host_mask);\
const struct net_prefixes *__n = rcu_dereference(n); \
struct net_prefix __p = \
__n->len ? READ_ONCE(__n->nets[0]) \
: (struct net_prefix){}; \
DCIDR_PUT(__p.count ? __p.cidr : host_mask); \
})
#endif /* IP_SET_HASH_WITH_NETS */
@@ -318,27 +342,43 @@ struct mtype_resize_ad {
};
#ifdef IP_SET_HASH_WITH_NETS
/* Network cidr size book keeping when the hash stores different
* sized networks. cidr == real cidr + 1 to support /0.
/**
* mtype_add_cidr - Add a CIDR entry to hash table bookkeeping
* @set: Pointer to the ip_set
* @h: Pointer to the htype
* @cidr: The CIDR prefix length
* @n: The index of the net_prefix array to add @cidr to
*
* Performs an update if @cidr is found, otherwise performs COW-style
* allocation and replacement via RCU.
*
* Return: 0 on success, negative error code on failure.
*/
static int
mtype_add_cidr(struct ip_set *set, struct htype *h, u8 cidr, u8 n)
{
struct net_prefixes *nets, *tmp;
int i, j, found, len = 0, ret = 0;
struct net_prefixes *nets, *tmp;
struct net_prefix np;
spin_lock_bh(&set->lock);
nets = __ipset_dereference(h->rnets[n]);
/* Add in increasing prefix order, so larger cidr first */
for (i = 0, found = -1; i < nets->len; i++) {
if (nets->nets[i].count)
np = READ_ONCE(nets->nets[i]);
if (np.count)
len++;
if (found != -1) {
continue;
} else if (nets->nets[i].cidr < cidr) {
} else if (np.cidr < cidr) {
found = i;
} else if (nets->nets[i].cidr == cidr) {
nets->nets[i].count++;
} else if (np.cidr == cidr) {
if (np.count < CIDR_MAX_COUNT) {
np.count++;
WRITE_ONCE(nets->nets[i], np);
} else {
ret = -EOVERFLOW;
}
goto unlock;
}
}
@@ -350,6 +390,7 @@ mtype_add_cidr(struct ip_set *set, struct htype *h, u8 cidr, u8 n)
}
tmp->len = len;
seqcount_spinlock_init(&tmp->seq, &set->lock);
for (i = 0, j = 0; i < nets->len; i++) {
if (i == found) {
tmp->nets[j].cidr = cidr;
@@ -371,42 +412,60 @@ mtype_add_cidr(struct ip_set *set, struct htype *h, u8 cidr, u8 n)
return ret;
}
/**
* mtype_del_cidr - Remove CIDR entry and maintain array integrity.
* @set: Pointer to the ip_set.
* @h: Pointer to the htype.
* @cidr: The CIDR prefix length.
* @n: The index of the net_prefix array to remove @cidr from
*
* If CIDR entry count falls to 0, this function performs a "shift-left"
* operation on all following elements. This ensures that the array remains
* contiguous and maintains its descending order by CIDR. The vacated slot
* at the end of the array is zeroed out (cidr=0, count=0).
*/
static void
mtype_del_cidr(struct ip_set *set, struct htype *h, u8 cidr, u8 n)
{
struct net_prefixes *nets, *tmp;
u8 i, j, len = 0;
struct net_prefixes *nets;
struct net_prefix np;
int found;
u8 i, j;
BUILD_BUG_ON(sizeof(struct net_prefix) != sizeof(u32));
spin_lock_bh(&set->lock);
nets = __ipset_dereference(h->rnets[n]);
for (i = 0, found = -1; i < nets->len; i++) {
if (nets->nets[i].count)
len++;
if (nets->nets[i].cidr == cidr)
np = READ_ONCE(nets->nets[i]);
if (np.count && np.cidr == cidr) {
np.count--;
found = i;
break;
}
}
if (unlikely(found == -1))
goto unlock;
nets->nets[found].count--;
if (nets->nets[found].count)
if (np.count) {
WRITE_ONCE(nets->nets[found], np);
goto unlock;
len--;
tmp = kzalloc_flex(*tmp, nets, len, GFP_ATOMIC);
if (!tmp)
/* Leave a hole */
goto unlock;
tmp->len = len;
for (i = 0, j = 0; i < nets->len; i++) {
if (!nets->nets[i].count || i == found)
continue;
tmp->nets[j].cidr = nets->nets[i].cidr;
tmp->nets[j++].count = nets->nets[i].count;
}
rcu_assign_pointer(h->rnets[n], tmp);
kfree_rcu(nets, rcu);
write_seqcount_begin(&nets->seq);
for (i = 0, j = 0; i < nets->len; i++) {
if (i == found)
continue;
np = READ_ONCE(nets->nets[i]);
if (i != j)
WRITE_ONCE(nets->nets[j], np);
j++;
}
while (j < nets->len)
WRITE_ONCE(nets->nets[j++], (struct net_prefix){});
write_seqcount_end(&nets->seq);
unlock:
spin_unlock_bh(&set->lock);
}
@@ -451,7 +510,7 @@ mtype_flush(struct ip_set *set)
{
struct htype *h = set->data;
#ifdef IP_SET_HASH_WITH_NETS
struct net_prefixes *nets, *tmp;
struct net_prefixes *nets;
#endif
struct htable *t;
struct hbucket *n;
@@ -477,17 +536,15 @@ mtype_flush(struct ip_set *set)
}
#ifdef IP_SET_HASH_WITH_NETS
for (i = 0; i < IPSET_NET_COUNT; i++) {
nets = ipset_dereference_nfnl(h->rnets[i]);
tmp = kzalloc_obj(*tmp, GFP_ATOMIC);
if (!tmp) {
u8 j;
u8 j;
for (j = 0; j < nets->len; j++)
nets->nets[j].count = 0;
} else {
rcu_assign_pointer(h->rnets[i], tmp);
kfree_rcu(nets, rcu);
}
spin_lock_bh(&set->lock);
nets = ipset_dereference_nfnl(h->rnets[i]);
write_seqcount_begin(&nets->seq);
for (j = 0; j < nets->len; j++)
WRITE_ONCE(nets->nets[j], (struct net_prefix){});
write_seqcount_end(&nets->seq);
spin_unlock_bh(&set->lock);
}
#endif
}
@@ -1253,31 +1310,41 @@ mtype_test_cidrs(struct ip_set *set, struct mtype_elem *d,
#if IPSET_NET_COUNT == 2
struct net_prefixes *nets1;
struct mtype_elem orig = *d;
unsigned int seq1;
int ret, i, j, k;
#else
int ret, i, j;
#endif
u32 key, multi = 0;
unsigned int seq0;
u32 key, multi;
u8 pos;
pr_debug("test by nets\n");
rcu_read_lock_bh();
retry:
multi = 0;
nets0 = rcu_dereference_bh(h->rnets[0]);
seq0 = read_seqcount_begin(&nets0->seq);
#if IPSET_NET_COUNT == 2
nets1 = rcu_dereference_bh(h->rnets[1]);
seq1 = read_seqcount_begin(&nets1->seq);
#endif
for (j = 0; j < nets0->len && !multi; j++) {
if (!nets0->nets[j].count)
struct net_prefix p0 = READ_ONCE(nets0->nets[j]);
if (!p0.count)
continue;
#if IPSET_NET_COUNT == 2
mtype_data_reset_elem(d, &orig);
mtype_data_netmask(d, nets0->nets[j].cidr, false);
mtype_data_netmask(d, p0.cidr, false);
for (k = 0; k < nets1->len && !multi; k++) {
if (!nets1->nets[k].count)
struct net_prefix p1 = READ_ONCE(nets1->nets[k]);
if (!p1.count)
continue;
mtype_data_netmask(d, nets1->nets[k].cidr, true);
mtype_data_netmask(d, p1.cidr, true);
#else
mtype_data_netmask(d, nets0->nets[j].cidr);
mtype_data_netmask(d, p0.cidr);
#endif
key = HKEY(d, h->initval, t->htable_bits);
n = rcu_dereference_bh(hbucket(t, key));
@@ -1304,6 +1371,12 @@ mtype_test_cidrs(struct ip_set *set, struct mtype_elem *d,
}
ret = 0;
unlock:
if (read_seqcount_retry(&nets0->seq, seq0))
goto retry;
#if IPSET_NET_COUNT == 2
if (read_seqcount_retry(&nets1->seq, seq1))
goto retry;
#endif
rcu_read_unlock_bh();
return ret;
}
@@ -1707,6 +1780,7 @@ IPSET_TOKEN(HTYPE, _create)(struct net *net, struct ip_set *set,
kfree(rcu_dereference_raw(h->rnets[--i]));
goto free_hregion;
}
seqcount_spinlock_init(&nets->seq, &set->lock);
RCU_INIT_POINTER(h->rnets[i], nets);
}
#endif

View File

@@ -38,7 +38,6 @@ MODULE_ALIAS("ip_set_hash:net,iface");
#define HTYPE hash_netiface
#define IP_SET_HASH_WITH_NETS
#define IP_SET_HASH_WITH_MULTI
#define IP_SET_HASH_WITH_NET0
#define STRSCPY(a, b) strscpy(a, b, IFNAMSIZ)

View File

@@ -36,7 +36,6 @@ MODULE_ALIAS("ip_set_hash:net,port,net");
#define IP_SET_HASH_WITH_PROTO
#define IP_SET_HASH_WITH_NETS
#define IPSET_NET_COUNT 2
#define IP_SET_HASH_WITH_NET0
/* IPv4 variant */

View File

@@ -102,7 +102,7 @@ static int ip_vs_ftp_get_addrport(char *data, char *data_limit,
char *s, c;
unsigned char p[6];
char edelim;
__u16 hport;
__u32 hport;
int i = 0;
if (data_limit - data < plen) {
@@ -144,7 +144,11 @@ static int ip_vs_ftp_get_addrport(char *data, char *data_limit,
return -1;
c = *data;
if (isdigit(c)) {
p[i] = p[i]*10 + c - '0';
unsigned int val = p[i] * 10 + c - '0';
if (val > 255)
return -1;
p[i] = val;
} else if (c == ',' && i < 5) {
i++;
p[i] = 0;
@@ -222,6 +226,8 @@ static int ip_vs_ftp_get_addrport(char *data, char *data_limit,
if (!isdigit(*s))
break;
hport = hport * 10 + *s - '0';
if (hport > 65535)
return -1;
}
if (s == data_limit || !hport || *s != edelim)
return -1;

View File

@@ -477,6 +477,11 @@ static inline int __nf_ct_expect_check(struct nf_conntrack_expect *expect,
lockdep_nfct_expect_lock_held();
if (expect->flags & NF_CT_EXPECT_DEAD) {
DEBUG_NET_WARN_ON_ONCE(1);
return -EINVAL;
}
h = nf_ct_expect_dst_hash(net, &expect->tuple);
hlist_for_each_entry_safe(i, next, &nf_ct_expect_hash[h], hnode) {
if (nf_ct_exp_is_expired(i)) {
@@ -528,12 +533,6 @@ int nf_ct_expect_related_report(struct nf_conntrack_expect *expect,
int ret;
spin_lock_bh(&nf_conntrack_expect_lock);
if (expect->flags & NF_CT_EXPECT_DEAD) {
DEBUG_NET_WARN_ON_ONCE(1);
ret = -EINVAL;
goto out;
}
master_help = nfct_help(expect->master);
if (!master_help) {
ret = -ESHUTDOWN;

View File

@@ -3042,7 +3042,7 @@ ctnetlink_exp_dump_expect(struct sk_buff *skb,
#endif
if (nla_put_be32(skb, CTA_EXPECT_TIMEOUT, htonl(timeout)) ||
nla_put_be32(skb, CTA_EXPECT_ID, nf_expect_get_id(exp)) ||
nla_put_be32(skb, CTA_EXPECT_FLAGS, htonl(exp->flags)) ||
nla_put_be32(skb, CTA_EXPECT_FLAGS, htonl(exp->flags & NF_CT_EXPECT_MASK)) ||
nla_put_be32(skb, CTA_EXPECT_CLASS, htonl(exp->class)))
goto nla_put_failure;

View File

@@ -595,10 +595,15 @@ static void nft_trans_commit_list_add_tail(struct net *net, struct nft_trans *tr
static void nft_trans_commit_list_add_elem(struct net *net, struct nft_trans *trans)
{
struct nftables_pernet *nft_net = nft_pernet(net);
struct nft_trans_elem *te;
WARN_ON_ONCE(trans->msg_type != NFT_MSG_NEWSETELEM &&
trans->msg_type != NFT_MSG_DELSETELEM);
te = nft_trans_container_elem(trans);
if (te->set->ops->commit && list_empty(&te->set->pending_update))
list_add_tail(&te->set->pending_update, &nft_net->set_update_list);
if (nft_trans_try_collapse(nft_net, trans)) {
kfree(trans);
return;
@@ -7186,7 +7191,30 @@ static void nft_setelem_remove(const struct net *net,
}
static void nft_trans_elems_remove(const struct nft_ctx *ctx,
const struct nft_trans_elem *te)
const struct nft_trans_elem *te,
bool notify)
{
int i;
for (i = 0; i < te->nelems; i++) {
WARN_ON_ONCE(te->elems[i].update);
if (notify) {
nf_tables_setelem_notify(ctx, te->set,
te->elems[i].priv,
te->nft_trans.msg_type);
}
nft_setelem_remove(ctx->net, te->set, te->elems[i].priv);
if (!nft_setelem_is_catchall(te->set, te->elems[i].priv)) {
atomic_dec(&te->set->nelems);
te->set->ndeact--;
}
}
}
static void nft_trans_elems_remove_notify(const struct nft_ctx *ctx,
const struct nft_trans_elem *te)
{
int i;
@@ -7196,12 +7224,6 @@ static void nft_trans_elems_remove(const struct nft_ctx *ctx,
nf_tables_setelem_notify(ctx, te->set,
te->elems[i].priv,
te->nft_trans.msg_type);
nft_setelem_remove(ctx->net, te->set, te->elems[i].priv);
if (!nft_setelem_is_catchall(te->set, te->elems[i].priv)) {
atomic_dec(&te->set->nelems);
te->set->ndeact--;
}
}
}
@@ -8715,18 +8737,17 @@ static int nf_tables_delobj(struct sk_buff *skb, const struct nfnl_info *info,
return nft_delobj(&ctx, obj);
}
static void
__nft_obj_notify(struct net *net, const struct nft_table *table,
struct nft_object *obj, u32 portid, u32 seq, int event,
u16 flags, int family, int report, gfp_t gfp)
static struct sk_buff *
nft_obj_notify_alloc(struct net *net, const struct nft_table *table,
struct nft_object *obj, u32 portid, u32 seq, int event,
u16 flags, int family, int report, gfp_t gfp)
{
struct nftables_pernet *nft_net = nft_pernet(net);
struct sk_buff *skb;
int err;
if (!report &&
!nfnetlink_has_listeners(net, NFNLGRP_NFTABLES))
return;
return NULL;
skb = nlmsg_new(NLMSG_GOODSIZE, gfp);
if (skb == NULL)
@@ -8740,10 +8761,10 @@ __nft_obj_notify(struct net *net, const struct nft_table *table,
goto err;
}
nft_notify_enqueue(skb, report, &nft_net->notify_list);
return;
return skb;
err:
nfnetlink_set_err(net, portid, NFNLGRP_NFTABLES, -ENOBUFS);
return NULL;
}
void nft_obj_notify(struct net *net, const struct nft_table *table,
@@ -8752,6 +8773,7 @@ void nft_obj_notify(struct net *net, const struct nft_table *table,
{
char *buf = kasprintf(gfp, "%s:%u",
table->name, nft_base_seq(net));
struct sk_buff *skb;
audit_log_nfcfg(buf,
family,
@@ -8762,17 +8784,27 @@ void nft_obj_notify(struct net *net, const struct nft_table *table,
gfp);
kfree(buf);
__nft_obj_notify(net, table, obj, portid, seq, event,
flags, family, report, gfp);
/* Called from the packet path, holding no mutex: notify_list is
* serialised by commit_mutex, so send this notification directly.
*/
skb = nft_obj_notify_alloc(net, table, obj, portid, seq, event,
flags, family, report, gfp);
if (skb)
nfnetlink_send(skb, net, portid, NFNLGRP_NFTABLES, report, gfp);
}
EXPORT_SYMBOL_GPL(nft_obj_notify);
static void nf_tables_obj_notify(const struct nft_ctx *ctx,
struct nft_object *obj, int event)
{
__nft_obj_notify(ctx->net, ctx->table, obj, ctx->portid,
ctx->seq, event, ctx->flags, ctx->family,
ctx->report, GFP_KERNEL);
struct nftables_pernet *nft_net = nft_pernet(ctx->net);
struct sk_buff *skb;
skb = nft_obj_notify_alloc(ctx->net, ctx->table, obj, ctx->portid,
ctx->seq, event, ctx->flags, ctx->family,
ctx->report, GFP_KERNEL);
if (skb)
nft_notify_enqueue(skb, ctx->report, &nft_net->notify_list);
}
/*
@@ -10848,11 +10880,31 @@ static void nf_tables_commit_audit_log(struct list_head *adl, u32 generation)
}
}
static void nft_set_commit_update(struct list_head *set_update_list)
static void nft_set_commit_update(struct nft_ctx *ctx,
struct nftables_pernet *nft_net)
{
struct nft_set *set, *next;
struct nft_trans_elem *te;
struct nft_trans *trans;
list_for_each_entry_safe(set, next, set_update_list, pending_update) {
if (list_empty(&nft_net->set_update_list))
return;
list_for_each_entry(trans, &nft_net->commit_list, list) {
nft_ctx_update(ctx, trans);
switch (trans->msg_type) {
case NFT_MSG_DELSETELEM:
te = nft_trans_container_elem(trans);
if (!te->set->ops->commit)
break;
nft_trans_elems_remove(ctx, te, false);
break;
}
}
list_for_each_entry_safe(set, next, &nft_net->set_update_list, pending_update) {
list_del_init(&set->pending_update);
if (!set->ops->commit || set->dead)
@@ -10885,7 +10937,6 @@ static int nf_tables_commit(struct net *net, struct sk_buff *skb)
struct nft_trans_binding *trans_binding;
struct nft_trans *trans, *next;
unsigned int base_seq, gc_seq;
LIST_HEAD(set_update_list);
struct nft_trans_elem *te;
struct nft_chain *chain;
struct nft_table *table;
@@ -10960,6 +11011,8 @@ static int nf_tables_commit(struct net *net, struct sk_buff *skb)
}
/* step 2. Make rules_gen_X visible to packet path */
nft_set_commit_update(&ctx, nft_net);
list_for_each_entry(table, &nft_net->tables, list) {
list_for_each_entry(chain, &table->chains, list)
nf_tables_commit_chain(net, chain);
@@ -11091,27 +11144,16 @@ static int nf_tables_commit(struct net *net, struct sk_buff *skb)
break;
case NFT_MSG_NEWSETELEM:
te = nft_trans_container_elem(trans);
nft_trans_elems_add(&ctx, te);
if (te->set->ops->commit &&
list_empty(&te->set->pending_update)) {
list_add_tail(&te->set->pending_update,
&set_update_list);
}
nft_trans_destroy(trans);
break;
case NFT_MSG_DELSETELEM:
case NFT_MSG_DESTROYSETELEM:
te = nft_trans_container_elem(trans);
nft_trans_elems_remove(&ctx, te);
if (te->set->ops->commit &&
list_empty(&te->set->pending_update)) {
list_add_tail(&te->set->pending_update,
&set_update_list);
}
if (te->set->ops->commit)
nft_trans_elems_remove_notify(&ctx, te);
else
nft_trans_elems_remove(&ctx, te, true);
break;
case NFT_MSG_NEWOBJ:
if (nft_trans_obj_update(trans)) {
@@ -11180,8 +11222,6 @@ static int nf_tables_commit(struct net *net, struct sk_buff *skb)
}
}
nft_set_commit_update(&set_update_list);
nft_commit_notify(net, NETLINK_CB(skb).portid);
nf_tables_gen_notify(net, skb, NFT_MSG_NEWGEN);
nf_tables_commit_audit_log(&adl, nft_base_seq(net));
@@ -11247,11 +11287,11 @@ static void nf_tables_abort_release(struct nft_trans *trans)
kfree(trans);
}
static void nft_set_abort_update(struct list_head *set_update_list)
static void nft_set_abort_update(struct nftables_pernet *nft_net)
{
struct nft_set *set, *next;
list_for_each_entry_safe(set, next, set_update_list, pending_update) {
list_for_each_entry_safe(set, next, &nft_net->set_update_list, pending_update) {
list_del_init(&set->pending_update);
if (!set->ops->abort)
@@ -11386,33 +11426,22 @@ static int __nf_tables_abort(struct net *net, enum nfnl_abort_action action)
nft_trans_destroy(trans);
break;
case NFT_MSG_NEWSETELEM:
te = nft_trans_container_elem(trans);
if (nft_trans_elem_set_bound(trans)) {
list_del_init(&te->set->pending_update);
nft_trans_destroy(trans);
break;
}
te = nft_trans_container_elem(trans);
if (!nft_trans_elems_new_abort(&ctx, te)) {
nft_trans_destroy(trans);
break;
}
if (te->set->ops->abort &&
list_empty(&te->set->pending_update)) {
list_add_tail(&te->set->pending_update,
&set_update_list);
}
break;
case NFT_MSG_DELSETELEM:
case NFT_MSG_DESTROYSETELEM:
te = nft_trans_container_elem(trans);
nft_trans_elems_destroy_abort(&ctx, te);
if (te->set->ops->abort &&
list_empty(&te->set->pending_update)) {
list_add_tail(&te->set->pending_update,
&set_update_list);
}
nft_trans_destroy(trans);
break;
case NFT_MSG_NEWOBJ:
@@ -11458,7 +11487,7 @@ static int __nf_tables_abort(struct net *net, enum nfnl_abort_action action)
WARN_ON_ONCE(!list_empty(&nft_net->commit_set_list));
nft_set_abort_update(&set_update_list);
nft_set_abort_update(nft_net);
synchronize_rcu();
@@ -12142,6 +12171,7 @@ static int __net_init nf_tables_init_net(struct net *net)
INIT_LIST_HEAD(&nft_net->binding_list);
INIT_LIST_HEAD(&nft_net->module_list);
INIT_LIST_HEAD(&nft_net->notify_list);
INIT_LIST_HEAD(&nft_net->set_update_list);
mutex_init(&nft_net->commit_mutex);
net->nft.base_seq = 1;
nft_net->gc_seq = 0;
@@ -12186,6 +12216,7 @@ static void __net_exit nf_tables_exit_net(struct net *net)
WARN_ON_ONCE(!list_empty(&nft_net->module_list));
WARN_ON_ONCE(!list_empty(&nft_net->notify_list));
WARN_ON_ONCE(!list_empty(&nft_net->destroy_list));
WARN_ON_ONCE(!list_empty(&nft_net->set_update_list));
}
static void nf_tables_exit_batch(struct list_head *net_exit_list)

View File

@@ -28,10 +28,17 @@
#include <linux/netfilter/nfnetlink.h>
#include <linux/netfilter/nfnetlink_queue.h>
#include <linux/netfilter/nf_conntrack_common.h>
#include <linux/icmp.h>
#include <linux/icmpv6.h>
#include <linux/ip.h>
#include <linux/list.h>
#include <linux/sctp.h>
#include <linux/cgroup-defs.h>
#include <linux/rhashtable.h>
#include <linux/jhash.h>
#include <linux/tcp.h>
#include <linux/udp.h>
#include <net/gre.h>
#include <net/gso.h>
#include <net/sock.h>
#include <net/tcp_states.h>
@@ -1206,10 +1213,62 @@ static bool nfqnl_validate_ipopts(const struct iphdr *iph_new,
return memcmp(iph_new + 1, ip_hdr(e->skb) + 1, ihl - sizeof(*iph_orig)) == 0;
}
static bool nfqnl_validate_l4(const u8 *data, unsigned int data_len,
const struct nf_queue_entry *e, u8 proto,
bool fragment)
{
#if IS_ENABLED(CONFIG_NF_CONNTRACK)
enum ip_conntrack_info ctinfo;
const struct nf_conn *ct;
ct = nf_ct_get(e->skb, &ctinfo);
if (ct && !nf_ct_is_template(ct)) {
if (fragment || nf_ct_protonum(ct) != proto)
return false;
}
#endif
if (fragment)
return true;
switch (proto) {
case IPPROTO_TCP: {
const struct tcphdr *th = (const struct tcphdr *)data;
unsigned int thlen;
if (data_len < sizeof(*th))
return false;
thlen = __tcp_hdrlen(th);
if (thlen < sizeof(*th) || data_len < thlen)
return false;
return true;
}
case IPPROTO_UDP:
return data_len >= sizeof(struct udphdr);
case IPPROTO_ICMP:
return data_len >= sizeof(struct icmphdr);
case IPPROTO_ICMPV6:
return data_len >= sizeof(struct icmp6hdr);
case IPPROTO_SCTP:
return data_len >= sizeof(struct sctphdr);
case IPPROTO_GRE:
return data_len >= sizeof(struct gre_base_hdr);
case IPPROTO_AH:
return data_len >= sizeof(struct ip_auth_hdr);
case IPPROTO_ESP:
return data_len >= sizeof(struct ip_esp_hdr);
}
return true;
}
static bool nfqnl_validate_ip4(const struct iphdr *iph, unsigned int data_len,
const struct nf_queue_entry *e)
{
unsigned int ihl;
bool fragment;
if (data_len < sizeof(*iph))
return false;
@@ -1226,10 +1285,14 @@ static bool nfqnl_validate_ip4(const struct iphdr *iph, unsigned int data_len,
if (ntohs(iph->tot_len) != data_len)
return false;
fragment = iph->frag_off & htons(IP_MF | IP_OFFSET);
/* support for ipopts mangling would require
* recompile + skb transport header update.
*/
return nfqnl_validate_ipopts(iph, e);
return nfqnl_validate_ipopts(iph, e) &&
nfqnl_validate_l4((const u8 *)iph + ihl, data_len - ihl, e,
iph->protocol, fragment);
}
static bool nfqnl_validate_one_exthdr(const u8 *data,
@@ -1273,6 +1336,7 @@ static bool nfqnl_validate_exthdr(const struct ipv6hdr *ip6_new,
const u8 *data = (const u8 *)ip6_new;
u8 orig_nexthdr = ip6_orig->nexthdr;
u8 new_nexthdr = ip6_new->nexthdr;
bool fragment = false;
if (new_nexthdr != orig_nexthdr)
return false;
@@ -1286,7 +1350,8 @@ static bool nfqnl_validate_exthdr(const struct ipv6hdr *ip6_new,
int hdrlen;
if (orig_nexthdr == NEXTHDR_NONE)
return true;
return nfqnl_validate_l4(data, data_len, e,
new_nexthdr, fragment);
if (unlikely(exthdr_cnt++ >= IP6_MAX_EXT_HDRS_CNT))
return false;
@@ -1297,6 +1362,7 @@ static bool nfqnl_validate_exthdr(const struct ipv6hdr *ip6_new,
switch (orig_nexthdr) {
case NEXTHDR_FRAGMENT:
fragment = true;
hdrlen = sizeof(struct frag_hdr);
break;
case NEXTHDR_AUTH:
@@ -1323,7 +1389,7 @@ static bool nfqnl_validate_exthdr(const struct ipv6hdr *ip6_new,
data += hdrlen;
}
return true;
return nfqnl_validate_l4(data, data_len, e, new_nexthdr, fragment);
}
static bool nfqnl_validate_ip6(const struct ipv6hdr *ip6, unsigned int data_len,

View File

@@ -1067,6 +1067,17 @@ static bool nft_payload_csum_write_ok(const struct nft_pktinfo *pkt,
return false;
}
static bool nft_th_write_ok(const struct nft_pktinfo *pkt,
const struct nft_payload_set *priv)
{
unsigned int doff = offsetof(struct tcphdr, ack_seq) + sizeof(__be32);
if (pkt->tprot != IPPROTO_TCP)
return true;
return priv->offset > doff || priv->offset + priv->len <= doff;
}
static void nft_payload_set_eval(const struct nft_expr *expr,
struct nft_regs *regs,
const struct nft_pktinfo *pkt)
@@ -1105,6 +1116,8 @@ static void nft_payload_set_eval(const struct nft_expr *expr,
case NFT_PAYLOAD_TRANSPORT_HEADER:
if (!(pkt->flags & NFT_PKTINFO_L4PROTO) || pkt->fragoff)
goto err;
if (!nft_th_write_ok(pkt, priv))
goto err;
offset = nft_thoff(pkt);
break;
case NFT_PAYLOAD_INNER_HEADER: