summaryrefslogtreecommitdiffstats
path: root/kernel/net/netfilter
diff options
context:
space:
mode:
authorJozsef Kadlecsik <kadlec@netfilter.org>2020-01-25 18:55:06 +0100
committerJozsef Kadlecsik <kadlec@netfilter.org>2020-01-26 11:57:57 +0100
commit7725bf5ba041b56a78fb2283b317cfa098e94801 (patch)
tree3d5b09d1a71654cff6448c76de3b99c3c377f16b /kernel/net/netfilter
parent3f685a1f904daf5fe29adcabf9474c1c35f85db1 (diff)
netfilter: ipset: fix suspicious RCU usage in find_set_and_id
find_set_and_id() is called when the NFNL_SUBSYS_IPSET mutex is held. However, in the error path there can be a follow-up recvmsg() without the mutex held. Use the start() function of struct netlink_dump_control instead of dump() to verify and report if the specified set does not exist. Thanks to Pablo Neira Ayuso for helping me to understand the subleties of the netlink protocol. Reported-by: syzbot+fc69d7cb21258ab4ae4d@syzkaller.appspotmail.com Signed-off-by: Jozsef Kadlecsik <kadlec@netfilter.org>
Diffstat (limited to 'kernel/net/netfilter')
-rw-r--r--kernel/net/netfilter/ipset/ip_set_core.c49
1 files changed, 32 insertions, 17 deletions
diff --git a/kernel/net/netfilter/ipset/ip_set_core.c b/kernel/net/netfilter/ipset/ip_set_core.c
index e367f3b..1f506e6 100644
--- a/kernel/net/netfilter/ipset/ip_set_core.c
+++ b/kernel/net/netfilter/ipset/ip_set_core.c
@@ -1490,31 +1490,40 @@ ip_set_dump_policy[IPSET_ATTR_CMD_MAX + 1] = {
};
static int
-dump_init(struct netlink_callback *cb, struct ip_set_net *inst)
+ip_set_dump_start(struct netlink_callback *cb)
{
struct nlmsghdr *nlh = nlmsg_hdr(cb->skb);
int min_len = nlmsg_total_size(sizeof(struct nfgenmsg));
struct nlattr *cda[IPSET_ATTR_CMD_MAX + 1];
struct nlattr *attr = (void *)nlh + min_len;
+ struct sk_buff *skb = cb->skb;
+ struct ip_set_net *inst = ip_set_pernet(sock_net(skb->sk));
u32 dump_type;
- ip_set_id_t index;
int ret;
ret = NLA_PARSE(cda, IPSET_ATTR_CMD_MAX, attr,
nlh->nlmsg_len - min_len,
ip_set_dump_policy, NULL);
if (ret)
- return ret;
+ goto error;
cb->args[IPSET_CB_PROTO] = nla_get_u8(cda[IPSET_ATTR_PROTOCOL]);
if (cda[IPSET_ATTR_SETNAME]) {
+ ip_set_id_t index;
struct ip_set *set;
+#if HAVE_NETLINK_DUMP_START_ARGS != 4
+ read_lock_bh(&ip_set_ref_lock);
+#endif
set = find_set_and_id(inst, nla_data(cda[IPSET_ATTR_SETNAME]),
&index);
- if (!set)
- return -ENOENT;
-
+#if HAVE_NETLINK_DUMP_START_ARGS != 4
+ read_unlock_bh(&ip_set_ref_lock);
+#endif
+ if (!set) {
+ ret = -ENOENT;
+ goto error;
+ }
dump_type = DUMP_ONE;
cb->args[IPSET_CB_INDEX] = index;
} else {
@@ -1530,10 +1539,17 @@ dump_init(struct netlink_callback *cb, struct ip_set_net *inst)
cb->args[IPSET_CB_DUMP] = dump_type;
return 0;
+
+error:
+ /* We have to create and send the error message manually :-( */
+ if (nlh->nlmsg_flags & NLM_F_ACK) {
+ NETLINK_ACK(cb->skb, nlh, ret, NULL);
+ }
+ return ret;
}
static int
-ip_set_dump_start(struct sk_buff *skb, struct netlink_callback *cb)
+ip_set_dump_do(struct sk_buff *skb, struct netlink_callback *cb)
{
ip_set_id_t index = IPSET_INVALID_ID, max;
struct ip_set *set = NULL;
@@ -1545,16 +1561,14 @@ ip_set_dump_start(struct sk_buff *skb, struct netlink_callback *cb)
int ret = 0;
if (!cb->args[IPSET_CB_DUMP]) {
- ret = dump_init(cb, inst);
+#if HAVE_NETLINK_DUMP_START_ARGS == 4
+ return -EINVAL;
+#else
+ ret = ip_set_dump_start(cb);
if (ret < 0) {
- nlh = nlmsg_hdr(cb->skb);
- /* We have to create and send the error message
- * manually :-(
- */
- if (nlh->nlmsg_flags & NLM_F_ACK)
- NETLINK_ACK(cb->skb, nlh, ret, NULL);
return ret;
}
+#endif
}
if (cb->args[IPSET_CB_INDEX] >= inst->ip_set_max)
@@ -1692,16 +1706,17 @@ IPSET_CBFN(ip_set_dump, struct net *net, struct sock *ctnl,
#if HAVE_NETLINK_DUMP_START_ARGS == 5
return netlink_dump_start(ctnl, skb, nlh,
- ip_set_dump_start,
+ ip_set_dump_do,
ip_set_dump_done);
#elif HAVE_NETLINK_DUMP_START_ARGS == 6
return netlink_dump_start(ctnl, skb, nlh,
- ip_set_dump_start,
+ ip_set_dump_do,
ip_set_dump_done, 0);
#else
{
struct netlink_dump_control c = {
- .dump = ip_set_dump_start,
+ .start = ip_set_dump_start,
+ .dump = ip_set_dump_do,
.done = ip_set_dump_done,
};
return netlink_dump_start(ctnl, skb, nlh, &c);