Dev.to Security ๐Ÿ” Cybersecurity ๐Ÿ‘ 0 ๐Ÿ“– 3 min read

Before authentication: three bugs in Mullvad's gotatun

WireGuard is known for staying silent towards packets it can't authenticate. But a userspace implementation has to receive a UDP datagram before it can decide whether to ignore it. That receive path is attack surface too

WireGuard is known for staying silent towards packets it can't authenticate. But a userspace implementation has to receive a UDP datagram before it can decide whether to ignore it. That receive path is attack surface too.

I found three bugs there in gotatun, Mullvad's userspace WireGuard implementation in Rust. All three trigger before WireGuard authentication, and all three are fixed.

1. An empty datagram kills the Linux receive task

GHSA-6gxq-8xp9-742r ยท High (CVSS 7.5) ยท fixed in 0.10.1

On Linux, gotatun receives datagrams in batches with recvmmsg. For each result it took the payload like this:

result.iovs().next().expect("we create exactly one IoSlice")

For an empty datagram, the iterator returns None, and expect panics. That panic ends the receive task that all peers on the device share. One zero-length UDP datagram to the listening port, and inbound traffic stops for every peer until the device is restarted or reconfigured. No peer key or secret is needed.

Takeaway: an empty UDP datagram is perfectly legal. "This can't happen" in a receive path is a denial of service waiting for someone to send zero bytes.

2. An oversized datagram stops reception on Windows

GHSA-fc57-wfhg-7c2w ยท High (CVSS 7.5) ยท fixed in 0.10.0

On Windows without the optional windows-gro feature, gotatun receives into a 4096-byte buffer. If a datagram is larger, Windows returns WSAEMSGSIZE (10040). The socket itself is fine after that, the next receive works.

But gotatun passed the error up. The buffered receive task exited, and once the queued packets drained, the receive loop ended. It ended with Ok(()), so nothing registered a fatal error and nothing restarted reception.

I tested this on native Windows 11 with two connected devices over loopback. After a single 8192-byte datagram, the unpatched device did not deliver the next packet within five seconds. With my patch, traffic kept flowing in both directions. The patch was part of my report. It discards datagrams that fail with WSAEMSGSIZE and keeps receiving:

match self.socket().recv_from(&mut buf).await {
    Ok((n, src)) => { /* deliver the datagram */ }
    // The socket is still usable; skip the oversized datagram.
    Err(err) if err.raw_os_error() == Some(WinSock::WSAEMSGSIZE) => {
        tokio::task::yield_now().await;
    }
    Err(err) => return Err(err),
}

Takeaway: an error that is harmless for the socket was fatal for the task. And a task that exits with Ok(()) after failing is worse than one that crashes, because nobody notices.

3. A rate limiter that never forgets

GHSA-m2rr-7rr7-vmp7 ยท Medium ยท fixed in 0.10.1

To protect against handshake floods, gotatun counts handshake packets per source IP. A packet only gets counted if its MAC1 is valid. But the MAC1 key is derived from the device's public key, so anyone who knows the public key can produce valid MAC1 values.

The counters are cleared from the peer timer loop. That loop skips peers without an endpoint. If no peer has an endpoint, the cleanup never runs, and every new source IP adds an entry. On top of that, HashMap::clear() keeps the allocated capacity, so even a reset doesn't give the memory back.

This one needs traffic from many different source IPs, so network filtering and anti-spoofing make it much harder. It is rated Medium.

Takeaway: cleanup that depends on an unrelated condition will eventually not run. And clear() empties a map, it doesn't shrink it.

What they have in common

All three happen before the packet reaches authentication. A quick checklist for any UDP service:

  • What happens with a 0-byte datagram?
  • What happens with a datagram larger than my buffer?
  • What happens if a million different source IPs send me one packet each?

If you use gotatun, update to 0.10.1 or later.

Thanks to the Mullvad team for the quick fixes and the advisories.

๐Ÿ“ฐ Read the original article on Dev.to Security

Originally published by Dev.to Security. Aggregated on AIWithGhost for educational purposes โ€” full credit and traffic to the original publisher.