wireguard-go-awg2-lx/device/obf_guards_test.go
Leadaxe ee7ff1b77f lx: fix transport padding buffer overrun + harden AWG config guards
Transport padding (s4) crashed the whole process with
"index out of range" in RoutineSequentialSender on the first data
packet: InputPacket/InputPackets sized elem.buffer without headroom
for the in-buffer right-shift that prepends the random prefix.

- send.go: reserve paddings.transport in both injection-path
  allocLength computations; replace the manual backward byte loop
  with an overlap-safe copy; defensively grow the buffer (pool-backed)
  if it still lacks headroom, dropping packets that cannot fit a
  single WG message instead of overrunning.
- receive.go: drop the rxBytes/timers block duplicated by the AWG
  re-graft (rx accounting was doubled, keepKeyFreshReceiving fired
  twice per batch).
- send.go: swap jmin/jmax when configured inverted (UAPI validates
  the fields only individually; a swapped pair panicked rand.Int
  with a non-positive bound on the first handshake).
- obf*.go: bound obfuscator length args to [0, MaxMessageSize]
  (negative panicked slice bounds, huge ones OOMed the handshake).
- magic-header.go: widen to int64 before end-start+1 so a full-range
  header cannot wrap to a zero rand.Int bound.

Tests: transport_padding_test.go reproduces the on-device crash
byte-for-byte (red on the previous commit, green now) across both
injection paths and the tun path; obf_guards_test.go pins the
config-value guards.
2026-08-05 16:55:02 +03:00

108 lines
3.2 KiB
Go

/* SPDX-License-Identifier: MIT
*
* Guards around AWG obfuscation config values: these tests pin the
* crash-on-config-value fixes (swapped jmin/jmax, out-of-range obfuscator
* lengths, full-range magic headers).
*/
package device
import (
"context"
"encoding/hex"
"fmt"
"testing"
)
func TestParseObfLen(t *testing.T) {
cases := []struct {
val string
want int
wantErr bool
}{
{"0", 0, false},
{"100", 100, false},
{fmt.Sprintf("%d", MaxMessageSize), MaxMessageSize, false},
{"-1", 0, true}, // would panic slice bounds in Obfuscate
{fmt.Sprintf("%d", MaxMessageSize + 1), 0, true}, // would OOM the handshake make
{"2000000000", 0, true},
{"abc", 0, true},
}
for _, c := range cases {
got, err := parseObfLen(c.val)
if c.wantErr != (err != nil) {
t.Errorf("parseObfLen(%q): err = %v, wantErr = %v", c.val, err, c.wantErr)
}
if err == nil && got != c.want {
t.Errorf("parseObfLen(%q) = %d, want %d", c.val, got, c.want)
}
}
}
func TestMagicHeaderGenerateFullRange(t *testing.T) {
// end-start+1 computed in uint32 wraps to 0 for the full range and
// panics rand.Int; the fix widens to int64 before the arithmetic.
h := &magicHeader{start: 0, end: ^uint32(0)}
for i := 0; i < 8; i++ {
v := h.Generate()
if !h.Validate(v) {
t.Fatalf("generated value %d outside range", v)
}
}
}
// TestJunkSwappedBounds brings up a device pair whose junk config has
// jmin > jmax (passes per-field UAPI validation); without the swap guard
// the first handshake panics rand.Int with a non-positive bound.
func TestJunkSwappedBounds(t *testing.T) {
skA, err := newPrivateKey()
if err != nil {
t.Fatalf("newPrivateKey A: %v", err)
}
skB, err := newPrivateKey()
if err != nil {
t.Fatalf("newPrivateKey B: %v", err)
}
pkA := skA.publicKey()
pkB := skB.publicKey()
bindA, bindB := newChanBindPair()
tunA := newChanTun()
tunB := newChanTun()
devA := NewDevice(context.Background(), tunA, bindA, NewLogger(LogLevelError, "devA: "), 1)
devB := NewDevice(context.Background(), tunB, bindB, NewLogger(LogLevelError, "devB: "), 1)
t.Cleanup(devA.Close)
t.Cleanup(devB.Close)
// jmin deliberately greater than jmax: each field alone is valid.
junk := "jc=2\njmin=100\njmax=50\n"
cfgA := fmt.Sprintf(
"private_key=%s\n%sreplace_peers=true\npublic_key=%s\nendpoint=127.0.0.1:2\nallowed_ip=%s/32\n",
hex.EncodeToString(skA[:]), junk, hex.EncodeToString(pkB[:]), testIPB)
cfgB := fmt.Sprintf(
"private_key=%s\n%sreplace_peers=true\npublic_key=%s\nendpoint=127.0.0.1:1\nallowed_ip=%s/32\n",
hex.EncodeToString(skB[:]), junk, hex.EncodeToString(pkA[:]), testIPA)
if err := devA.IpcSet(cfgA); err != nil {
t.Fatalf("IpcSet A: %v", err)
}
if err := devB.IpcSet(cfgB); err != nil {
t.Fatalf("IpcSet B: %v", err)
}
if err := devA.Up(); err != nil {
t.Fatalf("Up A: %v", err)
}
if err := devB.Up(); err != nil {
t.Fatalf("Up B: %v", err)
}
// Drive a packet end-to-end: the handshake (junk packets included)
// must complete without panicking the process.
pkt := buildIPv4Packet(testIPA, testIPB, 28)
devA.InputPacket(testIPB.AsSlice(), [][]byte{pkt})
awaitPacket(t, tunB, pkt, func() {
devA.InputPacket(testIPB.AsSlice(), [][]byte{pkt})
})
}