Recently I noticed some calls I was making to a certain C API were returning pointers that I thought were invalid. A quick inspection with gdb confirmed my fears. A particular interaction showed:

(gdb) print *job->someMember
Cannot access memory at address 0xec00000005

Unfortunately I don’t have access to the source of the API and won’t be able to change it. So now I’m at a loss. How can I know if a pointer is valid without dereferencing it? :S

Enter msync!

NAME
       msync — synchronize memory with physical storage

SYNOPSIS
       #include <sys/mman.h>

       int msync(void *addr, size_t len, int flags);

DESCRIPTION
       The msync() function shall write all modified data to  permanent  storage  loca‐
       tions,  if any, in those whole pages containing any part of the address space of
       the process starting at address addr and continuing for len bytes.  If  no  such
       storage  exists,  msync()  need  not  have any effect. If requested, the msync()
       function shall then invalidate cached copies of data.
       ...

The most interesting part is in the “ERRORS” section:

ERRORS
       The msync() function shall fail if:
       ...
       ENOMEM The addresses in the range starting at addr and continuing for len  bytes
              are outside the range allowed for the address space of a process or spec‐
              ify one or more pages that are not mapped.

So, with msync we can at least be sure that obviously wrong pointers can be detected. We can do it like this:

#include <sys/mman.h>
#include <stdbool.h>
#include <unistd.h>

bool is_pointer_valid(void *p) {
    /* get the page size */
    size_t page_size = sysconf(_SC_PAGESIZE);
    /* find the address of the page that contains p */
    void *base = (void *)((((size_t)p) / page_size) * page_size);
    /* call msync, if it returns non-zero, return false */
    return msync(base, page_size, MS_ASYNC) == 0;
}

I’m still not sure how fail-proof is this method, but it looks good to me. Please let me know if you know a better method. Your mileage may vary, and you probably might want to actually change the last line to

int ret = msync(base, page_size, MS_ASYNC) != -1;
return ret ? ret : errno != ENOMEM;

Which would say a pointer is invalid only if the error was specifically triggered by an invalid address.

PS: If this works, it should work with any POSIX system, as opposed to reading memory mappings from /proc, which would only be Linux-specific.

Related content