TheLogicGuy
TheLogicGuy

Reputation: 691

Shared memory ignores read only flag in Linux c

I'm using shared memory with shmget and shmat for educational purpose.
I'm trying to make a memory chunk to be mutable only by it's creator and all other processes can read only.
But the reader processes can somehow write without any error.

This is my code for the creator of the shared memory:

#include <sys/shm.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>

int main(){
    int shmid = shmget((key_t)56666, 1, IPC_CREAT | O_RDONLY);
     if (shmid ==-1) {
        perror("Err0:");
        exit(EXIT_FAILURE);
    }
    void* shmaddr = shmat(shmid, (void *)0,0);
    if (shmaddr == (void *)-1) {
        perror("Err:");
        exit(EXIT_FAILURE);
    }
    *(char*)shmaddr = 'a'; 
    putchar(*(char*)shmaddr);
    while(1);
    return 0;
}

And this is my code for the reader:

#include <sys/shm.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
int main(){
    int shmid = shmget((key_t)56666, 4, O_RDONLY);
     if (shmid ==-1) {
        perror("Err0:");
        exit(EXIT_FAILURE);
    }
    void* shmaddr = shmat(shmid, (void *)0,0);
    if (shmaddr == (void *)-1) {
        perror("Err:");
        exit(EXIT_FAILURE);
    }
    *(char*)shmaddr = 'b'; 
    putchar(*(char*)shmaddr);
    return 0;
}

As you can see the reader can edit the memory but no error occures even though I open the memory as read only in the reader and created it with read only flag in the creator of the shared memory.

Upvotes: 4

Views: 2120

Answers (1)

Luis Colorado
Luis Colorado

Reputation: 12645

I have not seen any of O_RDONLY or SHM_RDONLY documented as flags for the shmat(2) system call in the linux or freebsd manual pages. Probably the problem is misuse or a misunderstanding on how it works. More on this at the end, as after trying I see that SHM_RDONLY is the flag you should use to control read only attachment, instead of O_RDONLY (which is of no use here)

Probably you have to specify permission bits in the creation shmget(2) system call to disable access for other user's processes, to implement what you want. With permissions, it does work, or you'd have serious security problems with systems that use shared memory (e.g. postgresql database uses sysvipc shared memory segments)

To my knowledge, the best way to implement is to run the writer of the shared memory segment as some user, and the processes allowed to read it as different users, adjusting the permission bits to allow them to read but not to write on the shared memory segment. Something like having all the processes in the same group id, with the writer process as the user who creates the shared memory segment and the others having only read access, with no permissions to other user ids, would be enough for any application.

shmget((key_t)56666, 1, IPC_CREAT | 0640);

and running the other processes as other different user in the same group id.

EDIT

after testing your code in a freebsd machine (sorry, no linux available, but ipc calls are SysV AT&T unix calls, so everything should be compatible) the creation process stops on error on shmat(2) call with the following message:

$ shm_creator
Err:: Permission denied

most probably because you didn't give permissions on shared memory creation, even to the owner (and I try to imagine you are not developing as root in your machine, are you? ;) )

ipcs(1) shows:

usr1@host ~$ ipcs -m
Shared Memory:
T           ID          KEY MODE        OWNER    GROUP   
m        65537        56666 ----------- usr1     usr1    

and you see there are no permission bits active for the shared memory segment, but it has been created. I have modified your program to, instead of doing busywait in a while(1); loop, doing a non consuming cpu wait with sleep(3600); that will put it to sleep for a whole hour.

shm_creator.c

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>

int main(){
    int shmid = shmget((key_t)56666, 1, IPC_CREAT | 0640 );
     if (shmid ==-1) {
        perror("Err0:");
        exit(EXIT_FAILURE);
    }
    void* shmaddr = shmat(shmid, (void *)0,0);
    if (shmaddr == (void *)-1) {
        perror("Err:");
        exit(EXIT_FAILURE);
    }
    *(char*)shmaddr = 'a'; 
    putchar(*(char*)shmaddr);
    puts("");
    sleep(3600);
    return 0;
}

which I run as user usr1:

usr1@host:~/shm$ shm_creator &
[2] 76950
a

then I switch to another user usr2, and run:

$ su usr2
Password:
[usr2@host /home/usr1/shm]$ shm_client &
[1] 76963
[usr2@host /home/usr1/shm]$ Err:: Permission denied

and as you labeled it, it happens in the shmat(2) system call. But if I run it as usr1 i get:

usr1@host:~/shm$ shm_client 
b

if using SHM_RDONLY as flag in the shm_client.c source file, on running (either as same or different user) I get the following:

usr1@host:~/shm$ shm_client
Segmentation fault (generated `core')

which is expected behaviour, as you tried to write unwritable memory (it was attached as read only memory)

EDIT 2

After browsing online the linux manual pages, there's a reference to SHM_RDONLY to allow to attach a shared memory segment as readonly. No support is offered for write only shared memory segments, otherwise. As it is not documented on freebsd, this option is also available there (the constant is included in the proper include files) and some other imprecisions are found in the freebsd manual (as the use of S_IROWN, S_IWOWN, S_IRGRP, S_IWGRP, S_IROTH and S_IWOTH flags to control the permission bits and no inclusion of #include <sys/stat.h> in the SYNOPSIS of the manual page)

CONCLUSSION

If the SHM_RDONLY is available in your system, then you can use it as a non-preemptive way to disallow write access to you shared memory, but if you want kernel enforced way, you have to switch to the user permission bits approach.

Upvotes: 1

Related Questions