DF-2824 / df2824_deadlock.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 | /* * DF-2824 -- DragonFlyBSD sys/kern/kern_lockf.c * * BUG: POSIX deadlock detection (EDEADLK) in lf_setlock() * (kern_lockf.c:406-413) only scans the blocked list of the *same* lockf * (same file) for the conflicting owner. A deadlock cycle that spans two * (or more) files is not detected: the process closing the cycle is put to * sleep instead of getting EDEADLK, and all members of the cycle hang in * F_SETLKW until signaled (the sleep is PCATCH, so they are killable). * * POSIX.1-2008 2.9.7 (locked when a deadlock would occur): fcntl(F_SETLKW) * shall fail with [EDEADLK] if the lock would cause a deadlock. * * Deterministic probe (two-phase sync so both children hold their first * locks before either attempts the second): * child1: F_SETLK WRLCK all of A --check rc-- [gate] F_SETLKW WRLCK on B * child2: F_SETLK WRLCK all of B --check rc-- [gate] F_SETLKW WRLCK on A * If detection worked, child2 (the cycle closer) returns EDEADLK(11) * quickly. Bug => both children remain blocked after the timeout window. */ #include <errno.h> #include <fcntl.h> #include <signal.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/wait.h> #include <unistd.h> static int lockall(int fd, int wait) { struct flock fl; memset(&fl, 0, sizeof(fl)); fl.l_type = F_WRLCK; fl.l_whence = SEEK_SET; fl.l_start = 0; fl.l_len = 0; return (fcntl(fd, wait ? F_SETLKW : F_SETLK, &fl)); } int main(void) { char pa[64], pb[64], c; int fda, fdb, syncready[2], syncgo[2]; int i, st, rc, blocked1 = 1, blocked2 = 1; pid_t c1, c2, wp; setvbuf(stdout, NULL, _IONBF, 0); snprintf(pa, sizeof(pa), "/tmp/df2824.a.%d", getpid()); snprintf(pb, sizeof(pb), "/tmp/df2824.b.%d", getpid()); fda = open(pa, O_RDWR | O_CREAT | O_TRUNC, 0600); fdb = open(pb, O_RDWR | O_CREAT | O_TRUNC, 0600); if (fda < 0 || fdb < 0 || pipe(syncready) != 0 || pipe(syncgo) != 0) { perror("setup"); exit(2); } unlink(pa); unlink(pb); c1 = fork(); if (c1 == 0) { setvbuf(stdout, NULL, _IONBF, 0); rc = lockall(fda, 0); /* child1 owns A */ if (rc != 0) { printf("child1: first lock A failed: %s\n", strerror(errno)); _exit(9); } write(syncready[1], "a", 1); if (read(syncgo[0], &c, 1) != 1) _exit(9); /* wait for gate */ errno = 0; rc = lockall(fdb, 1); /* F_SETLKW on B (held by c2) */ printf("child1[%d]: F_SETLKW(B) -> rc=%d errno=%d (%s)\n", getpid(), rc, errno, rc ? strerror(errno) : "ok"); _exit(0); } c2 = fork(); if (c2 == 0) { setvbuf(stdout, NULL, _IONBF, 0); rc = lockall(fdb, 0); /* child2 owns B */ if (rc != 0) { printf("child2: first lock B failed: %s\n", strerror(errno)); _exit(9); } write(syncready[1], "b", 1); if (read(syncgo[0], &c, 1) != 1) _exit(9); /* wait for gate */ errno = 0; rc = lockall(fda, 1); /* closes the cycle */ printf("child2[%d]: F_SETLKW(A) -> rc=%d errno=%d (%s)\n", getpid(), rc, errno, rc ? strerror(errno) : "ok"); if (rc == -1 && errno == EDEADLK) printf("child2: EDEADLK detected (correct)\n"); _exit(0); } /* wait until both children verifiably hold their first locks */ if (read(syncready[0], &c, 1) != 1 || read(syncready[0], &c, 1) != 1) { perror("ready sync"); exit(2); } /* open the gate simultaneously */ write(syncgo[1], "g", 1); write(syncgo[1], "g", 1); printf("T2: both children hold their first locks; gate opened\n"); /* 4s window for EDEADLK to be delivered to the cycle closer */ for (i = 0; i < 40; ++i) { usleep(100000); if (blocked1 && waitpid(c1, &st, WNOHANG) == c1) blocked1 = 0; if (blocked2 && waitpid(c2, &st, WNOHANG) == c2) blocked2 = 0; if (!blocked1 || !blocked2) break; } if (blocked1 && blocked2) { printf("T2_RESULT: BUG-REPRODUCED both processes still blocked " "in F_SETLKW after 4s; POSIX requires EDEADLK for the " "cycle closer (sleep is PCATCH: killable)\n"); } else { printf("T2_RESULT: NOT-REPRODUCED a child returned early " "(deadlock detection worked)\n"); } kill(c1, SIGKILL); kill(c2, SIGKILL); waitpid(c1, &st, 0); waitpid(c2, &st, 0); printf("T2: children reaped, guest clean\n"); return ((blocked1 && blocked2) ? 0 : 1); } |