Commit 615efed8 authored by David S. Miller's avatar David S. Miller

Merge tag 'nf-23-09-13' of git://git.kernel.org/pub/scm/linux/kernel/git/netfilter/nf

netfilter pull request 23-09-13

====================

The following patchset contains Netfilter fixes for net:

1) Do not permit to remove rules from chain binding, otherwise
   double rule release is possible, triggering UaF. This rule
   deletion support does not make sense and userspace does not use
   this. Problem exists since the introduction of chain binding support.

2) rbtree GC worker only collects the elements that have expired.
   This operation is not destructive, therefore, turn write into
   read spinlock to avoid datapath contention due to GC worker run.
   This was not fixed in the recent GC fix batch in the 6.5 cycle.

3) pipapo set backend performs sync GC, therefore, catchall elements
   must use sync GC queue variant. This bug was introduced in the
   6.5 cycle with the recent GC fixes.

4) Stop GC run if memory allocation fails in pipapo set backend,
   otherwise access to NULL pointer to GC transaction object might
   occur. This bug was introduced in the 6.5 cycle with the recent
   GC fixes.

5) rhash GC run uses an iterator that might hit EAGAIN to rewind,
   triggering double-collection of the same element. This bug was
   introduced in the 6.5 cycle with the recent GC fixes.

6) Do not permit to remove elements in anonymous sets, this type of
   sets are populated once and then bound to rules. This fix is
   similar to the chain binding patch coming first in this batch.
   API permits since the very beginning but it has no use case from
   userspace.
====================
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parents 350db8a5 e8dbde59
......@@ -1700,8 +1700,9 @@ void nft_trans_gc_queue_sync_done(struct nft_trans_gc *trans);
void nft_trans_gc_elem_add(struct nft_trans_gc *gc, void *priv);
struct nft_trans_gc *nft_trans_gc_catchall(struct nft_trans_gc *gc,
struct nft_trans_gc *nft_trans_gc_catchall_async(struct nft_trans_gc *gc,
unsigned int gc_seq);
struct nft_trans_gc *nft_trans_gc_catchall_sync(struct nft_trans_gc *gc);
void nft_setelem_data_deactivate(const struct net *net,
const struct nft_set *set,
......
......@@ -40,10 +40,10 @@ static const u8 nf_ct_ext_type_len[NF_CT_EXT_NUM] = {
[NF_CT_EXT_ECACHE] = sizeof(struct nf_conntrack_ecache),
#endif
#ifdef CONFIG_NF_CONNTRACK_TIMESTAMP
[NF_CT_EXT_TSTAMP] = sizeof(struct nf_conn_acct),
[NF_CT_EXT_TSTAMP] = sizeof(struct nf_conn_tstamp),
#endif
#ifdef CONFIG_NF_CONNTRACK_TIMEOUT
[NF_CT_EXT_TIMEOUT] = sizeof(struct nf_conn_tstamp),
[NF_CT_EXT_TIMEOUT] = sizeof(struct nf_conn_timeout),
#endif
#ifdef CONFIG_NF_CONNTRACK_LABELS
[NF_CT_EXT_LABELS] = sizeof(struct nf_conn_labels),
......
......@@ -1432,7 +1432,7 @@ static int nft_flush_table(struct nft_ctx *ctx)
if (!nft_is_active_next(ctx->net, chain))
continue;
if (nft_chain_is_bound(chain))
if (nft_chain_binding(chain))
continue;
ctx->chain = chain;
......@@ -1446,8 +1446,7 @@ static int nft_flush_table(struct nft_ctx *ctx)
if (!nft_is_active_next(ctx->net, set))
continue;
if (nft_set_is_anonymous(set) &&
!list_empty(&set->bindings))
if (nft_set_is_anonymous(set))
continue;
err = nft_delset(ctx, set);
......@@ -1477,7 +1476,7 @@ static int nft_flush_table(struct nft_ctx *ctx)
if (!nft_is_active_next(ctx->net, chain))
continue;
if (nft_chain_is_bound(chain))
if (nft_chain_binding(chain))
continue;
ctx->chain = chain;
......@@ -2910,6 +2909,9 @@ static int nf_tables_delchain(struct sk_buff *skb, const struct nfnl_info *info,
return PTR_ERR(chain);
}
if (nft_chain_binding(chain))
return -EOPNOTSUPP;
nft_ctx_init(&ctx, net, skb, info->nlh, family, table, chain, nla);
if (nla[NFTA_CHAIN_HOOK]) {
......@@ -3449,6 +3451,8 @@ static int __nf_tables_dump_rules(struct sk_buff *skb,
struct net *net = sock_net(skb->sk);
const struct nft_rule *rule, *prule;
unsigned int s_idx = cb->args[0];
unsigned int entries = 0;
int ret = 0;
u64 handle;
prule = NULL;
......@@ -3471,9 +3475,11 @@ static int __nf_tables_dump_rules(struct sk_buff *skb,
NFT_MSG_NEWRULE,
NLM_F_MULTI | NLM_F_APPEND,
table->family,
table, chain, rule, handle, reset) < 0)
return 1;
table, chain, rule, handle, reset) < 0) {
ret = 1;
break;
}
entries++;
nl_dump_check_consistent(cb, nlmsg_hdr(skb));
cont:
prule = rule;
......@@ -3481,10 +3487,10 @@ static int __nf_tables_dump_rules(struct sk_buff *skb,
(*idx)++;
}
if (reset && *idx)
audit_log_rule_reset(table, cb->seq, *idx);
if (reset && entries)
audit_log_rule_reset(table, cb->seq, entries);
return 0;
return ret;
}
static int nf_tables_dump_rules(struct sk_buff *skb,
......@@ -3971,6 +3977,11 @@ static int nf_tables_newrule(struct sk_buff *skb, const struct nfnl_info *info,
}
if (info->nlh->nlmsg_flags & NLM_F_REPLACE) {
if (nft_chain_binding(chain)) {
err = -EOPNOTSUPP;
goto err_destroy_flow_rule;
}
err = nft_delrule(&ctx, old_rule);
if (err < 0)
goto err_destroy_flow_rule;
......@@ -4078,7 +4089,7 @@ static int nf_tables_delrule(struct sk_buff *skb, const struct nfnl_info *info,
NL_SET_BAD_ATTR(extack, nla[NFTA_RULE_CHAIN]);
return PTR_ERR(chain);
}
if (nft_chain_is_bound(chain))
if (nft_chain_binding(chain))
return -EOPNOTSUPP;
}
......@@ -4112,7 +4123,7 @@ static int nf_tables_delrule(struct sk_buff *skb, const struct nfnl_info *info,
list_for_each_entry(chain, &table->chains, list) {
if (!nft_is_active_next(net, chain))
continue;
if (nft_chain_is_bound(chain))
if (nft_chain_binding(chain))
continue;
ctx.chain = chain;
......@@ -7183,8 +7194,10 @@ static int nf_tables_delsetelem(struct sk_buff *skb,
if (IS_ERR(set))
return PTR_ERR(set);
if (!list_empty(&set->bindings) &&
(set->flags & (NFT_SET_CONSTANT | NFT_SET_ANONYMOUS)))
if (nft_set_is_anonymous(set))
return -EOPNOTSUPP;
if (!list_empty(&set->bindings) && (set->flags & NFT_SET_CONSTANT))
return -EBUSY;
nft_ctx_init(&ctx, net, skb, info->nlh, family, table, NULL, nla);
......@@ -9605,8 +9618,9 @@ void nft_trans_gc_queue_sync_done(struct nft_trans_gc *trans)
call_rcu(&trans->rcu, nft_trans_gc_trans_free);
}
struct nft_trans_gc *nft_trans_gc_catchall(struct nft_trans_gc *gc,
unsigned int gc_seq)
static struct nft_trans_gc *nft_trans_gc_catchall(struct nft_trans_gc *gc,
unsigned int gc_seq,
bool sync)
{
struct nft_set_elem_catchall *catchall;
const struct nft_set *set = gc->set;
......@@ -9622,7 +9636,11 @@ struct nft_trans_gc *nft_trans_gc_catchall(struct nft_trans_gc *gc,
nft_set_elem_dead(ext);
dead_elem:
if (sync)
gc = nft_trans_gc_queue_sync(gc, GFP_ATOMIC);
else
gc = nft_trans_gc_queue_async(gc, gc_seq, GFP_ATOMIC);
if (!gc)
return NULL;
......@@ -9632,6 +9650,17 @@ struct nft_trans_gc *nft_trans_gc_catchall(struct nft_trans_gc *gc,
return gc;
}
struct nft_trans_gc *nft_trans_gc_catchall_async(struct nft_trans_gc *gc,
unsigned int gc_seq)
{
return nft_trans_gc_catchall(gc, gc_seq, false);
}
struct nft_trans_gc *nft_trans_gc_catchall_sync(struct nft_trans_gc *gc)
{
return nft_trans_gc_catchall(gc, 0, true);
}
static void nf_tables_module_autoload_cleanup(struct net *net)
{
struct nftables_pernet *nft_net = nft_pernet(net);
......@@ -11054,7 +11083,7 @@ static void __nft_release_table(struct net *net, struct nft_table *table)
ctx.family = table->family;
ctx.table = table;
list_for_each_entry(chain, &table->chains, list) {
if (nft_chain_is_bound(chain))
if (nft_chain_binding(chain))
continue;
ctx.chain = chain;
......
......@@ -338,13 +338,10 @@ static void nft_rhash_gc(struct work_struct *work)
while ((he = rhashtable_walk_next(&hti))) {
if (IS_ERR(he)) {
if (PTR_ERR(he) != -EAGAIN) {
nft_trans_gc_destroy(gc);
gc = NULL;
goto try_later;
}
continue;
}
/* Ruleset has been updated, try later. */
if (READ_ONCE(nft_net->gc_seq) != gc_seq) {
......@@ -372,7 +369,7 @@ static void nft_rhash_gc(struct work_struct *work)
nft_trans_gc_elem_add(gc, he);
}
gc = nft_trans_gc_catchall(gc, gc_seq);
gc = nft_trans_gc_catchall_async(gc, gc_seq);
try_later:
/* catchall list iteration requires rcu read side lock. */
......
......@@ -1596,7 +1596,7 @@ static void pipapo_gc(const struct nft_set *_set, struct nft_pipapo_match *m)
gc = nft_trans_gc_queue_sync(gc, GFP_ATOMIC);
if (!gc)
break;
return;
nft_pipapo_gc_deactivate(net, set, e);
pipapo_drop(m, rulemap);
......@@ -1610,7 +1610,7 @@ static void pipapo_gc(const struct nft_set *_set, struct nft_pipapo_match *m)
}
}
gc = nft_trans_gc_catchall(gc, 0);
gc = nft_trans_gc_catchall_sync(gc);
if (gc) {
nft_trans_gc_queue_sync_done(gc);
priv->last_gc = jiffies;
......
......@@ -622,8 +622,7 @@ static void nft_rbtree_gc(struct work_struct *work)
if (!gc)
goto done;
write_lock_bh(&priv->lock);
write_seqcount_begin(&priv->count);
read_lock_bh(&priv->lock);
for (node = rb_first(&priv->root); node != NULL; node = rb_next(node)) {
/* Ruleset has been updated, try later. */
......@@ -670,11 +669,10 @@ static void nft_rbtree_gc(struct work_struct *work)
nft_trans_gc_elem_add(gc, rbe);
}
gc = nft_trans_gc_catchall(gc, gc_seq);
gc = nft_trans_gc_catchall_async(gc, gc_seq);
try_later:
write_seqcount_end(&priv->count);
write_unlock_bh(&priv->lock);
read_unlock_bh(&priv->lock);
if (gc)
nft_trans_gc_queue_async_done(gc);
......
# SPDX-License-Identifier: GPL-2.0-only
nf-queue
connect_close
audit_logread
......@@ -6,13 +6,13 @@ TEST_PROGS := nft_trans_stress.sh nft_fib.sh nft_nat.sh bridge_brouter.sh \
nft_concat_range.sh nft_conntrack_helper.sh \
nft_queue.sh nft_meta.sh nf_nat_edemux.sh \
ipip-conntrack-mtu.sh conntrack_tcp_unreplied.sh \
conntrack_vrf.sh nft_synproxy.sh rpath.sh
conntrack_vrf.sh nft_synproxy.sh rpath.sh nft_audit.sh
HOSTPKG_CONFIG := pkg-config
CFLAGS += $(shell $(HOSTPKG_CONFIG) --cflags libmnl 2>/dev/null)
LDLIBS += $(shell $(HOSTPKG_CONFIG) --libs libmnl 2>/dev/null || echo -lmnl)
TEST_GEN_FILES = nf-queue connect_close
TEST_GEN_FILES = nf-queue connect_close audit_logread
include ../lib.mk
// SPDX-License-Identifier: GPL-2.0
#define _GNU_SOURCE
#include <errno.h>
#include <fcntl.h>
#include <poll.h>
#include <signal.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>
#include <linux/audit.h>
#include <linux/netlink.h>
static int fd;
#define MAX_AUDIT_MESSAGE_LENGTH 8970
struct audit_message {
struct nlmsghdr nlh;
union {
struct audit_status s;
char data[MAX_AUDIT_MESSAGE_LENGTH];
} u;
};
int audit_recv(int fd, struct audit_message *rep)
{
struct sockaddr_nl addr;
socklen_t addrlen = sizeof(addr);
int ret;
do {
ret = recvfrom(fd, rep, sizeof(*rep), 0,
(struct sockaddr *)&addr, &addrlen);
} while (ret < 0 && errno == EINTR);
if (ret < 0 ||
addrlen != sizeof(addr) ||
addr.nl_pid != 0 ||
rep->nlh.nlmsg_type == NLMSG_ERROR) /* short-cut for now */
return -1;
return ret;
}
int audit_send(int fd, uint16_t type, uint32_t key, uint32_t val)
{
static int seq = 0;
struct audit_message msg = {
.nlh = {
.nlmsg_len = NLMSG_SPACE(sizeof(msg.u.s)),
.nlmsg_type = type,
.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK,
.nlmsg_seq = ++seq,
},
.u.s = {
.mask = key,
.enabled = key == AUDIT_STATUS_ENABLED ? val : 0,
.pid = key == AUDIT_STATUS_PID ? val : 0,
}
};
struct sockaddr_nl addr = {
.nl_family = AF_NETLINK,
};
int ret;
do {
ret = sendto(fd, &msg, msg.nlh.nlmsg_len, 0,
(struct sockaddr *)&addr, sizeof(addr));
} while (ret < 0 && errno == EINTR);
if (ret != (int)msg.nlh.nlmsg_len)
return -1;
return 0;
}
int audit_set(int fd, uint32_t key, uint32_t val)
{
struct audit_message rep = { 0 };
int ret;
ret = audit_send(fd, AUDIT_SET, key, val);
if (ret)
return ret;
ret = audit_recv(fd, &rep);
if (ret < 0)
return ret;
return 0;
}
int readlog(int fd)
{
struct audit_message rep = { 0 };
int ret = audit_recv(fd, &rep);
const char *sep = "";
char *k, *v;
if (ret < 0)
return ret;
if (rep.nlh.nlmsg_type != AUDIT_NETFILTER_CFG)
return 0;
/* skip the initial "audit(...): " part */
strtok(rep.u.data, " ");
while ((k = strtok(NULL, "="))) {
v = strtok(NULL, " ");
/* these vary and/or are uninteresting, ignore */
if (!strcmp(k, "pid") ||
!strcmp(k, "comm") ||
!strcmp(k, "subj"))
continue;
/* strip the varying sequence number */
if (!strcmp(k, "table"))
*strchrnul(v, ':') = '\0';
printf("%s%s=%s", sep, k, v);
sep = " ";
}
if (*sep) {
printf("\n");
fflush(stdout);
}
return 0;
}
void cleanup(int sig)
{
audit_set(fd, AUDIT_STATUS_ENABLED, 0);
close(fd);
if (sig)
exit(0);
}
int main(int argc, char **argv)
{
struct sigaction act = {
.sa_handler = cleanup,
};
fd = socket(PF_NETLINK, SOCK_RAW, NETLINK_AUDIT);
if (fd < 0) {
perror("Can't open netlink socket");
return -1;
}
if (sigaction(SIGTERM, &act, NULL) < 0 ||
sigaction(SIGINT, &act, NULL) < 0) {
perror("Can't set signal handler");
close(fd);
return -1;
}
audit_set(fd, AUDIT_STATUS_ENABLED, 1);
audit_set(fd, AUDIT_STATUS_PID, getpid());
while (1)
readlog(fd);
}
......@@ -6,3 +6,4 @@ CONFIG_NFT_REDIR=m
CONFIG_NFT_MASQ=m
CONFIG_NFT_FLOW_OFFLOAD=m
CONFIG_NF_CT_NETLINK=m
CONFIG_AUDIT=y
#!/bin/bash
# SPDX-License-Identifier: GPL-2.0
#
# Check that audit logs generated for nft commands are as expected.
SKIP_RC=4
RC=0
nft --version >/dev/null 2>&1 || {
echo "SKIP: missing nft tool"
exit $SKIP_RC
}
logfile=$(mktemp)
echo "logging into $logfile"
./audit_logread >"$logfile" &
logread_pid=$!
trap 'kill $logread_pid; rm -f $logfile' EXIT
exec 3<"$logfile"
do_test() { # (cmd, log)
echo -n "testing for cmd: $1 ... "
cat <&3 >/dev/null
$1 >/dev/null || exit 1
sleep 0.1
res=$(diff -a -u <(echo "$2") - <&3)
[ $? -eq 0 ] && { echo "OK"; return; }
echo "FAIL"
echo "$res"
((RC++))
}
nft flush ruleset
for table in t1 t2; do
do_test "nft add table $table" \
"table=$table family=2 entries=1 op=nft_register_table"
do_test "nft add chain $table c1" \
"table=$table family=2 entries=1 op=nft_register_chain"
do_test "nft add chain $table c2; add chain $table c3" \
"table=$table family=2 entries=2 op=nft_register_chain"
cmd="add rule $table c1 counter"
do_test "nft $cmd" \
"table=$table family=2 entries=1 op=nft_register_rule"
do_test "nft $cmd; $cmd" \
"table=$table family=2 entries=2 op=nft_register_rule"
cmd=""
sep=""
for chain in c2 c3; do
for i in {1..3}; do
cmd+="$sep add rule $table $chain counter"
sep=";"
done
done
do_test "nft $cmd" \
"table=$table family=2 entries=6 op=nft_register_rule"
done
do_test 'nft reset rules t1 c2' \
'table=t1 family=2 entries=3 op=nft_reset_rule'
do_test 'nft reset rules table t1' \
'table=t1 family=2 entries=3 op=nft_reset_rule
table=t1 family=2 entries=3 op=nft_reset_rule
table=t1 family=2 entries=3 op=nft_reset_rule'
do_test 'nft reset rules' \
'table=t1 family=2 entries=3 op=nft_reset_rule
table=t1 family=2 entries=3 op=nft_reset_rule
table=t1 family=2 entries=3 op=nft_reset_rule
table=t2 family=2 entries=3 op=nft_reset_rule
table=t2 family=2 entries=3 op=nft_reset_rule
table=t2 family=2 entries=3 op=nft_reset_rule'
for ((i = 0; i < 500; i++)); do
echo "add rule t2 c3 counter accept comment \"rule $i\""
done | do_test 'nft -f -' \
'table=t2 family=2 entries=500 op=nft_register_rule'
do_test 'nft reset rules t2 c3' \
'table=t2 family=2 entries=189 op=nft_reset_rule
table=t2 family=2 entries=188 op=nft_reset_rule
table=t2 family=2 entries=126 op=nft_reset_rule'
do_test 'nft reset rules t2' \
'table=t2 family=2 entries=3 op=nft_reset_rule
table=t2 family=2 entries=3 op=nft_reset_rule
table=t2 family=2 entries=186 op=nft_reset_rule
table=t2 family=2 entries=188 op=nft_reset_rule
table=t2 family=2 entries=129 op=nft_reset_rule'
do_test 'nft reset rules' \
'table=t1 family=2 entries=3 op=nft_reset_rule
table=t1 family=2 entries=3 op=nft_reset_rule
table=t1 family=2 entries=3 op=nft_reset_rule
table=t2 family=2 entries=3 op=nft_reset_rule
table=t2 family=2 entries=3 op=nft_reset_rule
table=t2 family=2 entries=180 op=nft_reset_rule
table=t2 family=2 entries=188 op=nft_reset_rule
table=t2 family=2 entries=135 op=nft_reset_rule'
exit $RC
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment