[GH-ISSUE #189] jmin>jmax and negative <r N>/<rc N>/<rd N>/<dz N> panic the daemon; both regressed by the same commit (0361c54) #89

Open
opened 2026-10-13 21:03:28 +00:00 by bsakaguchi · 0 comments
Owner

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 by set= 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 (uint32 underflow). (2) A negative N argument to the <r N>/<rc N>/<rd N>/<dz N> CPS tags (used in i1-i5) is accepted with no error by set= and then panics on the next handshake send (makeslice: len out of range). Both are reachable because strconv.Atoi/ParseUint parsing of these fields never validates cross-field ordering or per-tag sign, and the values are only ever used, unchecked, inside make([]byte, ...) calls in SendHandshakeInitiation. Root cause for both: commit 0361c54 ("fix: refactor processing of junk packets", #103, first released in v0.2.16) deleted the maxSize < minSize rejection, an equal-value auto-increment guard, and a MaxSegmentSize clamp from handlePostConfig in 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-N case as a side effect.

Verified against upstream master at b5928ef (2026-08-28), which is identical to the newest tag v3.1.20260828 — git fetch shows no commits have landed since this campaign's original reproduction, so this report is not stale. The 1.x-line reproduction is verified against v0.2.19 (1cc9427), the newest tag on that line; device/obf.go and its obf_*.go siblings (the code path for defect 2) are byte-for-byte identical between v0.2.19 and v3.1.20260828, confirmed via diff.

Defect A: jmin > jmax panics the 1.x/1.5/2.x daemon, over-allocates on 3.1

Trigger. jmin=200, jmax=50 — both individually positive, both individually within whatever per-field bound each daemon version enforces. No field's own validation catches jmin > jmax.

Minimal reproduction (UAPI set= transaction):

set=1
private_key=<any valid key>
jc=1
jmin=200
jmax=50

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 a public_key/endpoint peer block.

Mechanism, 1.x/1.5/2.x line (verified at v0.2.19, tag 1cc9427). Peer.SendHandshakeInitiation in device/send.go computes:

jmin := peer.device.junk.min
jmax := peer.device.junk.max
nBig, _ := rand.Int(rand.Reader, big.NewInt(int64(jmax-jmin+1)))

With jmin=200, jmax=50, jmax-jmin+1 = -149. crypto/rand.Int is documented to panic when its max argument is <= 0. Reproduced directly against v0.2.19's device package: rand.Int(rand.Reader, big.NewInt(-149)) panics with exactly crypto/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 from int to atomic.Uint32 in the AWG 3.0 refactor (457d920), and the computation moved to Device.JunkPackets() in device/noise-protocol.go:

func (device *Device) JunkPackets() [][]byte {
	var bufs [][]byte
	min := device.junk.min.Load()
	max := device.junk.max.Load()
	for range device.junk.count.Load() {
		buf := make([]byte, min+fastrandn(max-min))
		...

max-min is uint32 subtraction; when max < min this wraps instead of panicking (Go's defined unsigned-integer behavior). For min=200, max=50: max-min computes to 4294967146 (2^32 - 150), i.e. make([]byte, 200+fastrandn(4294967146)) — a draw uniformly distributed up to ~4.29 GB per junk packet, repeated jc times per handshake attempt (jc is itself unbounded uint32 at this pin — no upper-bound check exists anywhere in device/uapi.go's jc case). This was reproduced live in an earlier pass against this exact same code: one run with jmin=100, jmax=50 allocated a single junk packet of 30,243,914 bytes; a second run with jmin=200, jmax=50 allocated 1,852,414,151 bytes (1.85 GB) in a single draw. Neither individual run panicked — Go's make succeeds for large-but-satisfiable lengths — but the mechanism has no ceiling, and a larger draw (or a larger jc) reaches fatal error: runtime: out of memory, a Go runtime fatal error that recover() cannot catch, which is process-fatal exactly like the 1.x panic, just non-deterministically instead of always.

Why it's reachable. Neither jmin nor jmax is validated against the other anywhere in handleDeviceLine/uapi.go at either pin — each is parsed and stored independently, and no post-config pass checks the pair. A jmin/jmax pair 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 N in <r N>/<rc N>/<rd N>/<dz N> panics on first handshake send

Trigger. Any of these four CPS tags with a negative argument in an i1-i5 value, e.g. i1=<r -5>.

Minimal reproduction (UAPI set= transaction):

set=1
private_key=<any valid key>
i1=<r -5>

Returns no error. Verified directly against master/b5928ef: newObfChain("<r -5>") (in device/obf.go) succeeds and returns a chain whose ObfuscatedLen(0) is -5.

Mechanism (identical at both pins — device/obf.go and device/obf_rand.go/obf_randchars.go/obf_randdigits.go/obf_datasize.go are byte-for-byte identical between v0.2.19 and v3.1.20260828). All four tag builders parse their argument with bare strconv.Atoi:

// device/obf_rand.go (newRandObf; newRandCharObf and newRandDigitsObf are identical in shape,
// newDataSizeObf in obf_datasize.go likewise)
func newRandObf(val string) (obf, error) {
	length, err := strconv.Atoi(val)
	if err != nil {
		return nil, err
	}
	return &randObf{length: length}, nil
}

strconv.Atoi succeeds on negative strings; nothing checks length >= 0. The negative length only surfaces when a handshake packet is actually built. In device/send.go's SendHandshakeInitiation, a few lines above the Defect A call site:

for _, ipacket := range peer.device.ipackets {
	if ipacket != nil {
		buf := make([]byte, ipacket.ObfuscatedLen(0))

ipacket.ObfuscatedLen(0) sums the per-tag length fields, so a negative tag argument yields a negative total, and make([]byte, negative) panics with runtime error: makeslice: len out of range. Reproduced directly: constructing chain, _ := newObfChain("<r -5>") and executing make([]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 0361c54 deleted three guards while adding today's positivity checks

Commit 0361c54dca92071463e9611796c04464386457ac ("fix: refactor processing of junk packets", PR #103, first shipped in v0.2.16) rewrote junk/obfuscation handling wholesale. In the same diff hunk to device/device.go's handlePostConfig, it:

  • Deleted the maxSize < minSize rejection:
    } else if tempAwg.Cfg.JunkPacketMaxSize < tempAwg.Cfg.JunkPacketMinSize {
        errs = append(errs, ipcErrorf(
            ipc.IpcErrorInvalid,
            "maxSize: %d; should be greater than minSize: %d",
            tempAwg.Cfg.JunkPacketMaxSize,
            tempAwg.Cfg.JunkPacketMinSize,
        ))
    }
    
  • Deleted the equal-value auto-increment guard that made jmin == jmax usable instead of feeding a zero-width range to the RNG:
    if device.awg.Cfg.JunkPacketCount > 0 &&
        tempAwg.Cfg.JunkPacketMaxSize == tempAwg.Cfg.JunkPacketMinSize {
        tempAwg.Cfg.JunkPacketMaxSize++ // to make rand gen work
    }
    
  • Deleted the MaxSegmentSize clamp on JunkPacketMaxSize:
    if tempAwg.Cfg.JunkPacketMaxSize >= MaxSegmentSize {
        device.awg.Cfg.JunkPacketMinSize = 0
        device.awg.Cfg.JunkPacketMaxSize = 1
        errs = append(errs, ipcErrorf(
            ipc.IpcErrorInvalid,
            "JunkPacketMaxSize: %d; should be smaller than maxSegmentSize: %d",
            tempAwg.Cfg.JunkPacketMaxSize,
            MaxSegmentSize,
        ))
    }
    
  • Added, in device/uapi.go, the jc/jmin/jmax <= 0 positivity 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's newRandomGeneratorBase, which had this bound check on exactly the tag argument that Defect B now lacks:

func newRandomGeneratorBase(param string) (*randomGeneratorBase, error) {
	size, err := strconv.Atoi(param)
	if err != nil {
		return nil, fmt.Errorf("parse int: %w", err)
	}
	if size > 1000 {
		return nil, fmt.Errorf("size must be less than 1000")
	}
	...

This check rejected size > 1000 but never rejected size < 0 either, so it would not by itself have prevented Defect B — but its removal, with nothing added in its place in the new obf_*.go builders, is also why docs.amnezia.org's documented N <= 1000 limit for r/rc/rd no 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 deleted handlePostConfig logic), reject jmax < jmin with a named IpcErrorInvalid, the way the pre-0361c54 code 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 from wg/awg at configuration time instead of an outage the next time a handshake fires.
  • <r N>/<rc N>/<rd N>/<dz N>: add if length < 0 { return nil, fmt.Errorf(...) } to each of newRandObf, newRandCharObf, newRandDigitsObf, and newDataSizeObf in device/obf_*.go. Optionally restore an upper bound (matching the removed size > 1000 check, 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/jmax and i1-i5 are 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 (where jmin/jmax accept 0 and any ordering silently) and then applied, unmodified, to a 1.x/1.5/2.x-line daemon, or a config generator that computes jmin/jmax from 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-go master = b5928ef = tag v3.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-go v0.2.19 (1cc9427), the newest tag on the 1.x/1.5/2.x line, for Defect A's big.Int panic path; device/obf.go and siblings confirmed byte-identical to v3.1.20260828 via diff, so Defect B applies unchanged to this pin too.
  • Root-cause commit 0361c54dca92071463e9611796c04464386457ac (PR #103), first released in v0.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 device package) if useful for a regression test.

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 by `set=` 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 (`uint32` underflow). (2) A negative `N` argument to the `<r N>`/`<rc N>`/`<rd N>`/`<dz N>` CPS tags (used in `i1`-`i5`) is accepted with no error by `set=` and then panics on the next handshake send (`makeslice: len out of range`). Both are reachable because `strconv.Atoi`/`ParseUint` parsing of these fields never validates cross-field ordering or per-tag sign, and the values are only ever used, unchecked, inside `make([]byte, ...)` calls in `SendHandshakeInitiation`. Root cause for both: commit `0361c54` ("fix: refactor processing of junk packets", #103, first released in `v0.2.16`) deleted the `maxSize < minSize` rejection, an equal-value auto-increment guard, and a `MaxSegmentSize` clamp from `handlePostConfig` in 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-`N` case as a side effect. Verified against upstream `master` at `b5928ef` (2026-08-28), which is identical to the newest tag `v3.1.20260828` — `git fetch` shows no commits have landed since this campaign's original reproduction, so this report is not stale. The 1.x-line reproduction is verified against `v0.2.19` (`1cc9427`), the newest tag on that line; `device/obf.go` and its `obf_*.go` siblings (the code path for defect 2) are byte-for-byte identical between `v0.2.19` and `v3.1.20260828`, confirmed via `diff`. ## Defect A: `jmin > jmax` panics the 1.x/1.5/2.x daemon, over-allocates on 3.1 **Trigger.** `jmin=200`, `jmax=50` — both individually positive, both individually within whatever per-field bound each daemon version enforces. No field's own validation catches `jmin > jmax`. **Minimal reproduction (UAPI `set=` transaction):** ``` set=1 private_key=<any valid key> jc=1 jmin=200 jmax=50 ``` 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 a `public_key`/`endpoint` peer block. **Mechanism, 1.x/1.5/2.x line (verified at `v0.2.19`, tag `1cc9427`).** `Peer.SendHandshakeInitiation` in `device/send.go` computes: ```go jmin := peer.device.junk.min jmax := peer.device.junk.max nBig, _ := rand.Int(rand.Reader, big.NewInt(int64(jmax-jmin+1))) ``` With `jmin=200, jmax=50`, `jmax-jmin+1 = -149`. `crypto/rand.Int` is documented to panic when its `max` argument is `<= 0`. Reproduced directly against `v0.2.19`'s `device` package: `rand.Int(rand.Reader, big.NewInt(-149))` panics with exactly `crypto/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 from `int` to `atomic.Uint32` in the AWG 3.0 refactor (`457d920`), and the computation moved to `Device.JunkPackets()` in `device/noise-protocol.go`: ```go func (device *Device) JunkPackets() [][]byte { var bufs [][]byte min := device.junk.min.Load() max := device.junk.max.Load() for range device.junk.count.Load() { buf := make([]byte, min+fastrandn(max-min)) ... ``` `max-min` is `uint32` subtraction; when `max < min` this wraps instead of panicking (Go's defined unsigned-integer behavior). For `min=200, max=50`: `max-min` computes to `4294967146` (`2^32 - 150`), i.e. `make([]byte, 200+fastrandn(4294967146))` — a draw uniformly distributed up to ~4.29 GB per junk packet, repeated `jc` times per handshake attempt (`jc` is itself unbounded `uint32` at this pin — no upper-bound check exists anywhere in `device/uapi.go`'s `jc` case). This was reproduced live in an earlier pass against this exact same code: one run with `jmin=100, jmax=50` allocated a single junk packet of 30,243,914 bytes; a second run with `jmin=200, jmax=50` allocated 1,852,414,151 bytes (1.85 GB) in a single draw. Neither individual run panicked — Go's `make` succeeds for large-but-satisfiable lengths — but the mechanism has no ceiling, and a larger draw (or a larger `jc`) reaches `fatal error: runtime: out of memory`, a Go runtime fatal error that `recover()` cannot catch, which is process-fatal exactly like the 1.x panic, just non-deterministically instead of always. **Why it's reachable.** Neither `jmin` nor `jmax` is validated against the other anywhere in `handleDeviceLine`/`uapi.go` at either pin — each is parsed and stored independently, and no post-config pass checks the pair. A `jmin`/`jmax` pair 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 `N` in `<r N>`/`<rc N>`/`<rd N>`/`<dz N>` panics on first handshake send **Trigger.** Any of these four CPS tags with a negative argument in an `i1`-`i5` value, e.g. `i1=<r -5>`. **Minimal reproduction (UAPI `set=` transaction):** ``` set=1 private_key=<any valid key> i1=<r -5> ``` Returns no error. Verified directly against `master`/`b5928ef`: `newObfChain("<r -5>")` (in `device/obf.go`) succeeds and returns a chain whose `ObfuscatedLen(0)` is `-5`. **Mechanism (identical at both pins — `device/obf.go` and `device/obf_rand.go`/`obf_randchars.go`/`obf_randdigits.go`/`obf_datasize.go` are byte-for-byte identical between `v0.2.19` and `v3.1.20260828`).** All four tag builders parse their argument with bare `strconv.Atoi`: ```go // device/obf_rand.go (newRandObf; newRandCharObf and newRandDigitsObf are identical in shape, // newDataSizeObf in obf_datasize.go likewise) func newRandObf(val string) (obf, error) { length, err := strconv.Atoi(val) if err != nil { return nil, err } return &randObf{length: length}, nil } ``` `strconv.Atoi` succeeds on negative strings; nothing checks `length >= 0`. The negative length only surfaces when a handshake packet is actually built. In `device/send.go`'s `SendHandshakeInitiation`, a few lines above the Defect A call site: ```go for _, ipacket := range peer.device.ipackets { if ipacket != nil { buf := make([]byte, ipacket.ObfuscatedLen(0)) ``` `ipacket.ObfuscatedLen(0)` sums the per-tag `length` fields, so a negative tag argument yields a negative total, and `make([]byte, negative)` panics with `runtime error: makeslice: len out of range`. Reproduced directly: constructing `chain, _ := newObfChain("<r -5>")` and executing `make([]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 `0361c54` deleted three guards while adding today's positivity checks Commit `0361c54dca92071463e9611796c04464386457ac` ("fix: refactor processing of junk packets", PR #103, first shipped in `v0.2.16`) rewrote junk/obfuscation handling wholesale. In the same diff hunk to `device/device.go`'s `handlePostConfig`, it: - **Deleted** the `maxSize < minSize` rejection: ```go } else if tempAwg.Cfg.JunkPacketMaxSize < tempAwg.Cfg.JunkPacketMinSize { errs = append(errs, ipcErrorf( ipc.IpcErrorInvalid, "maxSize: %d; should be greater than minSize: %d", tempAwg.Cfg.JunkPacketMaxSize, tempAwg.Cfg.JunkPacketMinSize, )) } ``` - **Deleted** the equal-value auto-increment guard that made `jmin == jmax` usable instead of feeding a zero-width range to the RNG: ```go if device.awg.Cfg.JunkPacketCount > 0 && tempAwg.Cfg.JunkPacketMaxSize == tempAwg.Cfg.JunkPacketMinSize { tempAwg.Cfg.JunkPacketMaxSize++ // to make rand gen work } ``` - **Deleted** the `MaxSegmentSize` clamp on `JunkPacketMaxSize`: ```go if tempAwg.Cfg.JunkPacketMaxSize >= MaxSegmentSize { device.awg.Cfg.JunkPacketMinSize = 0 device.awg.Cfg.JunkPacketMaxSize = 1 errs = append(errs, ipcErrorf( ipc.IpcErrorInvalid, "JunkPacketMaxSize: %d; should be smaller than maxSegmentSize: %d", tempAwg.Cfg.JunkPacketMaxSize, MaxSegmentSize, )) } ``` - **Added**, in `device/uapi.go`, the `jc`/`jmin`/`jmax` `<= 0` positivity 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`'s `newRandomGeneratorBase`, which had this bound check on exactly the tag argument that Defect B now lacks: ```go func newRandomGeneratorBase(param string) (*randomGeneratorBase, error) { size, err := strconv.Atoi(param) if err != nil { return nil, fmt.Errorf("parse int: %w", err) } if size > 1000 { return nil, fmt.Errorf("size must be less than 1000") } ... ``` This check rejected `size > 1000` but never rejected `size < 0` either, so it would not by itself have prevented Defect B — but its removal, with nothing added in its place in the new `obf_*.go` builders, is also why `docs.amnezia.org`'s documented `N <= 1000` limit for `r`/`rc`/`rd` no 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 deleted `handlePostConfig` logic), reject `jmax < jmin` with a named `IpcErrorInvalid`, the way the pre-`0361c54` code 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 from `wg`/`awg` at configuration time instead of an outage the next time a handshake fires. - `<r N>`/`<rc N>`/`<rd N>`/`<dz N>`: add `if length < 0 { return nil, fmt.Errorf(...) }` to each of `newRandObf`, `newRandCharObf`, `newRandDigitsObf`, and `newDataSizeObf` in `device/obf_*.go`. Optionally restore an upper bound (matching the removed `size > 1000` check, 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`/`jmax` and `i1`-`i5` are 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 (where `jmin`/`jmax` accept `0` and any ordering silently) and then applied, unmodified, to a 1.x/1.5/2.x-line daemon, or a config generator that computes `jmin`/`jmax` from 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-go` `master` = `b5928ef` = tag `v3.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-go` `v0.2.19` (`1cc9427`), the newest tag on the 1.x/1.5/2.x line, for Defect A's `big.Int` panic path; `device/obf.go` and siblings confirmed byte-identical to `v3.1.20260828` via `diff`, so Defect B applies unchanged to this pin too. - Root-cause commit `0361c54dca92071463e9611796c04464386457ac` (PR #103), first released in `v0.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 `device` package) if useful for a regression test.
Sign in to join this conversation.
No labels
pull-request
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
amnezia-vpn/amneziawg-go#89
No description provided.