Shared memory ignores read-only flag in Linux c
I use shared memory with shmget
and shmat
for educational purposes.
I am trying to make a chunk of memory mutable only by this creator and all other processes can only read.
But the reading processes can somehow write without errors.
This is my code for the shared memory creator:
#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 is able to edit the memory, but the error does not occur even though I open the memory as read only in the reader and create it with a single read-only flag in the shared memory creator.
source to share
I have not seen any of the O_RDONLY
or SHM_RDONLY
documented as flags for the system call shmat(2)
in the linux or freebsd man pages. The problem is likely a misuse or misunderstanding of how it works. More on this at the end, as after trying I see that SHM_RDONLY
- this is the flag you should use to control read-only attachment, instead of O_RDONLY
(which is useless here)
You probably need to specify the permission bits in the create system call shmget(2)
to disable access for other user processes in order to implement what you want. With permissions, it works, or you run into serious security issues on systems using shared memory (for example, the postgresql database uses sysvipc shared memory segments)
As far as I know, the best way to implement is to run the script of the shared memory segment as some user and the processes allowed it to be read as different users by setting the permission bits so that they can read but not write on the shared memory segment. Something like that all processes in the same group id with the write process as the user creating the shared memory segment and others having read-only access without rights to other user IDs would be sufficient for any application.
shmget((key_t)56666, 1, IPC_CREAT | 0640);
and start other processes as a different user on the same group ID.
EDIT
after testing your code on freebsd machine (sorry linux is not available, but ipc calls are AT&T unix SysV calls, so everything should be compatible) the build process stops on error when called shmat(2)
with the following message
$ shm_creator Err:: Permission denied
most likely because you didnโt give permission to create shared memory even to the owner (and Iโm trying to imagine that you donโt develop like root
in your machine, do 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 active permission bits for the shared memory segment, but it was created. I changed your program, instead of doing busywait in a loop while(1);
, doing the waiting processor with a help sleep(3600);
that will make it sleep for an 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 will 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 noted, it happens on a system call shmat(2)
. But if I run it like usr1
, I get:
usr1@host:~/shm$ shm_client
b
if used SHM_RDONLY
as a flag in a source file shm_client.c
, when run (either as the same or a different user) I get this:
usr1@host:~/shm$ shm_client
Segmentation fault (generated `core')
which is the expected behavior as you were trying to write non-playable memory (it was attached as read-only memory)
EDIT 2
After browsing the online linux manual pages, please refer to SHM_RDONLY
to allow attach shared memory segment as read-only. Otherwise, support is not supported for write-only shared memory segments. Because it is not registered on the freebsd, this option is also available there (the constant is included in the corresponding include files), and some other discrepancies found in the freebsd guide (as the use of S_IROWN
, S_IWOWN
, S_IRGRP
, S_IWGRP
, S_IROTH
and S_IWOTH
to control bits permits and without the inclusion #include <sys/stat.h>
in the manual pages SYNOPSIS)
CONCLUSSION
If SHM_RDONLY
available on your system, you can use it as an unmanaged way to deny write access to your shared memory, but if you want the kernel to be enforced you need to switch to the user permission bit.
source to share