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.
155 lines
3 KiB
Go
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
|
|
}
|