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.