Files
packfs/tests/test_dir.c
T
retoorandClaude Sonnet 5 72e3c900f2 Add PackFS v0: statically linked in-process VFS implementing concept.md
Implements the core design: a mount table published as an atomically-
swapped snapshot; mem/dir/pack/overlay backends; copy-on-write overlay
with copy-up and whiteout deletion; a checksummed append journal;
compaction with exact-duplicate elimination; single-writer/wait-free-
reader concurrency with a structural/content write split; openat2/
Landlock path containment for dir mounts; and load-time pack integrity
validation. Zero required third-party dependencies.

Sanitizer testing (ASan/UBSan) caught and led to fixing a genuine
heap-use-after-free in the snapshot-reclamation path: the textbook
"load pointer, then increment its refcount" pattern left a gap a
concurrent writer could free through. Closed with a small reclaim_gate
rwlock, documented in internal.h and CLAUDE.md since it's a pattern
every refcounted structure in the codebase now follows.

zip/tar import/export backends, recommended in concept.md Section 11,
will not be built — a permanent project decision recorded in CLAUDE.md
since concept.md itself is frozen and cannot be edited to reflect it.

Includes a runnable demo (examples/demo.c, `make demo`) exercising the
library end to end and proving cross-run persistence through the pack
file, plus open-source scaffolding: MIT license, README, CONTRIBUTING,
and a CI workflow running the test suite under ASan/UBSan/TSan.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UqJpkdJ6Njnt1pw3CbghzB
2026-09-14 05:44:08 +00:00

65 lines
2.1 KiB
C

/* test_dir.c — dir backend: host passthrough + path containment
* (Section 6.2): a normalized ".." can't reach it (blocked in the
* virtual namespace, Section 6.1), and an absolute symlink planted
* inside the mount cannot be used to read outside it. */
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <unistd.h>
#include "packfs.h"
#include "test_harness.h"
int main(void) {
char root[] = "/tmp/packfs_test_dir_XXXXXX";
CHECK(mkdtemp(root) != NULL);
char outside_dir[480];
snprintf(outside_dir, sizeof(outside_dir), "/tmp/packfs_secret_%d", (int)getpid());
mkdir(outside_dir, 0755);
char secret_file[512];
snprintf(secret_file, sizeof(secret_file), "%s/secret.txt", outside_dir);
FILE *sf = fopen(secret_file, "w");
CHECK(sf != NULL);
fputs("top secret", sf);
fclose(sf);
/* a symlink INSIDE root pointing OUTSIDE it */
char link_path[512];
snprintf(link_path, sizeof(link_path), "%s/escape", root);
CHECK(symlink(outside_dir, link_path) == 0);
Vfs *v = vfs_new();
int derr = 0;
Backend *dir = backend_dir_new(root, &derr);
CHECK(dir != NULL);
CHECK_EQ_INT(vfs_mount(v, "/", dir), VFS_OK);
int err = 0;
VfsFile *f = vfs_open(v, "/a.txt", VFS_O_WRONLY | VFS_O_CREAT, &err);
CHECK(f != NULL);
CHECK_EQ_INT(vfs_write(f, "abc", 3), 3);
CHECK_EQ_INT(vfs_close(f), VFS_OK);
f = vfs_open(v, "/a.txt", VFS_O_RDONLY, &err);
char buf[8] = {0};
CHECK_EQ_INT(vfs_read(f, buf, sizeof(buf)), 3);
CHECK_STR_EQ(buf, "abc");
CHECK_EQ_INT(vfs_close(f), VFS_OK);
/* virtual-namespace ".." escape: rejected before any backend is reached */
f = vfs_open(v, "/../etc/shadow", VFS_O_RDONLY, &err);
CHECK(f == NULL);
CHECK_EQ_INT(err, VFS_ERR_INVAL);
/* symlink escape through the dir backend itself: openat2/O_NOFOLLOW
* containment (Section 6.2) must refuse to follow it. */
f = vfs_open(v, "/escape/secret.txt", VFS_O_RDONLY, &err);
CHECK(f == NULL);
vfs_unmount(v, "/");
backend_free(dir);
vfs_free(v);
TEST_MAIN_END();
}