device: add test to ensure Peer fields are safe for atomic access on 32-bit
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>
This commit is contained in:
		
							parent
							
								
									224bc9e60c
								
							
						
					
					
						commit
						3dce460c88
					
				
					 2 changed files with 48 additions and 1 deletions
				
			
		|  | @ -27,7 +27,11 @@ type Peer struct { | |||
| 	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
 | ||||
|  |  | |||
							
								
								
									
										43
									
								
								device/peer_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								device/peer_test.go
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,43 @@ | |||
| /* SPDX-License-Identifier: MIT | ||||
|  * | ||||
|  * Copyright (C) 2017-2019 WireGuard LLC. All Rights Reserved. | ||||
|  */ | ||||
| 
 | ||||
| package device | ||||
| 
 | ||||
| import ( | ||||
| 	"reflect" | ||||
| 	"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 | ||||
| 
 | ||||
| 	typ := reflect.TypeOf(p) | ||||
| 	t.Logf("Peer type size: %d, with fields:", typ.Size()) | ||||
| 	for i := 0; i < typ.NumField(); i++ { | ||||
| 		field := typ.Field(i) | ||||
| 		t.Logf("\t%30s\toffset=%3v\t(type size=%3d, align=%d)", | ||||
| 			field.Name, | ||||
| 			field.Offset, | ||||
| 			field.Type.Size(), | ||||
| 			field.Type.Align(), | ||||
| 		) | ||||
| 	} | ||||
| 
 | ||||
| 	checkAlignment(t, "Peer.stats", unsafe.Offsetof(p.stats)) | ||||
| 	checkAlignment(t, "Peer.isRunning", unsafe.Offsetof(p.isRunning)) | ||||
| } | ||||
		Loading…
	
		Reference in a new issue