/* * fdguard_test.c -- exercises libfdguard.so. * * The function names imitate the real owners inside dragonxd so the shim's * built-in domain patterns apply unchanged: * __os_openhandle / __os_closehandle Berkeley DB os layer (domain bdb) * OpenDiskFile / FlushBlockFile block-file code (domain blk) * ConnectSocketDirectly / CloseSocket P2P sockets (domain p2p) * foreign_stale_close / foreign_* somebody who holds a stale number * * usage: fdguard_test (see main() for the list) */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #define NI __attribute__((noinline)) static char D[512]; static char *P(const char *rel) { static char b[4][700]; static int i; i = (i + 1) & 3; snprintf(b[i], sizeof b[i], "%s/%s", D, rel); return b[i]; } static void say(const char *m) { fprintf(stderr, "[test] %s\n", m); } /* --- "Berkeley DB" --- */ NI int __os_openhandle(const char *path) { return open(path, O_RDWR | O_CREAT, 0600); } NI int __os_closehandle(int fd) { return close(fd); } /* --- "block files" --- */ NI FILE *OpenDiskFile(const char *path) { FILE *f = fopen(path, "rb+"); if (!f) f = fopen(path, "wb+"); return f; } NI int FlushBlockFile(FILE *f) { fflush(f); return fclose(f); } /* --- "P2P" --- */ NI int ConnectSocketDirectly(void) { return socket(AF_INET, SOCK_STREAM, 0); } NI int CloseSocket(int *s) { int r = close(*s); if (r) fprintf(stderr, "[test] Socket close failed: %d. Error: %s\n", *s, strerror(errno)); return r; } /* --- foreigners --- */ NI int foreign_stale_close(int fd) { return close(fd); } NI int foreign_stale_fclose(FILE *f) { return fclose(f); } NI int foreign_dup2(int fd) { int dn = open("/dev/null", O_RDWR); int r = dup2(dn, fd); close(dn); return r; } NI int foreign_close_range(int fd) { return close_range((unsigned)fd, (unsigned)fd, 0); } NI void foreign_closefrom(int fd) { closefrom(fd); } /* --- plain code that opens and closes in the same function --- */ NI int copy_like(const char *src) { int fd = open(src, O_RDONLY); char b[16]; if (fd >= 0) { (void)!read(fd, b, sizeof b); close(fd); } return fd; } NI int plain_open(const char *p) { return open(p, O_RDWR | O_CREAT, 0600); } NI int plain_close(int fd) { return close(fd); } static int fd_valid(int fd) { return fcntl(fd, F_GETFD) != -1; } static int c_double(void) { int fd = plain_open(P("plain.txt")); plain_close(fd); int r = plain_close(fd); /* EBADF */ return r == -1 && errno == EBADF ? 0 : 1; } static int c_dbowner(void) { int fd = __os_openhandle(P("regtest/wallet.dat")); int r = __os_closehandle(fd); return r; } static int c_dbforeign(void) { int fd = __os_openhandle(P("regtest/wallet.dat")); foreign_stale_close(fd); /* HIT */ int r = __os_closehandle(fd); /* BDB's own close then fails: EBADF (unless deny) */ (void)r; return 0; } static int c_deny(void) { int fd = __os_openhandle(P("regtest/wallet.dat")); foreign_stale_close(fd); /* HIT + DENIED */ if (!fd_valid(fd)) { say("deny FAILED: fd closed"); return 1; } if (write(fd, "x", 1) != 1) { say("deny FAILED: write"); return 1; } return __os_closehandle(fd); } static int c_blkowner(void) { FILE *f = OpenDiskFile(P("regtest/blocks/blk00000.dat")); fputs("blk", f); return FlushBlockFile(f); } static int c_blkraw(void) { FILE *f = OpenDiskFile(P("regtest/blocks/blk00001.dat")); foreign_stale_close(fileno(f)); /* HIT: raw close under a live FILE */ FlushBlockFile(f); /* EBADF from fclose */ return 0; } static int c_stalefile(void) { FILE *f = OpenDiskFile(P("regtest/blocks/blk00002.dat")); int n = fileno(f); foreign_stale_close(n); /* HIT #1 */ int w = __os_openhandle(P("regtest/wallet.dat")); if (w != n) fprintf(stderr, "[test] note: wallet got fd %d, FILE had %d\n", w, n); FlushBlockFile(f); /* HIT #2: fclose of a stale FILE whose number is now wallet.dat */ if (w == n) __os_closehandle(w); /* EBADF: BDB lost its fd */ return 0; } static int c_reuse(void) { int s = ConnectSocketDirectly(); int keep = s; CloseSocket(&s); /* legit */ int w = __os_openhandle(P("regtest/wallet.dat")); /* reuses the number */ if (w != keep) fprintf(stderr, "[test] note: wallet got fd %d, socket was %d\n", w, keep); s = keep; CloseSocket(&s); /* HIT: stale socket number now = wallet.dat */ __os_closehandle(w); /* EBADF */ return 0; } static int c_dup2(void) { int w = __os_openhandle(P("regtest/wallet.dat")); foreign_dup2(w); __os_closehandle(w); return 0; } static int c_range(void) { int w = __os_openhandle(P("regtest/wallet.dat")); foreign_close_range(w); /* HIT */ w = __os_openhandle(P("regtest/database/log.0000000001")); int hi = fcntl(w, F_DUPFD, 600); /* move it high so closefrom only hits it */ close(w); foreign_closefrom(hi); /* HIT (domain mismatch: opened by test main via fcntl dup? -> fallback db) */ return 0; } static int c_owner_misc(void) { /* nothing here may be flagged */ copy_like(P("regtest/wallet.dat")); /* common-caller */ int fd = __os_openhandle(P("regtest/blocks/blk00003.dat")); FILE *f = fdopen(fd, "rb+"); /* ownership moves to FILE */ FlushBlockFile(f); int s = ConnectSocketDirectly(); CloseSocket(&s); fd = (int)syscall(SYS_openat, AT_FDCWD, P("regtest/wallet.dat"), O_RDWR); /* untracked open */ __os_closehandle(fd); /* fallback:bdb */ return 0; } static int c_fallback_hit(void) { int fd = (int)syscall(SYS_openat, AT_FDCWD, P("regtest/wallet.dat"), O_RDWR); /* untracked open */ foreign_stale_close(fd); /* HIT via fallback rule */ return 0; } static int c_epoll(void) { int ep = epoll_create1(0); int fd = plain_open(P("plain2.txt")); struct epoll_event ev = { .events = EPOLLIN }; int r = epoll_ctl(ep, EPOLL_CTL_ADD, fd, &ev); /* EPERM: regular file */ close(fd); r = epoll_ctl(ep, EPOLL_CTL_ADD, fd, &ev); /* EBADF */ close(ep); return r == -1 ? 0 : 1; } static int c_selfclose(void) { /* close every fd from 3 to 1100 by number, like a buggy daemonizer */ for (int i = 3; i < 1100; i++) close(i); return 0; } /* concurrency + micro-benchmark: open/close churn in many threads */ static int nthr = 8, iters = 20000; static void *worker(void *arg) { long id = (long)arg; char p[700]; snprintf(p, sizeof p, "%s/thr%ld.txt", D, id); for (int i = 0; i < iters; i++) { int s = ConnectSocketDirectly(); CloseSocket(&s); int fd = plain_open(p); plain_close(fd); FILE *f = fopen(p, "r"); if (f) fclose(f); if ((i & 255) == 0) { int w = __os_openhandle(P("regtest/wallet.dat")); __os_closehandle(w); } } return NULL; } static int c_threads(void) { const char *e = getenv("T_ITERS"); if (e) iters = atoi(e); pthread_t t[64]; struct timespec a, b; clock_gettime(CLOCK_MONOTONIC, &a); for (long i = 0; i < nthr; i++) pthread_create(&t[i], NULL, worker, (void *)i); for (int i = 0; i < nthr; i++) pthread_join(t[i], NULL); clock_gettime(CLOCK_MONOTONIC, &b); double ns = (b.tv_sec - a.tv_sec) * 1e9 + (b.tv_nsec - a.tv_nsec); long ops = (long)nthr * iters * 3; printf("threads=%d iters=%d ops=%ld (socket/open/fopen + close each) wall=%.3fs %.0f ns/op-pair/thread\n", nthr, iters, ops, ns / 1e9, ns * nthr / ops); return 0; } int main(int argc, char **argv) { if (argc < 3) { fprintf(stderr, "usage: %s \n", argv[0]); return 2; } snprintf(D, sizeof D, "%s", argv[1]); const char *c = argv[2]; if (!strcmp(c, "double")) return c_double(); if (!strcmp(c, "dbowner")) return c_dbowner(); if (!strcmp(c, "dbforeign")) return c_dbforeign(); if (!strcmp(c, "deny")) return c_deny(); if (!strcmp(c, "blkowner")) return c_blkowner(); if (!strcmp(c, "blkraw")) return c_blkraw(); if (!strcmp(c, "stalefile")) return c_stalefile(); if (!strcmp(c, "reuse")) return c_reuse(); if (!strcmp(c, "dup2")) return c_dup2(); if (!strcmp(c, "range")) return c_range(); if (!strcmp(c, "ownermisc")) return c_owner_misc(); if (!strcmp(c, "fallback")) return c_fallback_hit(); if (!strcmp(c, "epoll")) return c_epoll(); if (!strcmp(c, "selfclose")) return c_selfclose(); if (!strcmp(c, "threads")) return c_threads(); fprintf(stderr, "unknown case %s\n", c); return 2; }