Skip to content

fix: check IS_ERR(wg) before using the device in wg_set_device - #206

Open
ExzoTikStyle wants to merge 2 commits into
amnezia-vpn:masterfrom
ExzoTikStyle:fix/header-protection-null-deref
Open

fix: check IS_ERR(wg) before using the device in wg_set_device#206
ExzoTikStyle wants to merge 2 commits into
amnezia-vpn:masterfrom
ExzoTikStyle:fix/header-protection-null-deref

Conversation

@ExzoTikStyle

Copy link
Copy Markdown

wg_set_device() initialises has_protection from awg_has_header_protection(wg)
in the declaration block, above the IS_ERR(wg) check. When the request names an
interface that doesn't exist, lookup_interface() returns an ERR_PTR and the
helper dereferences it right away.

The oops lands inside down_read(), and genl_rcv_msg() holds the genl family
lock across the doit callback, so the lock is never released. Everything on the
amneziawg family blocks in uninterruptible sleep from then on — including
awg setconf for tunnels that were up and working — and genl_unregister_family()
hangs too, so the module can neither be used nor unloaded. Only a reboot clears it.
rmmod -f isn't a way out either, since stock Ubuntu kernels build without
CONFIG_MODULE_FORCE_UNLOAD.

BUG: kernel NULL pointer dereference, address: 000000000000050d
Oops: 0002 [#1] PREEMPT SMP NOPTI
RIP: 0010:down_read+0x1e/0xc0
Call Trace:
 <TASK>
 awg_has_header_protection+0x19/0x50 [amneziawg]
 wg_set_device+0x43/0xe50 [amneziawg]
 ? genl_family_rcv_msg_attrs_parse.constprop.0+0x93/0x100
 genl_family_rcv_msg_doit+0xfa/0x160
 genl_family_rcv_msg+0x189/0x260
 ? __pfx_wg_set_device+0x10/0x10 [amneziawg]
 genl_rcv_msg+0x4c/0xb0
 genl_rcv+0x28/0x50
 </TASK>
note: awg[80691] exited with irqs disabled
note: awg[80691] exited with preempt_count 1

CR2 being 0x50d rather than a near-zero address is the ERR_PTR giveaway:
(void *)-19 plus the offset of header_protection.lock in struct wg_device.

The call is the left operand of the ||, so it runs whether or not
WGDEVICE_A_HEADER_PROTECTION_KEY was passed — a plain
awg set doesnotexist listen-port 51820 reaches it too. Introduced in #192.

The first commit moves the check above the use. It also adds an
IS_ERR_OR_NULL() guard inside awg_has_header_protection(), since it takes a
device pointer from several call sites.

The second commit is unrelated to the crash but in the same file:
awg_header_protection_set_key() writes p->key[] and p->has_protection
while holding only down_read(). Concurrent setters can interleave, and
awg_header_protection_init() can read a half-updated key while building the
chacha state — silently wrong header protection rather than a reported error.
Happy to split it out if you'd rather keep this PR to the crash.

Verification

Built and run against 6.8.0-136-generic (Ubuntu 22.04.5), on the same host
where the oops was originally hit.

Before, on 3.0.20260731-04 from ppa:amnezia/ppa:

# awg set doesnotexist header-protection-key /dev/null
Killed
# echo $?
137
# awg-quick up awg0        # hangs, unkillable
# ps -eo pid,stat,wchan:24,cmd | awk '$2 ~ /D/'
  81680 D    genl_rcv_msg             awg setconf awg0 /dev/fd/63
  82328 D    genl_unregister_fami     rmmod amneziawg

After, with this branch:

# awg set doesnotexist header-protection-key /dev/null
Unable to modify interface: No such device
# echo $?
1
# awg set doesnotexist listen-port 51820
Unable to modify interface: No such device
# echo $?
1
# ps -eo pid,stat,cmd | awk '$2 ~ /D/' | wc -l
0

dmesg is clean, and an existing AWG 3 tunnel (with HeaderProtectionKey set)
comes back up on the patched module and passes traffic as before.

has_protection was initialised from awg_has_header_protection(wg) in the
declaration block, before the IS_ERR(wg) check below it. For a request
naming an interface that does not exist, lookup_interface() returns an
ERR_PTR and the helper dereferences it immediately.

The oops lands inside down_read(), while genl_rcv_msg() holds the genl
family lock across the doit callback, so the lock is never released.
Every subsequent netlink operation on the family then blocks in
uninterruptible sleep — including awg setconf for interfaces that were
working — and genl_unregister_family() hangs too, so the module can
neither be used nor unloaded. Only a reboot recovers the host.

  BUG: kernel NULL pointer dereference, address: 000000000000050d
  RIP: 0010:down_read+0x1e/0xc0
  Call Trace:
   awg_has_header_protection+0x19/0x50 [amneziawg]
   wg_set_device+0x43/0xe50 [amneziawg]
   genl_family_rcv_msg_doit+0xfa/0x160
   genl_rcv_msg+0x4c/0xb0

Reproduced with:

  awg set doesnotexist header-protection-key /dev/null

Note the call is the left operand of the ||, so it runs whether or not
WGDEVICE_A_HEADER_PROTECTION_KEY was passed.

Also guard awg_has_header_protection() itself, since it takes a device
pointer from several call sites.
awg_header_protection_set_key() mutates p->key[] and p->has_protection
while holding only a read lock, so concurrent setters can interleave and
awg_header_protection_init() can read a half-updated key while building
the chacha state.

The failure mode is silent: header protection is applied with a wrong key
for some packets rather than reporting an error.

Unrelated to the crash fixed in the previous commit; drop this commit if
you would rather keep that one on its own.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant