Permission struct
Read structure
Modify structure
Associated Data Structures
For every System V IPC object created, the kernel maintains an associated data structure. This structure stores metadata about the object — including ownership, permissions, usage statistics, and the IPC key.
The exact form of this structure depends on the IPC mechanism:
- Message queues →
struct msqid_ds(in<sys/msg.h>) - Semaphores →
struct semid_ds(in<sys/sem.h>) - Shared memory →
struct shmid_ds(in<sys/shm.h>)
Despite their differences, all three structures share a common sub-structure called ipc_perm that holds ownership and permission information.
The ipc_perm structure is embedded inside every IPC associated data structure. It is defined in <sys/ipc.h>:
struct ipc_perm {
key_t __key; /* Key, as supplied to the 'get' call */
uid_t uid; /* Owner's user ID (changeable) */
gid_t gid; /* Owner's group ID (changeable) */
uid_t cuid; /* Creator's user ID (immutable) */
gid_t cgid; /* Creator's group ID (immutable) */
unsigned short mode; /* Permissions bit mask */
unsigned short __seq; /* Sequence number (used in ID calculation) */
};
__key and __seq are required by the standard. Both are provided by most UNIX implementations as well.You interact with the associated data structure using the ctl calls (msgctl(), semctl(), shmctl()) with two generic operations:
IPC_STAT— copies the kernel’s associated data structure into a user-space buffer (requires read permission)IPC_SET— updates selected fields in the kernel from the user-space buffer (requires ownership or privilege)
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>
#include <stdlib.h>
int main(void) {
key_t key = 77777;
int shmid;
struct shmid_ds shmds;
/* Create a shared memory segment */
shmid = shmget(key, 4096, IPC_CREAT | 0664);
if (shmid == -1) { perror("shmget"); exit(EXIT_FAILURE); }
/* IPC_STAT: fetch the associated data structure from kernel */
if (shmctl(shmid, IPC_STAT, &shmds) == -1) {
perror("shmctl IPC_STAT");
exit(EXIT_FAILURE);
}
/* Print permission info from the embedded ipc_perm structure */
printf("--- Shared Memory Info (ID=%d) ---\n", shmid);
printf("Key: 0x%x\n", (unsigned int)shmds.shm_perm.__key);
printf("Owner UID: %d\n", shmds.shm_perm.uid);
printf("Owner GID: %d\n", shmds.shm_perm.gid);
printf("Creator UID: %d\n", shmds.shm_perm.cuid);
printf("Creator GID: %d\n", shmds.shm_perm.cgid);
printf("Mode (octal): %04o\n", shmds.shm_perm.mode & 0777);
printf("Segment size: %zu bytes\n", shmds.shm_segsz);
printf("Attached: %lu processes\n", (unsigned long)shmds.shm_nattch);
/* Cleanup */
shmctl(shmid, IPC_RMID, NULL);
return 0;
}
The following shows how to change the owner UID of a shared memory segment. This requires that you are the creator, current owner, or have the CAP_SYS_ADMIN capability:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>
#include <stdlib.h>
int main(void) {
key_t key = 88888;
int shmid;
struct shmid_ds shmds;
uid_t new_uid = 1001; /* Replace with actual UID on your system */
/* Create shared memory */
shmid = shmget(key, 4096, IPC_CREAT | 0600);
if (shmid == -1) { perror("shmget"); exit(EXIT_FAILURE); }
/* Step 1: Fetch current data structure from kernel */
if (shmctl(shmid, IPC_STAT, &shmds) == -1) {
perror("shmctl IPC_STAT");
exit(EXIT_FAILURE);
}
printf("Before: Owner UID = %d\n", shmds.shm_perm.uid);
/* Step 2: Modify the owner UID in our local copy */
shmds.shm_perm.uid = new_uid;
/* Step 3: Write the modified structure back to the kernel */
if (shmctl(shmid, IPC_SET, &shmds) == -1) {
perror("shmctl IPC_SET");
exit(EXIT_FAILURE);
}
/* Step 4: Verify the change */
if (shmctl(shmid, IPC_STAT, &shmds) == -1) {
perror("shmctl IPC_STAT");
exit(EXIT_FAILURE);
}
printf("After: Owner UID = %d\n", shmds.shm_perm.uid);
/* Cleanup */
shmctl(shmid, IPC_RMID, NULL);
return 0;
}
uid field (owner) is changeable. The cuid (creator) field is immutable — once set at creation time, it cannot be changed. You can also change gid and mode via IPC_SET.Like files, IPC permissions are divided into three categories: owner (user), group, and other. But there are important differences from file permissions:
| Aspect | File Permissions | IPC Permissions |
|---|---|---|
| Permission types | read, write, execute | read and write only (execute is ignored) |
| Check based on | Process’s filesystem IDs | Process’s effective user ID & effective/supplementary group IDs |
| Semaphore “write” | N/A | Commonly called “alter” permission |
| umask applied? | Yes, on creation | No — permissions set exactly as specified |
When a process tries to access an IPC object, the kernel applies these checks in order:
uid (owner) or cuid (creator), grant owner permissions.gid (owner group) or cgid (creator group), grant group permissions.| Operation | Required Permission |
|---|---|
| Read a message / get semaphore value / attach shm for reading | Read permission |
| Write a message / change semaphore / attach shm for writing | Write permission |
| IPC_STAT (fetch data structure) | Read permission |
| IPC_RMID (delete object) | Privileged (CAP_SYS_ADMIN) OR effective UID = owner UID or creator UID |
| IPC_SET (change data structure) | Privileged (CAP_SYS_ADMIN) OR effective UID = owner UID or creator UID |
This example demonstrates what happens when a second process tries to write to a queue that only allows read access for the group:
/* --- Process A (queue owner): creates queue with rw-r----- ---*/
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
struct mymsg { long mtype; char mtext[64]; };
int main(void) {
key_t key = 11111;
int msqid;
/* Permissions: owner rw, group r, others nothing
S_IRUSR | S_IWUSR | S_IRGRP = 0640 */
msqid = msgget(key, IPC_CREAT | S_IRUSR | S_IWUSR | S_IRGRP);
if (msqid == -1) { perror("msgget"); exit(EXIT_FAILURE); }
printf("Queue created with perms 0640, ID=%d\n", msqid);
/* --- Process B (same group, different user): tries to write ---
A user in the group tries to open the queue with write permission */
int id2 = msgget(key, S_IRUSR | S_IWUSR); /* requests owner-rw */
if (id2 == -1) {
if (errno == EACCES) {
printf("Access denied! Caller is not the owner.\n");
} else {
perror("msgget");
}
}
/* Correct way for a group member: open with 0 flags
(permission check happens at operation time, not get() time) */
int id3 = msgget(key, 0);
if (id3 != -1) {
printf("Opened queue (no flags). ID=%d\n", id3);
struct mymsg msg = { .mtype = 1 };
snprintf(msg.mtext, sizeof(msg.mtext), "test");
/* This will fail at runtime with EACCES because group has no write perm */
if (msgsnd(id3, &msg, sizeof(msg.mtext), 0) == -1) {
if (errno == EACCES)
printf("msgsnd denied — group has read-only permission.\n");
else
perror("msgsnd");
}
}
/* Cleanup */
msgctl(msqid, IPC_RMID, NULL);
return 0;
}
