wireguard-go-awg2-lx/device/obf.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

155 lines
3 KiB
Go

package device
import (
"errors"
"fmt"
"strconv"
"strings"
)
type obfBuilder func(val string) (obf, error)
// parseObfLen parses and bounds an obfuscator length argument: a negative
// value would panic slice bounds in obfChain.Obfuscate, a huge one would
// OOM in the handshake-time make (SendHandshakeInitiation).
func parseObfLen(val string) (int, error) {
length, err := strconv.Atoi(val)
if err != nil {
return 0, err
}
if length < 0 || length > MaxMessageSize {
return 0, fmt.Errorf("obfuscator length %d out of range [0, %d]", length, MaxMessageSize)
}
return length, nil
}
var obfBuilders = map[string]obfBuilder{
"b": newBytesObf,
"t": newTimestampObf,
"r": newRandObf,
"rc": newRandCharObf,
"rd": newRandDigitsObf,
"d": newDataObf,
"ds": newDataStringObf,
"dz": newDataSizeObf,
}
type obf interface {
Obfuscate(dst, src []byte)
Deobfuscate(dst, src []byte) bool
ObfuscatedLen(srcLen int) int
DeobfuscatedLen(srcLen int) int
}
type obfChain struct {
Spec string
obfs []obf
}
func newObfChain(spec string) (*obfChain, error) {
var (
obfs []obf
errs []error
)
remaining := spec[:]
for {
start := strings.IndexByte(remaining, '<')
if start == -1 {
break
}
end := strings.IndexByte(remaining[start:], '>')
if end == -1 {
return nil, errors.New("missing enclosing >")
}
end += start
tag := remaining[start+1 : end]
parts := strings.Fields(tag)
if len(parts) == 0 {
errs = append(errs, errors.New("empty tag"))
remaining = remaining[end+1:]
continue
}
key := parts[0]
builder, ok := obfBuilders[key]
if !ok {
errs = append(errs, fmt.Errorf("unknown tag <%s>", key))
remaining = remaining[end+1:]
continue
}
val := ""
if len(parts) > 1 {
val = parts[1]
}
o, err := builder(val)
if err != nil {
errs = append(errs, fmt.Errorf("failed to build <%s>: %w", key, err))
remaining = remaining[end+1:]
continue
}
obfs = append(obfs, o)
remaining = remaining[end+1:]
}
if len(errs) > 0 {
return nil, errors.Join(errs...)
}
return &obfChain{
Spec: spec,
obfs: obfs,
}, nil
}
func (c *obfChain) Obfuscate(dst, src []byte) {
written := 0
for _, o := range c.obfs {
obfLen := o.ObfuscatedLen(len(src))
o.Obfuscate(dst[written:written+obfLen], src)
written += obfLen
}
}
func (c *obfChain) Deobfuscate(dst, src []byte) bool {
dynamicLen := len(src) - c.ObfuscatedLen(0)
written, read := 0, 0
for _, o := range c.obfs {
deobfLen := o.DeobfuscatedLen(dynamicLen)
obfLen := o.ObfuscatedLen(deobfLen)
if !o.Deobfuscate(dst[written:written+deobfLen], src[read:read+obfLen]) {
return false
}
written += deobfLen
read += obfLen
}
return true
}
func (c *obfChain) ObfuscatedLen(n int) int {
total := 0
for _, o := range c.obfs {
total += o.ObfuscatedLen(n)
}
return total
}
func (c *obfChain) DeobfuscatedLen(n int) int {
dynamicLen := n - c.ObfuscatedLen(0)
total := 0
for _, o := range c.obfs {
total += o.DeobfuscatedLen(dynamicLen)
}
return total
}