DF-3079 / procfs_sticky_excl.c
/* * DF-3079 — procfs /proc/<pid>/mem exclusive-open (O_EXCL) flag is never * cleared when the last write-open did not use O_EXCL, permanently denying * O_EXCL opens for that pid until the vnode is reclaimed. * * sys/vfs/procfs/procfs_vnops.c: * procfs_open (lines ~184-205): on FWRITE open, pfs->pfs_flags = * a_mode & (FWRITE|O_EXCL) -> a plain O_RDWR open sets FWRITE only. * procfs_close (lines ~239-241): clears (FWRITE|O_EXCL) ONLY IF * (pfs->pfs_flags & O_EXCL) is already set -> plain-write close * leaves FWRITE latched. * next O_EXCL open: ((pfs_flags & FWRITE) && (mode & O_EXCL)) -> EBUSY. * * Build: cc -o procfs_sticky_excl procfs_sticky_excl.c * Run: ./procfs_sticky_excl (unprivileged is fine) * Expected (bug): "BUG-REPRODUCED: EBUSY on O_EXCL after non-EXCL write open+close" */ #include <stdio.h> #include <stdlib.h> #include <fcntl.h> #include <unistd.h> #include <errno.h> #include <string.h> #include <signal.h> #include <sys/wait.h> static int openmem(pid_t pid, int flags) { char p[64]; snprintf(p, sizeof(p), "/proc/%d/mem", (int)pid); errno = 0; return open(p, flags, 0); } int main(void) { pid_t child; int fd, status, bug_hit; /* fresh same-uid child pid, never opened before -> control case */ child = fork(); if (child == 0) { pause(); _exit(0); } if (child < 0) { perror("fork"); return 2; } /* Step 1: plain write open of OWN mem, no O_EXCL, then close */ fd = openmem(getpid(), O_RDWR); printf("open(/proc/self/mem, O_RDWR) = %-3d %s\n", fd, fd < 0 ? strerror(errno) : "OK"); if (fd >= 0) close(fd); /* Step 2: exclusive open of the same node -> expect the latched * FWRITE flag to produce EBUSY forever (the bug) */ fd = openmem(getpid(), O_RDWR | O_EXCL); printf("open(/proc/self/mem, O_RDWR|O_EXCL) = %-3d %s\n", fd, fd < 0 ? strerror(errno) : "OK"); bug_hit = (fd < 0 && errno == EBUSY); if (fd >= 0) close(fd); /* Control: fresh child pid, O_EXCL open must succeed */ fd = openmem(child, O_RDWR | O_EXCL); printf("open(/proc/child/mem, O_RDWR|O_EXCL) = %-3d %s\n", fd, fd < 0 ? strerror(errno) : "OK"); if (fd >= 0) close(fd); kill(child, SIGKILL); waitpid(child, &status, 0); printf("%s\n", bug_hit ? "BUG-REPRODUCED: EBUSY on O_EXCL after non-EXCL write open+close" : "NOT-REPRODUCED: O_EXCL open still works"); return bug_hit ? 0 : 1; } |