From e1f203488cc652bbb6b062bf1eb454982e95cca6 Mon Sep 17 00:00:00 2001 From: "Jason A. Donenfeld" Date: Fri, 25 Nov 2016 21:14:50 +0100 Subject: ratelimiter: load hashlimit at modinsert time This fixes a potential race with net_lock and rtnl_lock. --- contrib/kernel-tree/create-patch.sh | 4 +- src/main.c | 26 ++++++++---- src/ratelimiter.c | 81 +++++++++++++++++++++---------------- src/ratelimiter.h | 9 ++++- src/tests/qemu/Makefile | 2 +- 5 files changed, 74 insertions(+), 48 deletions(-) diff --git a/contrib/kernel-tree/create-patch.sh b/contrib/kernel-tree/create-patch.sh index e40e4a4..57ca44d 100755 --- a/contrib/kernel-tree/create-patch.sh +++ b/contrib/kernel-tree/create-patch.sh @@ -14,7 +14,7 @@ cat <<_EOF +source "net/wireguard/Kconfig" --- a/net/Makefile +++ b/net/Makefile -@@ -8,1 +8,2 @@ - obj-\$(CONFIG_NET) := socket.o core/ +@@ -9,1 +9,2 @@ + obj-\$(CONFIG_NETFILTER) += netfilter/ +obj-\$(CONFIG_WIREGUARD) += wireguard/ _EOF diff --git a/src/main.c b/src/main.c index 3102408..cd8c591 100644 --- a/src/main.c +++ b/src/main.c @@ -16,7 +16,7 @@ static int __init mod_init(void) { - int ret = 0; + int ret; #ifdef DEBUG if (!routing_table_selftest() || @@ -30,22 +30,31 @@ static int __init mod_init(void) chacha20poly1305_init(); noise_init(); + ret = ratelimiter_module_init(); + if (ret < 0) + goto out; + #ifdef CONFIG_WIREGUARD_PARALLEL ret = packet_init_data_caches(); if (ret < 0) - return ret; + goto err_packet; #endif ret = device_init(); - if (ret < 0) { -#ifdef CONFIG_WIREGUARD_PARALLEL - packet_deinit_data_caches(); -#endif - return ret; - } + if (ret < 0) + goto err_device; pr_info("WireGuard " WIREGUARD_VERSION " loaded. See www.wireguard.io for information.\n"); pr_info("Copyright (C) 2015-2016 Jason A. Donenfeld . All Rights Reserved.\n"); + + goto out; +err_device: +#ifdef CONFIG_WIREGUARD_PARALLEL + packet_deinit_data_caches(); +#endif +err_packet: + ratelimiter_module_deinit(); +out: return ret; } @@ -55,6 +64,7 @@ static void __exit mod_exit(void) #ifdef CONFIG_WIREGUARD_PARALLEL packet_deinit_data_caches(); #endif + ratelimiter_module_deinit(); pr_debug("WireGuard has been unloaded\n"); } diff --git a/src/ratelimiter.c b/src/ratelimiter.c index 8bfe46b..7c23d55 100644 --- a/src/ratelimiter.c +++ b/src/ratelimiter.c @@ -15,6 +15,11 @@ #error "WireGuard requires CONFIG_IP6_NF_IPTABLES when using CONFIG_IPV6." #endif +static struct xt_match *v4_match; +#if IS_ENABLED(CONFIG_IPV6) +static struct xt_match *v6_match; +#endif + enum { RATELIMITER_PACKETS_PER_SECOND = 75, RATELIMITER_PACKETS_BURSTABLE = 5 @@ -44,47 +49,29 @@ int ratelimiter_init(struct ratelimiter *ratelimiter, struct wireguard_device *w memset(ratelimiter, 0, sizeof(struct ratelimiter)); cfg_init(&ratelimiter->v4_info.cfg, NFPROTO_IPV4); - cfg_init(&ratelimiter->v6_info.cfg, NFPROTO_IPV6); memcpy(ratelimiter->v4_info.name, dev->name, IFNAMSIZ); - memcpy(ratelimiter->v6_info.name, dev->name, IFNAMSIZ); - - ratelimiter->v4_match = xt_request_find_match(NFPROTO_IPV4, "hashlimit", 1); - if (IS_ERR(ratelimiter->v4_match)) { - pr_err("The xt_hashlimit module for IPv4 is required"); - return PTR_ERR(ratelimiter->v4_match); - } - chk.matchinfo = &ratelimiter->v4_info; - chk.match = ratelimiter->v4_match; + chk.match = v4_match; chk.family = NFPROTO_IPV4; - ret = ratelimiter->v4_match->checkentry(&chk); - if (ret < 0) { - module_put(ratelimiter->v4_match->me); + ret = v4_match->checkentry(&chk); + if (ret < 0) return ret; - } #if IS_ENABLED(CONFIG_IPV6) - ratelimiter->v6_match = xt_request_find_match(NFPROTO_IPV6, "hashlimit", 1); - if (IS_ERR(ratelimiter->v6_match)) { - pr_err("The xt_hashlimit module for IPv6 is required"); - module_put(ratelimiter->v4_match->me); - return PTR_ERR(ratelimiter->v6_match); - } - + cfg_init(&ratelimiter->v6_info.cfg, NFPROTO_IPV6); + memcpy(ratelimiter->v6_info.name, dev->name, IFNAMSIZ); chk.matchinfo = &ratelimiter->v6_info; - chk.match = ratelimiter->v6_match; + chk.match = v6_match; chk.family = NFPROTO_IPV6; - ret = ratelimiter->v6_match->checkentry(&chk); + ret = v6_match->checkentry(&chk); if (ret < 0) { struct xt_mtdtor_param dtor_v4 = { .net = wg->creating_net, - .match = ratelimiter->v4_match, + .match = v4_match, .matchinfo = &ratelimiter->v4_info, .family = NFPROTO_IPV4 }; - ratelimiter->v4_match->destroy(&dtor_v4); - module_put(ratelimiter->v4_match->me); - module_put(ratelimiter->v6_match->me); + v4_match->destroy(&dtor_v4); return ret; } #endif @@ -97,18 +84,16 @@ void ratelimiter_uninit(struct ratelimiter *ratelimiter) { struct xt_mtdtor_param dtor = { .net = ratelimiter->net }; - dtor.match = ratelimiter->v4_match; + dtor.match = v4_match; dtor.matchinfo = &ratelimiter->v4_info; dtor.family = NFPROTO_IPV4; - ratelimiter->v4_match->destroy(&dtor); - module_put(ratelimiter->v4_match->me); + v4_match->destroy(&dtor); #if IS_ENABLED(CONFIG_IPV6) - dtor.match = ratelimiter->v6_match; + dtor.match = v6_match; dtor.matchinfo = &ratelimiter->v6_info; dtor.family = NFPROTO_IPV6; - ratelimiter->v6_match->destroy(&dtor); - module_put(ratelimiter->v6_match->me); + v6_match->destroy(&dtor); #endif } @@ -118,14 +103,14 @@ bool ratelimiter_allow(struct ratelimiter *ratelimiter, struct sk_buff *skb) if (unlikely(skb->len < sizeof(struct iphdr))) return false; if (ip_hdr(skb)->version == 4) { - action.match = ratelimiter->v4_match; + action.match = v4_match; action.matchinfo = &ratelimiter->v4_info; action.thoff = ip_hdrlen(skb); action.family = NFPROTO_IPV4; } #if IS_ENABLED(CONFIG_IPV6) else if (ip_hdr(skb)->version == 6) { - action.match = ratelimiter->v6_match; + action.match = v6_match; action.matchinfo = &ratelimiter->v6_info; action.family = NFPROTO_IPV6; } @@ -134,3 +119,29 @@ bool ratelimiter_allow(struct ratelimiter *ratelimiter, struct sk_buff *skb) return false; return action.match->match(skb, &action); } + +int ratelimiter_module_init(void) +{ + v4_match = xt_request_find_match(NFPROTO_IPV4, "hashlimit", 1); + if (IS_ERR(v4_match)) { + pr_err("The xt_hashlimit module for IPv4 is required"); + return PTR_ERR(v4_match); + } +#if IS_ENABLED(CONFIG_IPV6) + v6_match = xt_request_find_match(NFPROTO_IPV6, "hashlimit", 1); + if (IS_ERR(v6_match)) { + pr_err("The xt_hashlimit module for IPv6 is required"); + module_put(v4_match->me); + return PTR_ERR(v6_match); + } +#endif + return 0; +} + +void ratelimiter_module_deinit(void) +{ + module_put(v4_match->me); +#if IS_ENABLED(CONFIG_IPV6) + module_put(v6_match->me); +#endif +} diff --git a/src/ratelimiter.h b/src/ratelimiter.h index e23097d..d934eab 100644 --- a/src/ratelimiter.h +++ b/src/ratelimiter.h @@ -10,12 +10,17 @@ struct sk_buff; struct ratelimiter { struct net *net; - struct xt_match *v4_match, *v6_match; - struct xt_hashlimit_mtinfo1 v4_info, v6_info; + struct xt_hashlimit_mtinfo1 v4_info; +#if IS_ENABLED(CONFIG_IPV6) + struct xt_hashlimit_mtinfo1 v6_info; +#endif }; int ratelimiter_init(struct ratelimiter *ratelimiter, struct wireguard_device *wg); void ratelimiter_uninit(struct ratelimiter *ratelimiter); bool ratelimiter_allow(struct ratelimiter *ratelimiter, struct sk_buff *skb); +int ratelimiter_module_init(void); +void ratelimiter_module_deinit(void); + #endif diff --git a/src/tests/qemu/Makefile b/src/tests/qemu/Makefile index 19ad613..b60aa5a 100644 --- a/src/tests/qemu/Makefile +++ b/src/tests/qemu/Makefile @@ -106,7 +106,7 @@ $(KERNEL_PATH)/.installed: $(KERNEL_TAR) mkdir -p $(BUILD_PATH) tar -C $(BUILD_PATH) -xf $< sed -i "/^if INET\$$/a source \"net/wireguard/Kconfig\"" $(KERNEL_PATH)/net/Kconfig - sed -i "/^obj-\$$(CONFIG_NET).*:=/a obj-\$$(CONFIG_WIREGUARD) += wireguard/" $(KERNEL_PATH)/net/Makefile + sed -i "/^obj-\$$(CONFIG_NETFILTER).*+=/a obj-\$$(CONFIG_WIREGUARD) += wireguard/" $(KERNEL_PATH)/net/Makefile ln -sf $(shell readlink -f ../..) $(KERNEL_PATH)/net/wireguard touch $@ -- cgit v1.2.3-59-g8ed1b