mprotect always returns invalid arguments

5.5k views Asked by At

I'm trying to modify a value in the .text segment using protect to give me writing access:

 int pageSize = sysconf(_SC_PAGE_SIZE);

 int *toModify = (int *)(foo+5);
 if (mprotect(toModify, pageSize, PROT_WRITE) < 0 ) {
      perror("mprotect failed with error:");
      return -1;
  }
  *toModify = 5;
  printf("Modify :%i",foo());

mprotect does never work. It always returns an mprotect failed with error:: Invalid argument error.

foo is a method that returns an int that is stored 5bytes after the function(thats the reason for foo+5)

3

There are 3 answers

4
Eric Postpischil On BEST ANSWER

I have executed the following code on OS X 10.9, and it appears to have the desired behavior. The output is “foo returns 23.”

#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

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


extern int foo(void);


int main(void)
{
    //  New value to write into foo+5.
    int NewValue = 23;

    //  Find page size for this system.
    size_t pagesize = sysconf(_SC_PAGESIZE);

    //  Calculate start and end addresses for the write.
    uintptr_t start = (uintptr_t) &foo + 5;
    uintptr_t end = start + sizeof NewValue;

    //  Calculate start of page for mprotect.
    uintptr_t pagestart = start & -pagesize;

    //  Change memory protection.
    if (mprotect((void *) pagestart, end - pagestart,
            PROT_READ | PROT_WRITE | PROT_EXEC))
    {
        perror("mprotect");
        exit(EXIT_FAILURE);
    }

    //  Write new bytes to desired location.
    memcpy((void *) start, &NewValue, sizeof NewValue);

    //  Some systems could require an invalidate of instruction cache here.

    //  Try modified function.
    printf("foo returns %d.\n", foo());

    return 0;
}

For foo, I used this assembly code. Both sources were built with cc -arch i386.

    .globl  _foo
_foo:
    nop
    nop
    nop
    nop
    mov $42, %eax
    ret

You should modify code this way only as a learning exercise and not use it in any deployed application.

2
twalberg On

From man mprotect:

   EINVAL addr is not a valid pointer, or not a multiple of PAGESIZE.

You are not paying attention to the part where addr needs to be a multiple of PAGESIZE, apparently... Although in at least one version of the man page, that requirement is not made particularly clear, stating simply "specifies the desired protection for the memory page(s) containing part or all of the interval [addr,addr+len-1]".

Finding the address of the page containing a particular address is not particularly hard, since you've already done the pageSize = sysconf(_SC_PAGE_SIZE); bit:

static inline void *pageof(const void* p)
{ return (p & ~(pageSize - 1));
}

Then modify your mprotect call to say mprotect(pageof(toModify), pageSize, ...). Although, see the answer by @Zack for a warning regarding the permissions you are specifying. And do go back and read the man page for mprotect() and make sure you really understand what you are doing...

5
zwol On

The address argument to mprotect needs to be page-aligned, as well as the size argument being a whole number of pages. Also, setting the page to PROT_WRITE alone means you're not allowed to read it anymore -- and this particular page will be in the text segment, so it needs to be PROT_EXEC as well, or the program will crash the moment it returns from mprotect (because the code on that page is no longer considered executable).

This modification of your program does what you want:

/* file A */
#include <stdio.h>
#include <stdint.h>
#include <unistd.h>
#include <sys/mman.h>

extern const int foo;

int
main (int argc, char *argv[])
{
  printf("Before modification: foo = %d @ %p\n", foo, (void *)&foo);

  size_t pagesize = sysconf(_SC_PAGESIZE);
  void *foo_page = (void *) (((uintptr_t)&foo) & ~(pagesize - 1));

  if (mprotect(foo_page, pagesize, PROT_READ|PROT_WRITE|PROT_EXEC)) {
    perror("mprotect");
    return 1;
  }

  *(int *)&foo = 42; /* this is still undefined behavior! */

  printf("After modification: foo = %d @ %p\n", foo, (void *)&foo);
  return 0;
}

/* file B */
const int foo = 23;

WARNING: Writing to a const datum triggers undefined behavior, no matter that you have used operating system primitives to disable write protection for the page containing it. When I first tested this code, I didn't have const int foo = 23; in its own file, and GCC rewrote both printf calls as printf("...", 23, &foo). This is a valid optimization. If I turned link-time optimization on I would expect this to happen even though the definition of the constant was moved to its own file. Moreover, GCC would also be within its rights to replace *(int *)&foo = 42; with an unconditional abort() or a trap instruction.