mirror of
https://github.com/amnezia-vpn/amneziawg-go.git
synced 2026-10-13 21:34:25 +00:00
Labels
No labels
pull-request
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
amnezia-vpn/amneziawg-go#89
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Originally created by @blackkroxx-io on GitHub (Aug 31, 2026).
Original GitHub issue: https://github.com/amnezia-vpn/amneziawg-go/issues/189
Summary
Two independent operator-configurable value combinations panic or destabilize the daemon, and both trace back to the same commit deleting the same class of guard. (1)
jmin > jmax— both values individually legal — is accepted with no error byset=and then panics the whole process on the 1.x/1.5/2.x line (crypto/rand: argument to Int is <= 0) or silently attempts a multi-gigabyte allocation on the 3.1 line (uint32underflow). (2) A negativeNargument to the<r N>/<rc N>/<rd N>/<dz N>CPS tags (used ini1-i5) is accepted with no error byset=and then panics on the next handshake send (makeslice: len out of range). Both are reachable becausestrconv.Atoi/ParseUintparsing of these fields never validates cross-field ordering or per-tag sign, and the values are only ever used, unchecked, insidemake([]byte, ...)calls inSendHandshakeInitiation. Root cause for both: commit0361c54("fix: refactor processing of junk packets", #103, first released inv0.2.16) deleted themaxSize < minSizerejection, an equal-value auto-increment guard, and aMaxSegmentSizeclamp fromhandlePostConfigin the same hunk that added today's per-field positivity checks — and, in the same commit, deleted the one existing bound check (size > 1000) on the tag-argument parser that would have caught the negative-Ncase as a side effect.Verified against upstream
masteratb5928ef(2026-08-28), which is identical to the newest tagv3.1.20260828—git fetchshows no commits have landed since this campaign's original reproduction, so this report is not stale. The 1.x-line reproduction is verified againstv0.2.19(1cc9427), the newest tag on that line;device/obf.goand itsobf_*.gosiblings (the code path for defect 2) are byte-for-byte identical betweenv0.2.19andv3.1.20260828, confirmed viadiff.Defect A:
jmin > jmaxpanics the 1.x/1.5/2.x daemon, over-allocates on 3.1Trigger.
jmin=200,jmax=50— both individually positive, both individually within whatever per-field bound each daemon version enforces. No field's own validation catchesjmin > jmax.Minimal reproduction (UAPI
set=transaction):This transaction returns no error (
errno=0) at either pin. The daemon only misbehaves on the next handshake attempt to a peer, e.g. after also setting apublic_key/endpointpeer block.Mechanism, 1.x/1.5/2.x line (verified at
v0.2.19, tag1cc9427).Peer.SendHandshakeInitiationindevice/send.gocomputes:With
jmin=200, jmax=50,jmax-jmin+1 = -149.crypto/rand.Intis documented to panic when itsmaxargument is<= 0. Reproduced directly againstv0.2.19'sdevicepackage:rand.Int(rand.Reader, big.NewInt(-149))panics with exactlycrypto/rand: argument to Int is <= 0. The panic is unrecovered — it crashes the entire daemon process on the first handshake attempt to any peer that reaches this code path, not just the one connection.Mechanism, 3.1 line (verified at
master/v3.1.20260828,b5928ef). The junk-count field type changed frominttoatomic.Uint32in the AWG 3.0 refactor (457d920), and the computation moved toDevice.JunkPackets()indevice/noise-protocol.go:max-minisuint32subtraction; whenmax < minthis wraps instead of panicking (Go's defined unsigned-integer behavior). Formin=200, max=50:max-mincomputes to4294967146(2^32 - 150), i.e.make([]byte, 200+fastrandn(4294967146))— a draw uniformly distributed up to ~4.29 GB per junk packet, repeatedjctimes per handshake attempt (jcis itself unboundeduint32at this pin — no upper-bound check exists anywhere indevice/uapi.go'sjccase). This was reproduced live in an earlier pass against this exact same code: one run withjmin=100, jmax=50allocated a single junk packet of 30,243,914 bytes; a second run withjmin=200, jmax=50allocated 1,852,414,151 bytes (1.85 GB) in a single draw. Neither individual run panicked — Go'smakesucceeds for large-but-satisfiable lengths — but the mechanism has no ceiling, and a larger draw (or a largerjc) reachesfatal error: runtime: out of memory, a Go runtime fatal error thatrecover()cannot catch, which is process-fatal exactly like the 1.x panic, just non-deterministically instead of always.Why it's reachable. Neither
jminnorjmaxis validated against the other anywhere inhandleDeviceLine/uapi.goat either pin — each is parsed and stored independently, and no post-config pass checks the pair. Ajmin/jmaxpair authored against 3.1 semantics (where the field is silently accepted, since 3.1's positivity check was also removed — see below) and then applied to a 1.x-line daemon, or simply an operator/config-generator bug that swaps the two values, reaches this without any adversarial input.Defect B: negative
Nin<r N>/<rc N>/<rd N>/<dz N>panics on first handshake sendTrigger. Any of these four CPS tags with a negative argument in an
i1-i5value, e.g.i1=<r -5>.Minimal reproduction (UAPI
set=transaction):Returns no error. Verified directly against
master/b5928ef:newObfChain("<r -5>")(indevice/obf.go) succeeds and returns a chain whoseObfuscatedLen(0)is-5.Mechanism (identical at both pins —
device/obf.goanddevice/obf_rand.go/obf_randchars.go/obf_randdigits.go/obf_datasize.goare byte-for-byte identical betweenv0.2.19andv3.1.20260828). All four tag builders parse their argument with barestrconv.Atoi:strconv.Atoisucceeds on negative strings; nothing checkslength >= 0. The negative length only surfaces when a handshake packet is actually built. Indevice/send.go'sSendHandshakeInitiation, a few lines above the Defect A call site:ipacket.ObfuscatedLen(0)sums the per-taglengthfields, so a negative tag argument yields a negative total, andmake([]byte, negative)panics withruntime error: makeslice: len out of range. Reproduced directly: constructingchain, _ := newObfChain("<r -5>")and executingmake([]byte, chain.ObfuscatedLen(0))panics with exactly that message. Like Defect A, this is unrecovered and takes down the whole daemon process, not just the offending peer's handshake attempt.Root cause: commit
0361c54deleted three guards while adding today's positivity checksCommit
0361c54dca92071463e9611796c04464386457ac("fix: refactor processing of junk packets", PR #103, first shipped inv0.2.16) rewrote junk/obfuscation handling wholesale. In the same diff hunk todevice/device.go'shandlePostConfig, it:maxSize < minSizerejection:jmin == jmaxusable instead of feeding a zero-width range to the RNG:MaxSegmentSizeclamp onJunkPacketMaxSize:device/uapi.go, thejc/jmin/jmax<= 0positivity checks that exist today ("jc must be a positive value"and siblings) — a real improvement, but not a substitute for the deleted ordering check, since two individually-positive values can still be misordered.The same commit also deleted
device/awg/tag_generator.go'snewRandomGeneratorBase, which had this bound check on exactly the tag argument that Defect B now lacks:This check rejected
size > 1000but never rejectedsize < 0either, so it would not by itself have prevented Defect B — but its removal, with nothing added in its place in the newobf_*.gobuilders, is also whydocs.amnezia.org's documentedN <= 1000limit forr/rc/rdno longer holds anywhere in the current parser: there is now no upper bound either, positive or negative.Suggested fix
Restore ordering/bound validation at UAPI parse time (
set=transaction), not at send time, for both fields:jc/jmin/jmax: after parsing all three (or in a post-config pass, mirroring the deletedhandlePostConfiglogic), rejectjmax < jminwith a namedIpcErrorInvalid, the way the pre-0361c54code did. This is strictly better for operators than either of the two current failure modes (a full process crash, or a silent multi-GB allocation attempt) — an operator who mistypes the two values gets an error back fromwg/awgat configuration time instead of an outage the next time a handshake fires.<r N>/<rc N>/<rd N>/<dz N>: addif length < 0 { return nil, fmt.Errorf(...) }to each ofnewRandObf,newRandCharObf,newRandDigitsObf, andnewDataSizeObfindevice/obf_*.go. Optionally restore an upper bound (matching the removedsize > 1000check, or the six-timer-field precedent elsewhere in the 3.1 UAPI) if a segment-size-derived cap is still meaningful for the current send path.In both cases, rejecting bad input at
set=time rather than clamping or silently tolerating it at send time is the better failure mode: the operator gets a synchronous, attributable error instead of either an outage on the next handshake or (for Defect A's 3.1 mechanism) no error at all until memory pressure eventually shows up somewhere else.Impact framing
Both
jmin/jmaxandi1-i5are operator-configured values — set by whoever authors the peer/interface config, not attacker-supplied over the wire. This is a robustness / denial-of-service-by-misconfiguration issue, not a remote vulnerability: nothing here is reachable by an unauthenticated peer or by traffic on the wire. The realistic way to hit Defect A unintentionally is a config authored against 3.1 semantics (wherejmin/jmaxaccept0and any ordering silently) and then applied, unmodified, to a 1.x/1.5/2.x-line daemon, or a config generator that computesjmin/jmaxfrom user input without asserting their order — both plausible in a mixed-version deployment or a third-party config UI, neither requiring intent to break anything.Verified against
amneziawg-gomaster=b5928ef= tagv3.1.20260828(no upstream movement since this campaign's earlier pass; both defects and the root-cause commit re-confirmed directly against this exact revision).amneziawg-gov0.2.19(1cc9427), the newest tag on the 1.x/1.5/2.x line, for Defect A'sbig.Intpanic path;device/obf.goand siblings confirmed byte-identical tov3.1.20260828viadiff, so Defect B applies unchanged to this pin too.0361c54dca92071463e9611796c04464386457ac(PR #103), first released inv0.2.16.Happy to provide the small Go reproduction programs used for the panics above (each is under 40 lines, no external dependencies beyond the standard library and this repo's
devicepackage) if useful for a regression test.