Skip to content

v1.0.4: Device.Close races with other devices via package-global protocol types #155

Description

@autopeasant

Affected version

  • github.com/amnezia-vpn/amneziawg-go v1.0.4 (latest stable Go module release)
  • Go 1.26.4
  • Linux amd64

Minimal reproduction

Add this test under package device and run go test -race ./device -run TestResetProtocolRace -count=1:

func TestResetProtocolRace(t *testing.T) {
    var reader, closer Device
    done := make(chan struct{})
    var wg sync.WaitGroup
    wg.Add(1)
    go func() {
        defer wg.Done()
        for {
            select {
            case <-done:
                return
            default:
                reader.awg.ASecMux.RLock()
                _ = MessageTransportType
                reader.awg.ASecMux.RUnlock()
            }
        }
    }()

    for i := 0; i < 10_000; i++ {
        closer.resetProtocol()
    }
    close(done)
    wg.Wait()
}

A production-shaped consumer reproduction also trips reliably when two plain Device instances run in one process: closing one instance executes Device.Close -> resetProtocol, while the other instance's RoutineReceiveIncoming reads the package globals. In 10 iterations, the race detector reported the race once.

Observed behavior

The race detector reports a write/read race between:

WRITE: device.(*Device).resetProtocol
  device/device.go:590
  device.(*Device).Close

READ: device.(*Device).RoutineReceiveIncoming
  device/receive.go:174

resetProtocol writes the package-level MessageInitiationType, MessageResponseType, MessageCookieReplyType, and MessageTransportType. Receive/send/handshake paths read the same globals. device.awg.ASecMux does not synchronize the accesses because each Device owns a different mutex.

handlePostConfig also writes these package globals, plus the package-level packetSizeToMsgType and msgTypeToJunkSize maps, so multiple configured devices can both race and overwrite each other's protocol state.

Expected behavior

Distinct Device instances should not share mutable protocol-type or packet-shape state. Closing or configuring one device should neither race with nor alter another live device.

Moving the four message types and the two lookup maps into Device state would remove both the data race and cross-device configuration interference. Cookie-reply generation also needs the owning device's cookie message type rather than the package global.

Additional context

Current master appears to have moved header state onto each Device as part of the unreleased concealment/refactoring work, but the latest stable module release remains v1.0.4 and still contains resetProtocol plus the mutable package globals. A focused backport or a stable release containing the per-device state would address consumers pinned to stable versions.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions