Linux – Is it possible to update flags for existing memory maps?

Is it possible to update flags for existing memory maps?… here is a solution to the problem.

Is it possible to update flags for existing memory maps?

I’m writing a small program in an x64 assembly that will spawn children, all of whom share their memory mapping so that they can modify each other’s code. For this, since the sys_clone parameter CLONE_VM seems to put the program in undefined behavior, I plan to use the MAP_SHARED parameter of mmap.

However, I also need child to modify the father’s code. One option is to also assign an MAP_SHARED mapping and give it to the father, but I want to avoid this as much as possible (just for elegant reasons).

Since the program’s base mapping (0x400000 on 64-bit Linux) doesn’t have an MAP_SHARED flag by default, is it possible to use the system call that updates it to set this flag? mmap then mmap will not execute and cause SIGSEGV, and mprotect can only change RWX permissions.

Solution

You cannot change whether an existing mapping is private or shared, but you can map a new shared mapping on top of an existing private mapping. You can even do this in C as follows:

#define _GNU_SOURCE

#include <stdio.h>
#include <string.h>
#include <sys/mman.h>

int main(void) {
    FILE *stream = fopen("/proc/self/maps", "rb");
    if(!stream) {
        perror("fopen");
        return 1;
    }
    char *text_start, *text_end;
    do {
        if(fscanf(stream, " %p-%p%*[^\n]", &text_start, &text_end) != 2) {
            perror("scanf");
            return 1;
        }
    } while(!( text_start <= main && main < text_end));
    if(fclose(stream)) {
        perror("fclose");
        return 1;
    }
    size_t text_len = text_end - text_start;
    char *mem = mmap(NULL, text_len, PROT_READ| PROT_WRITE| PROT_EXEC, MAP_SHARED| MAP_ANONYMOUS, -1, 0);
    if(mem == MAP_FAILED) {
        perror("mmap");
        return 1;
    }
    memcpy(mem, text_start, text_len);
    __builtin___clear_cache(mem, mem + text_len);
    if(mremap(mem, text_len, text_len, MREMAP_MAYMOVE| MREMAP_FIXED, text_start) == MAP_FAILED) {
        perror("mremap");
        return 1;
    }
    /* you can check /proc/PID/maps now to see the new mapping */
    getchar();
}

As a bonus, the program supports ASLR and does not require the text section to start with 0x400000.

Related Problems and Solutions