1

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)

arnoapp
  • 2,416
  • 4
  • 38
  • 70
  • 2
    From [documentation](http://linux.die.net/man/2/mprotect): "`mprotect()` changes protection for the calling process's memory page(s) containing any part of the address range in the interval `[addr, addr+len-1]`. **`addr` must be aligned to a page boundary.**". – Guilherme Bernal Dec 04 '13 at 17:35

3 Answers3

5

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...

Community
  • 1
  • 1
twalberg
  • 59,951
  • 11
  • 89
  • 84
  • How can i find out which page boundary to use? – arnoapp Dec 04 '13 at 17:40
  • @AzzUrr1 Perhaps you need to read up on pages. What application are you writing that you want to make text pages writable but don't know how to figure out page boundaries? – Dan Fego Dec 04 '13 at 17:43
5

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.

Eric Postpischil
  • 195,579
  • 13
  • 168
  • 312
  • I currently not near a pc. This sounds pretty good but does it also works when foo is a declared function in The same file? – arnoapp Dec 04 '13 at 18:27
  • 1
    @AzzUrr1: There is generally no assurance from the C standard that it works at all; you are trying to do things that are not supported by the C standard and are not generally supported by C implementations. That is why you have been advised against it repeatedly. If you put `foo` in the same source file as the code that changes it, a compiler is likely to optimize the code so that `foo` is never called at all (the `printf` is completely prepared at compile time), so no change to `foo` will have any effect. Turning off optimization might defeat that. – Eric Postpischil Dec 04 '13 at 18:38
  • @EricPostpischil Thanks for this! Works for me. What exactly does this do: `uintptr_t pagestart = start & -pagesize;`? – Sujay Phadke Mar 15 '17 at 19:19
  • @SujayPhadke: It finds the start of the page containing `start`. `pagesize` contains the number of bytes in a page, and it should be a power of two. It has an unsigned type, so `-pagesize` is the two’s complement of the size. In binary two’s complement of a power of two has all the high bits on and the low bits off. E.g, in eight-bit arithmetic, the binary for `-4` is 11111100. When used with the AND operator, `&`, it is a mask that turns off the low bits. Thus, the low bits of the address in `start` are turned off, leaving only the high bits. This is the start of the page. – Eric Postpischil Mar 16 '17 at 21:52
2

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.

zwol
  • 135,547
  • 38
  • 252
  • 361
  • This can fail if `foo` happens to be located in the last bytes of a page, so that the bytes to be written lie in the next page. – Eric Postpischil Dec 04 '13 at 17:58
  • Also note that the write is desired at five bytes beyond `foo`, not at `foo` directly. – Eric Postpischil Dec 04 '13 at 18:04
  • The write can be done without pointer aliasing problems by using `memcpy(DesiredAddress, & (int) {42}, sizeof(int));` rather than using an assignment with converted pointers. – Eric Postpischil Dec 04 '13 at 18:05
  • @EricPostpischil I deliberately simplified the code to write directly to `foo`, and make `foo` a global constant rather than a function address, to focus attention on the correct use of `mprotect`. – zwol Dec 04 '13 at 18:21
  • @EricPostpischil Also, it is not a pointer-aliasing issue that leads me to tag the write to `foo` as "still undefined behavior", but the more fundamental point that an object declared `const` may not be modified by *any* means. In particular, even if you use OS primitives to make the page containing a `const` datum writable, the compiler is *still* entitled to assume you do not write to it, and may e.g. cache a previously loaded value in a register. – zwol Dec 04 '13 at 18:24