]> git.ipfire.org Git - thirdparty/wireguard-go.git/commitdiff
device: make Peer fields safe for atomic access on 32-bit.
authorDavid Anderson <danderson@tailscale.com>
Sun, 1 Mar 2020 08:39:24 +0000 (00:39 -0800)
committerDavid Crawshaw <david@zentus.com>
Mon, 30 Mar 2020 09:10:36 +0000 (20:10 +1100)
All atomic access must be aligned to 64 bits, even on 32-bit
platforms. Go promises that the start of allocated structs is
aligned to 64 bits. So, place the atomically-accessed things
first in the struct so that they benefit from that alignment.

As a side bonus, it cleanly separates fields that are accessed
by atomic ops, and those that should be accessed under mu.

Also adds a test that will fail consistently on 32-bit platforms
if the struct ever changes again to violate the rules. This is
likely not needed because unaligned access crashes reliably,
but this will reliably fail even if tests accidentally pass due
to lucky alignment.

Signed-Off-By: David Anderson <danderson@tailscale.com>
device/peer.go
device/peer_test.go [new file with mode: 0644]

index 8a8224c66204ff02683289ff1f7c7901b9481c4f..65581d5247ed96e0d193fa4a41c281244b5df6d2 100644 (file)
@@ -19,20 +19,27 @@ const (
 )
 
 type Peer struct {
-       isRunning                   AtomicBool
-       sync.RWMutex                // Mostly protects endpoint, but is generally taken whenever we modify peer
-       keypairs                    Keypairs
-       handshake                   Handshake
-       device                      *Device
-       endpoint                    Endpoint
-       persistentKeepaliveInterval uint16
-
-       // This must be 64-bit aligned, so make sure the above members come out to even alignment and pad accordingly
+       // These fields are accessed with atomic operations, which must be
+       // 64-bit aligned even on 32-bit platforms. Go guarantees that an
+       // allocated struct will be 64-bit aligned. So we place
+       // atomically-accessed fields up front, so that they can share in
+       // this alignment before smaller fields throw it off.
        stats struct {
                txBytes           uint64 // bytes send to peer (endpoint)
                rxBytes           uint64 // bytes received from peer
                lastHandshakeNano int64  // nano seconds since epoch
        }
+       // This field is only 32 bits wide, but is still aligned to 64
+       // bits. Don't place other atomic fields after this one.
+       isRunning AtomicBool
+
+       // Mostly protects endpoint, but is generally taken whenever we modify peer
+       sync.RWMutex
+       keypairs                    Keypairs
+       handshake                   Handshake
+       device                      *Device
+       endpoint                    Endpoint
+       persistentKeepaliveInterval uint16
 
        timers struct {
                retransmitHandshake     *Timer
diff --git a/device/peer_test.go b/device/peer_test.go
new file mode 100644 (file)
index 0000000..de87ab6
--- /dev/null
@@ -0,0 +1,29 @@
+/* SPDX-License-Identifier: MIT
+ *
+ * Copyright (C) 2017-2019 WireGuard LLC. All Rights Reserved.
+ */
+
+package device
+
+import (
+       "testing"
+       "unsafe"
+)
+
+func checkAlignment(t *testing.T, name string, offset uintptr) {
+       t.Helper()
+       if offset%8 != 0 {
+               t.Errorf("offset of %q within struct is %d bytes, which does not align to 64-bit word boundaries (missing %d bytes). Atomic operations will crash on 32-bit systems.", name, offset, 8-(offset%8))
+       }
+}
+
+// TestPeerAlignment checks that atomically-accessed fields are
+// aligned to 64-bit boundaries, as required by the atomic package.
+//
+// Unfortunately, violating this rule on 32-bit platforms results in a
+// hard segfault at runtime.
+func TestPeerAlignment(t *testing.T) {
+       var p Peer
+       checkAlignment(t, "Peer.stats", unsafe.Offsetof(p.stats))
+       checkAlignment(t, "Peer.isRunning", unsafe.Offsetof(p.isRunning))
+}