unix: align Ifreq so its union accessors cannot fault Uint16, SetUint16, Uint32, SetUint32, Inet4Addr and SetInet4Addr cast the ifreq union to uint16, uint32 or RawSockaddrInet4 and access it in place. The generated ifreq declares that union as a byte array, so the type guarantees only byte alignment and the compiler may put an Ifreq at any address - as it does for the elements of a []Ifreq, which pack at the struct's size with an alignment of one. A misaligned access is merely slow on amd64 and arm64; on sparc64 it faults, and TestIoctlIfreq died with SIGBUS. Align the wrapper rather than changing the accessors. The union lies at offset 16 within ifreq on every architecture, so aligning the wrapper aligns the union, and all six accessors are covered instead of only the four integer ones. TestIfreqAlignment pins the invariant. Without the fix it fails on every architecture, reporting an alignment of 1, so catching a regression does not need sparc64 hardware. Updates golang/go#55000 Change-Id: I70aca33419086de9eb68032e354e5f666cc20562 GitHub-Last-Rev: 637b50d0e63b84eb536019d7c893c5cb07289bf3 GitHub-Pull-Request: golang/sys#287 Reviewed-on: https://go-review.googlesource.com/c/sys/+/820000 Reviewed-by: Ian Lance Taylor <iant@golang.org> Reviewed-by: David Chase <drchase@google.com> LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Reviewed-by: Cherry Mui <cherryyz@google.com>
diff --git a/unix/ifreq_linux.go b/unix/ifreq_linux.go index 309f5a2..3f7fa76 100644 --- a/unix/ifreq_linux.go +++ b/unix/ifreq_linux.go
@@ -22,7 +22,13 @@ // fields can be get and set using the following methods: // - Uint16/SetUint16: flags // - Uint32/SetUint32: ifindex, metric, mtu -type Ifreq struct{ raw ifreq } +type Ifreq struct { + // Aligns the union for the accessors below, which cast it in place; + // the generated ifreq is all byte arrays, so its alignment is one. + _ [0]int64 + + raw ifreq +} // NewIfreq creates an Ifreq with the input network interface name after // validating the name does not exceed IFNAMSIZ-1 (trailing NULL required)
diff --git a/unix/ifreq_linux_test.go b/unix/ifreq_linux_test.go index f10040b..67188c6 100644 --- a/unix/ifreq_linux_test.go +++ b/unix/ifreq_linux_test.go
@@ -172,3 +172,18 @@ return ifr } + +func TestIfreqAlignment(t *testing.T) { + // The accessors cast the union in place, so it must be aligned for the + // widest type they use. Merely slow on amd64; a fault on sparc64. + want := unsafe.Alignof(uint32(0)) + if a := unsafe.Alignof(RawSockaddrInet4{}); a > want { + want = a + } + if got := unsafe.Alignof(Ifreq{}); got%want != 0 { + t.Errorf("unsafe.Alignof(Ifreq{}) = %d, want a multiple of %d", got, want) + } + if off := unsafe.Offsetof(Ifreq{}.raw) + unsafe.Offsetof(ifreq{}.Ifru); off%want != 0 { + t.Errorf("union lies at offset %d within Ifreq, want a multiple of %d", off, want) + } +}