Dal manuale :
On Linux it is always permissible to call mprotect() on any address in
a process's address space (except for the kernel vsyscall area). In
particular it can be used to change existing code mappings to be
writable.
Ecco un esempio di programma da dimostrare.
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#define PAGE_SIZE 4096
const unsigned char rodata[3*PAGE_SIZE] = {1,2,3};
int main(void)
{
printf("rodata = %p\n", rodata);
uintptr_t page_base = ((uintptr_t)rodata / PAGE_SIZE + 1) * PAGE_SIZE;
unsigned char *p = (unsigned char *)rodata + PAGE_SIZE;
//*p = '!'; // this would cause a segfault
puts("Before mprotect:");
system("cat /proc/$PPID/maps");
if (mprotect((void*)page_base, 1, PROT_READ | PROT_WRITE) < 0) {
perror("mprotect");
return 1;
}
puts("After mprotect:");
system("cat /proc/$PPID/maps");
*p = '!';
return 0;
}
Ovviamente tutti i dati che scrivi nella pagina rimarranno in memoria. Linux vede che il processo sta scrivendo su una pagina attualmente mappata in sola lettura e ne fa una copia. Al momento della scrittura, il kernel non distingue questo da copia su scrittura dopo che un processo è stato biforcato. Puoi osservare questo attraverso biforcazione, scrittura in un processo e lettura nell'altro: l'altro processo non vedrà la scrittura poiché è una scrittura nella memoria del processo di scrittura, non nella memoria del processo di lettura.
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <unistd.h>
#define PAGE_SIZE 4096
const unsigned char rodata[3*PAGE_SIZE] = {0};
void writer(char *p)
{
if (mprotect(p, 1, PROT_READ | PROT_WRITE) < 0) {
perror("mprotect");
return 1;
}
puts("After mprotect:");
system("cat /proc/$PPID/maps");
*p = 1;
printf("wrote %d\n", *p);
}
void reader(char *p)
{
printf("read %d\n", *p);
}
int main(void)
{
printf("rodata = %p\n", rodata);
uintptr_t page_base = (((uintptr_t)rodata / PAGE_SIZE + 1) * PAGE_SIZE);
volatile char *p = (volatile char *)page_base;
//*p = '!'; // this would cause a segfault
puts("Before mprotect:");
system("cat /proc/$PPID/maps");
if (fork() == 0) {
writer(p);
} else {
sleep(1);
reader(p);
}
return 0;
}
Ho il sospetto che ci siano patch indurenti che impediscono a un processo di modificare i propri mapping della memoria, ma non ne ho uno da offrire.