1

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).

hotpaw2
  • 70,107
  • 14
  • 90
  • 153
  • Although not specific to MacOS, I wonder if Peter's answer at https://stackoverflow.com/questions/1576300/checking-if-a-pointer-is-allocated-memory-or-not might be a useful starting point. (Sorry, will leave this comment, but I now realize you specifically asked about not using signals). – EdmCoff May 16 '19 at 23:42
  • 1
    The process could execute the `vmmap` command on itself and examine the output. (I do not know of a system call to provide the information.) – Eric Postpischil May 16 '19 at 23:57
  • Piping the output of vmmap back into the app, and comparing the pointer against the mapped address ranges seems to work. This should be at least one potential answer. – hotpaw2 May 17 '19 at 00:17
  • 1
    Are you writing a debugger or something? I'm curious what the application is here – Alexander May 17 '19 at 00:24
  • Safely supporting peek() and poke for a more retro-feature-complete Basic interpreter. – hotpaw2 May 17 '19 at 03:05
  • @hotpaw2 I don't think I would trust an interpreter doing that. Instead, I would create a virtual memory space (basically just a large array, where your indexes are your memory addresses), and have all basic commands address into that, rather than the real address space of the host OS. This also gives you the ability to implement a lot of cool features, like a memory debugger and the ability to save/restore VM snapshots – Alexander May 17 '19 at 23:47

1 Answers1

1

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.

Simon Kissane
  • 4,373
  • 3
  • 34
  • 59