Repository navigation
FieldInfo.GetValue/SetValue on an under-aligned field of a packed struct raises SIGBUS on arm64 #133236
Description
Description
FieldDesc::GetInstanceField and FieldDesc::SetInstanceField in src/coreclr/vm/field.cpp access 1/2/4/8-byte instance fields through VolatileLoad<T> / VolatileStore<T>:
runtime/src/coreclr/vm/field.cpp
Lines 335 to 337 in e1d442a
| case 8: | |
| *(INT64*)pOutVal = VolatileLoad<INT64>(PTR_INT64(pFieldAddress)); | |
| break; |
runtime/src/coreclr/vm/field.cpp
Lines 411 to 413 in e1d442a
| case 8: | |
| VolatileStore<INT64>((INT64*)pFieldAddress, *(INT64*)pInVal); | |
| break; |
On arm64 those lower to ldapr / stlr, which require natural alignment. [StructLayout(Pack = N)] can place a 4- or 8-byte field at an under-aligned offset, so the field address on a boxed instance is not naturally aligned and the access faults.
The condition is narrower than plain misalignment, which is why this presents as intermittent. On Apple Silicon an unaligned acquire/release access faults only when it crosses a 16-byte granule; within a single granule it succeeds. Boxed objects are 8-byte, not 16-byte, aligned, so a given field lands on a crossing address roughly half the time depending on allocation placement.
Measured on an M3 Max:
| Field | Address (mod 16) | Crosses granule | Result |
|---|---|---|---|
double at offset 12, Pack = 4 |
4 | no | ok |
double at offset 12, Pack = 4 |
12 | yes | SIGBUS |
int at offset 5, Pack = 1 |
5 | no | ok |
int at offset 5, Pack = 1 |
13 | yes | SIGBUS |
This is reachable with no reflection in user code. ValueType.Equals and ValueType.GetHashCode fall back to a FieldInfo.GetValue field walk whenever the type is ineligible for the bitwise fast path, which a floating-point or reference field causes. That is how we hit it: an NUnit Is.EquivalentTo assertion over a packed struct with a double field crashed the Unity editor process, at a rate of about 5 in 10 CI runs on macOS arm64.
Reproduction Steps
See attached Repro.cs.
Run with
dotnet run --file Repro.csIt reproduces 5/5 here. The struct under test:
[StructLayout(LayoutKind.Sequential, Pack = 4)]
struct Packed
{
public int A;
public int B;
public int C;
public double V; // Pack = 4 places this at offset 12
}The program allocates boxed instances until one lands across a 16-byte granule, keeps it pinned so it cannot move, then calls FieldInfo.GetValue on V. The search is what makes it deterministic. Looping over plain allocations is not enough, because boxes allocated back to back are equally spaced and one process therefore tends to see a single residue throughout.
Three one-line edits to the same file cover the rest of the evidence:
- Confirm the granule condition. Change the residue check from
address % 16 != 12toaddress % 16 != 4. The access is still 4-byte aligned rather than 8, but it no longer crosses a granule, and the read succeeds. - Confirm 4-byte fields are affected too. Change the struct to
[StructLayout(LayoutKind.Sequential, Pack = 1)] struct Packed { public byte Tag; public int A; public int B; }, read"B"(offset 5) instead of"V", and use offset5with residue13. Faults. Residue5succeeds. - Confirm
SetValueis affected. Replace theGetValuecall withtypeof(Packed).GetField("V").SetValue(boxed, 1.25). Faults identically.
For the implicit path, replacing the GetValue call with a ValueType.Equals against a second instance faults in the same place, with no reflection in user code:
object other = new Packed { A = 1, B = 2, C = 3, V = 1.23 };
boxed.Equals(other); // SIGBUS in FieldDesc::GetInstanceFieldA/B/C must match in both values so the field walk gets as far as V. Note that ValueType.Equals does not short-circuit on a bitwise compare first: bitwise-identical values fault just as readily, verified over 200-iteration loops.
Expected behavior
FieldInfo.GetValue returns the field value and FieldInfo.SetValue writes it, for any field of any loadable struct, regardless of packing. ValueType.Equals returns a bool. Nothing in the reflection API contract makes a field's alignment the caller's concern, and the same code works on x64.
Actual behavior
The process dies with SIGBUS. Exit code 138 (128 + 10). From the macOS crash report:
exception: EXC_BAD_ACCESS, signal SIGBUS
subtype: EXC_ARM_DA_ALIGN at 0x143e13d7c (4 mod 8, 12 mod 16)
codes: 0x0000000000000101, 0x0000000143e13d7c
frames:
libcoreclr.dylib FieldDesc::GetInstanceField(Object*, void*)
libcoreclr.dylib InvokeUtil::GetFieldValue(FieldDesc*, TypeHandle, Object**, TypeHandle, int*)
libcoreclr.dylib RuntimeFieldHandle_GetValue
code=257 is 0x101, EXC_ARM_DA_ALIGN. Disassembly of the faulting site, offset +172 in FieldDesc::GetInstanceField:
+172: ldapr x8, [x20] <-- faults
+176: str x8, [x19]
The 1/2/4-byte arms of the same switch are at +148, +196 and +88, using ldaprb / ldaprh / ldapr w8 respectively.
Regression?
No response
Known Workarounds
No response
Configuration
- .NET SDK 10.0.301, runtime 10.0.9,
osx-arm64 - macOS 26.6, Apple M3 Max
- Release and Debug both affected; not specific to a build configuration
- Does not reproduce on x64, where unaligned acquire/release accesses are legal
- Not specific to macOS in principle:
ldapr/stlralignment requirements apply to arm64 generally, so linux-arm64 and win-arm64 should be checked. I have only tested osx-arm64. - Originally found on a Unity fork of the runtime, then reproduced on stock Microsoft builds with no Unity code involved.
Other information
No response
Metadata
Metadata
Assignees
Labels
Type
Projects
- StatusShow more project fieldsNo status
Activity
dotnet-policy-service commentedon Sep 4, 2026
Tagging subscribers to this area: @JulieLeeMSFT, @jakobbotsch
See info in area-owners.md if you want to be subscribed.
dotnet-policy-service commentedon Sep 4, 2026
Tagging subscribers to this area: @agocke
See info in area-owners.md if you want to be subscribed.
tannergooding commentedon Sep 4, 2026
Notably .NET requires and expects data to be naturally aligned and the runtime spec (ECMA-335) documents this explicitly. From
I.12.6.2 Alignment:This extends to all operations throughout .NET, whether they explicitly document it or not. It's rather only those that explicitly document themselves as working with unaligned data that you can expect will actually function (and even then it may not have all the guarantees that aligned data would otherwise have; like there may not be atomicity).
Types that have explicit layout are fundamentally
unsafeand with theunsafe evolutionfeature will even start requiring you to annotate them as such. I don't recall off the top of my head if we've made a similar decision for explicit packing, but that should really be the same consideration..NET in general just leaves it to the underlying operating system and/or architecture. Some platforms tend to "just work" (albeit possibly slower) while others may not have any such handling. For some of the platforms without, the operating system may step in and have its own first chance fault handler to account for misalignment, but that doesn't light up in all contexts and isn't itself a guarantee either.
There's potentially some things we can here, but this is a behavior going back to .NET Framework 1.0, is
unsafe, and is explicitly documented as something that can lead to such faults.CC. @jkotas as well for input and possible direction
jkotas commentedon Sep 5, 2026
Opened dotnet/roslyn#85173 to handle this case in
unsafe evolution.Reflection behavior with misaligned fields is a separate problem. In general, reflection is expected to work for methods and fields regardless of whether their access is considered safe or unsafe. The problem is that reflection APIs like
FieldDesc.GetValue/SetValuedo not provide a way to specify volatile or unaligned access. As you can see above,FieldDesc.GetValue/SetValueaddressed this by using atomic volatile reads and writes unconditionally for selected sizes. This ensures correct behavior in case the field requires atomic volatile access. The extra overhead of the memory barrier is negligible compared to the total reflection overhead. We can consider similar approach for misaligned fields: check the field's alignment before performing an atomic volatile read or write, and fall back tomemcpywhen the field is misaligned.dotnet-policy-service commentedon Sep 5, 2026
Tagging subscribers to this area: @steveisok, @dotnet/area-system-reflection
See info in area-owners.md if you want to be subscribed.