Checking whether a pointer is valid in Linux
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.