microsoft / plcrashreporter

Reliable, open-source crash reporting for iOS, macOS and tvOS
Other
2.88k stars 537 forks source link

arm64e PAC stripping mask #316

Open enm4x opened 3 months ago

enm4x commented 3 months ago

Hello :) not 100% sure about this one but i would like a second read on this matter.

The default mask applied is hardcoded as followed : https://github.com/microsoft/plcrashreporter/blob/6752f71de206f6a53fa6a758c3660fd9a7fe7527/Source/PLCrashAsyncThread_arm.h#L39

This works well but i saw inside the XNU implementation that depending on the OS variant targeted the maximum virtual memory address range is different, see:

/* system-wide values */
#define MACH_VM_MIN_ADDRESS_RAW 0x0ULL
#if defined(XNU_PLATFORM_MacOSX) || defined(XNU_PLATFORM_DriverKit)
#define MACH_VM_MAX_ADDRESS_RAW 0x00007FFFFE000000ULL
#else
#define MACH_VM_MAX_ADDRESS_RAW 0x0000000FC0000000ULL
#endif

https://github.com/apple-oss-distributions/xnu/blob/94d3b452840153a99b38a3a9659680b2a006908e/osfmk/mach/arm/vm_param.h#L136

let me know if i'm interpreting this incorrectly otherwise i think we would need 2 different masks: macOSX : 0x00007FFFFFFFFFFF others: 0x0000000FFFFFFFFF

thanks !

MikhailSuendukov commented 3 months ago

Hello, and thank you for reaching out to us. I have prepared a pull request to improve our pointer mask definition. Thank you!

MikhailSuendukov commented 2 months ago

Hello, I would like to understand under what specific conditions this mask should be applied. Are there particular applications or systems where its usage is required? Thank you!

enm4x commented 2 months ago

Hey,

In my opinion this specific mask 0x00007FFFFFFFFFFF should be applied on every stack pointer address originated from any armv8.3 and above devices using macOS. So essentially every M(1/2/3/..) series processor based devices, so all arm64/macos device. Those processors are capable of using both classic arm64 instruction set or arm64e instruction set binaries.

Why you should apply it all the time ? every program compiled for the classic arm64 instruction set will not have the upper bit used for the PAC, so the mask will have no impact on the correctness of the pointer address but you still need to apply the mask because there is no guarantee your program will not call code in an arm64e binaries. For example now some macOS libraries are only compiled as arm64e, (for example libsystem_malloc.dylib in macos 14.4), so if you crash in this lib, for example you do a double free, then the pointer address you will receive will have the upper bit set because it's a lib using the PAC security feature. So when you will read the address of your callstack and try to use it for debug purposes it will point to an invalid memory-space.

What value the mask should be ? if the xnu oss repo is up to date, and until the maximum addressable range is unchanged, then this mask 0x00007FFFFFFFFFFF is valid for any MacOS devices using an M-series processor. edit:(You can also check that on macOS, when running an arm64e binaries, the PAC upper-bit used are only the first 5 byte, while on ios the 8 first byte are used.)

We need to use the mask because the compiler will refuse to use the native method available if you're not strictly compiling for arm64e, which is an issue in the example i explained earlier. If you're compiling for arm64e on a compatible devices then you can use ptrauth_strip(value, key) ptrauth.h or use the assembly method available xpaci.

To resume, i think you should use this mask all the time when compiling for the combo OSX/arm64 devices, because it will either do nothing on your address value or remove the PAC upper bits and give you a correct address. If you're compiling for the combo OSX/arm64e then you can replace the mask with the native method which is certainly more futureproof.

let me know if my explanation are unclear, if i need to explain something else, and obviously correct me if i'm wrong.

Thanks :)

IlyaBausovAkvelon commented 1 month ago

@enm4x Thanks for explaining this. During the investigation of this had you debugged it? Could you provide us a little demo app to reproduce this?

enm4x commented 1 month ago

Hey,

what do you mean by debugged it ?

if it is: "did i verified it ?"

if so then yes i did debugged it and verified it on iOS and macOS (arm64) but it was not while using the PLCrashreporter but another crash handler. Nonetheless the method used to cleanup the address from the PAC bits is the same, and i thought it would be good to share this with you.

unfortunatly, i'm on vacation and i do not have a mac/iphone combo at my disposition to allow me to create a demo to reproduce this.

but if you'd like to recreate this then you could generate a basic program that just does a double free. You just need to compile this for at least macOS 14.4 targeting the specific arm64 architecture. The program being compiled as an arm64 arch, you will not have access to the asm xpaci / native ptrauth_strip() method, but the double free will happen inside the libsystemmalloc_dylib which is a binary compiled in arm64e.

at least your first call stack addresses will contains PAC bits, if you generate the same crash on an iphone >= iphone XR and a mac with an M processor you will see the difference in the addresses PAC bits. Which on the iphone will be the 7 first bytes who are used for the PAC mechanism while on the mac only the 4 first bytes will be used for the PAC and your address on a small program will look like this 0x2AA30000100BFA34

to be fair, my point is that a case where we would see the iOS mask mess the address would be when our macOS had to allocate more than 64 Gbytes of ram which is not a common test case but is a irl possibility.

if needed i can help you reproduce this, i just don't have the hardware available at the moment, let me know

IlyaBausovAkvelon commented 1 month ago

Hi @enm4x, I've tried to reproduce the issue. I've created a simple console app with the next code:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        int *array = malloc(sizeof(int) * 100);
        memset(array, 0, sizeof(int) * 100);

        free(array);
        free(array);
    }
    return 0;
}

With bt command for lldb debugger when it's stopped on crash I've got this:

(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = signal SIGABRT
    frame #0: 0x000000019e6c95f0 libsystem_kernel.dylib`__pthread_kill + 8
    frame #1: 0x000000010073abe8 libsystem_pthread.dylib`pthread_kill + 288
    frame #2: 0x000000019e60ea30 libsystem_c.dylib`abort + 180
    frame #3: 0x000000019e51edc4 libsystem_malloc.dylib`malloc_vreport + 896
    frame #4: 0x000000019e542ea8 libsystem_malloc.dylib`malloc_zone_error + 104
    frame #5: 0x000000019e541d00 libsystem_malloc.dylib`free_tiny_botch + 40
  * frame #6: 0x0000000100003f54 PACReproWithoutPLCrashReporter`main(argc=1, argv=0x000000016fdff410) at main.m:17:9
    frame #7: 0x000000019e377154 dyld`start + 2476
Message from debugger: killed

As I see the call to lib system_malloc.dylib had happened but there is no PAC in the beginning of the address. So for now I cannot confirm the PAC behavior. I am building it for ARCH_STANDARD on the M1 machine and targeting macOS 14.5.

Could you please guide me through reproducing this? This is important for us to implement support for such addresses. I just need to reproduce this so I can debug it and properly implement support of this behavior.

enm4x commented 1 month ago

Hi Ilya !

Could you try rebuilding your program with the xcode parameter architechtures explicitly set to arm64 and not $(ARCHS_STANDARD) ? I think $(ARCHS_STANDARD) is building a universal binaries which means your program will contain both arm64 and arm64e binaries and would prevent the case we are trying to reproduce.

you can confirm this by doing the following command in your terminal to verify what are the available arch in your UniversalBinaries

lipo -archs ./path/to/your/executable

If this does not solve the ability to reproduce this I'll be back at the office tomorrow and have acces to a mac to check this and help you reproduce this :)

IlyaBausovAkvelon commented 3 weeks ago

Hi @enm4x, I've set build architecture to arm64 and rebuild my test app with double free. When I run bt command on double free crash I still don't see any PAC in addresses. It's all the same as before.

I've a thought that bt might be removing this PAC probably. What debugger did you used?