Commit c0391b6a authored by Pablo Neira Ayuso's avatar Pablo Neira Ayuso

netfilter: nf_tables: missing validation from the abort path

If userspace does not include the trailing end of batch message, then
nfnetlink aborts the transaction. This allows to check that ruleset
updates trigger no errors.

After this patch, invoking this command from the prerouting chain:

 # nft -c add rule x y fib saddr . oif type local

fails since oif is not supported there.

This patch fixes the lack of rule validation from the abort/check path
to catch configuration errors such as the one above.

Fixes: a654de8f ("netfilter: nf_tables: fix chain dependency validation")
Signed-off-by: default avatarPablo Neira Ayuso <pablo@netfilter.org>
parent 46d6c5ae
...@@ -24,6 +24,12 @@ struct nfnl_callback { ...@@ -24,6 +24,12 @@ struct nfnl_callback {
const u_int16_t attr_count; /* number of nlattr's */ const u_int16_t attr_count; /* number of nlattr's */
}; };
enum nfnl_abort_action {
NFNL_ABORT_NONE = 0,
NFNL_ABORT_AUTOLOAD,
NFNL_ABORT_VALIDATE,
};
struct nfnetlink_subsystem { struct nfnetlink_subsystem {
const char *name; const char *name;
__u8 subsys_id; /* nfnetlink subsystem ID */ __u8 subsys_id; /* nfnetlink subsystem ID */
...@@ -31,7 +37,8 @@ struct nfnetlink_subsystem { ...@@ -31,7 +37,8 @@ struct nfnetlink_subsystem {
const struct nfnl_callback *cb; /* callback for individual types */ const struct nfnl_callback *cb; /* callback for individual types */
struct module *owner; struct module *owner;
int (*commit)(struct net *net, struct sk_buff *skb); int (*commit)(struct net *net, struct sk_buff *skb);
int (*abort)(struct net *net, struct sk_buff *skb, bool autoload); int (*abort)(struct net *net, struct sk_buff *skb,
enum nfnl_abort_action action);
void (*cleanup)(struct net *net); void (*cleanup)(struct net *net);
bool (*valid_genid)(struct net *net, u32 genid); bool (*valid_genid)(struct net *net, u32 genid);
}; };
......
...@@ -8053,12 +8053,16 @@ static void nf_tables_abort_release(struct nft_trans *trans) ...@@ -8053,12 +8053,16 @@ static void nf_tables_abort_release(struct nft_trans *trans)
kfree(trans); kfree(trans);
} }
static int __nf_tables_abort(struct net *net, bool autoload) static int __nf_tables_abort(struct net *net, enum nfnl_abort_action action)
{ {
struct nft_trans *trans, *next; struct nft_trans *trans, *next;
struct nft_trans_elem *te; struct nft_trans_elem *te;
struct nft_hook *hook; struct nft_hook *hook;
if (action == NFNL_ABORT_VALIDATE &&
nf_tables_validate(net) < 0)
return -EAGAIN;
list_for_each_entry_safe_reverse(trans, next, &net->nft.commit_list, list_for_each_entry_safe_reverse(trans, next, &net->nft.commit_list,
list) { list) {
switch (trans->msg_type) { switch (trans->msg_type) {
...@@ -8190,7 +8194,7 @@ static int __nf_tables_abort(struct net *net, bool autoload) ...@@ -8190,7 +8194,7 @@ static int __nf_tables_abort(struct net *net, bool autoload)
nf_tables_abort_release(trans); nf_tables_abort_release(trans);
} }
if (autoload) if (action == NFNL_ABORT_AUTOLOAD)
nf_tables_module_autoload(net); nf_tables_module_autoload(net);
else else
nf_tables_module_autoload_cleanup(net); nf_tables_module_autoload_cleanup(net);
...@@ -8203,9 +8207,10 @@ static void nf_tables_cleanup(struct net *net) ...@@ -8203,9 +8207,10 @@ static void nf_tables_cleanup(struct net *net)
nft_validate_state_update(net, NFT_VALIDATE_SKIP); nft_validate_state_update(net, NFT_VALIDATE_SKIP);
} }
static int nf_tables_abort(struct net *net, struct sk_buff *skb, bool autoload) static int nf_tables_abort(struct net *net, struct sk_buff *skb,
enum nfnl_abort_action action)
{ {
int ret = __nf_tables_abort(net, autoload); int ret = __nf_tables_abort(net, action);
mutex_unlock(&net->nft.commit_mutex); mutex_unlock(&net->nft.commit_mutex);
...@@ -8836,7 +8841,7 @@ static void __net_exit nf_tables_exit_net(struct net *net) ...@@ -8836,7 +8841,7 @@ static void __net_exit nf_tables_exit_net(struct net *net)
{ {
mutex_lock(&net->nft.commit_mutex); mutex_lock(&net->nft.commit_mutex);
if (!list_empty(&net->nft.commit_list)) if (!list_empty(&net->nft.commit_list))
__nf_tables_abort(net, false); __nf_tables_abort(net, NFNL_ABORT_NONE);
__nft_release_tables(net); __nft_release_tables(net);
mutex_unlock(&net->nft.commit_mutex); mutex_unlock(&net->nft.commit_mutex);
WARN_ON_ONCE(!list_empty(&net->nft.tables)); WARN_ON_ONCE(!list_empty(&net->nft.tables));
......
...@@ -333,7 +333,7 @@ static void nfnetlink_rcv_batch(struct sk_buff *skb, struct nlmsghdr *nlh, ...@@ -333,7 +333,7 @@ static void nfnetlink_rcv_batch(struct sk_buff *skb, struct nlmsghdr *nlh,
return netlink_ack(skb, nlh, -EINVAL, NULL); return netlink_ack(skb, nlh, -EINVAL, NULL);
replay: replay:
status = 0; status = 0;
replay_abort:
skb = netlink_skb_clone(oskb, GFP_KERNEL); skb = netlink_skb_clone(oskb, GFP_KERNEL);
if (!skb) if (!skb)
return netlink_ack(oskb, nlh, -ENOMEM, NULL); return netlink_ack(oskb, nlh, -ENOMEM, NULL);
...@@ -499,7 +499,7 @@ static void nfnetlink_rcv_batch(struct sk_buff *skb, struct nlmsghdr *nlh, ...@@ -499,7 +499,7 @@ static void nfnetlink_rcv_batch(struct sk_buff *skb, struct nlmsghdr *nlh,
} }
done: done:
if (status & NFNL_BATCH_REPLAY) { if (status & NFNL_BATCH_REPLAY) {
ss->abort(net, oskb, true); ss->abort(net, oskb, NFNL_ABORT_AUTOLOAD);
nfnl_err_reset(&err_list); nfnl_err_reset(&err_list);
kfree_skb(skb); kfree_skb(skb);
module_put(ss->owner); module_put(ss->owner);
...@@ -510,11 +510,25 @@ static void nfnetlink_rcv_batch(struct sk_buff *skb, struct nlmsghdr *nlh, ...@@ -510,11 +510,25 @@ static void nfnetlink_rcv_batch(struct sk_buff *skb, struct nlmsghdr *nlh,
status |= NFNL_BATCH_REPLAY; status |= NFNL_BATCH_REPLAY;
goto done; goto done;
} else if (err) { } else if (err) {
ss->abort(net, oskb, false); ss->abort(net, oskb, NFNL_ABORT_NONE);
netlink_ack(oskb, nlmsg_hdr(oskb), err, NULL); netlink_ack(oskb, nlmsg_hdr(oskb), err, NULL);
} }
} else { } else {
ss->abort(net, oskb, false); enum nfnl_abort_action abort_action;
if (status & NFNL_BATCH_FAILURE)
abort_action = NFNL_ABORT_NONE;
else
abort_action = NFNL_ABORT_VALIDATE;
err = ss->abort(net, oskb, abort_action);
if (err == -EAGAIN) {
nfnl_err_reset(&err_list);
kfree_skb(skb);
module_put(ss->owner);
status |= NFNL_BATCH_FAILURE;
goto replay_abort;
}
} }
if (ss->cleanup) if (ss->cleanup)
ss->cleanup(net); ss->cleanup(net);
......
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