En el manual :
En Linux siempre está permitido llamar a mprotect () en cualquier dirección en
el espacio de direcciones de un proceso (excepto para el área de kernel vsyscall). En
en particular, se puede utilizar para cambiar las asignaciones de código existentes para ser
escribible.
Aquí hay un programa de ejemplo para demostrar.
#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;
}
Por supuesto, cualquier información que escriba en la página permanecerá en la memoria. Linux ve que el proceso está escribiendo en una página que actualmente está asignada como de solo lectura y hace una copia. En el momento de la escritura, el kernel no distingue esto de la copia en escritura después de que se haya bifurcado un proceso. Puede observar esto forzando, escribiendo en un proceso y leyendo en el otro: el otro proceso no verá la escritura ya que es una escritura en la memoria del proceso de escritura, no en la memoria del proceso de lectura.
#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;
}
Sospecho que hay parches de refuerzo que impiden que un proceso cambie sus propias asignaciones de memoria, pero no tengo una para ofrecer.