DF-0911 / df0911.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 | /* * DF-0911 - procfs ATTACH skips saving p_oppid for already-owned children * * Trigger: a process forks a child, then ATTACHes to it via /proc/<pid>/ctl * and DETACHes. Because the tracer is already the actual parent of the * target, procfs_control() takes the `p->p_pptr != curp` branch FALSE at * sys/vfs/procfs/procfs_ctl.c:153 and skips saving p_oppid (which stays 0). * * On DETACH (procfs_ctl.c:211) the check `p_oppid != p_pptr->p_pid` is * therefore TRUE (0 != parent's pid), so pfs_pfind(0) is called, which * returns &proc0 (procfs_subr.c:285-287), and proc_reparent() hands the * child to proc0 -- the child is now orphaned to the kernel swapper. * * Observable effect: after DETACH the child's getppid() returns 0 and the * parent's waitpid(child) returns ECHILD. Compare to ptrace(2) PT_ATTACH * (sys_process.c:314) which ALWAYS saves p_oppid -- procfs is inconsistent. * * Build: cc -o df0911 df0911.c * Run: ./df0911 * Expected on buggy kernel: child ppid after detach = 0, waitpid=ECHILD. * Expected on fixed kernel: child ppid after detach = parent's pid, waitpid ok. */ #include <sys/types.h> #include <sys/wait.h> #include <sys/ptrace.h> #include <err.h> #include <errno.h> #include <fcntl.h> #include <signal.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> static void write_ctl(pid_t pid, const char *cmd) { char path[64]; int fd, n; ssize_t w; snprintf(path, sizeof(path), "/proc/%d/ctl", pid); fd = open(path, O_WRONLY, 0); if (fd < 0) err(1, "open %s", path); w = write(fd, cmd, strlen(cmd)); if (w < 0) err(1, "write %s", path); n = (int)w; close(fd); fprintf(stderr, "[parent] wrote '%s' (%d bytes) to %s\n", cmd, n, path); } int main(void) { pid_t child, wp; int status; int pipefd[2]; char buf[64]; ssize_t r; if (pipe(pipefd) < 0) err(1, "pipe"); child = fork(); if (child < 0) err(1, "fork"); if (child == 0) { /* child: close write end, then loop reporting our ppid */ close(pipefd[0]); for (;;) { pid_t pp = getppid(); snprintf(buf, sizeof(buf), "child ppid=%d\n", pp); (void)write(pipefd[1], buf, strlen(buf)); sleep(1); } /* not reached */ _exit(0); } close(pipefd[1]); /* read one line before attach: should report parent pid */ r = read(pipefd[0], buf, sizeof(buf) - 1); if (r > 0) { buf[r] = 0; fprintf(stderr, "[parent] before attach: %s", buf); } /* ATTACH via procfs ctl. Tracer is the actual parent of the target, * so procfs_control() takes the FALSE branch at procfs_ctl.c:153 and * does NOT save p_oppid (stays 0). */ write_ctl(child, "attach"); usleep(200000); /* DETACH. p_oppid=0 != parent's pid -> pfs_pfind(0) -> proc0 -> * proc_reparent(child, &proc0). Child is orphaned to the swapper. */ write_ctl(child, "detach"); usleep(500000); /* read child's report after detach: should now report ppid=0 */ r = read(pipefd[0], buf, sizeof(buf) - 1); if (r > 0) { buf[r] = 0; fprintf(stderr, "[parent] after detach: %s", buf); if (strncmp(buf, "child ppid=0", 12) == 0) { printf("RESULT: BUG REPRODUCED -- child orphaned to " "proc0 (ppid=0) after procfs attach/detach\n"); } else { printf("RESULT: not reproduced -- child ppid after " "detach is non-zero\n"); } } else { printf("RESULT: no child report (read=%zd)\n", r); } /* Try to wait for the child. If the child was reparented away from * us, waitpid must return ECHILD. */ errno = 0; wp = waitpid(child, &status, WNOHANG); printf("waitpid(%d)=%d errno=%d (%s)\n", child, (int)wp, errno, errno == ECHILD ? "ECHILD" : strerror(errno)); /* Also dump /proc/<child>/status for an authoritative kernel view */ { char path[64]; int fd; snprintf(path, sizeof(path), "/proc/%d/status", child); fd = open(path, O_RDONLY, 0); if (fd >= 0) { char s[1024]; ssize_t n = read(fd, s, sizeof(s)-1); if (n > 0) { s[n] = 0; printf("---- /proc/%d/status ----\n%s", child, s); } close(fd); } } /* clean up */ (void)kill(child, SIGKILL); (void)waitpid(child, &status, 0); return 0; } |