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.
This commit is contained in:
parent
831d483366
commit
ee7ff1b77f
9 changed files with 502 additions and 12 deletions
108
device/obf_guards_test.go
Normal file
108
device/obf_guards_test.go
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
/* 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})
|
||||
})
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue