DF-2909 / df2909_varsymrace.c
/* DF-2909: sys_varsym_set replace is non-atomic remove-then-insert. * Two same-uid processes racing USER-level sets of one name can both pass * the remove and both insert -> duplicate shadowed TAILQ entries. * Detection: after the race, ONE unset must delete the var; if a value * still resolves afterwards, a second (duplicate) entry survived. * Also: invalid level (99) silently returns success. * * varsym syscalls: 450 set, 451 get, 452 list. * VARSYM_PROC=1, VARSYM_USER=2, VARSYM_SYS=3, VARSYM_PRISON=4. */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/syscall.h> #include <sys/wait.h> #include <errno.h> #define SYS_varsym_set 450 #define SYS_varsym_get 451 #define SYS_varsym_list 452 #define VARSYM_USER 2 #define VARSYM_USER_MASK (1 << VARSYM_USER) #define NAME "DFRACE2909" #define NPROC 4 #define ITERS 60000 static int vset(int level, const char *name, const char *data) { return syscall(SYS_varsym_set, level, name, data); } static int vget(int mask, const char *name, char *buf, int bufsize) { return syscall(SYS_varsym_get, mask, name, buf, bufsize); } int main(void) { char buf[256]; int i, j, st, dup = 0; /* invalid level claim: returns 0 doing nothing */ errno = 0; i = vset(99, NAME, "ignored"); printf("invalid-level set rc=%d errno=%d (claim: silent success)\n", i, errno); /* seed */ if (vset(VARSYM_USER, NAME, "seed") != 0) { perror("seed set"); return 1; } memset(buf, 0, sizeof(buf)); if (vget(VARSYM_USER_MASK, NAME, buf, sizeof(buf)) != 0 || strcmp(buf, "seed") != 0) { printf("seed get failed (buf=%s errno=%d)\n", buf, errno); return 1; } printf("seed ok: %s\n", buf); for (i = 0; i < NPROC; i++) { if (fork() == 0) { char v[64]; for (j = 0; j < ITERS; j++) { snprintf(v, sizeof(v), "p%d-%d", i, j); vset(VARSYM_USER, NAME, v); } _exit(0); } } for (i = 0; i < NPROC; i++) wait(&st); /* one delete must remove the var entirely unless duplicates exist */ vset(VARSYM_USER, NAME, NULL); memset(buf, 0, sizeof(buf)); errno = 0; memset(buf, 0, sizeof(buf)); errno = 0; i = vget(VARSYM_USER_MASK, NAME, buf, sizeof(buf)); if (i == 0) { printf("DUPLICATE CONFIRMED: after one unset, %s still = \"%s\" (len %d)\n", NAME, buf, i); dup = 1; /* cleanup remaining copy */ vset(VARSYM_USER, NAME, NULL); } else { printf("no duplicate survived this run (single unset removed it)\n"); } return dup ? 9 : 0; } |