DF-3111 / recheck.c
/* * DF-3111 recheck — after unmount+remount (dirents flushed to disk, * namecache cold): durable manifestation of the wrapped key_end. * stat(nameA) -> OK (first chain element at K) * stat(nameB) -> ENOENT (record at K+1: hammer_btree_iterate rejects * chain elements past the wrapped key_end) * unlink(nameB) -> ENOENT -> permanently unremovable, across remounts * readdir lists nameB -> phantom entry * each O_CREAT leaks another unremovable dirent+inode (K+2, K+3, ...) * Runs as uid 65534. */ #include <stdio.h> #include <stdint.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <fcntl.h> #include <errno.h> #include <sys/stat.h> #include <dirent.h> #include "solver.h" static int count_dirents(const char *name, int len) { DIR *d = opendir("."); struct dirent *de; int cnt = 0; if (!d) return -1; while ((de = readdir(d)) != NULL) if (de->d_namlen == len && memcmp(de->d_name, name, len) == 0) cnt++; closedir(d); return cnt; } int main(int argc, char **argv) { char nameA[16], nameB[16]; int lenA, lenB; int64_t key; struct stat st; int rc, cnt, i, fd; const char *dir = argc > 1 ? argv[1] : "/mnt/t"; if (df_find_pair(nameA, &lenA, nameB, &lenB, &key) < 0) return 1; if (setgid(65534) || setuid(65534)) { perror("setuid/setgid"); return 1; } printf("[after remount, uid=%d, dir=%s]\n", getuid(), dir); df_pr_name("nameA", nameA, lenA, key); df_pr_name("nameB", nameB, lenB, key); if (chdir(dir)) { perror("chdir"); return 1; } rc = lstat(nameA, &st); printf("persist stat nameA : %s\n", rc == 0 ? "OK" : strerror(errno)); rc = lstat(nameB, &st); printf("persist stat nameB : %s%s\n", rc == 0 ? "OK" : strerror(errno), (rc < 0 && errno == ENOENT) ? " <-- POISONED (dirent exists on disk)" : ""); cnt = count_dirents(nameB, lenB); printf("persist readdir nameB: %d%s\n", cnt, cnt > 0 ? " <-- listed but unfindable" : ""); rc = unlink(nameB); printf("persist unlink nameB : %s%s\n", rc == 0 ? "OK" : strerror(errno), (rc < 0 && errno == ENOENT) ? " <-- UNREMOVABLE" : ""); { int cnt2; for (i = 0; i < 5; i++) { fd = open(nameB, O_CREAT | O_WRONLY, 0644); if (fd >= 0) close(fd); } cnt2 = count_dirents(nameB, lenB); printf("after 5 re-creates : readdir nameB cnt = %d (was %d)%s\n", cnt2, cnt, cnt2 > cnt ? " <-- LEAK: one more unremovable dirent+inode each" : ""); printf("PERSIST_%s LEAK_%s\n", (lstat(nameB, &st) < 0 && errno == ENOENT && cnt > 0) ? "CONFIRMED" : "UNCLEAR", cnt2 > cnt ? "CONFIRMED" : "UNCLEAR"); } return 0; } |