Runtime: OSX-Arm64 pthread_jit_write_protect_np(bool) does not have a sane initial value

Created on 9 Sep 2020  路  10Comments  路  Source: dotnet/runtime

Discovered while debugging the Exceptions/ForeignThread/ForeignThreadExceptions/ForeignThreadExceptions.dll test.

The test creates a new thread which invokes JIT managed code.
https://github.com/dotnet/runtime/blob/00d0dba14ed9927b22de224de94d812449529f17/src/tests/Exceptions/ForeignThread/ForeignThreadExceptionsNative.cpp#L49-L60

(lldb) bt
* thread #1, queue = 'com.apple.main-thread'
  * frame #0: 0x00000001d51db3d0 libsystem_kernel.dylib`__ulock_wait + 8
    frame #1: 0x00000001d528ec3c libsystem_pthread.dylib`_pthread_join + 452
    frame #2: 0x0000000102377ed8 libForeignThreadExceptionsNative.dylib`::InvokeCallbackOnNewThread(callback=(0x000000028003c210)) at ForeignThreadExceptionsNative.cpp:60:10
    frame #3: 0x000000028137ce34
    frame #4: 0x000000028137cbac
    frame #5: 0x000000028137c914
    frame #6: 0x000000010163f40c libcoreclr.dylib`CallDescrWorkerInternal at calldescrworkerarm64.S:71

The new thread is failing when it tries to execute the managed code.

* thread #9, stop reason = EXC_BAD_ACCESS (code=2, address=0x28003c210)
    frame #0: 0x000000028003c210
  * frame #1: 0x0000000102377e38 libForeignThreadExceptionsNative.dylib`InvokeCallbackUnix(callback=0x000000028003c210) at ForeignThreadExceptionsNative.cpp:31:5
    frame #2: 0x00000001d528d0f8 libsystem_pthread.dylib`_pthread_start + 320
(lldb) mem region 0x000000028003c210
[0x000000028003c000-0x0000000280040000) rwx

This is apparently occurring because the new thread does not default to pthread_jit_write_protect_np(true).

I believe we should try to have Apple set a default which allows new threads to execute JIT code.

/cc @janvorli

arch-arm64 area-VM-coreclr os-mac-os-x-big-sur

All 10 comments

I think we can handle that ourselves by calling the pthread_jit_write_protect_np in TheUMEntryPrestubWorker or maybe CreateThreadBlockThrow to establish the initial state.

I don't see how that can happen on the clients new thread which gets a pointer to our managed code.

InvokeCallbackOnNewThread(callback=(0x000000028003c210))

Unless all delegates get that as a wrapper and the wrapper is not in JIT generated code

I did wonder if we could hijack the exception and set the JIT write enable state in this case. I suspect we can, but it may introduce unnecessary security risk. A sane default seems safer.

All the callbacks go through the asm TheUMEntryPrestub that then calls the c++ TheUMEntryPrestubWorker.

UnmanagedCallersOnly methods don't go through TheUMEntryPrestub. They instead go through PreStubWorker_Preemptive IIRC.

Right, I was referring to regular callbacks.

I didn't find a test which failed due to PreStubWorker_Preemptive call from a foreign thread, perhaps there is no test coverage?

Did you run src\tests\Interop\UnmanagedCallersOnly ?

I think it was run.... I just reran it now too. Looks like there should be coverage for the new thread case.

The entry path is a little different, so perhaps it was getting JIT execute enabled someplace else....

Was this page helpful?
0 / 5 - 0 ratings