Search code examples
cswiftmacospointers

Safely checking a pointer for validity in macOS?


Is there a way (OS call, etc.) at runtime for a macOS app to check whether a pointer is valid, for read and/or for write, without crashing or causing a signal if the pointer is invalid? (points outside of the process address space, NULL+1, etc.)

Either in C ((char *)someLongInt), or in Swift (for an unsafe raw binding).


Solution

  • Yes, this can be achieved using the mach_vm_region call. See below sample code:

    #include <mach/mach.h>
    #include <mach/mach_vm.h>
    #include <stdio.h>
    #include <stdbool.h>
    
    bool ptr_is_valid(void *ptr, vm_prot_t needs_access) {
        vm_map_t task = mach_task_self();
        mach_vm_address_t address = (mach_vm_address_t)ptr;
        mach_vm_size_t size = 0;
        vm_region_basic_info_data_64_t info;
        mach_msg_type_number_t count = VM_REGION_BASIC_INFO_COUNT_64;
        mach_port_t object_name;
        kern_return_t ret = mach_vm_region(task, &address, &size, VM_REGION_BASIC_INFO_64, (vm_region_info_t)&info, &count, &object_name);
        if (ret != KERN_SUCCESS) return false;
        return ((mach_vm_address_t)ptr) >= address && ((info.protection & needs_access) == needs_access);
    }
    
    #define TEST(ptr,acc) printf("ptr_is_valid(%p,access=%d)=%d\n", (void*)(ptr), (acc), ptr_is_valid((void*)(ptr),(acc)))
    
    int main(int argc, char**argv) {
        TEST(0,0);
        TEST(0,VM_PROT_READ);
        TEST(123456789,VM_PROT_READ);
        TEST(main,0);
        TEST(main,VM_PROT_READ);
        TEST(main,VM_PROT_READ|VM_PROT_EXECUTE);
        TEST(main,VM_PROT_EXECUTE);
        TEST(main,VM_PROT_WRITE);
        TEST((void*)(-1),0);
        return 0;
    }
    

    For a memory address, the mach_vm_region returns the start, size and properties of the containing memory region, if the address is valid; if the address is invalid, it returns that information for the next highest valid address, or else the call fails if there is no higher valid address. So, by checking for call success, and that the memory address you passed is greater than or equal to the returned address, you can tell whether the address is valid.

    Furthermore, one assumably wants to read/write/execute the address, so it is also important to check that the memory protection of the containing memory region permit you to do that.