diff options
43 files changed, 1035 insertions, 278 deletions
diff --git a/contrib/examples/nat-hole-punching/README b/contrib/examples/nat-hole-punching/README index 46e6201..99c20e5 100644 --- a/contrib/examples/nat-hole-punching/README +++ b/contrib/examples/nat-hole-punching/README @@ -9,8 +9,8 @@ Compile with: Server is 1.2.3.4 and is on the public internet accepting UDP:49918. -Client A is NAT'd and doesnt't know its IP address. -Client B is NAT'd and doesnt't know its IP address. +Client A is NAT'd and doesn't know its IP address. +Client B is NAT'd and doesn't know its IP address. Server runs: diff --git a/contrib/examples/reresolve-dns/reresolve-dns.sh b/contrib/examples/reresolve-dns/reresolve-dns.sh index e579f86..8ab3635 100755 --- a/contrib/examples/reresolve-dns/reresolve-dns.sh +++ b/contrib/examples/reresolve-dns/reresolve-dns.sh @@ -15,7 +15,7 @@ INTERFACE="${BASH_REMATCH[1]}" process_peer() { [[ $PEER_SECTION -ne 1 || -z $PUBLIC_KEY || -z $ENDPOINT ]] && return 0 - [[ $(wg show "$INTERFACE" latest-handshakes) =~ ^${PUBLIC_KEY//+/\\+}\ ([0-9]+)$ ]] || return 0 + [[ $(wg show "$INTERFACE" latest-handshakes) =~ ${PUBLIC_KEY//+/\\+}\ ([0-9]+) ]] || return 0 (( ($(date +%s) - ${BASH_REMATCH[1]}) > 135 )) || return 0 wg set "$INTERFACE" peer "$PUBLIC_KEY" endpoint "$ENDPOINT" reset_peer_section @@ -5,6 +5,7 @@ ccflags-y := -O3 -fvisibility=hidden ccflags-$(CONFIG_WIREGUARD_DEBUG) += -DDEBUG -g ccflags-y += -D'pr_fmt(fmt)=KBUILD_MODNAME ": " fmt' +ccflags-y += -Wframe-larger-than=2048 wireguard-y := main.o noise.o device.o peer.o timers.o queueing.o send.o receive.o socket.o peerlookup.o allowedips.o ratelimiter.o cookie.o netlink.o diff --git a/src/Kconfig b/src/Kconfig index 3a68884..156e9db 100644 --- a/src/Kconfig +++ b/src/Kconfig @@ -4,7 +4,8 @@ config WIREGUARD depends on IPV6 || !IPV6 select NET_UDP_TUNNEL select DST_CACHE - select CRYPTO_BLKCIPHER + select CRYPTO + select CRYPTO_ALGAPI select VFP select VFPv3 if CPU_V7 select NEON if CPU_V7 diff --git a/src/Makefile b/src/Makefile index 24b2ba0..8226038 100644 --- a/src/Makefile +++ b/src/Makefile @@ -2,7 +2,8 @@ # # Copyright (C) 2015-2019 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved. -KERNELDIR ?= /lib/modules/$(shell uname -r)/build +KERNELRELEASE ?= $(shell uname -r) +KERNELDIR ?= /lib/modules/$(KERNELRELEASE)/build PREFIX ?= /usr DESTDIR ?= SRCDIR ?= $(PREFIX)/src @@ -45,7 +46,7 @@ clean: module-install: @$(MAKE) -C $(KERNELDIR) M=$(PWD) modules_install - $(DEPMOD) -a + $(DEPMOD) -a $(KERNELRELEASE) install: module-install tools-install diff --git a/src/allowedips.c b/src/allowedips.c index 610aab0..72667d5 100644 --- a/src/allowedips.c +++ b/src/allowedips.c @@ -299,14 +299,18 @@ void wg_allowedips_free(struct allowedips *table, struct mutex *lock) RCU_INIT_POINTER(table->root4, NULL); RCU_INIT_POINTER(table->root6, NULL); if (rcu_access_pointer(old4)) { - root_remove_peer_lists(old4); - call_rcu(&rcu_dereference_protected(old4, - lockdep_is_held(lock))->rcu, root_free_rcu); + struct allowedips_node *node = rcu_dereference_protected(old4, + lockdep_is_held(lock)); + + root_remove_peer_lists(node); + call_rcu(&node->rcu, root_free_rcu); } if (rcu_access_pointer(old6)) { - root_remove_peer_lists(old6); - call_rcu(&rcu_dereference_protected(old6, - lockdep_is_held(lock))->rcu, root_free_rcu); + struct allowedips_node *node = rcu_dereference_protected(old6, + lockdep_is_held(lock)); + + root_remove_peer_lists(node); + call_rcu(&node->rcu, root_free_rcu); } } diff --git a/src/compat/Kbuild.include b/src/compat/Kbuild.include index 1b555f8..db4b0a6 100644 --- a/src/compat/Kbuild.include +++ b/src/compat/Kbuild.include @@ -47,9 +47,15 @@ ccflags-y += -include $(kbuild-dir)/compat/memneq/include.h wireguard-y += compat/memneq/memneq.o endif +ifeq ($(shell grep -s -F "addr_gen_mode" "$(srctree)/include/linux/ipv6.h"),) +ccflags-y += -DCOMPAT_CANNOT_USE_DEV_CNF +endif + +ifdef CONFIG_HZ ifeq ($(wildcard $(srctree)/include/generated/timeconst.h),) ccflags-y += $(shell echo 'define gcd(a,b){auto t;while(b){t=b;b=a%b;a=t;};return a;};hz=$(CONFIG_HZ);cd=gcd(hz,1000000);print "-DHZ_TO_USEC_NUM=",1000000/cd," -DHZ_TO_USEC_DEN=",hz/cd;halt;' | bc -q) endif +endif ifeq ($(wildcard $(srctree)/arch/arm/include/asm/neon.h)$(CONFIG_ARM),y) ccflags-y += -I$(kbuild-dir)/compat/neon-arm/include diff --git a/src/compat/compat-asm.h b/src/compat/compat-asm.h index f5c5bc2..bafd70b 100644 --- a/src/compat/compat-asm.h +++ b/src/compat/compat-asm.h @@ -40,4 +40,9 @@ #undef pull #endif +#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 5, 0) +#define SYM_FUNC_START ENTRY +#define SYM_FUNC_END ENDPROC +#endif + #endif /* _WG_COMPATASM_H */ diff --git a/src/compat/compat.h b/src/compat/compat.h index 6288550..824f57c 100644 --- a/src/compat/compat.h +++ b/src/compat/compat.h @@ -21,6 +21,8 @@ #ifdef UTS_UBUNTU_RELEASE_ABI #if LINUX_VERSION_CODE == KERNEL_VERSION(3, 13, 11) #define ISUBUNTU1404 +#elif LINUX_VERSION_CODE >= KERNEL_VERSION(4, 4, 0) && LINUX_VERSION_CODE < KERNEL_VERSION(4, 5, 0) +#define ISUBUNTU1604 #endif #endif #ifdef CONFIG_SUSE_KERNEL @@ -45,6 +47,7 @@ #endif #include <linux/cache.h> +#include <linux/init.h> #ifndef __ro_after_init #define __ro_after_init __read_mostly #endif @@ -87,7 +90,7 @@ (LINUX_VERSION_CODE < KERNEL_VERSION(3, 18, 27) && LINUX_VERSION_CODE >= KERNEL_VERSION(3, 17, 0)) || \ (LINUX_VERSION_CODE < KERNEL_VERSION(3, 16, 8) && LINUX_VERSION_CODE >= KERNEL_VERSION(3, 15, 0)) || \ (LINUX_VERSION_CODE < KERNEL_VERSION(3, 14, 40) && LINUX_VERSION_CODE >= KERNEL_VERSION(3, 13, 0)) || \ - (LINUX_VERSION_CODE < KERNEL_VERSION(3, 12, 54))) && !defined(ISUBUNTU1404) + (LINUX_VERSION_CODE < KERNEL_VERSION(3, 12, 54))) && !defined(ISUBUNTU1404) && (!defined(ISRHEL7) || RHEL_MINOR < 7) /* TODO: remove < 7 workaround once CentOS 7.7 comes out. */ #include <linux/if.h> #include <net/ip_tunnels.h> #define IP6_ECN_set_ce(a, b) IP6_ECN_set_ce(b) @@ -323,7 +326,7 @@ static inline int wait_for_random_bytes(void) } #endif -#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 19, 0) && LINUX_VERSION_CODE >= KERNEL_VERSION(4, 2, 0) +#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 19, 0) && LINUX_VERSION_CODE >= KERNEL_VERSION(4, 2, 0) && !defined(ISRHEL8) #include <linux/random.h> #include <linux/slab.h> struct rng_is_initialized_callback { @@ -600,10 +603,6 @@ static int wg_get_device_dump_real(a, b) #define COMPAT_CANNOT_USE_IN6_DEV_GET #endif -#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 11, 0) -#define COMPAT_CANNOT_USE_DEV_CNF -#endif - #if LINUX_VERSION_CODE < KERNEL_VERSION(4, 3, 0) #define COMPAT_CANNOT_USE_IFF_NO_QUEUE #endif @@ -832,7 +831,7 @@ static inline void skb_mark_not_on_list(struct sk_buff *skb) } #endif -#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 20, 0) +#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 20, 0) && !defined(ISRHEL8) #define NLA_EXACT_LEN NLA_UNSPEC #endif #if LINUX_VERSION_CODE < KERNEL_VERSION(5, 2, 0) @@ -844,6 +843,45 @@ static inline void skb_mark_not_on_list(struct sk_buff *skb) #define cpu_have_named_feature(name) (elf_hwcap & (HWCAP_ ## name)) #endif +#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 1, 0) +#include <linux/stddef.h> +#ifndef offsetofend +#define offsetofend(TYPE, MEMBER) (offsetof(TYPE, MEMBER) + sizeof(((TYPE *)0)->MEMBER)) +#endif +#endif + +#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 5, 0) +#define genl_dumpit_info(cb) ({ \ + struct { struct nlattr **attrs; } *a = (void *)((u8 *)cb->args + offsetofend(struct dump_ctx, next_allowedip)); \ + BUILD_BUG_ON(sizeof(cb->args) < offsetofend(struct dump_ctx, next_allowedip) + sizeof(*a)); \ + a->attrs = genl_family_attrbuf(&genl_family); \ + if (nlmsg_parse(cb->nlh, GENL_HDRLEN + genl_family.hdrsize, a->attrs, genl_family.maxattr, device_policy, NULL) < 0) \ + memset(a->attrs, 0, (genl_family.maxattr + 1) * sizeof(struct nlattr *)); \ + a; \ +}) +#endif + +#if (LINUX_VERSION_CODE < KERNEL_VERSION(5, 4, 5) && LINUX_VERSION_CODE >= KERNEL_VERSION(5, 4, 0)) || LINUX_VERSION_CODE < KERNEL_VERSION(5, 3, 18) +#define ipv6_dst_lookup_flow(a, b, c, d) ipv6_dst_lookup(a, b, &dst, c) + (void *)0 ?: dst +#endif + +#if defined(ISUBUNTU1604) +#include <linux/siphash.h> +#ifndef _WG_LINUX_SIPHASH_H +#define hsiphash_2u32 siphash_2u32 +#define hsiphash_3u32 siphash_3u32 +#define hsiphash_key_t siphash_key_t +#endif +#endif + +#ifdef CONFIG_VE +#include <linux/netdev_features.h> +#ifdef NETIF_F_VIRTUAL +#undef NETIF_F_LLTX +#define NETIF_F_LLTX (__NETIF_F(LLTX) | __NETIF_F(VIRTUAL)) +#endif +#endif + /* https://github.com/ClangBuiltLinux/linux/issues/7 */ #if defined( __clang__) && (!defined(CONFIG_CLANG_VERSION) || CONFIG_CLANG_VERSION < 80000) #include <linux/bug.h> diff --git a/src/crypto/include/zinc/chacha20poly1305.h b/src/crypto/include/zinc/chacha20poly1305.h index ce72740..e3339f0 100644 --- a/src/crypto/include/zinc/chacha20poly1305.h +++ b/src/crypto/include/zinc/chacha20poly1305.h @@ -22,9 +22,9 @@ void chacha20poly1305_encrypt(u8 *dst, const u8 *src, const size_t src_len, const u64 nonce, const u8 key[CHACHA20POLY1305_KEY_SIZE]); -bool __must_check chacha20poly1305_encrypt_sg( - struct scatterlist *dst, struct scatterlist *src, const size_t src_len, - const u8 *ad, const size_t ad_len, const u64 nonce, +bool __must_check chacha20poly1305_encrypt_sg_inplace( + struct scatterlist *src, const size_t src_len, const u8 *ad, + const size_t ad_len, const u64 nonce, const u8 key[CHACHA20POLY1305_KEY_SIZE], simd_context_t *simd_context); bool __must_check @@ -32,9 +32,9 @@ chacha20poly1305_decrypt(u8 *dst, const u8 *src, const size_t src_len, const u8 *ad, const size_t ad_len, const u64 nonce, const u8 key[CHACHA20POLY1305_KEY_SIZE]); -bool __must_check chacha20poly1305_decrypt_sg( - struct scatterlist *dst, struct scatterlist *src, const size_t src_len, - const u8 *ad, const size_t ad_len, const u64 nonce, +bool __must_check chacha20poly1305_decrypt_sg_inplace( + struct scatterlist *src, size_t src_len, const u8 *ad, + const size_t ad_len, const u64 nonce, const u8 key[CHACHA20POLY1305_KEY_SIZE], simd_context_t *simd_context); void xchacha20poly1305_encrypt(u8 *dst, const u8 *src, const size_t src_len, diff --git a/src/crypto/zinc/blake2s/blake2s-x86_64.S b/src/crypto/zinc/blake2s/blake2s-x86_64.S index 8591938..24910b7 100644 --- a/src/crypto/zinc/blake2s/blake2s-x86_64.S +++ b/src/crypto/zinc/blake2s/blake2s-x86_64.S @@ -47,7 +47,7 @@ SIGMA2: .text #ifdef CONFIG_AS_SSSE3 -ENTRY(blake2s_compress_ssse3) +SYM_FUNC_START(blake2s_compress_ssse3) testq %rdx,%rdx je .Lendofloop movdqu (%rdi),%xmm0 @@ -173,11 +173,11 @@ ENTRY(blake2s_compress_ssse3) movdqu %xmm14,0x20(%rdi) .Lendofloop: ret -ENDPROC(blake2s_compress_ssse3) +SYM_FUNC_END(blake2s_compress_ssse3) #endif /* CONFIG_AS_SSSE3 */ #ifdef CONFIG_AS_AVX512 -ENTRY(blake2s_compress_avx512) +SYM_FUNC_START(blake2s_compress_avx512) vmovdqu (%rdi),%xmm0 vmovdqu 0x10(%rdi),%xmm1 vmovdqu 0x20(%rdi),%xmm4 @@ -254,5 +254,5 @@ ENTRY(blake2s_compress_avx512) vmovdqu %xmm4,0x20(%rdi) vzeroupper retq -ENDPROC(blake2s_compress_avx512) +SYM_FUNC_END(blake2s_compress_avx512) #endif /* CONFIG_AS_AVX512 */ diff --git a/src/crypto/zinc/chacha20/chacha20-unrolled-arm.S b/src/crypto/zinc/chacha20/chacha20-unrolled-arm.S index 2140319..8fb4bc2 100644 --- a/src/crypto/zinc/chacha20/chacha20-unrolled-arm.S +++ b/src/crypto/zinc/chacha20/chacha20-unrolled-arm.S @@ -394,7 +394,7 @@ * void chacha20_arm(u8 *out, const u8 *in, size_t len, const u32 key[8], * const u32 iv[4]); */ -ENTRY(chacha20_arm) +SYM_FUNC_START(chacha20_arm) cmp r2, #0 // len == 0? reteq lr @@ -428,12 +428,12 @@ ENTRY(chacha20_arm) add sp, #76 pop {r4-r11, pc} -ENDPROC(chacha20_arm) +SYM_FUNC_END(chacha20_arm) /* * void hchacha20_arm(const u32 state[16], u32 out[8]); */ -ENTRY(hchacha20_arm) +SYM_FUNC_START(hchacha20_arm) push {r1,r4-r11,lr} mov r14, r0 @@ -458,4 +458,4 @@ ENTRY(hchacha20_arm) stm r4, {X0,X1,X2,X3,X12,X13,X14,X15} pop {r4-r11,pc} -ENDPROC(hchacha20_arm) +SYM_FUNC_END(hchacha20_arm) diff --git a/src/crypto/zinc/chacha20/chacha20-x86_64.pl b/src/crypto/zinc/chacha20/chacha20-x86_64.pl index cec7572..29906a6 100644 --- a/src/crypto/zinc/chacha20/chacha20-x86_64.pl +++ b/src/crypto/zinc/chacha20/chacha20-x86_64.pl @@ -124,7 +124,7 @@ sub declare_function() { my ($name, $align, $nargs) = @_; if($kernel) { $code .= ".align $align\n"; - $code .= "ENTRY($name)\n"; + $code .= "SYM_FUNC_START($name)\n"; $code .= ".L$name:\n"; } else { $code .= ".globl $name\n"; @@ -137,7 +137,7 @@ sub declare_function() { sub end_function() { my ($name) = @_; if($kernel) { - $code .= "ENDPROC($name)\n"; + $code .= "SYM_FUNC_END($name)\n"; } else { $code .= ".size $name,.-$name\n"; } diff --git a/src/crypto/zinc/chacha20poly1305.c b/src/crypto/zinc/chacha20poly1305.c index 0001c92..571a64e 100644 --- a/src/crypto/zinc/chacha20poly1305.c +++ b/src/crypto/zinc/chacha20poly1305.c @@ -18,16 +18,7 @@ #include <linux/init.h> #include <crypto/scatterwalk.h> // For blkcipher_walk. -static const u8 pad0[16] = { 0 }; - -static struct blkcipher_desc desc = { .tfm = &(struct crypto_blkcipher){ - .base = { .__crt_alg = &(struct crypto_alg){ - .cra_blocksize = 1, -#ifndef CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS - .cra_alignmask = sizeof(u32) - 1 -#endif - } } -} }; +static const u8 pad0[CHACHA20_BLOCK_SIZE] = { 0 }; static inline void __chacha20poly1305_encrypt(u8 *dst, const u8 *src, const size_t src_len, @@ -82,22 +73,25 @@ void chacha20poly1305_encrypt(u8 *dst, const u8 *src, const size_t src_len, } EXPORT_SYMBOL(chacha20poly1305_encrypt); -bool chacha20poly1305_encrypt_sg(struct scatterlist *dst, - struct scatterlist *src, const size_t src_len, - const u8 *ad, const size_t ad_len, - const u64 nonce, - const u8 key[CHACHA20POLY1305_KEY_SIZE], - simd_context_t *simd_context) +bool chacha20poly1305_encrypt_sg_inplace(struct scatterlist *src, + const size_t src_len, + const u8 *ad, const size_t ad_len, + const u64 nonce, + const u8 key[CHACHA20POLY1305_KEY_SIZE], + simd_context_t *simd_context) { struct poly1305_ctx poly1305_state; struct chacha20_ctx chacha20_state; - int ret = 0; - struct blkcipher_walk walk; + struct sg_mapping_iter miter; + size_t partial = 0; + ssize_t sl; union { + u8 chacha20_stream[CHACHA20_BLOCK_SIZE]; u8 block0[POLY1305_KEY_SIZE]; u8 mac[POLY1305_MAC_SIZE]; __le64 lens[2]; - } b = { { 0 } }; + } b __aligned(16) = { { 0 } }; + chacha20_init(&chacha20_state, key, nonce); chacha20(&chacha20_state, b.block0, b.block0, sizeof(b.block0), @@ -108,32 +102,43 @@ bool chacha20poly1305_encrypt_sg(struct scatterlist *dst, poly1305_update(&poly1305_state, pad0, (0x10 - ad_len) & 0xf, simd_context); - if (likely(src_len)) { - blkcipher_walk_init(&walk, dst, src, src_len); - ret = blkcipher_walk_virt_block(&desc, &walk, - CHACHA20_BLOCK_SIZE); - while (walk.nbytes >= CHACHA20_BLOCK_SIZE) { - size_t chunk_len = - rounddown(walk.nbytes, CHACHA20_BLOCK_SIZE); - - chacha20(&chacha20_state, walk.dst.virt.addr, - walk.src.virt.addr, chunk_len, simd_context); - poly1305_update(&poly1305_state, walk.dst.virt.addr, - chunk_len, simd_context); - simd_relax(simd_context); - ret = blkcipher_walk_done(&desc, &walk, - walk.nbytes % CHACHA20_BLOCK_SIZE); + sg_miter_start(&miter, src, sg_nents(src), SG_MITER_TO_SG | SG_MITER_ATOMIC); + for (sl = src_len; sl > 0 && sg_miter_next(&miter); sl -= miter.length) { + u8 *addr = miter.addr; + size_t length = min_t(size_t, sl, miter.length); + + if (unlikely(partial)) { + size_t l = min(length, CHACHA20_BLOCK_SIZE - partial); + + crypto_xor(addr, b.chacha20_stream + partial, l); + partial = (partial + l) & (CHACHA20_BLOCK_SIZE - 1); + + addr += l; + length -= l; } - if (walk.nbytes) { - chacha20(&chacha20_state, walk.dst.virt.addr, - walk.src.virt.addr, walk.nbytes, simd_context); - poly1305_update(&poly1305_state, walk.dst.virt.addr, - walk.nbytes, simd_context); - ret = blkcipher_walk_done(&desc, &walk, 0); + + if (likely(length >= CHACHA20_BLOCK_SIZE || length == sl)) { + size_t l = length; + + if (unlikely(length < sl)) + l &= ~(CHACHA20_BLOCK_SIZE - 1); + chacha20(&chacha20_state, addr, addr, l, simd_context); + addr += l; + length -= l; + } + + if (unlikely(length > 0)) { + chacha20(&chacha20_state, b.chacha20_stream, pad0, + CHACHA20_BLOCK_SIZE, simd_context); + crypto_xor(addr, b.chacha20_stream, length); + partial = length; } + + poly1305_update(&poly1305_state, miter.addr, + min_t(size_t, sl, miter.length), simd_context); + + simd_relax(simd_context); } - if (unlikely(ret)) - goto err; poly1305_update(&poly1305_state, pad0, (0x10 - src_len) & 0xf, simd_context); @@ -143,14 +148,22 @@ bool chacha20poly1305_encrypt_sg(struct scatterlist *dst, poly1305_update(&poly1305_state, (u8 *)b.lens, sizeof(b.lens), simd_context); - poly1305_final(&poly1305_state, b.mac, simd_context); - scatterwalk_map_and_copy(b.mac, dst, src_len, sizeof(b.mac), 1); -err: + if (likely(sl <= -POLY1305_MAC_SIZE)) + poly1305_final(&poly1305_state, miter.addr + miter.length + sl, + simd_context); + + sg_miter_stop(&miter); + + if (unlikely(sl > -POLY1305_MAC_SIZE)) { + poly1305_final(&poly1305_state, b.mac, simd_context); + scatterwalk_map_and_copy(b.mac, src, src_len, sizeof(b.mac), 1); + } + memzero_explicit(&chacha20_state, sizeof(chacha20_state)); memzero_explicit(&b, sizeof(b)); - return !ret; + return true; } -EXPORT_SYMBOL(chacha20poly1305_encrypt_sg); +EXPORT_SYMBOL(chacha20poly1305_encrypt_sg_inplace); static inline bool __chacha20poly1305_decrypt(u8 *dst, const u8 *src, const size_t src_len, @@ -217,29 +230,32 @@ bool chacha20poly1305_decrypt(u8 *dst, const u8 *src, const size_t src_len, } EXPORT_SYMBOL(chacha20poly1305_decrypt); -bool chacha20poly1305_decrypt_sg(struct scatterlist *dst, - struct scatterlist *src, const size_t src_len, - const u8 *ad, const size_t ad_len, - const u64 nonce, - const u8 key[CHACHA20POLY1305_KEY_SIZE], - simd_context_t *simd_context) +bool chacha20poly1305_decrypt_sg_inplace(struct scatterlist *src, + size_t src_len, + const u8 *ad, const size_t ad_len, + const u64 nonce, + const u8 key[CHACHA20POLY1305_KEY_SIZE], + simd_context_t *simd_context) { struct poly1305_ctx poly1305_state; struct chacha20_ctx chacha20_state; - struct blkcipher_walk walk; - int ret = 0; - size_t dst_len; + struct sg_mapping_iter miter; + size_t partial = 0; + ssize_t sl; union { + u8 chacha20_stream[CHACHA20_BLOCK_SIZE]; u8 block0[POLY1305_KEY_SIZE]; struct { u8 read_mac[POLY1305_MAC_SIZE]; u8 computed_mac[POLY1305_MAC_SIZE]; }; __le64 lens[2]; - } b = { { 0 } }; + } b __aligned(16) = { { 0 } }; + bool ret = false; if (unlikely(src_len < POLY1305_MAC_SIZE)) - return false; + return ret; + src_len -= POLY1305_MAC_SIZE; chacha20_init(&chacha20_state, key, nonce); chacha20(&chacha20_state, b.block0, b.block0, sizeof(b.block0), @@ -250,52 +266,74 @@ bool chacha20poly1305_decrypt_sg(struct scatterlist *dst, poly1305_update(&poly1305_state, pad0, (0x10 - ad_len) & 0xf, simd_context); - dst_len = src_len - POLY1305_MAC_SIZE; - if (likely(dst_len)) { - blkcipher_walk_init(&walk, dst, src, dst_len); - ret = blkcipher_walk_virt_block(&desc, &walk, - CHACHA20_BLOCK_SIZE); - while (walk.nbytes >= CHACHA20_BLOCK_SIZE) { - size_t chunk_len = - rounddown(walk.nbytes, CHACHA20_BLOCK_SIZE); - - poly1305_update(&poly1305_state, walk.src.virt.addr, - chunk_len, simd_context); - chacha20(&chacha20_state, walk.dst.virt.addr, - walk.src.virt.addr, chunk_len, simd_context); - simd_relax(simd_context); - ret = blkcipher_walk_done(&desc, &walk, - walk.nbytes % CHACHA20_BLOCK_SIZE); + sg_miter_start(&miter, src, sg_nents(src), SG_MITER_TO_SG | SG_MITER_ATOMIC); + for (sl = src_len; sl > 0 && sg_miter_next(&miter); sl -= miter.length) { + u8 *addr = miter.addr; + size_t length = min_t(size_t, sl, miter.length); + + poly1305_update(&poly1305_state, addr, length, simd_context); + + if (unlikely(partial)) { + size_t l = min(length, CHACHA20_BLOCK_SIZE - partial); + + crypto_xor(addr, b.chacha20_stream + partial, l); + partial = (partial + l) & (CHACHA20_BLOCK_SIZE - 1); + + addr += l; + length -= l; } - if (walk.nbytes) { - poly1305_update(&poly1305_state, walk.src.virt.addr, - walk.nbytes, simd_context); - chacha20(&chacha20_state, walk.dst.virt.addr, - walk.src.virt.addr, walk.nbytes, simd_context); - ret = blkcipher_walk_done(&desc, &walk, 0); + + if (likely(length >= CHACHA20_BLOCK_SIZE || length == sl)) { + size_t l = length; + + if (unlikely(length < sl)) + l &= ~(CHACHA20_BLOCK_SIZE - 1); + chacha20(&chacha20_state, addr, addr, l, simd_context); + addr += l; + length -= l; + } + + if (unlikely(length > 0)) { + chacha20(&chacha20_state, b.chacha20_stream, pad0, + CHACHA20_BLOCK_SIZE, simd_context); + crypto_xor(addr, b.chacha20_stream, length); + partial = length; } + + simd_relax(simd_context); } - if (unlikely(ret)) - goto err; - poly1305_update(&poly1305_state, pad0, (0x10 - dst_len) & 0xf, + poly1305_update(&poly1305_state, pad0, (0x10 - src_len) & 0xf, simd_context); b.lens[0] = cpu_to_le64(ad_len); - b.lens[1] = cpu_to_le64(dst_len); + b.lens[1] = cpu_to_le64(src_len); poly1305_update(&poly1305_state, (u8 *)b.lens, sizeof(b.lens), simd_context); - poly1305_final(&poly1305_state, b.computed_mac, simd_context); + if (likely(sl <= -POLY1305_MAC_SIZE)) { + poly1305_final(&poly1305_state, b.computed_mac, simd_context); + ret = !crypto_memneq(b.computed_mac, + miter.addr + miter.length + sl, + POLY1305_MAC_SIZE); + } + + sg_miter_stop(&miter); + + if (unlikely(sl > -POLY1305_MAC_SIZE)) { + poly1305_final(&poly1305_state, b.computed_mac, simd_context); + scatterwalk_map_and_copy(b.read_mac, src, src_len, + sizeof(b.read_mac), 0); + ret = !crypto_memneq(b.read_mac, b.computed_mac, + POLY1305_MAC_SIZE); + + } - scatterwalk_map_and_copy(b.read_mac, src, dst_len, POLY1305_MAC_SIZE, 0); - ret = crypto_memneq(b.read_mac, b.computed_mac, POLY1305_MAC_SIZE); -err: memzero_explicit(&chacha20_state, sizeof(chacha20_state)); memzero_explicit(&b, sizeof(b)); - return !ret; + return ret; } -EXPORT_SYMBOL(chacha20poly1305_decrypt_sg); +EXPORT_SYMBOL(chacha20poly1305_decrypt_sg_inplace); void xchacha20poly1305_encrypt(u8 *dst, const u8 *src, const size_t src_len, const u8 *ad, const size_t ad_len, diff --git a/src/crypto/zinc/curve25519/curve25519-arm.S b/src/crypto/zinc/curve25519/curve25519-arm.S index 0ef1431..8eca8a1 100644 --- a/src/crypto/zinc/curve25519/curve25519-arm.S +++ b/src/crypto/zinc/curve25519/curve25519-arm.S @@ -15,7 +15,7 @@ .arch armv7-a .align 4 -ENTRY(curve25519_neon) +SYM_FUNC_START(curve25519_neon) push {r4-r11, lr} mov ip, sp sub r3, sp, #704 @@ -2060,5 +2060,5 @@ ENTRY(curve25519_neon) movw r0, #0 mov sp, ip pop {r4-r11, pc} -ENDPROC(curve25519_neon) +SYM_FUNC_END(curve25519_neon) #endif diff --git a/src/crypto/zinc/poly1305/poly1305-x86_64.pl b/src/crypto/zinc/poly1305/poly1305-x86_64.pl index 37ed869..f994855 100644 --- a/src/crypto/zinc/poly1305/poly1305-x86_64.pl +++ b/src/crypto/zinc/poly1305/poly1305-x86_64.pl @@ -35,7 +35,7 @@ # Skylake-X system performance. Since we are likely to suppress # AVX512F capability flag [at least on Skylake-X], conversion serves # as kind of "investment protection". Note that next *lake processor, -# Cannolake, has AVX512IFMA code path to execute... +# Cannonlake, has AVX512IFMA code path to execute... # # Numbers are cycles per processed byte with poly1305_blocks alone, # measured with rdtsc at fixed clock frequency. @@ -109,7 +109,7 @@ sub declare_function() { my ($name, $align, $nargs) = @_; if($kernel) { $code .= ".align $align\n"; - $code .= "ENTRY($name)\n"; + $code .= "SYM_FUNC_START($name)\n"; $code .= ".L$name:\n"; } else { $code .= ".globl $name\n"; @@ -122,7 +122,7 @@ sub declare_function() { sub end_function() { my ($name) = @_; if($kernel) { - $code .= "ENDPROC($name)\n"; + $code .= "SYM_FUNC_END($name)\n"; } else { $code .= ".size $name,.-$name\n"; } diff --git a/src/crypto/zinc/selftest/chacha20poly1305.c b/src/crypto/zinc/selftest/chacha20poly1305.c index dba9cd7..c58ac6e 100644 --- a/src/crypto/zinc/selftest/chacha20poly1305.c +++ b/src/crypto/zinc/selftest/chacha20poly1305.c @@ -8879,15 +8879,15 @@ decryption_success(bool func_ret, bool expect_failure, int memcmp_result) static bool __init chacha20poly1305_selftest(void) { enum { MAXIMUM_TEST_BUFFER_LEN = 1UL << 12 }; - size_t i; - u8 *computed_output = NULL, *heap_src = NULL; + size_t i, j, k, total_len; + u8 *computed_output = NULL, *input = NULL; bool success = true, ret; simd_context_t simd_context; - struct scatterlist sg_src, sg_dst; + struct scatterlist sg_src[3]; - heap_src = kmalloc(MAXIMUM_TEST_BUFFER_LEN, GFP_KERNEL); computed_output = kmalloc(MAXIMUM_TEST_BUFFER_LEN, GFP_KERNEL); - if (!heap_src || !computed_output) { + input = kmalloc(MAXIMUM_TEST_BUFFER_LEN, GFP_KERNEL); + if (!computed_output || !input) { pr_err("chacha20poly1305 self-test malloc: FAIL\n"); success = false; goto out; @@ -8916,15 +8916,12 @@ static bool __init chacha20poly1305_selftest(void) for (i = 0; i < ARRAY_SIZE(chacha20poly1305_enc_vectors); ++i) { if (chacha20poly1305_enc_vectors[i].nlen != 8) continue; - memset(computed_output, 0, MAXIMUM_TEST_BUFFER_LEN); - memcpy(heap_src, chacha20poly1305_enc_vectors[i].input, + memcpy(computed_output, chacha20poly1305_enc_vectors[i].input, chacha20poly1305_enc_vectors[i].ilen); - sg_init_one(&sg_src, heap_src, - chacha20poly1305_enc_vectors[i].ilen); - sg_init_one(&sg_dst, computed_output, + sg_init_one(sg_src, computed_output, chacha20poly1305_enc_vectors[i].ilen + POLY1305_MAC_SIZE); - ret = chacha20poly1305_encrypt_sg(&sg_dst, &sg_src, + ret = chacha20poly1305_encrypt_sg_inplace(sg_src, chacha20poly1305_enc_vectors[i].ilen, chacha20poly1305_enc_vectors[i].assoc, chacha20poly1305_enc_vectors[i].alen, @@ -8963,15 +8960,11 @@ static bool __init chacha20poly1305_selftest(void) } simd_get(&simd_context); for (i = 0; i < ARRAY_SIZE(chacha20poly1305_dec_vectors); ++i) { - memset(computed_output, 0, MAXIMUM_TEST_BUFFER_LEN); - memcpy(heap_src, chacha20poly1305_dec_vectors[i].input, + memcpy(computed_output, chacha20poly1305_dec_vectors[i].input, chacha20poly1305_dec_vectors[i].ilen); - sg_init_one(&sg_src, heap_src, + sg_init_one(sg_src, computed_output, chacha20poly1305_dec_vectors[i].ilen); - sg_init_one(&sg_dst, computed_output, - chacha20poly1305_dec_vectors[i].ilen - - POLY1305_MAC_SIZE); - ret = chacha20poly1305_decrypt_sg(&sg_dst, &sg_src, + ret = chacha20poly1305_decrypt_sg_inplace(sg_src, chacha20poly1305_dec_vectors[i].ilen, chacha20poly1305_dec_vectors[i].assoc, chacha20poly1305_dec_vectors[i].alen, @@ -9027,8 +9020,57 @@ static bool __init chacha20poly1305_selftest(void) } } + simd_get(&simd_context); + for (total_len = POLY1305_MAC_SIZE; IS_ENABLED(DEBUG_CHACHA20POLY1305_SLOW_CHUNK_TEST) + && total_len <= 1 << 10; ++total_len) { + for (i = 0; i <= total_len; ++i) { + for (j = i; j <= total_len; ++j) { + sg_init_table(sg_src, 3); + sg_set_buf(&sg_src[0], input, i); + sg_set_buf(&sg_src[1], input + i, j - i); + sg_set_buf(&sg_src[2], input + j, total_len - j); + memset(computed_output, 0, total_len); + memset(input, 0, total_len); + + if (!chacha20poly1305_encrypt_sg_inplace(sg_src, + total_len - POLY1305_MAC_SIZE, NULL, 0, + 0, enc_key001, &simd_context)) + goto chunkfail; + chacha20poly1305_encrypt(computed_output, + computed_output, + total_len - POLY1305_MAC_SIZE, NULL, 0, 0, + enc_key001); + if (memcmp(computed_output, input, total_len)) + goto chunkfail;; + if (!chacha20poly1305_decrypt(computed_output, + input, total_len, NULL, 0, 0, enc_key001)) + goto chunkfail; + for (k = 0; k < total_len - POLY1305_MAC_SIZE; ++k) { + if (computed_output[k]) + goto chunkfail; + } + if (!chacha20poly1305_decrypt_sg_inplace(sg_src, + total_len, NULL, 0, 0, enc_key001, + &simd_context)) + goto chunkfail; + for (k = 0; k < total_len - POLY1305_MAC_SIZE; ++k) { + if (input[k]) + goto chunkfail; + } + continue; + + chunkfail: + pr_err("chacha20poly1305 chunked self-test %zu/%zu/%zu: FAIL\n", + total_len, i, j); + success = false; + } + + } + } + simd_put(&simd_context); + out: - kfree(heap_src); kfree(computed_output); + kfree(input); return success; } diff --git a/src/device.c b/src/device.c index 532d399..e888ac3 100644 --- a/src/device.c +++ b/src/device.c @@ -171,8 +171,8 @@ static netdev_tx_t wg_xmit(struct sk_buff *skb, struct net_device *dev) dev_kfree_skb(skb); skb = segs; } - do { - next = skb->next; + + skb_list_walk_safe(skb, skb, next) { skb_mark_not_on_list(skb); skb = skb_share_check(skb, GFP_ATOMIC); @@ -187,7 +187,7 @@ static netdev_tx_t wg_xmit(struct sk_buff *skb, struct net_device *dev) PACKET_CB(skb)->mtu = mtu; __skb_queue_tail(&packets, skb); - } while ((skb = next) != NULL); + } spin_lock_bh(&peer->staged_packet_queue.lock); /* If the queue is getting too big, we start removing the oldest packets diff --git a/src/device.h b/src/device.h index b15a8be..c91f305 100644 --- a/src/device.h +++ b/src/device.h @@ -62,4 +62,12 @@ struct wg_device { int wg_device_init(void); void wg_device_uninit(void); +/* Later after the dust settles, this can be moved into include/linux/skbuff.h, + * where virtually all code that deals with GSO segs can benefit, around ~30 + * drivers as of writing. + */ +#define skb_list_walk_safe(first, skb, next) \ + for (skb = first, next = skb->next; skb; \ + skb = next, next = skb ? skb->next : NULL) + #endif /* _WG_DEVICE_H */ diff --git a/src/dkms.conf b/src/dkms.conf index f72841c..cdcd2e7 100644 --- a/src/dkms.conf +++ b/src/dkms.conf @@ -1,5 +1,5 @@ PACKAGE_NAME="wireguard" -PACKAGE_VERSION="0.0.20190702" +PACKAGE_VERSION="0.0.20191219" AUTOINSTALL=yes BUILT_MODULE_NAME="wireguard" @@ -12,7 +12,6 @@ #include "uapi/wireguard.h" #include "crypto/zinc.h" -#include <linux/version.h> #include <linux/init.h> #include <linux/module.h> #include <linux/genetlink.h> diff --git a/src/messages.h b/src/messages.h index 3cfd1c5..f415cdd 100644 --- a/src/messages.h +++ b/src/messages.h @@ -38,7 +38,7 @@ enum counter_values { }; enum limits { - REKEY_AFTER_MESSAGES = U64_MAX - 0xffff, + REKEY_AFTER_MESSAGES = 1ULL << 60, REJECT_AFTER_MESSAGES = U64_MAX - COUNTER_WINDOW_SIZE - 1, REKEY_TIMEOUT = 5, REKEY_TIMEOUT_JITTER_MAX_JIFFIES = HZ / 3, diff --git a/src/netlink.c b/src/netlink.c index eb94f4d..9bf2f84 100644 --- a/src/netlink.c +++ b/src/netlink.c @@ -94,8 +94,8 @@ static int get_allowedips(struct sk_buff *skb, const u8 *ip, u8 cidr, struct dump_ctx { struct wg_device *wg; struct wg_peer *next_peer; - struct allowedips_node *next_allowedip; u64 allowedips_seq; + struct allowedips_node *next_allowedip; }; #define DUMP_CTX(cb) ((struct dump_ctx *)(cb)->args) @@ -196,15 +196,9 @@ err: static int wg_get_device_start(struct netlink_callback *cb) { - struct nlattr **attrs = genl_family_attrbuf(&genl_family); struct wg_device *wg; - int ret; - ret = nlmsg_parse(cb->nlh, GENL_HDRLEN + genl_family.hdrsize, attrs, - genl_family.maxattr, device_policy, NULL); - if (ret < 0) - return ret; - wg = lookup_interface(attrs, cb->skb); + wg = lookup_interface(genl_dumpit_info(cb)->attrs, cb->skb); if (IS_ERR(wg)) return PTR_ERR(wg); DUMP_CTX(cb)->wg = wg; @@ -389,10 +383,10 @@ static int set_peer(struct wg_device *wg, struct nlattr **attrs) peer = wg_pubkey_hashtable_lookup(wg->peer_hashtable, nla_data(attrs[WGPEER_A_PUBLIC_KEY])); + ret = 0; if (!peer) { /* Peer doesn't exist yet. Add a new one. */ - ret = -ENODEV; - if (flags & WGPEER_F_REMOVE_ME) - goto out; /* Tried to remove a non-existing peer. */ + if (flags & (WGPEER_F_REMOVE_ME | WGPEER_F_UPDATE_ONLY)) + goto out; /* The peer is new, so there aren't allowed IPs to remove. */ flags &= ~WGPEER_F_REPLACE_ALLOWEDIPS; @@ -413,17 +407,22 @@ static int set_peer(struct wg_device *wg, struct nlattr **attrs) } up_read(&wg->static_identity.lock); - ret = -ENOMEM; peer = wg_peer_create(wg, public_key, preshared_key); - if (!peer) + if (IS_ERR(peer)) { + /* Similar to the above, if the key is invalid, we skip + * it without fanfare, so that services don't need to + * worry about doing key validation themselves. + */ + ret = PTR_ERR(peer) == -EKEYREJECTED ? 0 : PTR_ERR(peer); + peer = NULL; goto out; + } /* Take additional reference, as though we've just been * looked up. */ wg_peer_get(peer); } - ret = 0; if (flags & WGPEER_F_REMOVE_ME) { wg_peer_remove(peer); goto out; diff --git a/src/noise.c b/src/noise.c index 57f749c..269b69f 100644 --- a/src/noise.c +++ b/src/noise.c @@ -566,6 +566,7 @@ wg_noise_handshake_consume_initiation(struct message_handshake_initiation *src, u8 s[NOISE_PUBLIC_KEY_LEN]; u8 e[NOISE_PUBLIC_KEY_LEN]; u8 t[NOISE_TIMESTAMP_LEN]; + u64 initiation_consumption; down_read(&wg->static_identity.lock); if (unlikely(!wg->static_identity.has_identity)) @@ -614,11 +615,14 @@ wg_noise_handshake_consume_initiation(struct message_handshake_initiation *src, /* Success! Copy everything to peer */ down_write(&handshake->lock); memcpy(handshake->remote_ephemeral, e, NOISE_PUBLIC_KEY_LEN); - memcpy(handshake->latest_timestamp, t, NOISE_TIMESTAMP_LEN); + if (memcmp(t, handshake->latest_timestamp, NOISE_TIMESTAMP_LEN) > 0) + memcpy(handshake->latest_timestamp, t, NOISE_TIMESTAMP_LEN); memcpy(handshake->hash, hash, NOISE_HASH_LEN); memcpy(handshake->chaining_key, chaining_key, NOISE_HASH_LEN); handshake->remote_index = src->sender_index; - handshake->last_initiation_consumption = ktime_get_coarse_boottime_ns(); + if ((s64)(handshake->last_initiation_consumption - + (initiation_consumption = ktime_get_coarse_boottime_ns())) < 0) + handshake->last_initiation_consumption = initiation_consumption; handshake->state = HANDSHAKE_CONSUMED_INITIATION; up_write(&handshake->lock); ret_peer = peer; @@ -22,20 +22,23 @@ struct wg_peer *wg_peer_create(struct wg_device *wg, const u8 preshared_key[NOISE_SYMMETRIC_KEY_LEN]) { struct wg_peer *peer; + int ret = -ENOMEM; lockdep_assert_held(&wg->device_update_lock); if (wg->num_peers >= MAX_PEERS_PER_DEVICE) - return NULL; + return ERR_PTR(ret); peer = kzalloc(sizeof(*peer), GFP_KERNEL); if (unlikely(!peer)) - return NULL; + return ERR_PTR(ret); peer->device = wg; if (!wg_noise_handshake_init(&peer->handshake, &wg->static_identity, - public_key, preshared_key, peer)) + public_key, preshared_key, peer)) { + ret = -EKEYREJECTED; goto err_1; + } if (dst_cache_init(&peer->endpoint_cache, GFP_KERNEL)) goto err_1; if (wg_packet_queue_init(&peer->tx_queue, wg_packet_tx_worker, false, @@ -74,7 +77,7 @@ err_2: dst_cache_destroy(&peer->endpoint_cache); err_1: kfree(peer); - return NULL; + return ERR_PTR(ret); } struct wg_peer *wg_peer_get_maybe_zero(struct wg_peer *peer) diff --git a/src/receive.c b/src/receive.c index 247a56b..e00f0f4 100644 --- a/src/receive.c +++ b/src/receive.c @@ -281,9 +281,9 @@ static bool decrypt_packet(struct sk_buff *skb, struct noise_symmetric_key *key, if (skb_to_sgvec(skb, sg, 0, skb->len) <= 0) return false; - if (!chacha20poly1305_decrypt_sg(sg, sg, skb->len, NULL, 0, - PACKET_CB(skb)->nonce, key->key, - simd_context)) + if (!chacha20poly1305_decrypt_sg_inplace(sg, skb->len, NULL, 0, + PACKET_CB(skb)->nonce, key->key, + simd_context)) return false; /* Another ugly situation of pushing and pulling the header so as to @@ -382,7 +382,7 @@ static void wg_packet_consume_data_done(struct wg_peer *peer, /* We've already verified the Poly1305 auth tag, which means this packet * was not modified in transit. We can therefore tell the networking * stack that all checksums of every layer of encapsulation have already - * been checked "by the hardware" and therefore is unneccessary to check + * been checked "by the hardware" and therefore is unnecessary to check * again in software. */ skb->ip_summed = CHECKSUM_UNNECESSARY; diff --git a/src/selftest/allowedips.c b/src/selftest/allowedips.c index 6e244a9..846db14 100644 --- a/src/selftest/allowedips.c +++ b/src/selftest/allowedips.c @@ -307,12 +307,12 @@ static __init bool randomized_test(void) if (wg_allowedips_insert_v4(&t, (struct in_addr *)ip, cidr, peer, &mutex) < 0) { pr_err("allowedips random self-test malloc: FAIL\n"); - goto free; + goto free_locked; } if (horrible_allowedips_insert_v4(&h, (struct in_addr *)ip, cidr, peer) < 0) { pr_err("allowedips random self-test malloc: FAIL\n"); - goto free; + goto free_locked; } for (j = 0; j < NUM_MUTATED_ROUTES; ++j) { memcpy(mutated, ip, 4); @@ -334,12 +334,12 @@ static __init bool randomized_test(void) (struct in_addr *)mutated, cidr, peer, &mutex) < 0) { pr_err("allowedips random malloc: FAIL\n"); - goto free; + goto free_locked; } if (horrible_allowedips_insert_v4(&h, (struct in_addr *)mutated, cidr, peer)) { pr_err("allowedips random self-test malloc: FAIL\n"); - goto free; + goto free_locked; } } } @@ -351,12 +351,12 @@ static __init bool randomized_test(void) if (wg_allowedips_insert_v6(&t, (struct in6_addr *)ip, cidr, peer, &mutex) < 0) { pr_err("allowedips random self-test malloc: FAIL\n"); - goto free; + goto free_locked; } if (horrible_allowedips_insert_v6(&h, (struct in6_addr *)ip, cidr, peer) < 0) { pr_err("allowedips random self-test malloc: FAIL\n"); - goto free; + goto free_locked; } for (j = 0; j < NUM_MUTATED_ROUTES; ++j) { memcpy(mutated, ip, 16); @@ -378,13 +378,13 @@ static __init bool randomized_test(void) (struct in6_addr *)mutated, cidr, peer, &mutex) < 0) { pr_err("allowedips random self-test malloc: FAIL\n"); - goto free; + goto free_locked; } if (horrible_allowedips_insert_v6( &h, (struct in6_addr *)mutated, cidr, peer)) { pr_err("allowedips random self-test malloc: FAIL\n"); - goto free; + goto free_locked; } } } @@ -417,6 +417,7 @@ static __init bool randomized_test(void) free: mutex_lock(&mutex); +free_locked: wg_allowedips_free(&t, &mutex); mutex_unlock(&mutex); horrible_allowedips_free(&h); @@ -207,9 +207,10 @@ static bool encrypt_packet(struct sk_buff *skb, struct noise_keypair *keypair, if (skb_to_sgvec(skb, sg, sizeof(struct message_data), noise_encrypted_len(plaintext_len)) <= 0) return false; - return chacha20poly1305_encrypt_sg(sg, sg, plaintext_len, NULL, 0, - PACKET_CB(skb)->nonce, - keypair->sending.key, simd_context); + return chacha20poly1305_encrypt_sg_inplace(sg, plaintext_len, NULL, 0, + PACKET_CB(skb)->nonce, + keypair->sending.key, + simd_context); } void wg_packet_send_keepalive(struct wg_peer *peer) @@ -233,17 +234,6 @@ void wg_packet_send_keepalive(struct wg_peer *peer) wg_packet_send_staged_packets(peer); } -#define skb_walk_null_queue_safe(first, skb, next) \ - for (skb = first, next = skb->next; skb; \ - skb = next, next = skb ? skb->next : NULL) -static void skb_free_null_queue(struct sk_buff *first) -{ - struct sk_buff *skb, *next; - - skb_walk_null_queue_safe(first, skb, next) - dev_kfree_skb(skb); -} - static void wg_packet_create_data_done(struct sk_buff *first, struct wg_peer *peer) { @@ -252,7 +242,7 @@ static void wg_packet_create_data_done(struct sk_buff *first, wg_timers_any_authenticated_packet_traversal(peer); wg_timers_any_authenticated_packet_sent(peer); - skb_walk_null_queue_safe(first, skb, next) { + skb_list_walk_safe(first, skb, next) { is_keepalive = skb->len == message_data_len(0); if (likely(!wg_socket_send_skb_to_peer(peer, skb, PACKET_CB(skb)->ds) && !is_keepalive)) @@ -284,7 +274,7 @@ void wg_packet_tx_worker(struct work_struct *work) if (likely(state == PACKET_STATE_CRYPTED)) wg_packet_create_data_done(first, peer); else - skb_free_null_queue(first); + kfree_skb_list(first); wg_noise_keypair_put(keypair, false); wg_peer_put(peer); @@ -302,7 +292,7 @@ void wg_packet_encrypt_worker(struct work_struct *work) while ((first = ptr_ring_consume_bh(&queue->ring)) != NULL) { enum packet_state state = PACKET_STATE_CRYPTED; - skb_walk_null_queue_safe(first, skb, next) { + skb_list_walk_safe(first, skb, next) { if (likely(encrypt_packet(skb, PACKET_CB(first)->keypair, &simd_context))) { @@ -343,7 +333,7 @@ err: return; wg_noise_keypair_put(PACKET_CB(first)->keypair, false); wg_peer_put(peer); - skb_free_null_queue(first); + kfree_skb_list(first); } void wg_packet_purge_staged_packets(struct wg_peer *peer) diff --git a/src/socket.c b/src/socket.c index d4f65bb..c46256d 100644 --- a/src/socket.c +++ b/src/socket.c @@ -31,7 +31,7 @@ static int send4(struct wg_device *wg, struct sk_buff *skb, struct sock *sock; int ret = 0; - skb->next = skb->prev = NULL; + skb_mark_not_on_list(skb); skb->dev = wg->dev; skb->mark = wg->fwmark; @@ -117,7 +117,7 @@ static int send6(struct wg_device *wg, struct sk_buff *skb, struct sock *sock; int ret = 0; - skb->next = skb->prev = NULL; + skb_mark_not_on_list(skb); skb->dev = wg->dev; skb->mark = wg->fwmark; @@ -142,9 +142,10 @@ static int send6(struct wg_device *wg, struct sk_buff *skb, if (cache) dst_cache_reset(cache); } - ret = ipv6_stub->ipv6_dst_lookup(sock_net(sock), sock, &dst, - &fl); - if (unlikely(ret)) { + dst = ipv6_stub->ipv6_dst_lookup_flow(sock_net(sock), sock, &fl, + NULL); + if (unlikely(IS_ERR(dst))) { + ret = PTR_ERR(dst); net_dbg_ratelimited("%s: No route to %pISpfsc, error %d\n", wg->dev->name, &endpoint->addr, ret); goto err; @@ -410,7 +411,7 @@ retry: } #endif - wg_socket_reinit(wg, new4 ? new4->sk : NULL, new6 ? new6->sk : NULL); + wg_socket_reinit(wg, new4->sk, new6 ? new6->sk : NULL); return 0; } diff --git a/src/tests/netns.sh b/src/tests/netns.sh index 9edf12b..e7310d9 100755 --- a/src/tests/netns.sh +++ b/src/tests/netns.sh @@ -241,7 +241,7 @@ ip2 link del wg0 # │ ┌─────┐ ┌─────┐ │ │ ┌──────┐ ┌──────┐ │ │ ┌─────┐ ┌─────┐ │ # │ │ wg0 │─────────────│vethc│───────────┼────┼────│vethrc│ │vethrs│──────────────┼─────┼──│veths│────────────│ wg0 │ │ # │ ├─────┴──────────┐ ├─────┴──────────┐│ │ ├──────┴─────────┐ ├──────┴────────────┐ │ │ ├─────┴──────────┐ ├─────┴──────────┐ │ -# │ │192.168.241.1/24│ │192.168.1.100/24││ │ │192.168.1.100/24│ │10.0.0.1/24 │ │ │ │10.0.0.100/24 │ │192.168.241.2/24│ │ +# │ │192.168.241.1/24│ │192.168.1.100/24││ │ │192.168.1.1/24 │ │10.0.0.1/24 │ │ │ │10.0.0.100/24 │ │192.168.241.2/24│ │ # │ │fd00::1/24 │ │ ││ │ │ │ │SNAT:192.168.1.0/24│ │ │ │ │ │fd00::2/24 │ │ # │ └────────────────┘ └────────────────┘│ │ └────────────────┘ └───────────────────┘ │ │ └────────────────┘ └────────────────┘ │ # └────────────────────────────────────────┘ └────────────────────────────────────────────────┘ └────────────────────────────────────────┘ @@ -280,6 +280,26 @@ n2 ping -W 1 -c 1 192.168.241.1 # Demonstrate n2 can still send packets to n1, since persistent-keepalive will prevent connection tracking entry from expiring (to see entries: `n0 conntrack -L`). pp sleep 3 n2 ping -W 1 -c 1 192.168.241.1 +n1 wg set wg0 peer "$pub2" persistent-keepalive 0 + +# Do a wg-quick(8)-style policy routing for the default route, making sure vethc has a v6 address to tease out bugs. +ip1 -6 addr add fc00::9/96 dev vethc +ip1 -6 route add default via fc00::1 +ip2 -4 addr add 192.168.99.7/32 dev wg0 +ip2 -6 addr add abab::1111/128 dev wg0 +n1 wg set wg0 fwmark 51820 peer "$pub2" allowed-ips 192.168.99.7,abab::1111 +ip1 -6 route add default dev wg0 table 51820 +ip1 -6 rule add not fwmark 51820 table 51820 +ip1 -6 rule add table main suppress_prefixlength 0 +ip1 -4 route add default dev wg0 table 51820 +ip1 -4 rule add not fwmark 51820 table 51820 +ip1 -4 rule add table main suppress_prefixlength 0 +# suppress_prefixlength only got added in 3.12, and we want to support 3.10+. +if [[ $(ip1 -4 rule show all) == *suppress_prefixlength* ]]; then + # Flood the pings instead of sending just one, to trigger routing table reference counting bugs. + n1 ping -W 1 -c 100 -f 192.168.99.7 + n1 ping -W 1 -c 100 -f abab::1111 +fi n0 iptables -t nat -F ip0 link del vethrc diff --git a/src/tests/qemu/Makefile b/src/tests/qemu/Makefile index acae128..55aea6a 100644 --- a/src/tests/qemu/Makefile +++ b/src/tests/qemu/Makefile @@ -6,7 +6,7 @@ PWD := $(shell pwd) CHOST := $(shell gcc -dumpmachine) ifneq (,$(ARCH)) -CBUILD := $(subst -gcc,,$(lastword $(subst /, ,$(firstword $(filter-out android,$(wildcard /usr/bin/$(ARCH)-*-gcc)))))) +CBUILD := $(subst -gcc,,$(lastword $(subst /, ,$(firstword $(filter-out android,$(wildcard $(foreach bindir,$(subst :, ,$(PATH)),$(bindir)/$(ARCH)-*-gcc))))))) endif ifeq (,$(CBUILD)) CBUILD := $(CHOST) @@ -14,7 +14,7 @@ endif ARCH := $(firstword $(subst -, ,$(CBUILD))) # Set these from the environment to override -KERNEL_VERSION ?= 5.1.15 +KERNEL_VERSION ?= 5.4 KERNEL_VERSION := $(KERNEL_VERSION)$(if $(DEBUG_KERNEL),$(if $(findstring -debug,$(KERNEL_VERSION)),,-debug),) BUILD_PATH ?= $(PWD)/../../../qemu-build/$(ARCH) DISTFILES_PATH ?= $(PWD)/distfiles diff --git a/src/tests/qemu/arch/powerpc64le.config b/src/tests/qemu/arch/powerpc64le.config index b28b547..990c510 100644 --- a/src/tests/qemu/arch/powerpc64le.config +++ b/src/tests/qemu/arch/powerpc64le.config @@ -3,6 +3,7 @@ CONFIG_PPC_PSERIES=y CONFIG_ALTIVEC=y CONFIG_VSX=y CONFIG_PPC_OF_BOOT_TRAMPOLINE=y +CONFIG_PPC_RADIX_MMU=y CONFIG_HVC_CONSOLE=y CONFIG_CPU_LITTLE_ENDIAN=y CONFIG_CMDLINE_BOOL=y diff --git a/src/tests/qemu/kernel.config b/src/tests/qemu/kernel.config index 85e7d20..0458314 100644 --- a/src/tests/qemu/kernel.config +++ b/src/tests/qemu/kernel.config @@ -22,6 +22,9 @@ CONFIG_NF_NAT_IPV4=y CONFIG_IP_NF_IPTABLES=y CONFIG_IP_NF_FILTER=y CONFIG_IP_NF_NAT=y +CONFIG_IP_ADVANCED_ROUTER=y +CONFIG_IP_MULTIPLE_TABLES=y +CONFIG_IPV6_MULTIPLE_TABLES=y CONFIG_TTY=y CONFIG_BINFMT_ELF=y CONFIG_BINFMT_SCRIPT=y diff --git a/src/tools/ipc.c b/src/tools/ipc.c index e0be413..7207efc 100644 --- a/src/tools/ipc.c +++ b/src/tools/ipc.c @@ -96,7 +96,7 @@ static int add_next_to_inflatable_buffer(struct inflatable_buffer *buffer) } #ifndef WINCOMPAT -static FILE *userspace_interface_file(const char *interface) +static FILE *userspace_interface_file(const char *iface) { struct stat sbuf; struct sockaddr_un addr = { .sun_family = AF_UNIX }; @@ -104,9 +104,9 @@ static FILE *userspace_interface_file(const char *interface) FILE *f = NULL; errno = EINVAL; - if (strchr(interface, '/')) + if (strchr(iface, '/')) goto out; - ret = snprintf(addr.sun_path, sizeof(addr.sun_path), SOCK_PATH "%s" SOCK_SUFFIX, interface); + ret = snprintf(addr.sun_path, sizeof(addr.sun_path), SOCK_PATH "%s" SOCK_SUFFIX, iface); if (ret < 0) goto out; ret = stat(addr.sun_path, &sbuf); @@ -140,15 +140,15 @@ out: return f; } -static bool userspace_has_wireguard_interface(const char *interface) +static bool userspace_has_wireguard_interface(const char *iface) { struct stat sbuf; struct sockaddr_un addr = { .sun_family = AF_UNIX }; int fd, ret; - if (strchr(interface, '/')) + if (strchr(iface, '/')) return false; - if (snprintf(addr.sun_path, sizeof(addr.sun_path), SOCK_PATH "%s" SOCK_SUFFIX, interface) < 0) + if (snprintf(addr.sun_path, sizeof(addr.sun_path), SOCK_PATH "%s" SOCK_SUFFIX, iface) < 0) return false; if (stat(addr.sun_path, &sbuf) < 0) return false; @@ -288,7 +288,7 @@ static int userspace_set_device(struct wgdevice *dev) num; \ }) -static int userspace_get_device(struct wgdevice **out, const char *interface) +static int userspace_get_device(struct wgdevice **out, const char *iface) { struct wgdevice *dev; struct wgpeer *peer = NULL; @@ -302,23 +302,24 @@ static int userspace_get_device(struct wgdevice **out, const char *interface) if (!dev) return -errno; - f = userspace_interface_file(interface); - if (!f) - return -errno; + f = userspace_interface_file(iface); + if (!f) { + ret = -errno; + free(dev); + *out = NULL; + return ret; + } fprintf(f, "get=1\n\n"); fflush(f); - strncpy(dev->name, interface, IFNAMSIZ - 1); + strncpy(dev->name, iface, IFNAMSIZ - 1); dev->name[IFNAMSIZ - 1] = '\0'; while (getline(&key, &line_buffer_len, f) > 0) { line_len = strlen(key); - if (line_len == 1 && key[0] == '\n') { - free(key); - fclose(f); - return ret; - } + if (line_len == 1 && key[0] == '\n') + goto err; value = strchr(key, '='); if (!value || line_len == 0 || key[line_len - 1] != '\n') break; @@ -382,7 +383,7 @@ static int userspace_get_device(struct wgdevice **out, const char *interface) *end++ = '\0'; } if (getaddrinfo(begin, end, &hints, &resolved) != 0) { - errno = ENETUNREACH; + ret = ENETUNREACH; goto err; } if ((resolved->ai_family == AF_INET && resolved->ai_addrlen == sizeof(struct sockaddr_in)) || @@ -437,8 +438,10 @@ static int userspace_get_device(struct wgdevice **out, const char *interface) ret = -EPROTO; err: free(key); - free_wgdevice(dev); - *out = NULL; + if (ret) { + free_wgdevice(dev); + *out = NULL; + } fclose(f); errno = -ret; return ret; @@ -889,7 +892,7 @@ static void coalesce_peers(struct wgdevice *device) } } -static int kernel_get_device(struct wgdevice **device, const char *interface) +static int kernel_get_device(struct wgdevice **device, const char *iface) { int ret = 0; struct nlmsghdr *nlh; @@ -908,7 +911,7 @@ try_again: } nlh = mnlg_msg_prepare(nlg, WG_CMD_GET_DEVICE, NLM_F_REQUEST | NLM_F_ACK | NLM_F_DUMP); - mnl_attr_put_strz(nlh, WGDEVICE_A_IFNAME, interface); + mnl_attr_put_strz(nlh, WGDEVICE_A_IFNAME, iface); if (mnlg_socket_send(nlg, nlh) < 0) { ret = -errno; goto out; @@ -963,14 +966,14 @@ cleanup: return buffer.buffer; } -int ipc_get_device(struct wgdevice **dev, const char *interface) +int ipc_get_device(struct wgdevice **dev, const char *iface) { #ifdef __linux__ - if (userspace_has_wireguard_interface(interface)) - return userspace_get_device(dev, interface); - return kernel_get_device(dev, interface); + if (userspace_has_wireguard_interface(iface)) + return userspace_get_device(dev, iface); + return kernel_get_device(dev, iface); #else - return userspace_get_device(dev, interface); + return userspace_get_device(dev, iface); #endif } diff --git a/src/tools/man/wg.8 b/src/tools/man/wg.8 index afff749..ac281bc 100644 --- a/src/tools/man/wg.8 +++ b/src/tools/man/wg.8 @@ -94,6 +94,14 @@ Appends the contents of \fI<configuration-filename>\fP, which must be in the format described by \fICONFIGURATION FILE FORMAT\fP below, to the current configuration of \fI<interface>\fP. .TP +\fBsyncconf\fP \fI<interface>\fP \fI<configuration-filename>\fP +Like \fBsetconf\fP, but reads back the existing configuration first +and only makes changes that are explicitly different between the configuration +file and the interface. This is much less efficient than \fBsetconf\fP, +but has the benefit of not disrupting current peer sessions. The contents of +\fI<configuration-filename>\fP must be in the format described by +\fICONFIGURATION FILE FORMAT\fP below. +.TP \fBgenkey\fP Generates a random \fIprivate\fP key in base64 and prints it to standard output. @@ -114,7 +122,7 @@ A private key and a corresponding public key may be generated at once by calling $ wg genkey | tee private.key | wg pubkey > public.key .TP \fBhelp\fP -Show usage message. +Shows usage message. .SH CONFIGURATION FILE FORMAT The configuration file format is based on \fIINI\fP. There are two top level sections diff --git a/src/tools/setconf.c b/src/tools/setconf.c index 8211ebd..f778f40 100644 --- a/src/tools/setconf.c +++ b/src/tools/setconf.c @@ -13,6 +13,91 @@ #include "ipc.h" #include "subcommands.h" +struct pubkey_origin { + uint8_t *pubkey; + bool from_file; +}; + +static int pubkey_cmp(const void *first, const void *second) +{ + const struct pubkey_origin *a = first, *b = second; + int ret = memcmp(a->pubkey, b->pubkey, WG_KEY_LEN); + if (ret) + return ret; + return a->from_file - b->from_file; +} + +static bool sync_conf(struct wgdevice *file) +{ + struct wgdevice *runtime; + struct wgpeer *peer; + struct pubkey_origin *pubkeys; + size_t peer_count = 0, i = 0; + + if (!file->first_peer) + return true; + + for_each_wgpeer(file, peer) + ++peer_count; + + if (ipc_get_device(&runtime, file->name) != 0) { + perror("Unable to retrieve current interface configuration"); + return false; + } + + if (!runtime->first_peer) { + free_wgdevice(runtime); + return true; + } + + file->flags &= ~WGDEVICE_REPLACE_PEERS; + + for_each_wgpeer(runtime, peer) + ++peer_count; + + pubkeys = calloc(peer_count, sizeof(*pubkeys)); + if (!pubkeys) { + free_wgdevice(runtime); + perror("Public key allocation"); + return false; + } + + for_each_wgpeer(file, peer) { + pubkeys[i].pubkey = peer->public_key; + pubkeys[i].from_file = true; + ++i; + } + for_each_wgpeer(runtime, peer) { + pubkeys[i].pubkey = peer->public_key; + pubkeys[i].from_file = false; + ++i; + } + qsort(pubkeys, peer_count, sizeof(*pubkeys), pubkey_cmp); + + for (i = 0; i < peer_count; ++i) { + if (pubkeys[i].from_file) + continue; + if (i == peer_count - 1 || !pubkeys[i + 1].from_file || memcmp(pubkeys[i].pubkey, pubkeys[i + 1].pubkey, WG_KEY_LEN)) { + peer = calloc(1, sizeof(struct wgpeer)); + if (!peer) { + free_wgdevice(runtime); + free(pubkeys); + perror("Peer allocation"); + return false; + } + peer->flags = WGPEER_REMOVE_ME; + memcpy(peer->public_key, pubkeys[i].pubkey, WG_KEY_LEN); + peer->next_peer = file->first_peer; + file->first_peer = peer; + if (!file->last_peer) + file->last_peer = peer; + } + } + free_wgdevice(runtime); + free(pubkeys); + return true; +} + int setconf_main(int argc, char *argv[]) { struct wgdevice *device = NULL; @@ -50,6 +135,11 @@ int setconf_main(int argc, char *argv[]) strncpy(device->name, argv[1], IFNAMSIZ - 1); device->name[IFNAMSIZ - 1] = '\0'; + if (!strcmp(argv[0], "syncconf")) { + if (!sync_conf(device)) + goto cleanup; + } + if (ipc_set_device(device) != 0) { perror("Unable to modify interface"); goto cleanup; diff --git a/src/tools/wg-quick/android.c b/src/tools/wg-quick/android.c index e8ea4b4..ad05895 100644 --- a/src/tools/wg-quick/android.c +++ b/src/tools/wg-quick/android.c @@ -20,6 +20,7 @@ #include <unistd.h> #include <errno.h> #include <regex.h> +#include <dlfcn.h> #include <sys/types.h> #include <sys/stat.h> #include <sys/wait.h> @@ -37,6 +38,7 @@ #define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0])) static bool is_exiting = false; +static bool binder_available = false; static void *xmalloc(size_t size) { @@ -235,6 +237,376 @@ _printf_(1, 2) static void cndc(const char *cmd_fmt, ...) } } +/* Values are from AOSP repository platform/frameworks/native in libs/binder/ndk/include_ndk/android/binder_status.h. */ +enum { + STATUS_OK = 0, + STATUS_UNKNOWN_ERROR = -2147483647 - 1, + STATUS_NO_MEMORY = -ENOMEM, + STATUS_INVALID_OPERATION = -ENOSYS, + STATUS_BAD_VALUE = -EINVAL, + STATUS_BAD_TYPE = STATUS_UNKNOWN_ERROR + 1, + STATUS_NAME_NOT_FOUND = -ENOENT, + STATUS_PERMISSION_DENIED = -EPERM, + STATUS_NO_INIT = -ENODEV, + STATUS_ALREADY_EXISTS = -EEXIST, + STATUS_DEAD_OBJECT = -EPIPE, + STATUS_FAILED_TRANSACTION = STATUS_UNKNOWN_ERROR + 2, + STATUS_BAD_INDEX = -EOVERFLOW, + STATUS_NOT_ENOUGH_DATA = -ENODATA, + STATUS_WOULD_BLOCK = -EWOULDBLOCK, + STATUS_TIMED_OUT = -ETIMEDOUT, + STATUS_UNKNOWN_TRANSACTION = -EBADMSG, + STATUS_FDS_NOT_ALLOWED = STATUS_UNKNOWN_ERROR + 7, + STATUS_UNEXPECTED_NULL = STATUS_UNKNOWN_ERROR + 8 +}; +enum { + EX_NONE = 0, + EX_SECURITY = -1, + EX_BAD_PARCELABLE = -2, + EX_ILLEGAL_ARGUMENT = -3, + EX_NULL_POINTER = -4, + EX_ILLEGAL_STATE = -5, + EX_NETWORK_MAIN_THREAD = -6, + EX_UNSUPPORTED_OPERATION = -7, + EX_SERVICE_SPECIFIC = -8, + EX_PARCELABLE = -9, + EX_TRANSACTION_FAILED = -129 +}; +enum { + FLAG_ONEWAY = 0x01, +}; +enum { + FIRST_CALL_TRANSACTION = 0x00000001, + LAST_CALL_TRANSACTION = 0x00ffffff +}; +struct AIBinder; +struct AParcel; +struct AStatus; +struct AIBinder_Class; +typedef struct AIBinder AIBinder; +typedef struct AParcel AParcel; +typedef struct AStatus AStatus; +typedef struct AIBinder_Class AIBinder_Class; +typedef int32_t binder_status_t; +typedef int32_t binder_exception_t; +typedef uint32_t transaction_code_t; +typedef uint32_t binder_flags_t; +typedef void *(*AIBinder_Class_onCreate)(void *args); +typedef void (*AIBinder_Class_onDestroy)(void *userData); +typedef binder_status_t (*AIBinder_Class_onTransact)(AIBinder *binder, transaction_code_t code, const AParcel *in, AParcel *out); +typedef const char *(*AParcel_stringArrayElementGetter)(const void *arrayData, size_t index, int32_t *outLength); +static AIBinder_Class *(*AIBinder_Class_define)(const char *interfaceDescriptor, AIBinder_Class_onCreate onCreate, AIBinder_Class_onDestroy onDestroy, AIBinder_Class_onTransact onTransact) __attribute__((warn_unused_result)); +static bool (*AIBinder_associateClass)(AIBinder *binder, const AIBinder_Class *clazz); +static void (*AIBinder_decStrong)(AIBinder *binder); +static binder_status_t (*AIBinder_prepareTransaction)(AIBinder *binder, AParcel **in); +static binder_status_t (*AIBinder_transact)(AIBinder *binder, transaction_code_t code, AParcel **in, AParcel **out, binder_flags_t flags); +static binder_status_t (*AIBinder_ping)(AIBinder *binder); +static binder_status_t (*AIBinder_dump)(AIBinder *binder, int fd, const char **args, uint32_t numArgs); +static binder_status_t (*AParcel_readStatusHeader)(const AParcel *parcel, AStatus **status); +static binder_status_t (*AParcel_readBool)(const AParcel *parcel, bool *value); +static void (*AParcel_delete)(AParcel *parcel); +static binder_status_t (*AParcel_setDataPosition)(const AParcel *parcel, int32_t position); +static int32_t (*AParcel_getDataPosition)(const AParcel *parcel); +static binder_status_t (*AParcel_writeInt32)(AParcel *parcel, int32_t value); +static binder_status_t (*AParcel_writeStringArray)(AParcel *parcel, const void *arrayData, int32_t length, AParcel_stringArrayElementGetter getter); +static binder_status_t (*AParcel_writeString)(AParcel *parcel, const char *string, int32_t length); +static bool (*AStatus_isOk)(const AStatus *status); +static void (*AStatus_delete)(AStatus *status); +static binder_exception_t (*AStatus_getExceptionCode)(const AStatus *status); +static int32_t (*AStatus_getServiceSpecificError)(const AStatus *status); +static const char* (*AStatus_getMessage)(const AStatus *status); +static binder_status_t (*AStatus_getStatus)(const AStatus *status); +static AIBinder *(*AServiceManager_getService)(const char *instance) __attribute__((__warn_unused_result__)); + +static __attribute__((__constructor__(65535))) void load_symbols(void) +{ + void *handle = dlopen("libbinder_ndk.so", RTLD_LAZY); + binder_available = !!handle; + if (!binder_available) + return; + +#define X(symb) do { \ + if (!((symb) = (typeof(symb))dlsym(handle, #symb))) { \ + fprintf(stderr, "Error: unable to import " #symb " from libbinder_ndk.so\n"); \ + exit(ELIBACC); \ + } \ + } while (0) + X(AIBinder_Class_define); + X(AIBinder_associateClass); + X(AIBinder_decStrong); + X(AIBinder_prepareTransaction); + X(AIBinder_transact); + X(AIBinder_ping); + X(AIBinder_dump); + X(AParcel_readStatusHeader); + X(AParcel_readBool); + X(AParcel_delete); + X(AParcel_setDataPosition); + X(AParcel_getDataPosition); + X(AParcel_writeInt32); + X(AParcel_writeStringArray); + X(AParcel_writeString); + X(AStatus_isOk); + X(AStatus_delete); + X(AStatus_getExceptionCode); + X(AStatus_getServiceSpecificError); + X(AStatus_getMessage); + X(AStatus_getStatus); + X(AServiceManager_getService); +#undef X +} + +static void cleanup_binder(AIBinder **binder) +{ + if (*binder) + AIBinder_decStrong(*binder); +} +static void cleanup_status(AStatus **status) +{ + if (*status) + AStatus_delete(*status); +} +static void cleanup_parcel(AParcel **parcel) +{ + if (*parcel) + AParcel_delete(*parcel); +} + +#define _cleanup_status_ __attribute__((__cleanup__(cleanup_status))) +#define _cleanup_parcel_ __attribute__((__cleanup__(cleanup_parcel))) +#define _cleanup_binder_ __attribute__((__cleanup__(cleanup_binder))) + +static int32_t string_size(const char *str) +{ + return str ? strlen(str) : -1; +} + +static int32_t string_array_size(char *const *array) +{ + int32_t size = -1; + if (!array) + return size; + for (size = 0; array[size]; ++size); + return size; +} + +static const char *string_array_getter(const void *array_data, size_t index, int32_t *out_length) +{ + const char **array = (const char **)array_data; + *out_length = array[index] ? strlen(array[index]) : -1; + return array[index]; +} + +static binder_status_t meaningful_binder_status(const AStatus *status_out) +{ + binder_status_t status = STATUS_OK; + binder_exception_t exc_code; + int32_t exc_code_service; + const char *message; + + if (!AStatus_isOk(status_out)) { + exc_code = AStatus_getExceptionCode(status_out); + if (exc_code == EX_TRANSACTION_FAILED) { + status = AStatus_getStatus(status_out); + fprintf(stderr, "Error: transaction failed: %d\n", status); + } + else { + message = AStatus_getMessage(status_out); + + if (exc_code == EX_SERVICE_SPECIFIC) { + exc_code_service = AStatus_getServiceSpecificError(status_out); + fprintf(stderr, "Error: service specific exception code: %d%s%s\n", exc_code_service, message ? ": " : "", message ?: ""); + } + else + fprintf(stderr, "Error: exception code: %d%s%s\n", exc_code, message ? ": " : "", message ?: ""); + + status = STATUS_FAILED_TRANSACTION; + } + } + + return status; +} + +/* These values are default values observed in AOSP. */ +enum { + DNSRESOLVER_SAMPLE_VALIDITY = 1800 /* sec */, + DNSRESOLVER_SUCCESS_THRESHOLD = 25, + DNSRESOLVER_MIN_SAMPLES = 8, + DNSRESOLVER_MAX_SAMPLES = 8, + DNSRESOLVER_BASE_TIMEOUT = 5000 /* msec */, + DNSRESOLVER_RETRY_COUNT = 2 +}; + +struct dnsresolver_params { + int32_t netid; + int32_t sample_validity_seconds; + int32_t success_threshold; + int32_t min_samples; + int32_t max_samples; + int32_t base_timeout_msec; + int32_t retry_count; + char **servers; /* NULL terminated array of zero-terminated UTF-8 strings */ + char **domains; /* NULL terminated array of zero-terminated UTF-8 strings */ + char *tls_name; /* zero-terminated UTF-8 string */ + char **tls_servers; /* NULL terminated array of zero-terminated UTF-8 strings */ + char **tls_fingerprints; /* NULL terminated array of zero-terminated UTF-8 strings */ +}; + +static void *on_create() +{ + fprintf(stderr, "Error: on_create called on proxy object\n"); + exit(ENOTSUP); + return NULL; +} + +static void on_destroy() +{ + fprintf(stderr, "Error: on_destroy called on proxy object\n"); + exit(ENOTSUP); +} + +static binder_status_t on_transact() +{ + fprintf(stderr, "Error: on_transact called on a proxy object\n"); + exit(ENOTSUP); + return 0; +} + +static AIBinder *dnsresolver_get_handle(void) +{ + AIBinder *binder; + AIBinder_Class *clazz; + + if (!binder_available) + return NULL; + + binder = AServiceManager_getService("dnsresolver"); + if (!binder) + return NULL; + clazz = AIBinder_Class_define("android.net.IDnsResolver", &on_create, &on_destroy, &on_transact); + if (!clazz) + goto error; + + if (!AIBinder_associateClass(binder, clazz)) + goto error; + + return binder; +error: + AIBinder_decStrong(binder); + return NULL; +} + +static int32_t dnsresolver_create_network_cache(void *handle, int32_t netid) +{ + AIBinder *const binder = handle; + binder_status_t status; + _cleanup_parcel_ AParcel *parcel_in = NULL; + _cleanup_parcel_ AParcel *parcel_out = NULL; + _cleanup_status_ AStatus *status_out = NULL; + + status = AIBinder_prepareTransaction(binder, &parcel_in); + if (status != STATUS_OK) + return status; + + status = AParcel_writeInt32(parcel_in, netid); + if (status != STATUS_OK) + return status; + + status = AIBinder_transact(binder, FIRST_CALL_TRANSACTION + 7 /* createNetworkCache */, &parcel_in, &parcel_out, 0); + if (status != STATUS_OK) + return status; + + status = AParcel_readStatusHeader(parcel_out, &status_out); + if (status != STATUS_OK) + return status; + + if (!AStatus_isOk(status_out)) + return meaningful_binder_status(status_out); + + return STATUS_OK; +} + +static int32_t dnsresolver_set_resolver_configuration(void *handle, const struct dnsresolver_params *params) +{ + AIBinder *const binder = handle; + binder_status_t status; + _cleanup_parcel_ AParcel *parcel_in = NULL; + _cleanup_parcel_ AParcel *parcel_out = NULL; + _cleanup_status_ AStatus *status_out = NULL; + int32_t start_position, end_position; + + status = AIBinder_prepareTransaction(binder, &parcel_in); + if (status != STATUS_OK) + return status; + + status = AParcel_writeInt32(parcel_in, 1); + if (status != STATUS_OK) + return status; + + start_position = AParcel_getDataPosition(parcel_in); + status = AParcel_writeInt32(parcel_in, 0); + if (status != STATUS_OK) + return status; + + status = AParcel_writeInt32(parcel_in, params->netid); + if (status != STATUS_OK) + return status; + status = AParcel_writeInt32(parcel_in, params->sample_validity_seconds); + if (status != STATUS_OK) + return status; + status = AParcel_writeInt32(parcel_in, params->success_threshold); + if (status != STATUS_OK) + return status; + status = AParcel_writeInt32(parcel_in, params->min_samples); + if (status != STATUS_OK) + return status; + status = AParcel_writeInt32(parcel_in, params->max_samples); + if (status != STATUS_OK) + return status; + status = AParcel_writeInt32(parcel_in, params->base_timeout_msec); + if (status != STATUS_OK) + return status; + status = AParcel_writeInt32(parcel_in, params->retry_count); + if (status != STATUS_OK) + return status; + status = AParcel_writeStringArray(parcel_in, params->servers, string_array_size(params->servers), &string_array_getter); + if (status != STATUS_OK) + return status; + status = AParcel_writeStringArray(parcel_in, params->domains, string_array_size(params->domains), &string_array_getter); + if (status != STATUS_OK) + return status; + status = AParcel_writeString(parcel_in, params->tls_name, string_size(params->tls_name)); + if (status != STATUS_OK) + return status; + status = AParcel_writeStringArray(parcel_in, params->tls_servers, string_array_size(params->tls_servers), &string_array_getter); + if (status != STATUS_OK) + return status; + status = AParcel_writeStringArray(parcel_in, params->tls_fingerprints, string_array_size(params->tls_fingerprints), &string_array_getter); + if (status != STATUS_OK) + return status; + + end_position = AParcel_getDataPosition(parcel_in); + status = AParcel_setDataPosition(parcel_in, start_position); + if (status != STATUS_OK) + return status; + status = AParcel_writeInt32(parcel_in, end_position - start_position); + if (status != STATUS_OK) + return status; + status = AParcel_setDataPosition(parcel_in, end_position); + if (status != STATUS_OK) + return status; + + status = AIBinder_transact(binder, FIRST_CALL_TRANSACTION + 2 /* setResolverConfiguration */, &parcel_in, &parcel_out, 0); + if (status != STATUS_OK) + return status; + + status = AParcel_readStatusHeader(parcel_out, &status_out); + if (status != STATUS_OK) + return status; + + return meaningful_binder_status(status_out); +} + static void auto_su(int argc, char *argv[]) { char *args[argc + 4]; @@ -440,22 +812,75 @@ static void set_dnses(unsigned int netid, const char *dnses) if (len > (1<<16)) return; _cleanup_free_ char *mutable = xstrdup(dnses); - _cleanup_free_ char *arglist = xmalloc(len * 4 + 1); + _cleanup_free_ char *shell_arglist = xmalloc(len * 4 + 1); + _cleanup_free_ char *function_arglist = xmalloc(len * 4 + 1); _cleanup_free_ char *arg = xmalloc(len + 4); + _cleanup_free_ char **dns_list = NULL; + _cleanup_binder_ AIBinder *handle = NULL; + size_t dns_list_size = 0; if (!len) return; - arglist[0] = '\0'; + for (char *dns = strtok(mutable, ", \t\n"); dns; dns = strtok(NULL, ", \t\n")) { + if (strchr(dns, '\'') || strchr(dns, '\\')) + continue; + ++dns_list_size; + } + if (!dns_list_size) + return; + dns_list = xcalloc(dns_list_size + 1, sizeof(*dns_list)); + free(mutable); + mutable = xstrdup(dnses); + shell_arglist[0] = '\0'; + function_arglist[0] = '\0'; + dns_list_size = 0; for (char *dns = strtok(mutable, ", \t\n"); dns; dns = strtok(NULL, ", \t\n")) { if (strchr(dns, '\'') || strchr(dns, '\\')) continue; snprintf(arg, len + 3, "'%s' ", dns); - strncat(arglist, arg, len * 4 - 1); + strncat(shell_arglist, arg, len * 4 - 1); + snprintf(arg, len + 2, function_arglist[0] == '\0' ? "%s" : ", %s", dns); + strncat(function_arglist, arg, len * 4 - 1); + dns_list[dns_list_size++] = dns; } - if (!strlen(arglist)) - return; - cndc("resolver setnetdns %u '' %s", netid, arglist); + + if ((handle = dnsresolver_get_handle())) { + binder_status_t status; + + printf("[#] <binder>::dnsResolver->createNetworkCache(%u)\n", netid); + status = dnsresolver_create_network_cache(handle, netid); + if (status != 0) { + fprintf(stderr, "Error: unable to create network cache\n"); + exit(ENONET); + } + + struct dnsresolver_params params = { + .netid = netid, + .sample_validity_seconds = DNSRESOLVER_SAMPLE_VALIDITY, + .success_threshold = DNSRESOLVER_SUCCESS_THRESHOLD, + .min_samples = DNSRESOLVER_MIN_SAMPLES, + .max_samples = DNSRESOLVER_MAX_SAMPLES, + .base_timeout_msec = DNSRESOLVER_BASE_TIMEOUT, + .retry_count = DNSRESOLVER_RETRY_COUNT, + .servers = dns_list, + .domains = (char *[]){NULL}, + .tls_name = "", + .tls_servers = (char *[]){NULL}, + .tls_fingerprints = (char *[]){NULL} + }; + + printf("[#] <binder>::dnsResolver->setResolverConfiguration(%u, [%s], [], %d, %d, %d, %d, %d, %d, [], [])\n", + netid, function_arglist, DNSRESOLVER_SAMPLE_VALIDITY, DNSRESOLVER_SUCCESS_THRESHOLD, + DNSRESOLVER_MIN_SAMPLES, DNSRESOLVER_MAX_SAMPLES, DNSRESOLVER_BASE_TIMEOUT, DNSRESOLVER_RETRY_COUNT); + status = dnsresolver_set_resolver_configuration(handle, ¶ms); + + if (status != 0) { + fprintf(stderr, "Error: unable to set DNS servers through Binder: %d\n", status); + exit(ENONET); + } + } else + cndc("resolver setnetdns %u '' %s", netid, shell_arglist); } static void add_addr(const char *iface, const char *addr) @@ -662,8 +1087,8 @@ static void cmd_up(const char *iface, const char *config, unsigned int mtu, cons add_if(iface); set_config(iface, config); listen_port = determine_listen_port(iface); - set_addr(iface, addrs); up_if(&netid, iface, listen_port); + set_addr(iface, addrs); set_dnses(netid, dnses); set_routes(iface, netid); set_mtu(iface, mtu); diff --git a/src/tools/wg-quick/linux.bash b/src/tools/wg-quick/linux.bash index 2f36dee..e9c9052 100755 --- a/src/tools/wg-quick/linux.bash +++ b/src/tools/wg-quick/linux.bash @@ -95,17 +95,18 @@ add_if() { del_if() { local table [[ $HAVE_SET_DNS -eq 0 ]] || unset_dns + [[ $HAVE_SET_FIREWALL -eq 0 ]] || remove_firewall if [[ -z $TABLE || $TABLE == auto ]] && get_fwmark table && [[ $(wg show "$INTERFACE" allowed-ips) =~ /0(\ |$'\n'|$) ]]; then - while [[ $(ip -4 rule show) == *"lookup $table"* ]]; do + while [[ $(ip -4 rule show 2>/dev/null) == *"lookup $table"* ]]; do cmd ip -4 rule delete table $table done - while [[ $(ip -4 rule show) == *"from all lookup main suppress_prefixlength 0"* ]]; do + while [[ $(ip -4 rule show 2>/dev/null) == *"from all lookup main suppress_prefixlength 0"* ]]; do cmd ip -4 rule delete table main suppress_prefixlength 0 done - while [[ $(ip -6 rule show) == *"lookup $table"* ]]; do + while [[ $(ip -6 rule show 2>/dev/null) == *"lookup $table"* ]]; do cmd ip -6 rule delete table $table done - while [[ $(ip -6 rule show) == *"from all lookup main suppress_prefixlength 0"* ]]; do + while [[ $(ip -6 rule show 2>/dev/null) == *"from all lookup main suppress_prefixlength 0"* ]]; do cmd ip -6 rule delete table main suppress_prefixlength 0 done fi @@ -155,7 +156,7 @@ set_dns() { unset_dns() { [[ ${#DNS[@]} -gt 0 ]] || return 0 - cmd resolvconf -d "$(resolvconf_iface_prefix)$INTERFACE" + cmd resolvconf -d "$(resolvconf_iface_prefix)$INTERFACE" -f } add_route() { @@ -180,23 +181,64 @@ get_fwmark() { return 0 } +remove_firewall() { + if type -p nft >/dev/null; then + local table nftcmd + while read -r table; do + [[ $table == *" wg-quick-$INTERFACE" ]] && printf -v nftcmd '%sdelete %s\n' "$nftcmd" "$table" + done < <(nft list tables 2>/dev/null) + [[ -z $nftcmd ]] || cmd nft -f <(echo -n "$nftcmd") + fi + if type -p iptables >/dev/null; then + local line iptables found restore + for iptables in iptables ip6tables; do + restore="" found=0 + while read -r line; do + [[ $line == "*"* || $line == COMMIT || $line == "-A "*"-m comment --comment \"wg-quick(8) rule for $INTERFACE\""* ]] || continue + [[ $line == "-A"* ]] && found=1 + printf -v restore '%s%s\n' "$restore" "${line/#-A/-D}" + done < <($iptables-save 2>/dev/null) + [[ $found -ne 1 ]] || echo -n "$restore" | cmd $iptables-restore -n + done + fi +} + +HAVE_SET_FIREWALL=0 add_default() { - local table proto key value + local table line if ! get_fwmark table; then table=51820 - while [[ -n $(ip -4 route show table $table) || -n $(ip -6 route show table $table) ]]; do + while [[ -n $(ip -4 route show table $table 2>/dev/null) || -n $(ip -6 route show table $table 2>/dev/null) ]]; do ((table++)) done cmd wg set "$INTERFACE" fwmark $table fi - proto=-4 - [[ $1 == *:* ]] && proto=-6 + local proto=-4 iptables=iptables pf=ip + [[ $1 == *:* ]] && proto=-6 iptables=ip6tables pf=ip6 cmd ip $proto route add "$1" dev "$INTERFACE" table $table cmd ip $proto rule add not fwmark $table table $table cmd ip $proto rule add table main suppress_prefixlength 0 - while read -r key _ value; do - [[ $value -eq 1 ]] && sysctl -q "$key=2" - done < <(sysctl -a -r '^net\.ipv4.conf\.[^ .=]+\.rp_filter$') + + local marker="-m comment --comment \"wg-quick(8) rule for $INTERFACE\"" restore=$'*raw\n' nftable="wg-quick-$INTERFACE" nftcmd + printf -v nftcmd '%sadd table %s %s\n' "$nftcmd" "$pf" "$nftable" + printf -v nftcmd '%sadd chain %s %s preraw { type filter hook prerouting priority -300; }\n' "$nftcmd" "$pf" "$nftable" + printf -v nftcmd '%sadd chain %s %s premangle { type filter hook prerouting priority -150; }\n' "$nftcmd" "$pf" "$nftable" + printf -v nftcmd '%sadd chain %s %s postmangle { type filter hook postrouting priority -150; }\n' "$nftcmd" "$pf" "$nftable" + while read -r line; do + [[ $line =~ .*inet6?\ ([0-9a-f:.]+)/[0-9]+.* ]] || continue + printf -v restore '%s-I PREROUTING ! -i %s -d %s -m addrtype ! --src-type LOCAL -j DROP %s\n' "$restore" "$INTERFACE" "${BASH_REMATCH[1]}" "$marker" + printf -v nftcmd '%sadd rule %s %s preraw iifname != %s %s daddr %s fib saddr type != local drop\n' "$nftcmd" "$pf" "$nftable" "$INTERFACE" "$pf" "${BASH_REMATCH[1]}" + done < <(ip -o $proto addr show dev "$INTERFACE" 2>/dev/null) + printf -v restore '%sCOMMIT\n*mangle\n-I POSTROUTING -m mark --mark %d -p udp -j CONNMARK --save-mark %s\n-I PREROUTING -p udp -j CONNMARK --restore-mark %s\nCOMMIT\n' "$restore" $table "$marker" "$marker" + printf -v nftcmd '%sadd rule %s %s postmangle meta l4proto udp mark %d ct mark set mark \n' "$nftcmd" "$pf" "$nftable" $table + printf -v nftcmd '%sadd rule %s %s premangle meta l4proto udp meta mark set ct mark \n' "$nftcmd" "$pf" "$nftable" + [[ $proto == -4 ]] && cmd sysctl -q net.ipv4.conf.all.src_valid_mark=1 + if type -p nft >/dev/null; then + cmd nft -f <(echo -n "$nftcmd") + else + echo -n "$restore" | cmd $iptables-restore -n + fi + HAVE_SET_FIREWALL=1 return 0 } @@ -300,7 +342,8 @@ cmd_down() { execute_hooks "${PRE_DOWN[@]}" [[ $SAVE_CONFIG -eq 0 ]] || save_config del_if - unset_dns + unset_dns || true + remove_firewall || true execute_hooks "${POST_DOWN[@]}" } diff --git a/src/tools/wg-quick/openbsd.bash b/src/tools/wg-quick/openbsd.bash index b234609..2cadeec 100755 --- a/src/tools/wg-quick/openbsd.bash +++ b/src/tools/wg-quick/openbsd.bash @@ -293,7 +293,7 @@ add_route() { fi if [[ -n $TABLE && $TABLE != auto ]]; then - cmd route -q -n add "-$family" -rdomain "$TABLE" "$1" -iface "$ifaceroute" + cmd route -q -n -T "$TABLE" add "-$family" "$1" -iface "$ifaceroute" elif [[ $1 == */0 ]]; then if [[ $1 == *:* ]]; then AUTO_ROUTE6=1 diff --git a/src/tools/wg.c b/src/tools/wg.c index 550d9b4..7b5d3af 100644 --- a/src/tools/wg.c +++ b/src/tools/wg.c @@ -21,6 +21,7 @@ static const struct { { "set", set_main, "Change the current configuration, add peers, remove peers, or change peers" }, { "setconf", setconf_main, "Applies a configuration file to a WireGuard interface" }, { "addconf", setconf_main, "Appends a configuration file to a WireGuard interface" }, + { "syncconf", setconf_main, "Synchronizes a configuration file to a WireGuard interface" }, { "genkey", genkey_main, "Generates a new private key and writes it to stdout" }, { "genpsk", genkey_main, "Generates a new preshared key and writes it to stdout" }, { "pubkey", pubkey_main, "Reads a private key from stdin and writes a public key to stdout" } diff --git a/src/tools/wincompat/ipc.c b/src/tools/wincompat/ipc.c index a7a59f8..25471b0 100644 --- a/src/tools/wincompat/ipc.c +++ b/src/tools/wincompat/ipc.c @@ -5,18 +5,23 @@ #include <windows.h> #include <tlhelp32.h> +#include <accctrl.h> +#include <aclapi.h> #include <stdio.h> #include <stdbool.h> #include <fcntl.h> -static FILE *userspace_interface_file(const char *interface) +static FILE *userspace_interface_file(const char *iface) { char fname[MAX_PATH], error_message[1024 * 128] = { 0 }; HANDLE thread_token, process_snapshot, winlogon_process, winlogon_token, duplicated_token, pipe_handle = INVALID_HANDLE_VALUE; PROCESSENTRY32 entry = { .dwSize = sizeof(PROCESSENTRY32) }; + PSECURITY_DESCRIPTOR pipe_sd; + PSID pipe_sid; + SID expected_sid; BOOL ret; int fd; - DWORD last_error = ERROR_SUCCESS; + DWORD last_error = ERROR_SUCCESS, bytes = sizeof(expected_sid); TOKEN_PRIVILEGES privileges = { .PrivilegeCount = 1, .Privileges = {{ .Attributes = SE_PRIVILEGE_ENABLED }} @@ -24,6 +29,8 @@ static FILE *userspace_interface_file(const char *interface) if (!LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &privileges.Privileges[0].Luid)) goto err; + if (!CreateWellKnownSid(WinLocalSystemSid, NULL, &expected_sid, &bytes)) + goto err; process_snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); if (process_snapshot == INVALID_HANDLE_VALUE) @@ -63,13 +70,24 @@ static FILE *userspace_interface_file(const char *interface) } CloseHandle(duplicated_token); - snprintf(fname, sizeof(fname), "\\\\.\\pipe\\WireGuard\\%s", interface); + snprintf(fname, sizeof(fname), "\\\\.\\pipe\\ProtectedPrefix\\Administrators\\WireGuard\\%s", iface); pipe_handle = CreateFile(fname, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL); last_error = GetLastError(); - if (pipe_handle != INVALID_HANDLE_VALUE) { - last_error = ERROR_SUCCESS; - break; + if (pipe_handle == INVALID_HANDLE_VALUE) + continue; + last_error = GetSecurityInfo(pipe_handle, SE_FILE_OBJECT, OWNER_SECURITY_INFORMATION, &pipe_sid, NULL, NULL, NULL, &pipe_sd); + if (last_error != ERROR_SUCCESS) { + CloseHandle(pipe_handle); + continue; + } + last_error = EqualSid(&expected_sid, pipe_sid) ? ERROR_SUCCESS : ERROR_ACCESS_DENIED; + LocalFree(pipe_sd); + if (last_error != ERROR_SUCCESS) { + CloseHandle(pipe_handle); + continue; } + last_error = ERROR_SUCCESS; + break; } RevertToSelf(); CloseHandle(process_snapshot); diff --git a/src/uapi/wireguard.h b/src/uapi/wireguard.h index 5a3e5a7..ae88be1 100644 --- a/src/uapi/wireguard.h +++ b/src/uapi/wireguard.h @@ -18,13 +18,13 @@ * one but not both of: * * WGDEVICE_A_IFINDEX: NLA_U32 - * WGDEVICE_A_IFNAME: NLA_NUL_STRING, maxlen IFNAMESIZ - 1 + * WGDEVICE_A_IFNAME: NLA_NUL_STRING, maxlen IFNAMSIZ - 1 * * The kernel will then return several messages (NLM_F_MULTI) containing the * following tree of nested items: * * WGDEVICE_A_IFINDEX: NLA_U32 - * WGDEVICE_A_IFNAME: NLA_NUL_STRING, maxlen IFNAMESIZ - 1 + * WGDEVICE_A_IFNAME: NLA_NUL_STRING, maxlen IFNAMSIZ - 1 * WGDEVICE_A_PRIVATE_KEY: NLA_EXACT_LEN, len WG_KEY_LEN * WGDEVICE_A_PUBLIC_KEY: NLA_EXACT_LEN, len WG_KEY_LEN * WGDEVICE_A_LISTEN_PORT: NLA_U16 @@ -77,7 +77,7 @@ * WGDEVICE_A_IFINDEX and WGDEVICE_A_IFNAME: * * WGDEVICE_A_IFINDEX: NLA_U32 - * WGDEVICE_A_IFNAME: NLA_NUL_STRING, maxlen IFNAMESIZ - 1 + * WGDEVICE_A_IFNAME: NLA_NUL_STRING, maxlen IFNAMSIZ - 1 * WGDEVICE_A_FLAGS: NLA_U32, 0 or WGDEVICE_F_REPLACE_PEERS if all current * peers should be removed prior to adding the list below. * WGDEVICE_A_PRIVATE_KEY: len WG_KEY_LEN, all zeros to remove @@ -87,10 +87,12 @@ * 0: NLA_NESTED * WGPEER_A_PUBLIC_KEY: len WG_KEY_LEN * WGPEER_A_FLAGS: NLA_U32, 0 and/or WGPEER_F_REMOVE_ME if the - * specified peer should be removed rather than - * added/updated and/or WGPEER_F_REPLACE_ALLOWEDIPS - * if all current allowed IPs of this peer should be - * removed prior to adding the list below. + * specified peer should not exist at the end of the + * operation, rather than added/updated and/or + * WGPEER_F_REPLACE_ALLOWEDIPS if all current allowed + * IPs of this peer should be removed prior to adding + * the list below and/or WGPEER_F_UPDATE_ONLY if the + * peer should only be set if it already exists. * WGPEER_A_PRESHARED_KEY: len WG_KEY_LEN, all zeros to remove * WGPEER_A_ENDPOINT: struct sockaddr_in or struct sockaddr_in6 * WGPEER_A_PERSISTENT_KEEPALIVE_INTERVAL: NLA_U16, 0 to disable @@ -119,7 +121,7 @@ * filling in information not contained in the prior. Note that if * WGDEVICE_F_REPLACE_PEERS is specified in the first message, it probably * should not be specified in fragments that come after, so that the list - * of peers is only cleared the first time but appened after. Likewise for + * of peers is only cleared the first time but appended after. Likewise for * peers, if WGPEER_F_REPLACE_ALLOWEDIPS is specified in the first message * of a peer, it likely should not be specified in subsequent fragments. * @@ -162,7 +164,9 @@ enum wgdevice_attribute { enum wgpeer_flag { WGPEER_F_REMOVE_ME = 1U << 0, WGPEER_F_REPLACE_ALLOWEDIPS = 1U << 1, - __WGPEER_F_ALL = WGPEER_F_REMOVE_ME | WGPEER_F_REPLACE_ALLOWEDIPS + WGPEER_F_UPDATE_ONLY = 1U << 2, + __WGPEER_F_ALL = WGPEER_F_REMOVE_ME | WGPEER_F_REPLACE_ALLOWEDIPS | + WGPEER_F_UPDATE_ONLY }; enum wgpeer_attribute { WGPEER_A_UNSPEC, diff --git a/src/version.h b/src/version.h index ae36872..ddba103 100644 --- a/src/version.h +++ b/src/version.h @@ -1 +1 @@ -#define WIREGUARD_VERSION "0.0.20190702" +#define WIREGUARD_VERSION "0.0.20191219" |