Question Details

No question body available.

Tags

c linux memory process

Answers (2)

March 30, 2025 Score: 2 Rep: 2,071 Quality: Low Completeness: 60%

In C, you can't "test" whether an arbitrary pointer is valid by simply passing it to a system call.

When you do:

ssizet result = write(devnull, addr, sizeof(void ));

The kernel tries to read sizeof(void ) bytes starting at addr in your process's memory. If addr is totally out of range or otherwise invalid the kernel can't even copy from that userspace pointer into its internal buffer. It triggers a page fault at the CPU level, which ends up delivering a SISSEGV to your process. Your process dies before the write() call can return with errno = EFAULT.

Also, in your loop:

for (int i = 0; i < 1000000; i++) {
    if (isMemoryMapped(&ptrArr[i])) {
        ...
    }
    else {
        ...
        break;
    }
}

you're iterating way past the end of ptrAddr, which only has 3 elements (&a, &b, &c).

If you really need to check for "mappable" memory, you can use proc/self/maps, parsing your own memory maps to see which address ranges are "valid" for your process.

In practice, though, it's usually best to avoid code that tries to test if a pointer is valid. If it's under your program's control, you should already know if it's valid.

March 31, 2025 Score: 1 Rep: 18,143 Quality: Medium Completeness: 80%

On Linux, The write system call will eventually call the vfswrite() function in the kernel (in "fs/readwrite.c"). It will call accessok() on the user space address region to verify that it lies within the permissible range for user-space addresses, but it does not check that the region is mapped. If the accessok() check fails, it will fail with an EFAULT error.

If the accessok() check passes, it checks that the file region being written is valid. If all is OK, it calls either the write or writeiter file operation handler for the file. For the /dev/null file, the function called is writenull() in "drivers/char/mem.c". The only thing that function does is return the length of the region being written. It does not access the user-space memory.

I think the SIGSEGV signal is being raised during the call to printf(). If isMemoryMapped() returns 1 incorrectly for an unmapped address, the evaluation of the argument expression ptrArr[i] will dereference the pointer, which will cause the signal to be raised.

One thing that I noticed is that the program runs OK under the GDB debugger (which makes debugging the problem tricky!), but that seems to be because it disabled randomization of the virtual address space (i.e. disabled ASLR), so that the stack was mapped to the top of the user-space portion of the virtual address space. This caused the accessok() check in the kernel's vfswrite() function to fail with the EFAULT error. The same thing occurs when ASLR is disabled by other means, for example when the program is run via setarch $(uname -m) -R ./progname (where ./progname is the executable under test).

This answer suggests writing to a dummy pipe() file descriptor, but does not supply example code. Here is a modified version of isMemoryMapped() that uses a temporary pipe. It seems to work for me:

int isMemoryMapped(void* addr) {
    int saveerrno = errno;
    int pipefd[2];
    // Create pipe
    if (pipe(pipefd) == -1) {
        perror("pipe");
        exit(EXITFAILURE);
    }
    // Attempt to write to pipe
    ssizet result = write(pipefd[1], addr, 1);
    int resulterrno = errno;
    // Close pipe
    close(pipefd[0]);
    close(pipefd[1]);
    errno = saveerrno;
    // Return 1 if accessible, 0 if not
    return (result != -1 || result_errno != EFAULT);
}

Note that this only tests if the address is readable, not writeable, and would not be good for addresses that have been mmap()ed to memory mapped I/O hardware registers due to possible nasty side-effects.