phunix/minix/tests/test88.c
David van Moolenbroek 3083d603ba Resolve a number of GCC-generated warnings
The warnings in test47 seem to be a symptom of a larger problem,
i.e., not an issue with the test set code but rather with the GCC
configuration.  Hopefully the switch to LLVM will resolve those.

Change-Id: Ic9fa3b8bc9b728947c993f2e1ed49d9a3b731344
2016-08-05 16:24:04 +02:00

3012 lines
73 KiB
C

/* Tests for System V IPC semaphores - by D.C. van Moolenbroek */
/* This test must be run as root, as it includes permission checking tests. */
#include <stdlib.h>
#include <limits.h>
#include <pwd.h>
#include <grp.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/wait.h>
#include <sys/mman.h>
#include <sys/sysctl.h>
#include <signal.h>
#include "common.h"
#define ITERATIONS 3
#define WAIT_USECS 100000 /* time for processes to get ready */
#define KEY_A 0x73570001
#define KEY_B (KEY_A + 1)
#define KEY_C (KEY_A + 2)
#define ROOT_USER "root" /* name of root */
#define ROOT_GROUP "operator" /* name of root's group */
#define NONROOT_USER "bin" /* name of any unprivileged user */
#define NONROOT_GROUP "bin" /* name of any unprivileged group */
enum {
DROP_NONE,
DROP_USER,
DROP_ALL,
};
enum {
SUGID_NONE,
SUGID_ROOT_USER,
SUGID_NONROOT_USER,
SUGID_ROOT_GROUP,
SUGID_NONROOT_GROUP,
};
struct link {
pid_t pid;
int sndfd;
int rcvfd;
};
/*
* Test semaphore properties. This is a macro, so that it prints useful line
* information if an error occurs.
*/
#define TEST_SEM(id, num, val, pid, ncnt, zcnt) do { \
if (semctl(id, num, GETVAL) != val) e(0); \
if (pid != -1 && semctl(id, num, GETPID) != pid) e(1); \
if (ncnt != -1 && semctl(id, num, GETNCNT) != ncnt) e(2); \
if (zcnt != -1 && semctl(id, num, GETZCNT) != zcnt) e(3); \
} while (0);
static int nr_signals = 0;
static size_t page_size;
static char *page_ptr;
static void *bad_ptr;
/*
* Spawn a child process, with a pair of pipes to talk to it bidirectionally.
* Drop user and group privileges in the child process if requested.
*/
static void
spawn(struct link * link, void (* proc)(struct link *), int drop)
{
struct passwd *pw;
struct group *gr;
int up[2], dn[2];
fflush(stdout);
fflush(stderr);
if (pipe(up) != 0) e(0);
if (pipe(dn) != 0) e(0);
link->pid = fork();
switch (link->pid) {
case 0:
close(up[1]);
close(dn[0]);
link->pid = getppid();
link->rcvfd = up[0];
link->sndfd = dn[1];
errct = 0;
switch (drop) {
case DROP_ALL:
if (setgroups(0, NULL) != 0) e(0);
if ((gr = getgrnam(NONROOT_GROUP)) == NULL) e(0);
if (setgid(gr->gr_gid) != 0) e(0);
if (setegid(gr->gr_gid) != 0) e(0);
/* FALLTHROUGH */
case DROP_USER:
if ((pw = getpwnam(NONROOT_USER)) == NULL) e(0);
if (setuid(pw->pw_uid) != 0) e(0);
}
proc(link);
/* Close our pipe FDs on exit, so that we can make zombies. */
exit(errct);
case -1:
e(0);
break;
}
close(up[0]);
close(dn[1]);
link->sndfd = up[1];
link->rcvfd = dn[0];
}
/*
* Wait for a child process to terminate, and clean up.
*/
static void
collect(struct link * link)
{
int status;
close(link->sndfd);
close(link->rcvfd);
if (waitpid(link->pid, &status, 0) != link->pid) e(0);
if (!WIFEXITED(status)) e(0);
else errct += WEXITSTATUS(status);
}
/*
* Forcibly terminate a child process, and clean up.
*/
static void
terminate(struct link * link)
{
int status;
if (kill(link->pid, SIGKILL) != 0) e(0);
close(link->sndfd);
close(link->rcvfd);
if (waitpid(link->pid, &status, 0) <= 0) e(0);
if (WIFSIGNALED(status)) {
if (WTERMSIG(status) != SIGKILL) e(0);
} else {
if (!WIFEXITED(status)) e(0);
else errct += WEXITSTATUS(status);
}
}
/*
* Send an integer value to the child or parent.
*/
static void
snd(struct link * link, int val)
{
if (write(link->sndfd, (void *)&val, sizeof(val)) != sizeof(val)) e(0);
}
/*
* Receive an integer value from the child or parent, or -1 on EOF.
*/
static int
rcv(struct link * link)
{
int r, val;
if ((r = read(link->rcvfd, (void *)&val, sizeof(val))) == 0)
return -1;
if (r != sizeof(val)) e(0);
return val;
}
/*
* Child procedure that creates semaphore sets.
*/
static void
test_perm_child(struct link * parent)
{
struct passwd *pw;
struct group *gr;
struct semid_ds semds;
uid_t uid;
gid_t gid;
int mask, rmask, sugid, id[3];
/*
* Repeatedly create a number of semaphores with the masks provided by
* the parent process.
*/
while ((mask = rcv(parent)) != -1) {
rmask = rcv(parent);
sugid = rcv(parent);
/*
* Create the semaphores. For KEY_A, if we are going to set
* the mode through IPC_SET anyway, start with a zero mask to
* check that the replaced mode is used (thus testing IPC_SET).
*/
if ((id[0] = semget(KEY_A, 3,
IPC_CREAT | IPC_EXCL |
((sugid == SUGID_NONE) ? mask : 0))) == -1) e(0);
if ((id[1] = semget(KEY_B, 3,
IPC_CREAT | IPC_EXCL | mask | rmask)) == -1) e(0);
if ((id[2] = semget(KEY_C, 3,
IPC_CREAT | IPC_EXCL | rmask)) == -1) e(0);
uid = geteuid();
gid = getegid();
if (sugid != SUGID_NONE) {
switch (sugid) {
case SUGID_ROOT_USER:
if ((pw = getpwnam(ROOT_USER)) == NULL) e(0);
uid = pw->pw_uid;
break;
case SUGID_NONROOT_USER:
if ((pw = getpwnam(NONROOT_USER)) == NULL)
e(0);
uid = pw->pw_uid;
break;
case SUGID_ROOT_GROUP:
if ((gr = getgrnam(ROOT_GROUP)) == NULL) e(0);
gid = gr->gr_gid;
break;
case SUGID_NONROOT_GROUP:
if ((gr = getgrnam(NONROOT_GROUP)) == NULL)
e(0);
gid = gr->gr_gid;
break;
}
semds.sem_perm.uid = uid;
semds.sem_perm.gid = gid;
semds.sem_perm.mode = mask;
if (semctl(id[0], 0, IPC_SET, &semds) != 0) e(0);
semds.sem_perm.mode = mask | rmask;
if (semctl(id[1], 0, IPC_SET, &semds) != 0) e(0);
semds.sem_perm.mode = rmask;
if (semctl(id[2], 0, IPC_SET, &semds) != 0) e(0);
}
/* Do a quick test to confirm the right privileges. */
if (mask & IPC_R) {
if (semctl(id[0], 0, IPC_STAT, &semds) != 0) e(0);
if (semds.sem_perm.mode != (SEM_ALLOC | mask)) e(0);
if (semds.sem_perm.uid != uid) e(0);
if (semds.sem_perm.gid != gid) e(0);
if (semds.sem_perm.cuid != geteuid()) e(0);
if (semds.sem_perm.cgid != getegid()) e(0);
}
snd(parent, id[0]);
snd(parent, id[1]);
snd(parent, id[2]);
/* The other child process runs here. */
if (rcv(parent) != 0) e(0);
/*
* For owner tests, the other child may already have removed
* the semaphore sets, so ignore return values here.
*/
(void)semctl(id[0], 0, IPC_RMID);
(void)semctl(id[1], 0, IPC_RMID);
(void)semctl(id[2], 0, IPC_RMID);
}
}
/*
* Perform a permission test. The given procedure will be called for various
* access masks, which it can use to determine whether operations on three
* created semaphore sets should succeed or fail. The first two semaphore sets
* are created with appropriate privileges, the third one is not. If the
* 'owner_test' variable is set, the test will change slightly so as to allow
* testing of operations that require a matching uid/cuid.
*/
static void
test_perm(void (* proc)(struct link *), int owner_test)
{
struct link child1, child2;
int n, shift, bit, mask, rmask, drop1, drop2, sugid, id[3];
for (n = 0; n < 7; n++) {
/*
* Child 1 creates the semaphores, and child 2 opens them.
* For shift 6 (0700), child 1 drops its privileges to match
* child 2's (n=0). For shift 3 (0070), child 2 drops its user
* privileges (n=3). For shift 0 (0007), child 2 drops its
* group in addition to its user privileges (n=6). Also try
* with differing uid/cuid (n=1,2) and gid/cgid (n=4,5), where
* the current ownership (n=1,4) or the creator's ownership
* (n=2,5) is tested.
*/
switch (n) {
case 0:
shift = 6;
drop1 = DROP_ALL;
drop2 = DROP_ALL;
sugid = SUGID_NONE;
break;
case 1:
shift = 6;
drop1 = DROP_NONE;
drop2 = DROP_ALL;
sugid = SUGID_NONROOT_USER;
break;
case 2:
shift = 6;
drop1 = DROP_USER;
drop2 = DROP_ALL;
sugid = SUGID_ROOT_USER;
break;
case 3:
shift = 3;
drop1 = DROP_NONE;
drop2 = DROP_USER;
sugid = SUGID_NONE;
break;
case 4:
shift = 3;
drop1 = DROP_NONE;
drop2 = DROP_ALL;
sugid = SUGID_NONROOT_GROUP;
break;
case 5:
/* The root group has no special privileges. */
shift = 3;
drop1 = DROP_NONE;
drop2 = DROP_USER;
sugid = SUGID_NONROOT_GROUP;
break;
case 6:
shift = 0;
drop1 = DROP_NONE;
drop2 = DROP_ALL;
sugid = SUGID_NONE;
break;
}
spawn(&child1, test_perm_child, drop1);
spawn(&child2, proc, drop2);
for (bit = 0; bit <= 7; bit++) {
mask = bit << shift;
rmask = 0777 & ~(7 << shift);
snd(&child1, mask);
snd(&child1, rmask);
snd(&child1, sugid);
id[0] = rcv(&child1);
id[1] = rcv(&child1);
id[2] = rcv(&child1);
snd(&child2, (owner_test) ? shift : bit);
snd(&child2, id[0]);
snd(&child2, id[1]);
snd(&child2, id[2]);
if (rcv(&child2) != 0) e(0);
snd(&child1, 0);
}
/* We use a bitmask of -1 to terminate the children. */
snd(&child1, -1);
snd(&child2, -1);
collect(&child1);
collect(&child2);
}
}
/*
* Test semget(2) permission checks. Please note that the checks are advisory:
* nothing keeps a process from opening a semaphore set with fewer privileges
* than required by the operations the process subsequently issues on the set.
*/
static void
test88a_perm(struct link * parent)
{
int r, tbit, bit, mask, id[3];
while ((tbit = rcv(parent)) != -1) {
id[0] = rcv(parent);
id[1] = rcv(parent);
id[2] = rcv(parent);
/*
* We skip setting lower bits, as it is not clear what effect
* that should have. We assume that zero bits should result in
* failure.
*/
for (bit = 0; bit <= 7; bit++) {
mask = bit << 6;
/*
* Opening semaphore set A must succeed iff the given
* bits are all set in the relevant three-bit section
* of the creation mask.
*/
r = semget(KEY_A, 0, mask);
if (r < 0 && (r != -1 || errno != EACCES)) e(0);
if ((bit != 0 && (bit & tbit) == bit) != (r != -1))
e(0);
if (r != -1 && r != id[0]) e(0);
/*
* Same for semaphore set B, which was created with all
* irrelevant mode bits inverted.
*/
r = semget(KEY_B, 0, mask);
if (r < 0 && (r != -1 || errno != EACCES)) e(0);
if ((bit != 0 && (bit & tbit) == bit) != (r != -1))
e(0);
if (r != -1 && r != id[1]) e(0);
/*
* Semaphore set C was created with only irrelevant
* mode bits set, so opening it must always fail.
*/
if (semget(KEY_C, 0, mask) != -1) e(0);
if (errno != EACCES) e(0);
}
snd(parent, 0);
}
}
/*
* Test the basic semget(2) functionality.
*/
static void
test88a(void)
{
struct seminfo seminfo;
struct semid_ds semds;
time_t now;
unsigned int i, j;
int id[3], *idp;
subtest = 0;
/*
* The key IPC_PRIVATE must always yield a new semaphore set identifier
* regardless of whether IPC_CREAT and IPC_EXCL are supplied.
*/
if ((id[0] = semget(IPC_PRIVATE, 1, IPC_CREAT | 0600)) < 0) e(0);
if ((id[1] = semget(IPC_PRIVATE, 1, IPC_CREAT | IPC_EXCL | 0600)) < 0)
e(0);
if ((id[2] = semget(IPC_PRIVATE, 1, 0600)) < 0) e(0);
if (id[0] == id[1]) e(0);
if (id[1] == id[2]) e(0);
if (id[0] == id[2]) e(0);
if (semctl(id[0], 0, IPC_RMID) != 0) e(0);
if (semctl(id[1], 0, IPC_RMID) != 0) e(0);
if (semctl(id[2], 0, IPC_RMID) != 0) e(0);
/* Remove any leftovers from previous test runs. */
if ((id[0] = semget(KEY_A, 0, 0600)) >= 0 &&
semctl(id[0], 0, IPC_RMID) == -1) e(0);
if ((id[0] = semget(KEY_B, 0, 0600)) >= 0 &&
semctl(id[0], 0, IPC_RMID) == -1) e(0);
/*
* For non-IPC_PRIVATE keys, open(2)-like semantics apply with respect
* to IPC_CREAT and IPC_EXCL flags. The behavior of supplying IPC_EXCL
* without IPC_CREAT is undefined, so we do not test for that here.
*/
if (semget(KEY_A, 1, 0600) != -1) e(0);
if (errno != ENOENT);
if ((id[0] = semget(KEY_A, 1, IPC_CREAT | IPC_EXCL | 0600)) < 0) e(0);
if (semget(KEY_B, 1, 0600) != -1) e(0);
if (errno != ENOENT);
if ((id[1] = semget(KEY_B, 1, IPC_CREAT | 0600)) < 0) e(0);
if (id[0] == id[1]) e(0);
if ((id[2] = semget(KEY_A, 1, 0600)) < 0) e(0);
if (id[2] != id[0]) e(0);
if ((id[2] = semget(KEY_B, 1, IPC_CREAT | 0600)) < 0) e(0);
if (id[2] != id[2]) e(0);
if (semget(KEY_A, 1, IPC_CREAT | IPC_EXCL | 0600) != -1) e(0);
if (errno != EEXIST) e(0);
if (semctl(id[0], 0, IPC_RMID) != 0) e(0);
if (semctl(id[1], 0, IPC_RMID) != 0) e(0);
/*
* Check that we get the right error when we run out of semaphore sets.
* It is possible that other processes in the system are using sets
* right now, so see if we can anywhere from three (the number we had
* already) to SEMMNI semaphore sets, and check for ENOSPC after that.
*/
if (semctl(0, 0, IPC_INFO, &seminfo) == -1) e(0);
if (seminfo.semmni < 3 || seminfo.semmni > USHRT_MAX) e(0);
if ((idp = malloc(sizeof(int) * (seminfo.semmni + 1))) == NULL) e(0);
for (i = 0; i < seminfo.semmni + 1; i++) {
if ((idp[i] = semget(KEY_A + i, 1, IPC_CREAT | 0600)) < 0)
break;
/* Ensure that there are no ID collisions. O(n**2). */
for (j = 0; j < i; j++)
if (idp[i] == idp[j]) e(0);
}
if (errno != ENOSPC) e(0);
if (i < 3) e(0);
if (i == seminfo.semmni + 1) e(0);
while (i-- > 0)
if (semctl(idp[i], 0, IPC_RMID) != 0) e(0);
free(idp);
/*
* The given number of semaphores must be within bounds.
*/
if (semget(KEY_A, -1, IPC_CREAT | 0600) != -1) e(0);
if (errno != EINVAL) e(0);
if (semget(KEY_A, 0, IPC_CREAT | 0600) != -1) e(0);
if (errno != EINVAL) e(0);
if (seminfo.semmsl < 3 || seminfo.semmsl > USHRT_MAX) e(0);
if (semget(KEY_A, seminfo.semmsl + 1, IPC_CREAT | 0600) != -1) e(0);
if (errno != EINVAL) e(0);
if ((id[0] = semget(KEY_A, seminfo.semmsl, IPC_CREAT | 0600)) < 0)
e(0);
if (semctl(id[0], 0, IPC_RMID) != 0) e(0);
if ((id[0] = semget(KEY_A, 2, IPC_CREAT | 0600)) < 0) e(0);
if ((id[1] = semget(KEY_A, 0, 0600)) < 0) e(0);
if (id[0] != id[1]) e(0);
if ((id[1] = semget(KEY_A, 1, 0600)) < 0) e(0);
if (id[0] != id[1]) e(0);
if ((id[1] = semget(KEY_A, 2, 0600)) < 0) e(0);
if (id[0] != id[1]) e(0);
if ((id[1] = semget(KEY_A, 3, 0600)) != -1) e(0);
if (errno != EINVAL) e(0);
if ((id[1] = semget(KEY_A, seminfo.semmsl + 1, 0600)) != -1) e(0);
if (errno != EINVAL) e(0);
if (semctl(id[0], 0, IPC_RMID) != 0) e(0);
/*
* Verify that the initial values for the semaphore set are as
* expected.
*/
time(&now);
if (seminfo.semmns < 3 + seminfo.semmsl) e(0);
if ((id[0] = semget(IPC_PRIVATE, 3, IPC_CREAT | IPC_EXCL | 0642)) < 0)
e(0);
if ((id[1] = semget(KEY_A, seminfo.semmsl, IPC_CREAT | 0613)) < 0)
e(0);
if (semctl(id[0], 0, IPC_STAT, &semds) != 0) e(0);
if (semds.sem_perm.uid != geteuid()) e(0);
if (semds.sem_perm.gid != getegid()) e(0);
if (semds.sem_perm.cuid != geteuid()) e(0);
if (semds.sem_perm.cgid != getegid()) e(0);
if (semds.sem_perm.mode != (SEM_ALLOC | 0642)) e(0);
if (semds.sem_perm._key != IPC_PRIVATE) e(0);
if (semds.sem_nsems != 3) e(0);
if (semds.sem_otime != 0) e(0);
if (semds.sem_ctime < now || semds.sem_ctime >= now + 10) e(0);
for (i = 0; i < semds.sem_nsems; i++)
TEST_SEM(id[0], i, 0, 0, 0, 0);
if (semctl(id[1], 0, IPC_STAT, &semds) != 0) e(0);
if (semds.sem_perm.uid != geteuid()) e(0);
if (semds.sem_perm.gid != getegid()) e(0);
if (semds.sem_perm.cuid != geteuid()) e(0);
if (semds.sem_perm.cgid != getegid()) e(0);
if (semds.sem_perm.mode != (SEM_ALLOC | 0613)) e(0);
if (semds.sem_perm._key != KEY_A) e(0);
if (semds.sem_nsems != seminfo.semmsl) e(0);
if (semds.sem_otime != 0) e(0);
if (semds.sem_ctime < now || semds.sem_ctime >= now + 10) e(0);
for (i = 0; i < semds.sem_nsems; i++)
TEST_SEM(id[1], i, 0, 0, 0, 0);
if (semctl(id[1], 0, IPC_RMID) != 0) e(0);
if (semctl(id[0], 0, IPC_RMID) != 0) e(0);
/*
* Finally, perform a number of permission-related checks. Since the
* main test program is running with superuser privileges, most of the
* permission tests use an unprivileged child process.
*/
/* The superuser can always open and destroy a semaphore set. */
if ((id[0] = semget(KEY_A, 1, IPC_CREAT | IPC_EXCL | 0000)) < 0) e(0);
if ((id[1] = semget(KEY_A, 0, 0600)) < 0) e(0);
if (id[0] != id[1]) e(0);
if ((id[1] = semget(KEY_A, 0, 0000)) < 0) e(0);
if (id[0] != id[1]) e(0);
if (semctl(id[0], 0, IPC_RMID) != 0) e(0);
/*
* When an unprivileged process tries to open a semaphore set, the
* given upper three permission bits from the mode (0700) are tested
* against the appropriate permission bits from the semaphore set.
*/
test_perm(test88a_perm, 0 /*owner_test*/);
}
/*
* Test semop(2) permission checks.
*/
static void
test88b_perm(struct link * parent)
{
struct sembuf sops[2];
size_t nsops;
int i, r, tbit, bit, id[3];
while ((tbit = rcv(parent)) != -1) {
id[0] = rcv(parent);
id[1] = rcv(parent);
id[2] = rcv(parent);
/*
* This loop is designed such that failure of any bit-based
* subset will not result in subsequent operations blocking.
*/
for (i = 0; i < 8; i++) {
memset(sops, 0, sizeof(sops));
switch (i) {
case 0:
nsops = 1;
bit = 4;
break;
case 1:
sops[0].sem_op = 1;
nsops = 1;
bit = 2;
break;
case 2:
sops[0].sem_op = -1;
nsops = 1;
bit = 2;
break;
case 3:
sops[1].sem_op = 1;
nsops = 2;
bit = 6;
break;
case 4:
sops[0].sem_num = 1;
sops[1].sem_op = -1;
nsops = 2;
bit = 6;
break;
case 5:
sops[1].sem_num = 1;
nsops = 2;
bit = 4;
break;
case 6:
/*
* Two operations on the same semaphore. As
* such, this verifies that operations are
* processed in array order.
*/
sops[0].sem_op = 1;
sops[1].sem_op = -1;
nsops = 2;
bit = 2;
break;
case 7:
/*
* Test the order of checks. Since IPC_STAT
* requires read permission, it is reasonable
* that the check against sem_nsems be done
* only after the permission check as well.
* For this test we rewrite EFBIG to OK below.
*/
sops[0].sem_num = USHRT_MAX;
nsops = 2;
bit = 4;
break;
}
r = semop(id[0], sops, nsops);
if (i == 7 && r == -1 && errno == EFBIG) r = 0;
if (r < 0 && (r != -1 || errno != EACCES)) e(0);
if (((bit & tbit) == bit) != (r != -1)) e(0);
r = semop(id[1], sops, nsops);
if (i == 7 && r == -1 && errno == EFBIG) r = 0;
if (r < 0 && (r != -1 || errno != EACCES)) e(0);
if (((bit & tbit) == bit) != (r != -1)) e(0);
if (semop(id[2], sops, nsops) != -1) e(0);
if (errno != EACCES) e(0);
}
snd(parent, 0);
}
}
/*
* Signal handler.
*/
static void
got_signal(int sig)
{
if (sig != SIGHUP) e(0);
if (nr_signals != 0) e(0);
nr_signals++;
}
/*
* Child process for semop(2) tests, mainly testing blocking operations.
*/
static void
test88b_child(struct link * parent)
{
struct sembuf sops[5];
struct sigaction act;
int id;
id = rcv(parent);
memset(sops, 0, sizeof(sops));
if (semop(id, sops, 1) != 0) e(0);
if (rcv(parent) != 1) e(0);
sops[0].sem_op = -3;
if (semop(id, sops, 1) != 0) e(0);
if (rcv(parent) != 2) e(0);
sops[0].sem_num = 2;
sops[0].sem_op = 2;
sops[1].sem_num = 1;
sops[1].sem_op = -1;
sops[2].sem_num = 0;
sops[2].sem_op = 1;
if (semop(id, sops, 3) != 0) e(0);
if (rcv(parent) != 3) e(0);
sops[0].sem_num = 1;
sops[0].sem_op = 0;
sops[1].sem_num = 1;
sops[1].sem_op = 1;
sops[2].sem_num = 0;
sops[2].sem_op = 0;
sops[3].sem_num = 2;
sops[3].sem_op = 0;
sops[4].sem_num = 2;
sops[4].sem_op = 1;
if (semop(id, sops, 5) != 0) e(0);
if (rcv(parent) != 4) e(0);
sops[0].sem_num = 1;
sops[0].sem_op = -2;
sops[1].sem_num = 2;
sops[1].sem_op = 0;
if (semop(id, sops, 2) != 0) e(0);
if (rcv(parent) != 5) e(0);
sops[0].sem_num = 0;
sops[0].sem_op = -1;
sops[1].sem_num = 1;
sops[1].sem_op = -1;
sops[1].sem_flg = IPC_NOWAIT;
if (semop(id, sops, 2) != 0) e(0);
if (rcv(parent) != 6) e(0);
sops[0].sem_num = 1;
sops[0].sem_op = 0;
sops[1].sem_num = 0;
sops[1].sem_op = 0;
sops[1].sem_flg = IPC_NOWAIT;
if (semop(id, sops, 2) != -1) e(0);
if (errno != EAGAIN) e(0);
if (rcv(parent) != 7) e(0);
sops[0].sem_num = 0;
sops[0].sem_op = 0;
sops[1].sem_num = 1;
sops[1].sem_op = 1;
sops[1].sem_flg = 0;
if (semop(id, sops, 2) != 0) e(0);
if (rcv(parent) != 8) e(0);
sops[0].sem_num = 0;
sops[0].sem_op = -1;
sops[1].sem_num = 1;
sops[1].sem_op = 2;
if (semop(id, sops, 2) != -1) e(0);
if (errno != ERANGE) e(0);
memset(&act, 0, sizeof(act));
act.sa_handler = got_signal;
sigfillset(&act.sa_mask);
if (sigaction(SIGHUP, &act, NULL) != 0) e(0);
if (rcv(parent) != 9) e(0);
memset(sops, 0, sizeof(sops));
sops[0].sem_num = 0;
sops[0].sem_op = 0;
sops[1].sem_num = 0;
sops[1].sem_op = 1;
sops[2].sem_num = 1;
sops[2].sem_op = 0;
if (semop(id, sops, 3) != -1)
if (errno != EINTR) e(0);
if (nr_signals != 1) e(0);
TEST_SEM(id, 0, 0, parent->pid, 0, 0);
TEST_SEM(id, 1, 1, parent->pid, 0, 0);
if (rcv(parent) != 10) e(0);
memset(sops, 0, sizeof(sops));
sops[0].sem_op = -3;
if (semop(id, sops, 1) != -1) e(0);
if (errno != EIDRM) e(0);
id = rcv(parent);
sops[0].sem_num = 0;
sops[0].sem_op = -1;
sops[1].sem_num = 1;
sops[1].sem_op = 1;
if (semop(id, sops, 2) != -1) e(0);
if (errno != ERANGE) e(0);
if (rcv(parent) != 11) e(0);
sops[0].sem_num = 1;
sops[0].sem_op = 0;
sops[1].sem_num = 0;
sops[1].sem_op = -1;
if (semop(id, sops, 2) != 0) e(0);
id = rcv(parent);
sops[0].sem_num = 0;
sops[0].sem_op = -1;
sops[1].sem_num = 1;
sops[1].sem_op = 0;
if (semop(id, sops, 2) != 0) e(0);
snd(parent, errct);
if (rcv(parent) != 12) e(0);
/* The child will be killed during this call. It should not return. */
sops[0].sem_num = 1;
sops[0].sem_op = -1;
sops[1].sem_num = 0;
sops[1].sem_op = 3;
(void)semop(id, sops, 2);
e(0);
}
/*
* Test the basic semop(2) functionality.
*/
static void
test88b(void)
{
struct seminfo seminfo;
struct semid_ds semds;
struct sembuf *sops, *sops2;
size_t size;
struct link child;
time_t now;
unsigned short val[2];
int id;
subtest = 1;
/* Allocate a buffer for operations. */
if (semctl(0, 0, IPC_INFO, &seminfo) == -1) e(0);
if (seminfo.semopm < 3 || seminfo.semopm > USHRT_MAX) e(0);
size = sizeof(sops[0]) * (seminfo.semopm + 1);
if ((sops = malloc(size)) == NULL) e(0);
memset(sops, 0, size);
/* Do a few first tests with a set containing one semaphore. */
if ((id = semget(IPC_PRIVATE, 1, IPC_CREAT | 0600)) == -1) e(0);
/* If no operations are given, the call should succeed. */
if (semop(id, NULL, 0) != 0) e(0);
/*
* If any operations are given, the pointer must be valid. Moreover,
* partially valid buffers must never be processed partially.
*/
if (semop(id, NULL, 1) != -1) e(0);
if (errno != EFAULT) e(0);
if (semop(id, bad_ptr, 1) != -1) e(0);
if (errno != EFAULT) e(0);
memset(page_ptr, 0, page_size);
sops2 = ((struct sembuf *)bad_ptr) - 1;
sops2->sem_op = 1;
if (semop(id, sops2, 2) != -1) e(0);
if (errno != EFAULT) e(0);
TEST_SEM(id, 0, 0, 0, 0, 0);
if (semctl(id, 0, IPC_STAT, &semds) != 0) e(0);
if (semds.sem_otime != 0) e(0);
/*
* A new semaphore set is initialized to an all-zeroes state, and a
* zeroed operation tests for a zeroed semaphore. This should pass.
*/
time(&now);
if (semop(id, sops, 1) != 0) e(0);
TEST_SEM(id, 0, 0, getpid(), 0, 0);
if (semctl(id, 0, IPC_STAT, &semds) != 0) e(0);
if (semds.sem_otime < now || semds.sem_otime >= now + 10) e(0);
/* Test the limit on the number of operations. */
if (semop(id, sops, seminfo.semopm) != 0) e(0);
if (semop(id, sops, seminfo.semopm + 1) != -1) e(0);
if (errno != E2BIG) e(0);
if (semop(id, sops, SIZE_MAX) != -1) e(0);
if (errno != E2BIG) e(0);
/* Test the range check on the semaphore numbers. */
sops[1].sem_num = 1;
if (semop(id, sops, 2) != -1) e(0);
if (errno != EFBIG) e(0);
sops[1].sem_num = USHRT_MAX;
if (semop(id, sops, 2) != -1) e(0);
if (errno != EFBIG) e(0);
/*
* Test nonblocking operations on a single semaphore, starting with
* value limit and overflow cases.
*/
if (seminfo.semvmx < 3 || seminfo.semvmx > SHRT_MAX) e(0);
sops[0].sem_flg = IPC_NOWAIT;
/* This block does not trigger on MINIX3. */
if (seminfo.semvmx < SHRT_MAX) {
sops[0].sem_op = seminfo.semvmx + 1;
if (semop(id, sops, 1) != -1) e(0);
if (errno != ERANGE) e(0);
if (semctl(id, 0, GETVAL) != 0) e(0);
}
sops[0].sem_op = seminfo.semvmx;
if (semop(id, sops, 1) != 0) e(0);
if (semctl(id, 0, GETVAL) != seminfo.semvmx) e(0);
/* As of writing, the proper checks for this is missing on NetBSD. */
sops[0].sem_op = 1;
if (semop(id, sops, 1) != -1) e(0);
if (errno != ERANGE) e(0);
if (semctl(id, 0, GETVAL) != seminfo.semvmx) e(0);
sops[0].sem_op = seminfo.semvmx;
if (semop(id, sops, 1) != -1) e(0);
if (errno != ERANGE) e(0);
if (semctl(id, 0, GETVAL) != seminfo.semvmx) e(0);
sops[0].sem_op = SHRT_MAX;
if (semop(id, sops, 1) != -1) e(0);
if (errno != ERANGE) e(0);
if (semctl(id, 0, GETVAL) != seminfo.semvmx) e(0);
/* This block does trigger on MINIX3. */
if (seminfo.semvmx < -(int)SHRT_MIN) {
sops[0].sem_op = -seminfo.semvmx - 1;
if (semop(id, sops, 1) != -1) e(0);
if (errno != EAGAIN) e(0);
if (semctl(id, 0, GETVAL) != seminfo.semvmx) e(0);
}
sops[0].sem_op = -seminfo.semvmx;
if (semop(id, sops, 1) != 0) e(0);
if (semctl(id, 0, GETVAL) != 0) e(0);
/*
* Test basic nonblocking operations on a single semaphore.
*/
sops[0].sem_op = 0;
if (semop(id, sops, 1) != 0) e(0);
sops[0].sem_op = 2;
if (semop(id, sops, 1) != 0) e(0);
if (semctl(id, 0, GETVAL) != 2) e(0);
sops[0].sem_op = 0;
if (semop(id, sops, 1) != -1) e(0);
if (errno != EAGAIN) e(0);
sops[0].sem_op = -3;
if (semop(id, sops, 1) != -1) e(0);
if (errno != EAGAIN) e(0);
sops[0].sem_op = 1;
if (semop(id, sops, 1) != 0) e(0);
if (semctl(id, 0, GETVAL) != 3) e(0);
sops[0].sem_op = -1;
if (semop(id, sops, 1) != 0) e(0);
if (semctl(id, 0, GETVAL) != 2) e(0);
sops[0].sem_op = 0;
if (semop(id, sops, 1) != -1) e(0);
if (errno != EAGAIN) e(0);
sops[0].sem_op = -2;
if (semop(id, sops, 1) != 0) e(0);
if (semctl(id, 0, GETVAL) != 0) e(0);
sops[0].sem_op = 0;
if (semop(id, sops, 1) != 0) e(0);
/* Make sure that not too much data is being read in. */
sops2->sem_op = 0;
sops2--;
if (semop(id, sops2, 2) != 0) e(0);
/* Even if no operations are given, the identifier must be valid. */
if (semctl(id, 0, IPC_RMID) != 0) e(0);
if (semop(id, NULL, 0) != -1) e(0);
if (errno != EINVAL) e(0);
if (semop(-1, NULL, 0) != -1) e(0);
if (errno != EINVAL) e(0);
if (semop(INT_MIN, NULL, 0) != -1) e(0);
if (errno != EINVAL) e(0);
memset(&semds, 0, sizeof(semds));
id = IXSEQ_TO_IPCID(seminfo.semmni, semds.sem_perm);
if (semop(id, NULL, 0) != -1) e(0);
if (errno != EINVAL) e(0);
/*
* Test permission checks. As part of this, test basic nonblocking
* multi-operation calls, including operation processing in array order
* and the order of (permission vs other) checks.
*/
test_perm(test88b_perm, 0 /*owner_test*/);
/*
* Test blocking operations, starting with a single blocking operation.
*/
if ((id = semget(IPC_PRIVATE, 3, 0600)) == -1) e(0);
memset(sops, 0, sizeof(sops[0]));
sops[0].sem_op = 1;
if (semop(id, sops, 1) != 0) e(0);
TEST_SEM(id, 0, 1, getpid(), 0, 0);
spawn(&child, test88b_child, DROP_NONE);
snd(&child, id);
/*
* In various places, we have to sleep in order to allow the child to
* get itself blocked in a semop(2) call.
*/
usleep(WAIT_USECS);
TEST_SEM(id, 0, 1, getpid(), 0, 1);
sops[0].sem_op = -1;
if (semop(id, sops, 1) != 0) e(0);
usleep(WAIT_USECS);
TEST_SEM(id, 0, 0, child.pid, 0, 0);
sops[0].sem_op = 1;
if (semop(id, sops, 1) != 0) e(0);
TEST_SEM(id, 0, 1, getpid(), 0, 0);
snd(&child, 1);
usleep(WAIT_USECS);
TEST_SEM(id, 0, 1, getpid(), 1, 0);
/* This should cause a (fruitless) retry of the blocking operation. */
sops[0].sem_op = 1;
if (semop(id, sops, 1) != 0) e(0);
usleep(WAIT_USECS);
TEST_SEM(id, 0, 2, getpid(), 1, 0);
sops[0].sem_op = 1;
if (semop(id, sops, 1) != 0) e(0);
usleep(WAIT_USECS);
TEST_SEM(id, 0, 0, child.pid, 0, 0);
/*
* Test blocking operations, verifying the correct operation of
* multiple (partially) blocking operations and atomicity.
*/
memset(sops, 0, sizeof(sops[0]) * 2);
if (semop(id, sops, 1) != 0) e(0);
/* One blocking operation. */
snd(&child, 2);
usleep(WAIT_USECS);
TEST_SEM(id, 0, 0, getpid(), 0, 0);
TEST_SEM(id, 1, 0, 0, 1, 0);
TEST_SEM(id, 2, 0, 0, 0, 0);
sops[0].sem_num = 1;
sops[0].sem_op = 1;
if (semop(id, sops, 1) != 0) e(0);
usleep(WAIT_USECS);
TEST_SEM(id, 0, 1, child.pid, 0, 0);
TEST_SEM(id, 1, 0, child.pid, 0, 0);
TEST_SEM(id, 2, 2, child.pid, 0, 0);
/* Two blocking operations in one call, resolved at once. */
snd(&child, 3);
usleep(WAIT_USECS);
TEST_SEM(id, 0, 1, child.pid, 0, 1);
TEST_SEM(id, 1, 0, child.pid, 0, 0);
TEST_SEM(id, 2, 2, child.pid, 0, 0);
sops[0].sem_num = 0;
sops[0].sem_op = -1;
sops[1].sem_num = 2;
sops[1].sem_op = -2;
if (semop(id, sops, 2) != 0) e(0);
usleep(WAIT_USECS);
TEST_SEM(id, 0, 0, child.pid, 0, 0);
TEST_SEM(id, 1, 1, child.pid, 0, 0);
TEST_SEM(id, 2, 1, child.pid, 0, 0);
/* Two blocking operations in one call, resolved one by one. */
snd(&child, 4);
usleep(WAIT_USECS);
TEST_SEM(id, 0, 0, child.pid, 0, 0);
TEST_SEM(id, 1, 1, child.pid, 1, 0);
TEST_SEM(id, 2, 1, child.pid, 0, 0);
sops[0].sem_num = 1;
sops[0].sem_op = 1;
if (semop(id, sops, 1) != 0) e(0);
usleep(WAIT_USECS);
TEST_SEM(id, 0, 0, child.pid, 0, 0);
TEST_SEM(id, 1, 2, getpid(), 0, 0);
TEST_SEM(id, 2, 1, child.pid, 0, 1);
sops[0].sem_num = 2;
sops[0].sem_op = -1;
if (semop(id, sops, 1) != 0) e(0);
usleep(WAIT_USECS);
TEST_SEM(id, 0, 0, child.pid, 0, 0);
TEST_SEM(id, 1, 0, child.pid, 0, 0);
TEST_SEM(id, 2, 0, child.pid, 0, 0);
/* One blocking op followed by a nonblocking one, cleared at once. */
sops[0].sem_num = 0;
sops[0].sem_op = 0;
sops[1].sem_num = 1;
sops[1].sem_op = 0;
if (semop(id, sops, 2) != 0) e(0);
snd(&child, 5);
usleep(WAIT_USECS);
TEST_SEM(id, 0, 0, getpid(), 1, 0);
TEST_SEM(id, 1, 0, getpid(), 0, 0);
sops[0].sem_num = 0;
sops[0].sem_op = 1;
sops[1].sem_num = 1;
sops[1].sem_op = 1;
if (semop(id, sops, 2) != 0) e(0);
usleep(WAIT_USECS);
TEST_SEM(id, 0, 0, child.pid, 0, 0);
TEST_SEM(id, 1, 0, child.pid, 0, 0);
/* One blocking op followed by a nonblocking one, only one cleared. */
sops[0].sem_num = 0;
sops[0].sem_op = 1;
sops[1].sem_num = 1;
sops[1].sem_op = 1;
if (semop(id, sops, 2) != 0) e(0);
snd(&child, 6);
usleep(WAIT_USECS);
TEST_SEM(id, 0, 1, getpid(), 0, 0);
TEST_SEM(id, 1, 1, getpid(), 0, 1);
sops[0].sem_num = 1;
sops[0].sem_op = -1;
if (semop(id, sops, 1) != 0) e(0);
usleep(WAIT_USECS);
TEST_SEM(id, 0, 1, getpid(), 0, 0);
TEST_SEM(id, 1, 0, getpid(), 0, 0);
/*
* Ensure that all semaphore numbers are checked immediately, which
* given the earlier test results also implies that permissions are
* checked immediately (so we don't have to recheck that too). We do
* not check whether permissions are rechecked after a blocking
* operation, because the specification does not describe the intended
* behavior on this point.
*/
sops[0].sem_num = 0;
sops[0].sem_op = 0;
sops[1].sem_num = 4;
sops[1].sem_op = 0;
if (semop(id, sops, 2) != -1) e(0);
if (errno != EFBIG) e(0);
/*
* Ensure that semaphore value overflow is detected properly, at the
* moment that the operation is actually processed.
*/
sops[0].sem_num = 1;
sops[0].sem_op = seminfo.semvmx;
if (semop(id, sops, 1) != 0) e(0);
snd(&child, 7);
usleep(WAIT_USECS);
TEST_SEM(id, 0, 1, getpid(), 0, 1);
TEST_SEM(id, 1, seminfo.semvmx, getpid(), 0, 0);
sops[0].sem_num = 0;
sops[0].sem_op = -1;
sops[1].sem_num = 1;
sops[1].sem_op = -1;
if (semop(id, sops, 2) != 0) e(0);
TEST_SEM(id, 0, 0, child.pid, 0, 0);
TEST_SEM(id, 1, seminfo.semvmx, child.pid, 0, 0);
sops[0].sem_num = 1;
sops[0].sem_op = -2;
if (semop(id, sops, 1) != 0) e(0);
snd(&child, 8);
usleep(WAIT_USECS);
TEST_SEM(id, 0, 0, child.pid, 1, 0);
TEST_SEM(id, 1, seminfo.semvmx - 2, getpid(), 0, 0);
sops[0].sem_num = 0;
sops[0].sem_op = 1;
sops[1].sem_num = 1;
sops[1].sem_op = 1;
if (semop(id, sops, 2) != 0) e(0);
TEST_SEM(id, 0, 1, getpid(), 0, 0);
TEST_SEM(id, 1, seminfo.semvmx - 1, getpid(), 0, 0);
sops[0].sem_num = 0;
sops[0].sem_op = seminfo.semvmx - 1;
sops[1].sem_num = 0;
sops[1].sem_op = seminfo.semvmx - 1;
sops[2].sem_num = 0;
sops[2].sem_op = 2;
/*
* With the current SEMVMX, the sum of the values is now USHRT_MAX-1,
* which if processed could result in a zero semaphore value. That
* should not happen. Looking at you, NetBSD.
*/
if (semop(id, sops, 3) != -1) e(0);
if (errno != ERANGE) e(0);
TEST_SEM(id, 0, 1, getpid(), 0, 0);
/*
* Check that a blocking semop(2) call fails with EINTR if a signal is
* caught by the process after the call has blocked.
*/
if (semctl(id, 1, SETVAL, 0) != 0) e(0);
sops[0].sem_num = 0;
sops[0].sem_op = -1;
sops[1].sem_num = 1;
sops[1].sem_op = 1;
if (semop(id, sops, 2) != 0) e(0);
TEST_SEM(id, 0, 0, getpid(), 0, 0);
TEST_SEM(id, 1, 1, getpid(), 0, 0);
snd(&child, 9);
usleep(WAIT_USECS);
TEST_SEM(id, 0, 0, getpid(), 0, 0);
TEST_SEM(id, 1, 1, getpid(), 0, 1);
kill(child.pid, SIGHUP);
/*
* Kills are not guaranteed to be delivered immediately to processes
* other than the caller of kill(2), so let the child perform checks.
*/
/*
* Check that a blocking semop(2) call fails with EIDRM if the
* semaphore set is removed after the call has blocked.
*/
snd(&child, 10);
usleep(WAIT_USECS);
if (semctl(id, 0, IPC_RMID) != 0) e(0);
/*
* Check if sem_otime is updated correctly. Instead of sleeping for
* whole seconds so as to be able to detect differences, use SETVAL,
* which does not update sem_otime at all. This doubles as a first
* test to see if SETVAL correctly wakes up a blocked semop(2) call.
*/
if ((id = semget(IPC_PRIVATE, 2, 0600)) == -1) e(0);
snd(&child, id);
usleep(WAIT_USECS);
TEST_SEM(id, 0, 0, 0, 1, 0);
TEST_SEM(id, 1, 0, 0, 0, 0);
if (semctl(id, 0, IPC_STAT, &semds) != 0) e(0);
if (semds.sem_otime != 0) e(0);
if (semctl(id, 1, SETVAL, seminfo.semvmx) != 0) e(0);
TEST_SEM(id, 0, 0, 0, 1, 0);
TEST_SEM(id, 1, seminfo.semvmx, 0, 0, 0);
if (semctl(id, 0, IPC_STAT, &semds) != 0) e(0);
if (semds.sem_otime != 0) e(0);
if (semctl(id, 0, SETVAL, 1) != 0) e(0);
TEST_SEM(id, 0, 1, 0, 0, 0);
TEST_SEM(id, 1, seminfo.semvmx, 0, 0, 0);
if (semctl(id, 0, SETVAL, 0) != 0) e(0);
snd(&child, 11);
usleep(WAIT_USECS);
TEST_SEM(id, 0, 0, 0, 0, 0);
TEST_SEM(id, 1, seminfo.semvmx, 0, 0, 1);
if (semctl(id, 0, IPC_STAT, &semds) != 0) e(0);
if (semds.sem_otime != 0) e(0);
if (semctl(id, 1, SETVAL, 0) != 0) e(0);
TEST_SEM(id, 0, 0, 0, 1, 0);
TEST_SEM(id, 1, 0, 0, 0, 0);
if (semctl(id, 0, IPC_STAT, &semds) != 0) e(0);
if (semds.sem_otime != 0) e(0);
time(&now);
if (semctl(id, 0, SETVAL, 2) != 0) e(0);
TEST_SEM(id, 0, 1, child.pid, 0, 0);
TEST_SEM(id, 1, 0, child.pid, 0, 0);
if (semctl(id, 0, IPC_STAT, &semds) != 0) e(0);
if (semds.sem_otime < now || semds.sem_otime >= now + 10) e(0);
if (semctl(id, 0, IPC_RMID) != 0) e(0);
/*
* Perform a similar test for SETALL, ensuring that it causes an
* ongoing semop(2) to behave correctly.
*/
if ((id = semget(IPC_PRIVATE, 2, 0600)) == -1) e(0);
snd(&child, id);
usleep(WAIT_USECS);
TEST_SEM(id, 0, 0, 0, 1, 0);
TEST_SEM(id, 1, 0, 0, 0, 0);
val[0] = 1;
val[1] = 1;
if (semctl(id, 0, SETALL, val) != 0) e(0);
TEST_SEM(id, 0, 1, 0, 0, 0);
TEST_SEM(id, 1, 1, 0, 0, 1);
val[0] = 0;
val[1] = 1;
if (semctl(id, 0, SETALL, val) != 0) e(0);
TEST_SEM(id, 0, 0, 0, 1, 0);
TEST_SEM(id, 1, 1, 0, 0, 0);
val[0] = 1;
val[1] = 1;
if (semctl(id, 0, SETALL, val) != 0) e(0);
TEST_SEM(id, 0, 1, 0, 0, 0);
TEST_SEM(id, 1, 1, 0, 0, 1);
val[0] = 0;
val[1] = 0;
if (semctl(id, 0, SETALL, val) != 0) e(0);
TEST_SEM(id, 0, 0, 0, 1, 0);
TEST_SEM(id, 1, 0, 0, 0, 0);
if (semctl(id, 0, IPC_STAT, &semds) != 0) e(0);
if (semds.sem_otime != 0) e(0);
time(&now);
val[0] = 1;
val[1] = 0;
if (semctl(id, 0, SETALL, val) != 0) e(0);
TEST_SEM(id, 0, 0, child.pid, 0, 0);
TEST_SEM(id, 1, 0, child.pid, 0, 0);
if (semctl(id, 0, IPC_STAT, &semds) != 0) e(0);
if (semds.sem_otime < now || semds.sem_otime >= now + 10) e(0);
/*
* Finally, ensure that if the child is killed, its blocked semop(2)
* call is properly cancelled.
*/
sops[0].sem_num = 0;
sops[0].sem_op = 0;
sops[1].sem_num = 1;
sops[1].sem_op = 0;
if (semop(id, sops, 2) != 0) e(0);
TEST_SEM(id, 0, 0, getpid(), 0, 0);
TEST_SEM(id, 1, 0, getpid(), 0, 0);
/* We'll be terminating the child, so let it report its errors now. */
if (rcv(&child) != 0) e(0);
snd(&child, 12);
usleep(WAIT_USECS);
TEST_SEM(id, 0, 0, getpid(), 0, 0);
TEST_SEM(id, 1, 0, getpid(), 1, 0);
terminate(&child);
TEST_SEM(id, 0, 0, getpid(), 0, 0);
TEST_SEM(id, 1, 0, getpid(), 0, 0);
if (semctl(id, 0, IPC_RMID) != 0) e(0);
free(sops);
}
/*
* Test semctl(2) permission checks, part 1: regular commands.
*/
static void
test88c_perm1(struct link * parent)
{
static const int cmds[] = { GETVAL, GETPID, GETNCNT, GETZCNT };
struct semid_ds semds;
struct seminfo seminfo;
unsigned short val[3];
int i, r, tbit, bit, id[3], cmd;
void *ptr;
while ((tbit = rcv(parent)) != -1) {
id[0] = rcv(parent);
id[1] = rcv(parent);
id[2] = rcv(parent);
/* First the read-only, no-argument cases. */
bit = 4;
for (i = 0; i < __arraycount(cmds); i++) {
r = semctl(id[0], 0, cmds[i]);
if (r < 0 && (r != -1 || errno != EACCES)) e(0);
if (((bit & tbit) == bit) != (r != -1)) e(0);
r = semctl(id[1], 0, cmds[i]);
if (r < 0 && (r != -1 || errno != EACCES)) e(0);
if (((bit & tbit) == bit) != (r != -1)) e(0);
if (semctl(id[2], 0, cmds[i]) != -1) e(0);
if (errno != EACCES) e(0);
}
/*
* Then SETVAL, which requires write permission and is the only
* one that takes an integer argument.
*/
bit = 2;
r = semctl(id[0], 0, SETVAL, 0);
if (r < 0 && (r != -1 || errno != EACCES)) e(0);
if (((bit & tbit) == bit) != (r != -1)) e(0);
r = semctl(id[1], 0, SETVAL, 0);
if (r < 0 && (r != -1 || errno != EACCES)) e(0);
if (((bit & tbit) == bit) != (r != -1)) e(0);
if (semctl(id[2], 0, SETVAL, 0) != -1) e(0);
if (errno != EACCES) e(0);
/*
* Finally the commands that require read or write permission
* and take a pointer as argument.
*/
memset(val, 0, sizeof(val));
for (i = 0; i < 3; i++) {
switch (i) {
case 0:
cmd = GETALL;
ptr = val;
bit = 4;
break;
case 1:
cmd = SETALL;
ptr = val;
bit = 2;
break;
case 2:
cmd = IPC_STAT;
ptr = &semds;
bit = 4;
break;
default:
abort();
}
r = semctl(id[0], 0, cmd, ptr);
if (r < 0 && (r != -1 || errno != EACCES)) e(0);
if (((bit & tbit) == bit) != (r != -1)) e(0);
r = semctl(id[1], 0, cmd, ptr);
if (r < 0 && (r != -1 || errno != EACCES)) e(0);
if (((bit & tbit) == bit) != (r != -1)) e(0);
if (semctl(id[2], 0, cmd, ptr) != -1) e(0);
if (errno != EACCES) e(0);
}
/*
* I was hoping to avoid this, but otherwise we have to make
* the other child iterate through all semaphore sets to find
* the right index for each of the identifiers. As noted in
* the IPC server itself as well, duplicating these macros is
* not a big deal since the split is firmly hardcoded through
* the exposure of IXSEQ_TO_IPCID to userland.
*/
#ifndef IPCID_TO_IX
#define IPCID_TO_IX(id) ((id) & 0xffff)
#endif
bit = 4;
r = semctl(IPCID_TO_IX(id[0]), 0, SEM_STAT, &semds);
if (r < 0 && (r != -1 || errno != EACCES)) e(0);
if (((bit & tbit) == bit) != (r != -1)) e(0);
r = semctl(IPCID_TO_IX(id[1]), 0, SEM_STAT, &semds);
if (r < 0 && (r != -1 || errno != EACCES)) e(0);
if (((bit & tbit) == bit) != (r != -1)) e(0);
if (semctl(IPCID_TO_IX(id[2]), 0, SEM_STAT, &semds) != -1)
e(0);
if (errno != EACCES) e(0);
/*
* IPC_INFO and SEM_INFO should always succeed. They do not
* even take a semaphore set identifier.
*/
if (semctl(0, 0, IPC_INFO, &seminfo) == -1) e(0);
if (semctl(0, 0, SEM_INFO, &seminfo) == -1) e(0);
snd(parent, 0);
}
}
/*
* Test semctl(2) permission checks, part 2: the IPC_SET command.
*/
static void
test88c_perm2(struct link * parent)
{
struct semid_ds semds;
int r, shift, id[3];
while ((shift = rcv(parent)) != -1) {
id[0] = rcv(parent);
id[1] = rcv(parent);
id[2] = rcv(parent);
/*
* Test IPC_SET. Ideally, we would set the permissions to what
* they currently are, but we do not actually know what they
* are, and IPC_STAT requires read permission which we may not
* have! However, no matter what we do, we cannot prevent the
* other child from being able to remove the semaphore sets
* afterwards. So, we just set the permissions to all-zeroes;
* even though those values are meaningful (mode 0000, uid 0,
* gid 0) they could be anything: the API will accept anything.
* This does mean we need to test IPC_RMID permissions from
* another procedure, because we may now be locking ourselves
* out. The System V IPC interface is pretty strange that way.
*/
memset(&semds, 0, sizeof(semds));
r = semctl(id[0], 0, IPC_SET, &semds);
if (r < 0 && (r != -1 || errno != EPERM)) e(0);
if ((shift == 6) != (r != -1)) e(0);
r = semctl(id[1], 0, IPC_SET, &semds);
if (r < 0 && (r != -1 || errno != EPERM)) e(0);
if ((shift == 6) != (r != -1)) e(0);
/* For once, this too should succeed. */
r = semctl(id[2], 0, IPC_SET, &semds);
if (r < 0 && (r != -1 || errno != EPERM)) e(0);
if ((shift == 6) != (r != -1)) e(0);
snd(parent, 0);
}
}
/*
* Test semctl(2) permission checks, part 3: the IPC_RMID command.
*/
static void
test88c_perm3(struct link * parent)
{
int r, shift, id[3];
while ((shift = rcv(parent)) != -1) {
id[0] = rcv(parent);
id[1] = rcv(parent);
id[2] = rcv(parent);
r = semctl(id[0], 0, IPC_RMID);
if (r < 0 && (r != -1 || errno != EPERM)) e(0);
if ((shift == 6) != (r != -1)) e(0);
r = semctl(id[1], 0, IPC_RMID);
if (r < 0 && (r != -1 || errno != EPERM)) e(0);
if ((shift == 6) != (r != -1)) e(0);
/* Okay, twice then. */
r = semctl(id[2], 0, IPC_RMID);
if (r < 0 && (r != -1 || errno != EPERM)) e(0);
if ((shift == 6) != (r != -1)) e(0);
snd(parent, 0);
}
}
/*
* Test the basic semctl(2) functionality.
*/
static void
test88c(void)
{
static const int cmds[] = { GETVAL, GETPID, GETNCNT, GETZCNT };
struct seminfo seminfo;
struct semid_ds semds, osemds;
unsigned short val[4], seen[2];
char statbuf[sizeof(struct semid_ds) + 1];
unsigned int i, j;
time_t now;
int r, id, id2, badid1, badid2, cmd;
subtest = 2;
if (semctl(0, 0, IPC_INFO, &seminfo) == -1) e(0);
/*
* Start with permission checks on the commands. IPC_SET and IPC_RMID
* are special: they check for ownership (uid/cuid) and return EPERM
* rather than EACCES on permission failure.
*/
test_perm(test88c_perm1, 0 /*owner_test*/);
test_perm(test88c_perm2, 1 /*owner_test*/);
test_perm(test88c_perm3, 1 /*owner_test*/);
/* Create identifiers known to be invalid. */
if ((badid1 = semget(IPC_PRIVATE, 1, 0600)) < 0) e(0);
if (semctl(badid1, 0, IPC_RMID) != 0) e(0);
memset(&semds, 0, sizeof(semds));
badid2 = IXSEQ_TO_IPCID(seminfo.semmni, semds.sem_perm);
if ((id = semget(IPC_PRIVATE, 3, IPC_CREAT | 0600)) < 0) e(0);
if (semctl(id, 0, IPC_STAT, &semds) != 0) e(0);
if (semds.sem_otime != 0) e(0);
if (semds.sem_ctime == 0) e(0);
/* In this case we can't avoid sleeping for longer periods.. */
while (time(&now) == semds.sem_ctime)
usleep(250000);
/*
* Test the simple GET commands. The actual functionality of these
* commands have already been tested thoroughly as part of the
* semop(2) part of the test set, so we do not repeat that here.
*/
for (i = 0; i < __arraycount(cmds); i++) {
for (j = 0; j < 3; j++)
if (semctl(id, j, cmds[i]) != 0) e(0);
if (semctl(badid1, 0, cmds[i]) != -1) e(0);
if (errno != EINVAL) e(0);
if (semctl(badid2, 0, cmds[i]) != -1) e(0);
if (errno != EINVAL) e(0);
if (semctl(-1, 0, cmds[i]) != -1) e(0);
if (errno != EINVAL) e(0);
if (semctl(INT_MIN, 0, cmds[i]) != -1) e(0);
if (errno != EINVAL) e(0);
if (semctl(id, -1, cmds[i]) != -1) e(0);
if (errno != EINVAL) e(0);
if (semctl(id, 3, cmds[i]) != -1) e(0);
if (errno != EINVAL) e(0);
/* These commands should not update ctime or otime. */
if (semctl(id, 0, IPC_STAT, &semds) != 0) e(0);
if (semds.sem_otime != 0) e(0);
if (semds.sem_ctime >= now) e(0);
}
/*
* Test the GETALL command.
*/
/*
* Contrary to what the Open Group specification suggests, actual
* implementations agree that the semnum parameter is to be ignored for
* calls not involving a specific semaphore in the set.
*/
for (j = 0; j < 5; j++) {
for (i = 0; i < __arraycount(val); i++)
val[i] = USHRT_MAX;
if (semctl(id, (int)j - 1, GETALL, val) != 0) e(0);
for (i = 0; i < 3; i++)
if (val[i] != 0) e(0);
if (val[i] != USHRT_MAX) e(0);
}
for (i = 0; i < __arraycount(val); i++)
val[i] = USHRT_MAX;
if (semctl(badid1, 0, GETALL, val) != -1) e(0);
if (errno != EINVAL) e(0);
if (semctl(badid2, 0, GETALL, val) != -1) e(0);
if (errno != EINVAL) e(0);
if (semctl(-1, 0, GETALL, val) != -1) e(0);
if (errno != EINVAL) e(0);
if (semctl(INT_MIN, 0, GETALL, val) != -1) e(0);
if (errno != EINVAL) e(0);
for (i = 0; i < __arraycount(val); i++)
if (val[i] != USHRT_MAX) e(0);
if (semctl(id, 0, GETALL, NULL) != -1) e(0);
if (errno != EFAULT) e(0);
if (semctl(id, 0, GETALL, bad_ptr) != -1) e(0);
if (errno != EFAULT) e(0);
if (semctl(id, 0, GETALL, ((unsigned short *)bad_ptr) - 2) != -1) e(0);
if (errno != EFAULT) e(0);
if (semctl(id, 0, GETALL, ((unsigned short *)bad_ptr) - 3) != 0) e(0);
/* Still no change in either otime or ctime. */
if (semctl(id, 0, IPC_STAT, &semds) != 0) e(0);
if (semds.sem_otime != 0) e(0);
if (semds.sem_ctime >= now) e(0);
/*
* Test the IPC_STAT command. This is the last command we are testing
* here that does not affect sem_ctime, so in order to avoid extra
* sleep times, we test this command first now.
*/
/*
* The basic IPC_STAT functionality has already been tested heavily as
* part of the semget(2) and permission tests, so we do not repeat that
* here.
*/
memset(statbuf, 0x5a, sizeof(statbuf));
if (semctl(badid1, 0, IPC_STAT, statbuf) != -1) e(0);
if (errno != EINVAL) e(0);
if (semctl(badid2, 0, IPC_STAT, statbuf) != -1) e(0);
if (errno != EINVAL) e(0);
if (semctl(-1, 0, IPC_STAT, statbuf) != -1) e(0);
if (errno != EINVAL) e(0);
if (semctl(INT_MIN, 0, IPC_STAT, statbuf) != -1) e(0);
if (errno != EINVAL) e(0);
for (i = 0; i < sizeof(statbuf); i++)
if (statbuf[i] != 0x5a) e(0);
if (semctl(id, 0, IPC_STAT, statbuf) != 0) e(0);
if (statbuf[sizeof(statbuf) - 1] != 0x5a) e(0);
if (semctl(id, 0, IPC_STAT, NULL) != -1) e(0);
if (errno != EFAULT) e(0);
if (semctl(id, 0, IPC_STAT, bad_ptr) != -1) e(0);
if (errno != EFAULT) e(0);
if (semctl(id, 0, IPC_STAT, ((struct semid_ds *)bad_ptr) - 1) != 0)
e(0);
if (semctl(id, -1, IPC_STAT, &semds) != 0) e(0);
if (semds.sem_otime != 0) e(0);
if (semds.sem_ctime >= now) e(0);
/*
* Test SEM_STAT.
*/
if ((id2 = semget(KEY_A, seminfo.semmsl, IPC_CREAT | 0642)) < 0) e(0);
memset(statbuf, 0x5a, sizeof(statbuf));
if (semctl(-1, 0, SEM_STAT, statbuf) != -1) e(0);
if (errno != EINVAL) e(0);
if (semctl(seminfo.semmni, 0, SEM_STAT, statbuf) != -1) e(0);
if (errno != EINVAL) e(0);
for (i = 0; i < sizeof(statbuf); i++)
if (statbuf[i] != 0x5a) e(0);
memset(seen, 0, sizeof(seen));
for (i = 0; i < seminfo.semmni; i++) {
errno = 0;
if ((r = semctl(i, i / 2 - 1, SEM_STAT, statbuf)) == -1) {
if (errno != EINVAL) e(0);
continue;
}
if (r < 0) e(0);
memcpy(&semds, statbuf, sizeof(semds));
if (!(semds.sem_perm.mode & SEM_ALLOC)) e(0);
if (semds.sem_ctime == 0) e(0);
if (IXSEQ_TO_IPCID(i, semds.sem_perm) != r) e(0);
if (r == id) {
seen[0]++;
if (semds.sem_perm.mode != (SEM_ALLOC | 0600)) e(0);
if (semds.sem_perm.uid != geteuid()) e(0);
if (semds.sem_perm.gid != getegid()) e(0);
if (semds.sem_perm.cuid != semds.sem_perm.uid) e(0);
if (semds.sem_perm.cgid != semds.sem_perm.gid) e(0);
if (semds.sem_perm._key != IPC_PRIVATE) e(0);
if (semds.sem_nsems != 3) e(0);
if (semds.sem_otime != 0) e(0);
/* This is here because we need a valid index. */
if (semctl(i, 0, SEM_STAT, NULL) != -1) e(0);
if (errno != EFAULT) e(0);
if (semctl(i, 0, SEM_STAT, bad_ptr) != -1) e(0);
if (errno != EFAULT) e(0);
} else if (r == id2) {
seen[1]++;
if (semds.sem_perm.mode != (SEM_ALLOC | 0642)) e(0);
if (semds.sem_perm.uid != geteuid()) e(0);
if (semds.sem_perm.gid != getegid()) e(0);
if (semds.sem_perm.cuid != semds.sem_perm.uid) e(0);
if (semds.sem_perm.cgid != semds.sem_perm.gid) e(0);
if (semds.sem_perm._key != KEY_A) e(0);
if (semds.sem_nsems != seminfo.semmsl) e(0);
}
}
if (seen[0] != 1) e(0);
if (seen[1] != 1) e(0);
if (statbuf[sizeof(statbuf) - 1] != 0x5a) e(0);
if (semctl(id, 5, IPC_STAT, &semds) != 0) e(0);
if (semds.sem_otime != 0) e(0);
if (semds.sem_ctime >= now) e(0);
/*
* Test SETVAL. We start with all the failure cases, so as to be able
* to check that sem_ctime is not changed in those cases.
*/
if (semctl(badid1, 0, SETVAL, 1) != -1) e(0);
if (errno != EINVAL) e(0);
if (semctl(badid2, 0, SETVAL, 1) != -1) e(0);
if (errno != EINVAL) e(0);
if (semctl(-1, 0, SETVAL, 1) != -1) e(0);
if (errno != EINVAL) e(0);
if (semctl(INT_MIN, 0, SETVAL, 1) != -1) e(0);
if (errno != EINVAL) e(0);
if (semctl(id, -1, SETVAL, 1) != -1) e(0);
if (errno != EINVAL) e(0);
if (semctl(id, 3, SETVAL, 1) != -1) e(0);
if (errno != EINVAL) e(0);
if (semctl(id, 0, SETVAL, -1) != -1) e(0);
if (errno != ERANGE) e(0);
if (semctl(id, 0, SETVAL, seminfo.semvmx + 1) != -1) e(0);
if (errno != ERANGE) e(0);
TEST_SEM(id, 0, 0, 0, 0, 0);
if (semctl(id, 0, IPC_STAT, &semds) != 0) e(0);
if (semds.sem_otime != 0) e(0);
if (semds.sem_ctime >= now) e(0);
/* Alright, there we go.. */
if (semctl(id, 1, SETVAL, 0) != 0) e(0);
if (semctl(id, 0, IPC_STAT, &semds) != 0) e(0);
if (semds.sem_otime != 0) e(0);
if (semds.sem_ctime < now || semds.sem_ctime >= now + 10) e(0);
TEST_SEM(id, 1, 0, 0, 0, 0);
if (semctl(id, 2, SETVAL, seminfo.semvmx) != 0) e(0);
TEST_SEM(id, 2, seminfo.semvmx, 0, 0, 0);
if (semctl(id, 0, SETVAL, 1) != 0) e(0);
TEST_SEM(id, 0, 1, 0, 0, 0);
TEST_SEM(id, 1, 0, 0, 0, 0);
TEST_SEM(id, 2, seminfo.semvmx, 0, 0, 0);
if (semctl(id, 0, GETALL, val) != 0) e(0);
if (val[0] != 1) e(0);
if (val[1] != 0) e(0);
if (val[2] != seminfo.semvmx) e(0);
if (semctl(id, 0, IPC_STAT, &semds) != 0) e(0);
while (time(&now) == semds.sem_ctime)
usleep(250000);
/*
* Test SETALL. Same idea: failure cases first.
*/
if (semctl(badid1, 0, SETALL, 1) != -1) e(0);
if (errno != EINVAL) e(0);
if (semctl(badid2, 0, SETALL, 1) != -1) e(0);
if (errno != EINVAL) e(0);
if (semctl(-1, 0, SETALL, 1) != -1) e(0);
if (errno != EINVAL) e(0);
if (semctl(INT_MIN, 0, SETALL, 1) != -1) e(0);
if (errno != EINVAL) e(0);
val[0] = seminfo.semvmx + 1;
val[1] = 0;
val[2] = 0;
if (semctl(id, 0, SETALL, val) != -1) e(0);
if (errno != ERANGE) e(0);
val[0] = 0;
val[1] = 1;
val[2] = seminfo.semvmx + 1;
if (semctl(id, 0, SETALL, val) != -1) e(0);
if (errno != ERANGE) e(0);
if (semctl(id, 0, SETALL, NULL) != -1) e(0);
if (errno != EFAULT) e(0);
if (semctl(id, 0, SETALL, bad_ptr) != -1) e(0);
if (errno != EFAULT) e(0);
if (semctl(id, 0, SETALL, ((unsigned short *)bad_ptr) - 2) != -1) e(0);
if (errno != EFAULT) e(0);
TEST_SEM(id, 0, 1, 0, 0, 0);
TEST_SEM(id, 1, 0, 0, 0, 0);
TEST_SEM(id, 2, seminfo.semvmx, 0, 0, 0);
if (semctl(id, 0, IPC_STAT, &semds) != 0) e(0);
if (semds.sem_otime != 0) e(0);
if (semds.sem_ctime >= now) e(0);
val[0] = seminfo.semvmx;
val[1] = 0;
val[2] = 0;
if (semctl(id, 0, SETALL, val) != 0) e(0);
if (semctl(id, 0, IPC_STAT, &semds) != 0) e(0);
if (semds.sem_otime != 0) e(0);
if (semds.sem_ctime < now || semds.sem_ctime >= now + 10) e(0);
TEST_SEM(id, 0, seminfo.semvmx, 0, 0, 0);
TEST_SEM(id, 1, 0, 0, 0, 0);
TEST_SEM(id, 2, 0, 0, 0, 0);
val[0] = 0;
val[1] = 1;
val[2] = seminfo.semvmx;
if (semctl(id, INT_MAX, SETALL, val) != 0) e(0);
TEST_SEM(id, 0, 0, 0, 0, 0);
TEST_SEM(id, 1, 1, 0, 0, 0);
TEST_SEM(id, 2, seminfo.semvmx, 0, 0, 0);
memset(page_ptr, 0, page_size);
if (semctl(id, 0, SETALL, ((unsigned short *)bad_ptr) - 3) != 0) e(0);
TEST_SEM(id, 0, 0, 0, 0, 0);
TEST_SEM(id, 1, 0, 0, 0, 0);
TEST_SEM(id, 2, 0, 0, 0, 0);
while (time(&now) == semds.sem_ctime)
usleep(250000);
/*
* Test IPC_SET. Its core functionality has already been tested
* thoroughly as part of the permission tests.
*/
if (semctl(badid1, 0, IPC_SET, &semds) != -1) e(0);
if (errno != EINVAL) e(0);
if (semctl(badid2, 0, IPC_SET, &semds) != -1) e(0);
if (errno != EINVAL) e(0);
if (semctl(-1, 0, IPC_SET, &semds) != -1) e(0);
if (errno != EINVAL) e(0);
if (semctl(INT_MIN, 0, IPC_SET, &semds) != -1) e(0);
if (errno != EINVAL) e(0);
if (semctl(id, 0, IPC_SET, NULL) != -1) e(0);
if (errno != EFAULT) e(0);
if (semctl(id, 0, IPC_SET, bad_ptr) != -1) e(0);
if (errno != EFAULT) e(0);
if (semctl(id, 0, IPC_STAT, &osemds) != 0) e(0);
if (osemds.sem_otime != 0) e(0);
if (osemds.sem_ctime >= now) e(0);
/*
* Only mode, uid, gid may be set. While the given mode is sanitized
* in our implementation (see below; the open group specification
* leaves this undefined), the uid and gid are not (we do not test this
* exhaustively). The other given fields must be ignored. The ctime
* field will be updated.
*/
memset(&semds, 0x5b, sizeof(semds));
semds.sem_perm.mode = 0712;
semds.sem_perm.uid = UID_MAX;
semds.sem_perm.gid = GID_MAX - 1;
if (semctl(id, 0, IPC_SET, &semds) != 0) e(0);
if (semctl(id, 0, IPC_STAT, &semds) != 0) e(0);
if (semds.sem_perm.mode != (SEM_ALLOC | 0712)) e(0);
if (semds.sem_perm.uid != UID_MAX) e(0);
if (semds.sem_perm.gid != GID_MAX - 1) e(0);
if (semds.sem_perm.cuid != osemds.sem_perm.cuid) e(0);
if (semds.sem_perm.cgid != osemds.sem_perm.cgid) e(0);
if (semds.sem_perm._seq != osemds.sem_perm._seq) e(0);
if (semds.sem_perm._key != osemds.sem_perm._key) e(0);
if (semds.sem_nsems != osemds.sem_nsems) e(0);
if (semds.sem_otime != osemds.sem_otime) e(0);
if (semds.sem_ctime < now || semds.sem_ctime >= now + 10) e(0);
/* It should be possible to set any mode, but mask 0777 is applied. */
semds.sem_perm.uid = osemds.sem_perm.uid;
semds.sem_perm.gid = osemds.sem_perm.gid;
for (i = 0; i < 0777; i++) {
semds.sem_perm.mode = i;
if (semctl(id, i / 2 - 1, IPC_SET, &semds) != 0) e(0);
if (semctl(id, i / 2 - 2, IPC_STAT, &semds) != 0) e(0);
if (semds.sem_perm.mode != (SEM_ALLOC | i)) e(0);
semds.sem_perm.mode = ~0777 | i;
if (semctl(id, i / 2 - 3, IPC_SET, &semds) != 0) e(0);
if (semctl(id, i / 2 - 4, IPC_STAT, &semds) != 0) e(0);
if (semds.sem_perm.mode != (SEM_ALLOC | i)) e(0);
}
if (semds.sem_perm.uid != osemds.sem_perm.uid) e(0);
if (semds.sem_perm.gid != osemds.sem_perm.gid) e(0);
if (semctl(id, 0, IPC_SET, ((struct semid_ds *)bad_ptr) - 1) != 0)
e(0);
/*
* Test IPC_RMID. Its basic functionality has already been tested
* multiple times over, so there is not much left to do here.
*/
if (semctl(badid1, 0, IPC_RMID) != -1) e(0);
if (errno != EINVAL) e(0);
if (semctl(badid2, 0, IPC_RMID) != -1) e(0);
if (errno != EINVAL) e(0);
if (semctl(-1, 0, IPC_RMID) != -1) e(0);
if (errno != EINVAL) e(0);
if (semctl(INT_MIN, 0, IPC_RMID) != -1) e(0);
if (errno != EINVAL) e(0);
if (semctl(id, 0, IPC_RMID) != 0) e(0);
if (semctl(id, 0, IPC_RMID) != -1) e(0);
if (errno != EINVAL) e(0);
if (semctl(id, 0, IPC_STAT, &semds) != -1) e(0);
if (errno != EINVAL) e(0);
if (semctl(id2, 1, IPC_RMID) != 0) e(0);
if (semctl(id2, 1, IPC_RMID) != -1) e(0);
if (errno != EINVAL) e(0);
/*
* Test IPC_INFO and SEM_INFO. Right now, for all practical purposes,
* these identifiers behave pretty much the same.
*/
if ((id = semget(IPC_PRIVATE, 3, 0600)) == -1) e(0);
if ((id2 = semget(IPC_PRIVATE, 1, 0600)) == -1) e(0);
for (i = 0; i <= 1; i++) {
cmd = (i == 0) ? IPC_INFO : SEM_INFO;
memset(&seminfo, 0xff, sizeof(seminfo));
if ((r = semctl(0, 0, cmd, &seminfo)) == -1) e(0);
/*
* These commands return the index of the highest in-use slot
* in the semaphore set table. Bad idea of course, because
* that means the value 0 has two potential meanings. Since we
* cannot guarantee that no other running application is using
* semaphores, we settle for "at least" tests based on the two
* semaphore sets we just created.
*/
if (r < 1 || r >= seminfo.semmni) e(0);
/*
* Many of these checks are rather basic because of missing
* SEM_UNDO support. The only difference between IPC_INFO and
* SEM_INFO is the meaning of the semusz and semaem fields.
*/
if (seminfo.semmap < 0) e(0);
if (seminfo.semmni < 3 || seminfo.semmni > USHRT_MAX) e(0);
if (seminfo.semmns < 3 || seminfo.semmns > USHRT_MAX) e(0);
if (seminfo.semmnu < 0) e(0);
if (seminfo.semmsl < 3 || seminfo.semmsl > USHRT_MAX) e(0);
if (seminfo.semopm < 3 || seminfo.semopm > USHRT_MAX) e(0);
if (seminfo.semume < 0) e(0);
if (cmd == SEM_INFO) {
if (seminfo.semusz < 2) e(0);
} else
if (seminfo.semusz < 0) e(0);
if (seminfo.semvmx < 3 || seminfo.semvmx > SHRT_MAX) e(0);
if (cmd == SEM_INFO) {
if (seminfo.semaem < 4) e(0);
} else
if (seminfo.semaem < 0) e(0);
if (semctl(INT_MAX, -1, cmd, &seminfo) == -1) e(0);
if (semctl(-1, INT_MAX, cmd, &seminfo) == -1) e(0);
if (semctl(0, 0, cmd, NULL) != -1) e(0);
if (errno != EFAULT) e(0);
if (semctl(0, 0, cmd, bad_ptr) != -1) e(0);
if (errno != EFAULT) e(0);
if (semctl(0, 0, cmd, ((struct seminfo *)bad_ptr) - 1) == -1)
e(0);
}
if (semctl(id2, 0, IPC_RMID) != 0) e(0);
/*
* Finally, test invalid commands. Well, hopefully invalid commands,
* anyway.
*/
if (semctl(id, 0, INT_MIN) != -1) e(0);
if (errno != EINVAL) e(0);
if (semctl(id, 0, INT_MAX) != -1) e(0);
if (errno != EINVAL) e(0);
if (semctl(id, 0, IPC_RMID) != 0) e(0);
}
/*
* Test SEM_UNDO support. Right now this functionality is missing altogether.
* For now, we test that any attempt to use SEM_UNDO fails.
*/
static void
test88d(void)
{
struct sembuf sop;
int id;
subtest = 3;
if ((id = semget(IPC_PRIVATE, 1, 0600)) == -1) e(0);
/*
* Use an all-ones (but positive) flag field. This will include
* SEM_UNDO, but also tell the IPC server to report no warning.
*/
if (!(SHRT_MAX & SEM_UNDO)) e(0);
sop.sem_num = 0;
sop.sem_op = 1;
sop.sem_flg = SHRT_MAX;
if (semop(id, &sop, 1) != -1) e(0);
if (errno != EINVAL) e(0);
if (semctl(id, 0, IPC_RMID) != 0) e(0);
}
enum {
RESUME_SEMOP, /* use semop() to resume blocked parties */
RESUME_SETVAL, /* use semctl(SETVAL) to resume blocked parties */
RESUME_SETALL, /* use semctl(SETALL) to resume blocked parties */
NR_RESUMES
};
enum {
MATCH_FIRST, /* first match completes, blocks second match */
MATCH_SECOND, /* first match does not complete, second match does */
MATCH_KILL, /* second match completes after first is aborted */
MATCH_BOTH, /* first and second match both complete */
MATCH_CASCADE, /* completed match in turn causes another match */
MATCH_ALL, /* a combination of the last two */
NR_MATCHES
};
/*
* Auxiliary child procedure. The auxiliary children will deadlock until the
* semaphore set is removed.
*/
static void
test88e_childaux(struct link * parent)
{
struct sembuf sops[3];
struct seminfo seminfo;
int child, id, num;
child = rcv(parent);
id = rcv(parent);
num = rcv(parent);
memset(sops, 0, sizeof(sops));
/* These operations are guaranteed to never return successfully. */
switch (child) {
case 1:
sops[0].sem_num = num;
sops[0].sem_op = 1;
sops[1].sem_num = num;
sops[1].sem_op = 0;
sops[2].sem_num = 0;
sops[2].sem_op = 1;
break;
case 2:
if (semctl(0, 0, IPC_INFO, &seminfo) == -1) e(0);
sops[0].sem_num = num;
sops[0].sem_op = -seminfo.semvmx;
sops[1].sem_num = num;
sops[1].sem_op = -seminfo.semvmx;
sops[2].sem_num = 0;
sops[2].sem_op = 1;
break;
default:
e(0);
}
snd(parent, 0);
if (semop(id, sops, 3) != -1) e(0);
if (errno != EIDRM) e(0);
}
/*
* First child procedure.
*/
static void
test88e_child1(struct link * parent)
{
struct sembuf sops[3];
size_t nsops;
int match, id, expect;
match = rcv(parent);
id = rcv(parent);
/* Start off with some defaults, then refine by match type. */
memset(sops, 0, sizeof(sops));
sops[0].sem_num = 2;
sops[0].sem_op = -1;
nsops = 2;
expect = 0;
switch (match) {
case MATCH_FIRST:
sops[1].sem_num = 3;
sops[1].sem_op = 1;
break;
case MATCH_SECOND:
sops[1].sem_num = 3;
sops[1].sem_op = -1;
sops[2].sem_num = 0;
sops[2].sem_op = 1;
nsops = 3;
expect = -1;
break;
case MATCH_KILL:
sops[1].sem_num = 0;
sops[1].sem_op = 1;
expect = INT_MIN;
break;
case MATCH_BOTH:
case MATCH_CASCADE:
case MATCH_ALL:
sops[1].sem_num = 3;
sops[1].sem_op = 1;
break;
default:
e(0);
}
snd(parent, 0);
if (semop(id, sops, nsops) != expect) e(0);
if (expect == -1 && errno != EIDRM) e(0);
}
/*
* Second child procedure.
*/
static void
test88e_child2(struct link * parent)
{
struct sembuf sops[2];
size_t nsops;
int match, id, expect;
match = rcv(parent);
id = rcv(parent);
/* Start off with some defaults, then refine by match type. */
memset(sops, 0, sizeof(sops));
sops[0].sem_num = 2;
sops[0].sem_op = -1;
nsops = 2;
expect = 0;
switch (match) {
case MATCH_FIRST:
sops[1].sem_num = 0;
sops[1].sem_op = 1;
expect = -1;
break;
case MATCH_SECOND:
case MATCH_KILL:
nsops = 1;
break;
case MATCH_BOTH:
case MATCH_ALL:
sops[1].sem_num = 3;
sops[1].sem_op = 1;
break;
case MATCH_CASCADE:
sops[0].sem_num = 3;
nsops = 1;
break;
default:
e(0);
}
snd(parent, 0);
if (semop(id, sops, nsops) != expect) e(0);
if (expect == -1 && errno != EIDRM) e(0);
}
/*
* Third child procedure.
*/
static void
test88e_child3(struct link * parent)
{
struct sembuf sops[1];
size_t nsops;
int match, id;
match = rcv(parent);
id = rcv(parent);
/* Things are a bit simpler here. */
memset(sops, 0, sizeof(sops));
nsops = 1;
switch (match) {
case MATCH_ALL:
sops[0].sem_num = 3;
sops[0].sem_op = -2;
break;
default:
e(0);
}
snd(parent, 0);
if (semop(id, sops, nsops) != 0) e(0);
}
/*
* Perform one test for operations affecting multiple processes.
*/
static void
sub88e(unsigned int match, unsigned int resume, unsigned int aux)
{
struct link aux1, aux2, child1, child2, child3;
struct sembuf sop;
unsigned short val[4];
int id, inc, aux_zcnt, aux_ncnt;
/*
* For this test we use one single semaphore set, with four semaphores.
* The first semaphore is increased in the case that an operation that
* should never complete does complete, and thus should stay zero.
* Depending on 'aux', the second or third semaphore is used by the
* auxiliary children (if any, also depending on 'aux') to deadlock on.
* The third and higher semaphores are used in the main operations.
*/
if ((id = semget(IPC_PRIVATE, __arraycount(val), 0666)) == -1) e(0);
aux_zcnt = aux_ncnt = 0;
/* Start the first auxiliary child if desired, before all others. */
if (aux & 1) {
spawn(&aux1, test88e_childaux, DROP_ALL);
snd(&aux1, 1);
snd(&aux1, id);
snd(&aux1, (aux & 4) ? 2 : 1);
if (rcv(&aux1) != 0) e(0);
if (aux & 4)
aux_zcnt++;
}
/* Start and configure all children for this specific match test. */
spawn(&child1, test88e_child1, DROP_ALL);
snd(&child1, match);
snd(&child1, id);
if (rcv(&child1) != 0) e(0);
/*
* For fairness tests, we must ensure that the first child blocks on
* the semaphore before the second child does.
*/
switch (match) {
case MATCH_FIRST:
case MATCH_SECOND:
case MATCH_KILL:
usleep(WAIT_USECS);
break;
}
spawn(&child2, test88e_child2, DROP_NONE);
snd(&child2, match);
snd(&child2, id);
if (rcv(&child2) != 0) e(0);
if (match == MATCH_ALL) {
spawn(&child3, test88e_child3, DROP_USER);
snd(&child3, match);
snd(&child3, id);
if (rcv(&child3) != 0) e(0);
}
/* Start the second auxiliary child if desired, after all others. */
if (aux & 2) {
spawn(&aux2, test88e_childaux, DROP_NONE);
snd(&aux2, 2);
snd(&aux2, id);
snd(&aux2, (aux & 4) ? 2 : 1);
if (rcv(&aux2) != 0) e(0);
if (aux & 4)
aux_ncnt++;
}
usleep(WAIT_USECS);
/*
* Test semaphore values and determine the value with which to increase
* the third semaphore. For MATCH_KILL, also kill the first child.
*/
inc = 1;
switch (match) {
case MATCH_FIRST:
case MATCH_SECOND:
TEST_SEM(id, 2, 0, 0, 2 + aux_ncnt, aux_zcnt);
TEST_SEM(id, 3, 0, 0, 0, 0);
break;
case MATCH_KILL:
TEST_SEM(id, 2, 0, 0, 2 + aux_ncnt, aux_zcnt);
terminate(&child1);
/* As stated before, non-self kills need not be instant. */
usleep(WAIT_USECS);
TEST_SEM(id, 2, 0, 0, 1 + aux_ncnt, aux_zcnt);
TEST_SEM(id, 3, 0, 0, 0, 0);
break;
case MATCH_BOTH:
TEST_SEM(id, 2, 0, 0, 2 + aux_ncnt, aux_zcnt);
TEST_SEM(id, 3, 0, 0, 0, 0);
inc = 2;
break;
case MATCH_CASCADE:
TEST_SEM(id, 2, 0, 0, 1 + aux_ncnt, aux_zcnt);
TEST_SEM(id, 3, 0, 0, 1, 0);
break;
case MATCH_ALL:
TEST_SEM(id, 2, 0, 0, 2 + aux_ncnt, aux_zcnt);
TEST_SEM(id, 3, 0, 0, 1, 0);
inc = 2;
break;
default:
e(0);
}
TEST_SEM(id, 0, 0, 0, 0, 0);
TEST_SEM(id, 1, 0, 0, -1, -1);
/* Resume the appropriate set of children. */
switch (resume) {
case RESUME_SEMOP:
memset(&sop, 0, sizeof(sop));
sop.sem_num = 2;
sop.sem_op = inc;
if (semop(id, &sop, 1) != 0) e(0);
break;
case RESUME_SETVAL:
if (semctl(id, 2, SETVAL, inc) != 0) e(0);
break;
case RESUME_SETALL:
memset(val, 0, sizeof(val));
val[2] = inc;
if (semctl(id, 0, SETALL, val) != 0) e(0);
break;
default:
e(0);
}
/*
* See if the right children were indeed resumed, and retest the
* semaphore values.
*/
switch (match) {
case MATCH_FIRST:
TEST_SEM(id, 2, 0, child1.pid, 1 + aux_ncnt, aux_zcnt);
TEST_SEM(id, 3, 1, child1.pid, 0, 0);
collect(&child1);
break;
case MATCH_SECOND:
TEST_SEM(id, 2, 0, child2.pid, 1 + aux_ncnt, aux_zcnt);
TEST_SEM(id, 3, 0, 0, 0, 0);
collect(&child2);
break;
case MATCH_KILL:
TEST_SEM(id, 2, 0, child2.pid, aux_ncnt, aux_zcnt);
TEST_SEM(id, 3, 0, 0, 0, 0);
collect(&child2);
break;
case MATCH_BOTH:
/*
* The children are not ordered in this case, so we do not know
* which one gets access to the semaphores last.
*/
TEST_SEM(id, 2, 0, -1, aux_ncnt, aux_zcnt);
TEST_SEM(id, 3, 2, -1, 0, 0);
collect(&child1);
collect(&child2);
break;
case MATCH_CASCADE:
TEST_SEM(id, 2, 0, child1.pid, aux_ncnt, aux_zcnt);
TEST_SEM(id, 3, 0, child2.pid, 0, 0);
collect(&child1);
collect(&child2);
break;
case MATCH_ALL:
TEST_SEM(id, 2, 0, -1, aux_ncnt, aux_zcnt);
TEST_SEM(id, 3, 0, child3.pid, 0, 0);
collect(&child1);
collect(&child2);
collect(&child3);
break;
default:
e(0);
}
TEST_SEM(id, 0, 0, 0, 0, 0);
TEST_SEM(id, 1, 0, 0, -1, -1);
/* Remove the semaphore set. This should unblock remaining callers. */
if (semctl(id, 0, IPC_RMID) != 0) e(0);
/* Wait for the children that were not resumed, but should be now. */
switch (match) {
case MATCH_FIRST:
collect(&child2);
break;
case MATCH_SECOND:
collect(&child1);
break;
case MATCH_KILL:
case MATCH_BOTH:
case MATCH_CASCADE:
case MATCH_ALL:
break;
default:
e(0);
}
/* Wait for the auxiliary children as well. */
if (aux & 1)
collect(&aux1);
if (aux & 2)
collect(&aux2);
}
/*
* Test operations affecting multiple processes, ensuring the following points:
* 1) an operation resumes all possible waiters; 2) a resumed operation in turn
* correctly resumes other now-unblocked operations; 3) a basic level of FIFO
* fairness is provided between blocked parties; 4) all the previous points are
* unaffected by additional waiters that are not being resumed; 5) identifier
* removal properly resumes all affected waiters.
*/
static void
test88e(void)
{
unsigned int resume, match, aux;
subtest = 4;
for (match = 0; match < NR_MATCHES; match++)
for (resume = 0; resume < NR_RESUMES; resume++)
for (aux = 1; aux <= 8; aux++) /* 0 and 4 are equal */
sub88e(match, resume, aux);
}
/*
* Verify that non-root processes can use sysctl(2) to see semaphore sets
* created by root.
*/
static void
test88f_child(struct link * parent)
{
static const int mib[] = { CTL_KERN, KERN_SYSVIPC, KERN_SYSVIPC_INFO,
KERN_SYSVIPC_SEM_INFO };
struct sem_sysctl_info *semsi;
size_t len;
int id[2], id2, seen[2];
int32_t i;
id[0] = rcv(parent);
id[1] = rcv(parent);
if (sysctl(mib, __arraycount(mib), NULL, &len, NULL, 0) != 0) e(0);
if ((semsi = malloc(len)) == NULL) e(0);
if (sysctl(mib, __arraycount(mib), semsi, &len, NULL, 0) != 0) e(0);
seen[0] = seen[1] = 0;
for (i = 0; i < semsi->seminfo.semmni; i++) {
if (!(semsi->semids[i].sem_perm.mode & SEM_ALLOC))
continue;
id2 = IXSEQ_TO_IPCID(i, semsi->semids[i].sem_perm);
if (id2 == id[0])
seen[0]++;
else if (id2 == id[1])
seen[1]++;
}
free(semsi);
if (seen[0] != 1) e(0);
if (seen[1] != 1) e(0);
}
/*
* Test sysctl(2) based information retrieval. This test aims to ensure that
* in particular ipcs(1) and ipcrm(1) will be able to do their jobs.
*/
static void
test88f(void)
{
static const int mib[] = { CTL_KERN, KERN_SYSVIPC, KERN_SYSVIPC_INFO,
KERN_SYSVIPC_SEM_INFO };
struct seminfo seminfo, seminfo2;
struct sem_sysctl_info *semsi;
struct semid_ds_sysctl *semds;
struct link child;
size_t len, size;
int id[2], id2;
int32_t i, slot[2];
/*
* Verify that we can retrieve only the general semaphore information,
* without any actual semaphore set entries. This is actually a dirty
* sysctl-level hack, as sysctl requests should not behave differently
* based on the requested length. However, ipcs(1) relies on this.
*/
len = sizeof(seminfo);
if (sysctl(mib, __arraycount(mib), &seminfo, &len, NULL, 0) != 0) e(0);
if (len != sizeof(seminfo)) e(0);
if (semctl(0, 0, IPC_INFO, &seminfo2) == -1) e(0);
if (memcmp(&seminfo, &seminfo2, sizeof(seminfo)) != 0) e(0);
/* Verify that the correct size estimation is returned. */
if (seminfo.semmni <= 0) e(0);
if (seminfo.semmni > SHRT_MAX) e(0);
size = sizeof(*semsi) +
sizeof(semsi->semids[0]) * (seminfo.semmni - 1);
len = 0;
if (sysctl(mib, __arraycount(mib), NULL, &len, NULL, 0) != 0) e(0);
if (len != size) e(0);
/* Create two semaphore sets that should show up in the listing. */
if ((id[0] = semget(KEY_A, 5, IPC_CREAT | 0612)) < 0) e(0);
if ((id[1] = semget(IPC_PRIVATE, 3, 0650)) < 0) e(0);
/*
* Retrieve the entire semaphore array, and verify that the general
* semaphore information is still correct.
*/
if ((semsi = malloc(size)) == NULL) e(0);
len = size;
if (sysctl(mib, __arraycount(mib), semsi, &len, NULL, 0) != 0) e(0);
if (len != size) e(0);
if (sizeof(semsi->seminfo) != sizeof(seminfo)) e(0);
if (memcmp(&semsi->seminfo, &seminfo, sizeof(semsi->seminfo)) != 0)
e(0);
/* Verify that our semaphore sets are each in the array once. */
slot[0] = slot[1] = -1;
for (i = 0; i < seminfo.semmni; i++) {
if (!(semsi->semids[i].sem_perm.mode & SEM_ALLOC))
continue;
id2 = IXSEQ_TO_IPCID(i, semsi->semids[i].sem_perm);
if (id2 == id[0]) {
if (slot[0] != -1) e(0);
slot[0] = i;
} else if (id2 == id[1]) {
if (slot[1] != -1) e(0);
slot[1] = i;
}
}
if (slot[0] < 0) e(0);
if (slot[1] < 0) e(0);
/* Check that the semaphore sets have the expected properties. */
semds = &semsi->semids[slot[0]];
if (semds->sem_perm.uid != geteuid()) e(0);
if (semds->sem_perm.gid != getegid()) e(0);
if (semds->sem_perm.cuid != geteuid()) e(0);
if (semds->sem_perm.cgid != getegid()) e(0);
if (semds->sem_perm.mode != (SEM_ALLOC | 0612)) e(0);
if (semds->sem_perm._key != KEY_A) e(0);
if (semds->sem_nsems != 5) e(0);
if (semds->sem_otime != 0) e(0);
if (semds->sem_ctime == 0) e(0);
semds = &semsi->semids[slot[1]];
if (semds->sem_perm.uid != geteuid()) e(0);
if (semds->sem_perm.gid != getegid()) e(0);
if (semds->sem_perm.cuid != geteuid()) e(0);
if (semds->sem_perm.cgid != getegid()) e(0);
if (semds->sem_perm.mode != (SEM_ALLOC | 0650)) e(0);
if (semds->sem_perm._key != IPC_PRIVATE) e(0);
if (semds->sem_nsems != 3) e(0);
if (semds->sem_otime != 0) e(0);
if (semds->sem_ctime == 0) e(0);
/* Make sure that non-root users can see them as well. */
spawn(&child, test88f_child, DROP_ALL);
snd(&child, id[0]);
snd(&child, id[1]);
collect(&child);
/* Clean up, and verify that the sets are no longer in the listing. */
if (semctl(id[0], 0, IPC_RMID) != 0) e(0);
if (semctl(id[1], 0, IPC_RMID) != 0) e(0);
len = size;
if (sysctl(mib, __arraycount(mib), semsi, &len, NULL, 0) != 0) e(0);
if (len != size) e(0);
for (i = 0; i < seminfo.semmni; i++) {
if (!(semsi->semids[i].sem_perm.mode & SEM_ALLOC))
continue;
id2 = IXSEQ_TO_IPCID(i, semsi->semids[i].sem_perm);
if (id2 == id[0]) e(0);
if (id2 == id[1]) e(0);
}
free(semsi);
}
/*
* Initialize the test.
*/
static void
test88_init(void)
{
static const int mib[] = { CTL_KERN, KERN_SYSVIPC, KERN_SYSVIPC_SEM };
struct group *gr;
size_t len;
int i;
/* Start with full root privileges. */
setuid(geteuid());
if ((gr = getgrnam(ROOT_GROUP)) == NULL) e(0);
setgid(gr->gr_gid);
setegid(gr->gr_gid);
/*
* Verify that the IPC service is running at all. If not, there is
* obviously no point in running this test.
*/
len = sizeof(i);
if (sysctl(mib, __arraycount(mib), &i, &len, NULL, 0) != 0) e(0);
if (len != sizeof(i)) e(0);
if (i == 0) {
printf("skipped\n");
cleanup();
exit(0);
}
/* Allocate a memory page followed by an unmapped page. */
page_size = getpagesize();
page_ptr = mmap(NULL, page_size * 2, PROT_READ | PROT_WRITE,
MAP_ANON | MAP_PRIVATE, -1, 0);
if (page_ptr == MAP_FAILED) e(0);
bad_ptr = page_ptr + page_size;
if (munmap(bad_ptr, page_size) != 0) e(0);
}
/*
* Test program for SysV IPC semaphores.
*/
int
main(int argc, char ** argv)
{
int i, m;
start(88);
test88_init();
if (argc == 2)
m = atoi(argv[1]);
else
m = 0xFF;
for (i = 0; i < ITERATIONS; i++) {
if (m & 0x01) test88a();
if (m & 0x02) test88b();
if (m & 0x04) test88c();
if (m & 0x08) test88d();
if (m & 0x10) test88e();
if (m & 0x20) test88f();
}
quit();
}