summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--include/net/netfilter/nf_tables.h1
-rw-r--r--net/netfilter/ipset/ip_set_hash_gen.h168
-rw-r--r--net/netfilter/ipset/ip_set_hash_netiface.c1
-rw-r--r--net/netfilter/ipset/ip_set_hash_netportnet.c1
-rw-r--r--net/netfilter/ipvs/ip_vs_ftp.c10
-rw-r--r--net/netfilter/nf_conntrack_expect.c11
-rw-r--r--net/netfilter/nf_conntrack_netlink.c2
-rw-r--r--net/netfilter/nf_tables_api.c137
-rw-r--r--net/netfilter/nfnetlink_queue.c72
-rw-r--r--net/netfilter/nft_payload.c13
10 files changed, 302 insertions, 114 deletions
diff --git a/include/net/netfilter/nf_tables.h b/include/net/netfilter/nf_tables.h
index 3be612145c13..238f6ecb90e9 100644
--- a/include/net/netfilter/nf_tables.h
+++ b/include/net/netfilter/nf_tables.h
@@ -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;
diff --git a/net/netfilter/ipset/ip_set_hash_gen.h b/net/netfilter/ipset/ip_set_hash_gen.h
index f00c82acd7f0..80ca523f304b 100644
--- a/net/netfilter/ipset/ip_set_hash_gen.h
+++ b/net/netfilter/ipset/ip_set_hash_gen.h
@@ -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 @@ unlock:
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)
- goto unlock;
- len--;
- tmp = kzalloc_flex(*tmp, nets, len, GFP_ATOMIC);
- if (!tmp)
- /* Leave a hole */
+ if (np.count) {
+ WRITE_ONCE(nets->nets[found], np);
goto unlock;
+ }
- tmp->len = len;
+ write_seqcount_begin(&nets->seq);
for (i = 0, j = 0; i < nets->len; i++) {
- if (!nets->nets[i].count || i == found)
+ if (i == found)
continue;
- tmp->nets[j].cidr = nets->nets[i].cidr;
- tmp->nets[j++].count = nets->nets[i].count;
+
+ np = READ_ONCE(nets->nets[i]);
+ if (i != j)
+ WRITE_ONCE(nets->nets[j], np);
+ j++;
}
- rcu_assign_pointer(h->rnets[n], tmp);
- kfree_rcu(nets, rcu);
+
+ 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
diff --git a/net/netfilter/ipset/ip_set_hash_netiface.c b/net/netfilter/ipset/ip_set_hash_netiface.c
index b44b95f766b7..b602cc43565d 100644
--- a/net/netfilter/ipset/ip_set_hash_netiface.c
+++ b/net/netfilter/ipset/ip_set_hash_netiface.c
@@ -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)
diff --git a/net/netfilter/ipset/ip_set_hash_netportnet.c b/net/netfilter/ipset/ip_set_hash_netportnet.c
index 6291532be7a5..61af1ce27127 100644
--- a/net/netfilter/ipset/ip_set_hash_netportnet.c
+++ b/net/netfilter/ipset/ip_set_hash_netportnet.c
@@ -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 */
diff --git a/net/netfilter/ipvs/ip_vs_ftp.c b/net/netfilter/ipvs/ip_vs_ftp.c
index b315c608fda4..9e3e005a8263 100644
--- a/net/netfilter/ipvs/ip_vs_ftp.c
+++ b/net/netfilter/ipvs/ip_vs_ftp.c
@@ -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;
diff --git a/net/netfilter/nf_conntrack_expect.c b/net/netfilter/nf_conntrack_expect.c
index f1f0c582db5d..06242c86e1dc 100644
--- a/net/netfilter/nf_conntrack_expect.c
+++ b/net/netfilter/nf_conntrack_expect.c
@@ -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;
diff --git a/net/netfilter/nf_conntrack_netlink.c b/net/netfilter/nf_conntrack_netlink.c
index fc3f60099af3..9b4e29557ec3 100644
--- a/net/netfilter/nf_conntrack_netlink.c
+++ b/net/netfilter/nf_conntrack_netlink.c
@@ -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;
diff --git a/net/netfilter/nf_tables_api.c b/net/netfilter/nf_tables_api.c
index af357f6c5070..c112ecc4fca3 100644
--- a/net/netfilter/nf_tables_api.c
+++ b/net/netfilter/nf_tables_api.c
@@ -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,16 +7191,19 @@ 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);
- nf_tables_setelem_notify(ctx, te->set,
- te->elems[i].priv,
- te->nft_trans.msg_type);
+ 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)) {
@@ -7205,6 +7213,20 @@ static void nft_trans_elems_remove(const struct nft_ctx *ctx,
}
}
+static void nft_trans_elems_remove_notify(const struct nft_ctx *ctx,
+ const struct nft_trans_elem *te)
+{
+ int i;
+
+ for (i = 0; i < te->nelems; i++) {
+ WARN_ON_ONCE(te->elems[i].update);
+
+ nf_tables_setelem_notify(ctx, te->set,
+ te->elems[i].priv,
+ te->nft_trans.msg_type);
+ }
+}
+
static bool nft_setelem_valid_key_end(const struct nft_set *set,
struct nlattr **nla, u32 flags)
{
@@ -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;
+
+ 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, 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->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)
diff --git a/net/netfilter/nfnetlink_queue.c b/net/netfilter/nfnetlink_queue.c
index b8aaf39cb4d8..c727668b0c5b 100644
--- a/net/netfilter/nfnetlink_queue.c
+++ b/net/netfilter/nfnetlink_queue.c
@@ -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,
diff --git a/net/netfilter/nft_payload.c b/net/netfilter/nft_payload.c
index 8a4472fd77d9..e315d35f73d4 100644
--- a/net/netfilter/nft_payload.c
+++ b/net/netfilter/nft_payload.c
@@ -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: