Shared object code does not seem to be shared among processes on Linux

93 views Asked by At

The code (aka .text section) of a shared object (.so file) is normally being shared among processes. You can read it here.

I have written a little example where things seem to behave different.

In short: The program proofs that changes made to the code section of a shared object do not affect other instances of the program. Thus the code section of the shared object is not being shared among processes.

Long explanation: The code consists of a program (main.c) which loads a .so file (mylib.c).

The library consists of one single function which returns an integer. This integer is stored in the code section among with the machine instructions. If you don't belive the integer is stored in the code section you can run objdump -d libmy.so.

The program loads the library and then modifies the integer in the code section of the shared object. Before that the program runs mprotect() to avoid a segmentation fault. The value is being changed to the current timestamp.

Now I run the program twice with a delay of 2 seconds so every instance writes its own value to the .so code section. By surprise the value of the first instance is not being overwritten by the value of the second instance.

How is that possible?

I've tested this program on x64 only. Simply save the files and run the bash script.

libmy.c:

int getval()
{
    return 123;
}

main.c:

#include <stdio.h>
#include <time.h>
#include <unistd.h>
#include <stdint.h>
#include <stdlib.h>
#include <sys/mman.h>

int getval();

void unprotect(uint64_t addr)
{
    uint64_t pagesize = sysconf(_SC_PAGE_SIZE);
    addr -= addr % pagesize;
    if (mprotect((void*)addr, pagesize, PROT_READ | PROT_WRITE | PROT_EXEC) != 0)
    {
        printf("mprotect failed\n");
        exit(1);
    }
}

void main()
{
    for (int i = 0; i < 30; i++)
    {
        int* ip = (int*)((char*)&getval + i);
        if (*ip == 123)
        {
            printf("found value at offset %i\n", i);
            unprotect((uint64_t)ip);
            unprotect((uint64_t)ip + 3);
            *ip = (int)time(NULL);
            printf("value changed successfully\n");
            break;
        }
    }

    while (1)
    {
        printf("getval() returns %i\n", getval());
        sleep(1);
    }
}

run.bash:

#!/bin/bash
set -e
gcc -shared -fPIC -o libmy.so libmy.c
gcc main.c -L. -lmy -Wl,-rpath . -o bin
./bin &
sleep 2
./bin
2

There are 2 answers

0
Barmar On BEST ANSWER

A shared object is mapped with the MAP_PRIVATE flag. This means that the memory is initially shared among all the users, but with the "copy on write" flag set for each page. As a result, if a process modifies the memory they get their own copy, it doesn't affect the other processes that have linked with the same object file.

It's obvious that this is necessary for the data section, since each process is expected to make local changes to this. But it's also done for the text section, even though it's read-only by default. As you discovered, you can override the read-only flag, but since it's still COW you get a private copy when you write to it.

0
Luis Colorado On

In short: The program proofs that changes made to the code section of a shared object do not affect other instances of the program. Thus the code section of the shared object is not being shared among processes.

Of course not!!!! if you modify other program's .text segment, without it's knowledge, it will surely crash. Think about having a shared library shared between different users and being modified arbitrarily by one user. What security vulnerabilities can arise from this!!!!

To make a .text segment read-write, you have to specifically allow it. And of course, bypass all the security implications that that has. Normally, if you do that, your .text segment will be marked as COPY ON WRITE and will be copied the first time you happen to change it, making it partially not shared anymore.