0

I have this simple program where I am trying to protect a block of memory, and then read a file into that memory, releasing it when it segfaults.. first I thought there was only a problem if the file is a fifo.. but now it seems that even for a normal file it fails,

this is the code:

#include <errno.h>
#include <string.h>
#include <iostream>
#include <assert.h>
#include <malloc.h>
#include <sys/mman.h>
#include <unistd.h>
#include <map>
#include <algorithm>
#include <unistd.h>
#include <signal.h>
using namespace std;

#define BUFFER_SIZE 8000
#define handle_error(msg) \
    do { cout << __LINE__ << endl ;perror(msg); exit(EXIT_FAILURE); } while (0)

volatile int fault_count = 0;
char* buffer = 0;
int size = 40960;

int my_fault_handler(void* addr, int serious) {
    if (mprotect(buffer, size,
                 PROT_READ | PROT_WRITE) == -1)
         handle_error("mprotect");
    ++fault_count;
    cout << "Segfaulting" << endl;
    return 1;
}


static void handler(int sig, siginfo_t *si, void *unused) {
    my_fault_handler(si ->si_addr, sig);
}
int main (int argc, char *argv[])
{
    long pagesize = sysconf(_SC_PAGESIZE);
    struct sigaction sa;

   sa.sa_flags = SA_SIGINFO | SA_NOCLDWAIT;
    sigemptyset(&sa.sa_mask);
    sa.sa_sigaction = &handler;
    if (sigaction(SIGSEGV, &sa, NULL) == -1)
        perror("sigaction");

    cerr << "pageSize: " << pagesize << endl;

    buffer = (char*)memalign(pagesize, size);
    if (buffer == NULL)
        handle_error("memalign");
    if (mprotect(buffer, size, PROT_READ) == -1)
        handle_error("mprotect");

    FILE* file = fopen("test", "r");
    cout << "File Open" << endl;
    if (!file) {
        cout << "Failed opening file " << strerror(errno) << endl;
        return 0;
    }

    //*buffer = 0;
    while(fread(buffer, pagesize*2, 1, file)) {
       if (mprotect(buffer, size,
                    PROT_READ) == -1)
            handle_error("mprotect");
    }
    cout << ' ' << strerror(errno) << endl;

    return(0);
}

note the //*buffer = 0;, if I unmark this line the program segfaults and works correctly.. anyone has any idea? the errno is bad address.

Thanks!

UPDATE: It seems a similiar question was asked here: Loading MachineCode From File Into Memory and Executing in C -- mprotect Failing where posix_memalign was suggested, I have tried this and it didn't work.

Community
  • 1
  • 1
Alon
  • 1,776
  • 13
  • 31
  • Can you explain exactly what happens for you when you run the program unchanged, and what you expect to happen? For me, the program prints the page size as 4096, followed by "File Open" followed by "Success". This occurs both with the unchanged program, and after uncommenting the `*buffer = 0` line. – user4815162342 Apr 23 '14 at 08:08
  • do you have a 'test' file? for me when I comment the *buffer = 0; I get a bad address errno, not segfaulting when the file is reading – Alon Apr 23 '14 at 08:16
  • could you add the compiler command line you used? – Alon Apr 23 '14 at 08:16
  • I created an empty `test` file for test purposes. The compiler invocation was as simple as `g++ a.c`. (I also had to remove the `#include ` line, since that header file is not present on my system.) – user4815162342 Apr 23 '14 at 08:28
  • yeah that is not needed, could you add a cout inside the segfault function to insure you are segfaulting in both scenarios? Ive used simple g++ as well – Alon Apr 23 '14 at 08:33
  • It would be a good idea to update the test code in the question to make it clearer how it fails, so we are sure we're running the same code. – user4815162342 Apr 23 '14 at 08:53
  • have.. added a segfaulting print and removed the header.. I see that it failed for Petesh as well. – Alon Apr 23 '14 at 09:26

1 Answers1

2

The problem is that you're not checking for an error in the FILE handle after a short read.

What the system would tell you is that the first fread failed and didn't trigger the fault handler.

If you checked for ferror outside the loop (sloppy as an example):

while(fread(buffer, pagesize*2, 1, file)) {
   if (mprotect(buffer, size,
                PROT_READ) == -1)
        handle_error("mprotect");
}
if (ferror(file) != 0) {
    cout << "Error" << endl;
}

Why it failed is that the underlying read failed, and returned an errno of 14 (EFAULT), which is not quite what is documented to happen when read fails in this situation (it says that Buf points outside the allocated address space.)

You can only trust the signal handler to be triggered in the mprotect case when the code in question is running in the user context, most system calls will fail and return EFAULT in the case that the buffer is invalid or does not have the correct permissions.

Anya Shenanigans
  • 91,618
  • 3
  • 107
  • 122
  • yeah I did print out that the error was EFAULT and bad address.. so your answer is that there is no solution? the buffer MUST be valid? is there no other interface? – Alon Apr 23 '14 at 09:25
  • In the case of relying on system calls, then you have absolutely no guarantee that your handler will be called when the buffer's permission is different from what's needed - it will most often fail with an `EFAULT` errno. If you read into a temporary, writable buffer and memcpy'd to the address you would get the signal to trigger. – Anya Shenanigans Apr 23 '14 at 09:33
  • yup, but then I get an extra memcpy of the entire buffer. – Alon Apr 23 '14 at 09:34
  • Yes, but that's pretty much the only way to guarantee your signal handler is called. You could check for the short read, the error state and the `EFAULT` errno and then trigger the protection change manually and save dealing with the overhead of a signal trigger. – Anya Shenanigans Apr 23 '14 at 09:41
  • I'll give ya a +1 but that still does not answer it unless you show me the documentation that there is no guarantee for my seg handlers :) – Alon Apr 23 '14 at 10:12
  • @Alon Note that using stdio in the first place entails an extra copy, due to stdio's own buffering. If you replace `fread` with an OS-level `read` into a temporary buffer, and `memcpy` that buffer, you should get performance equivalent to stdio, but with the signal invoked as expected. – user4815162342 Apr 23 '14 at 10:22
  • @Alon the [`fread` specification](http://pubs.opengroup.org/onlinepubs/009695399/functions/fread.html) gives the guidance that you need to check `ferror` in the failed read case, *and* check the errno. While it doesn't mention the `EFAULT` case specifically, you have to be able to [`read`](http://linux.die.net/man/2/read) between the lines for the expected behaviour. – Anya Shenanigans Apr 23 '14 at 11:04