Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unsafe.ReadUnaligned causes System.AccessViolationException on Windows ARM64 #76194

Closed
k15tfu opened this issue Sep 26, 2022 · 7 comments · Fixed by #76341
Closed

Unsafe.ReadUnaligned causes System.AccessViolationException on Windows ARM64 #76194

k15tfu opened this issue Sep 26, 2022 · 7 comments · Fixed by #76341
Assignees
Labels
area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI
Milestone

Comments

@k15tfu
Copy link
Contributor

k15tfu commented Sep 26, 2022

Hi!

I faced with System.AccessViolationException when reading 5-byte structure in .NET 6 / .NET 7 RC1 / Arm64 .NET Framework 4.8.1 app on Windows ARM64, here is demo app:

using System.IO.MemoryMappedFiles;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

class Program
{
    [StructLayout(LayoutKind.Explicit, Size = 5)]
    public struct Data
    {
        [FieldOffset(0)]
        public byte Byte;

        [FieldOffset(1)]
        public int Int;
    }
    static unsafe void Main(string[] args)
    {
        var mappedFile = MemoryMappedFile.CreateNew(null, 0x1000);
        var viewAccessor = mappedFile.CreateViewAccessor();
        byte* viewPtr = null;
        viewAccessor.SafeMemoryMappedViewHandle.AcquirePointer(ref viewPtr);
        Unsafe.InitBlock(viewPtr, 0x01, (uint)viewAccessor.Capacity);

        IntPtr viewStart = new IntPtr(viewPtr);
        IntPtr viewEnd = viewStart + (int)viewAccessor.Capacity;
        IntPtr last = viewEnd - Unsafe.SizeOf<Data>();
        Console.WriteLine($"view {viewStart:X16} - {viewEnd:X16} last {last:X16}");

        Data x = Unsafe.ReadUnaligned<Data>(last.ToPointer());
        Console.WriteLine($"{x.Byte:X} {x.Int:X}");
    }
}

Output:

view 0000023537440000 - 0000023537441000 last 0000023537440FFB
Fatal error. System.AccessViolationException: Attempted to read or write protected memory. This is often an indication that other memory is corrupt.
   at System.Runtime.CompilerServices.Unsafe.ReadUnaligned[[Program+Data, Program, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]](Void*)
   at Program.Main(System.String[])

...\bin\Debug\net6.0\Program.exe (process 13224) exited with code -1073741819.

Looks like it tries to load 8 bytes from x0:

System.Runtime.CompilerServices.Unsafe.dll!System.Runtime.CompilerServices.Unsafe.ReadUnaligned<T>(void*):
00007FF7F8F0CBF0  stp         fp,lr,[sp,#-0x10]!
00007FF7F8F0CBF4  mov         fp,sp
00007FF7F8F0CBF8  ldr         x0,[x0]  <-- here, x0 is 0x0000023537440FFB
00007FF7F8F0CBFC  ldp         fp,lr,[sp],#0x10
00007FF7F8F0CC00  ret

.NET 6.0.401
.NET 7.0.100-rc.1.22431.12
.NET Framework 4.8.1 ARM64 4.8.9093.0
Windows 11 21H2 22000.978

@dotnet-issue-labeler
Copy link

I couldn't figure out the best area label to add to this issue. If you have write-permissions please help me learn by adding exactly one area label.

@ghost ghost added the untriaged New issue has not been triaged by the area owner label Sep 26, 2022
@EgorBo EgorBo added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label Sep 26, 2022
@ghost
Copy link

ghost commented Sep 26, 2022

Tagging subscribers to this area: @JulieLeeMSFT, @jakobbotsch
See info in area-owners.md if you want to be subscribed.

Issue Details

Hi!

I faced with System.AccessViolationException when reading 5-byte structure in .NET 6 / .NET 7 RC1 / Arm64 .NET Framework 4.8.1 app on Windows ARM64, here is demo app:

using System.IO.MemoryMappedFiles;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

class Program
{
    [StructLayout(LayoutKind.Explicit, Size = 5)]
    public struct Data
    {
        [FieldOffset(0)]
        public byte Byte;

        [FieldOffset(1)]
        public int Int;
    }
    static unsafe void Main(string[] args)
    {
        var mappedFile = MemoryMappedFile.CreateNew(null, 0x1000);
        var viewAccessor = mappedFile.CreateViewAccessor();
        byte* viewPtr = null;
        viewAccessor.SafeMemoryMappedViewHandle.AcquirePointer(ref viewPtr);
        Unsafe.InitBlock(viewPtr, 0x01, (uint)viewAccessor.Capacity);

        IntPtr viewStart = new IntPtr(viewPtr);
        IntPtr viewEnd = viewStart + (int)viewAccessor.Capacity;
        IntPtr last = viewEnd - Unsafe.SizeOf<Data>();
        Console.WriteLine($"view {viewStart:X16} - {viewEnd:X16} last {last:X16}");

        Data x = Unsafe.ReadUnaligned<Data>(last.ToPointer());
        Console.WriteLine($"{x.Byte:X} {x.Int:X}");
    }
}

Output:

view 0000023537440000 - 0000023537441000 last 0000023537440FFB
Fatal error. System.AccessViolationException: Attempted to read or write protected memory. This is often an indication that other memory is corrupt.
   at System.Runtime.CompilerServices.Unsafe.ReadUnaligned[[Program+Data, Program, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]](Void*)
   at Program.Main(System.String[])

...\bin\Debug\net6.0\Program.exe (process 13224) exited with code -1073741819.

Looks like it tries to load 8 bytes from x0:

System.Runtime.CompilerServices.Unsafe.dll!System.Runtime.CompilerServices.Unsafe.ReadUnaligned<T>(void*):
00007FF7F8F0CBF0  stp         fp,lr,[sp,#-0x10]!
00007FF7F8F0CBF4  mov         fp,sp
00007FF7F8F0CBF8  ldr         x0,[x0]  <-- here, x0 is 0x0000023537440FFB
00007FF7F8F0CBFC  ldp         fp,lr,[sp],#0x10
00007FF7F8F0CC00  ret

.NET 6.0.401
.NET 7.0.100-rc.1.22431.12
.NET Framework 4.8.1 ARM64 4.8.9093.0
Windows 11 21H2 22000.978

Author: k15tfu
Assignees: -
Labels:

area-CodeGen-coreclr, untriaged

Milestone: -

@EgorBo EgorBo added this to the 8.0.0 milestone Sep 26, 2022
@EgorBo EgorBo removed the untriaged New issue has not been triaged by the area owner label Sep 26, 2022
@EgorBo
Copy link
Member

EgorBo commented Sep 26, 2022

Looks like one of those issues where we read more then requested.

@EgorBo EgorBo self-assigned this Sep 26, 2022
@EgorBo
Copy link
Member

EgorBo commented Sep 26, 2022

Repro:

[StructLayout(LayoutKind.Explicit, Size = 5)]
public struct Data
{
    [FieldOffset(0)]
    public byte Byte;

    [FieldOffset(1)]
    public int Int;
}

static unsafe Data Foo(void* ptr) => 
    Unsafe.ReadUnaligned<Data>(ptr);

ARM Codegen (x86 is fine):

; Method Program:Foo(ulong):Data
G_M59270_IG01:              ;; offset=0000H
        A9BF7BFD          stp     fp, lr, [sp, #-0x10]!
        910003FD          mov     fp, sp
						;; size=8 bbWeight=1    PerfScore 1.50

G_M59270_IG02:              ;; offset=0008H
        F9400000          ldr     x0, [x0]
						;; size=4 bbWeight=1    PerfScore 3.00

G_M59270_IG03:              ;; offset=000CH
        A8C17BFD          ldp     fp, lr, [sp], #0x10
        D65F03C0          ret     lr
						;; size=8 bbWeight=1    PerfScore 2.00
; Total bytes of code: 20

Looks like it happens in lowering (struct IND -> long IND)

@SingleAccretion
Copy link
Contributor

LowerRetStruct needs to spill "mis-sized" structs from indirection nodes before retyping them. Something like below:

diff --git a/src/coreclr/jit/lower.cpp b/src/coreclr/jit/lower.cpp
index 176828d17ed..af8d5a13b85 100644
--- a/src/coreclr/jit/lower.cpp
+++ b/src/coreclr/jit/lower.cpp
@@ -3654,8 +3654,16 @@ void Lowering::LowerRetStruct(GenTreeUnOp* ret)
             break;

         case GT_OBJ:
+            if (genTypeSize(nativeReturnType) != retVal->AsObj()->Size())
+            {
+                LIR::Use retValUse(BlockRange(), &ret->gtOp1, ret);
+                ReplaceWithLclVar(retValUse);
+                LowerRetSingleRegStructLclVar(ret);
+                break;
+            }
             retVal->ChangeOper(GT_IND);
             FALLTHROUGH;
+
         case GT_IND:
             retVal->ChangeType(nativeReturnType);
             LowerIndir(retVal->AsIndir());

It's an incomplete fix though, the IND<struct> case is susceptible to the same bug.

@EgorBo
Copy link
Member

EgorBo commented Sep 26, 2022

I actually did the same locally, spilled to a local 🙂 but feel free to file a PR with a fix if you want @SingleAccretion

@SingleAccretion
Copy link
Contributor

I think I won't have the time for it this week.

@ghost ghost added the in-pr There is an active PR which will close this issue when it is merged label Sep 28, 2022
@ghost ghost removed the in-pr There is an active PR which will close this issue when it is merged label Nov 17, 2022
@ghost ghost locked as resolved and limited conversation to collaborators Dec 17, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants