Skip to content

ARCHITECTURE · 09

Running a binary that lives inside an image.

An interpreted application never execs anything, because the interpreter runs inside the mounts. A toolkit payload, however, ships real executables, and an exec target held by a virtual filesystem needs a path to the CPU. Tebako has four such paths, and the payload's manifest declares which one it takes; consumers never care which path a tool took.

Note

The dynamic, tfs-native, and static tiers are shipping.

The wrapped tier is named but not yet specified, and it is planned.

Choosing a tier, as a flow

The payload publisher makes this choice once, and the dispatcher honors it silently. The questions follow, in order:

does the payload exec a binary at all? ruby apps just run — the interpreter is already inside no interpreted no exec tier yes do you build the tool yourself? source access + the tebako SDK yes, fully tfs-native carries the VFS no — it's a third-party binary is it dynamically linked? almost everything is: inkscape, java, ffmpeg yes dynamic the preload shim no — static, or unservable by the shim static — extract the closure materialized to the exec cache, verified at install sees the host FS only — no VFS, no jails wrapped — PLANNED link-time interposition, when you build but can't go fully native One rule holds for every tier: tebako never uses FUSE and never needs a kernel module, a daemon, or root. Interposition stays in userland — no kernel component. try the dynamic tier by hand: tfs exec app.tfs:/mnt --jail deny -- /mnt/bin/tool --help

Figure 1 — Exec tier decision flow: interpreted, source-available dynamic, static, wrapped.

The dynamic tier: the preload shim

Note

This tier is shipped.

A small interposition library is injected into the process at launch, and it maps the standard file-IO calls onto the mounted images. The binary, and every library it dynamically loads, sees the images as ordinary directories. Nothing is extracted, and jails apply, because the IO flows through the same policy choke point.

The choice fits the mainline: any dynamically linked executable, for example Inkscape, a JVM, ffmpeg, or git.

The honest limits are dynamic binaries only: a raw-syscall caller bypasses interposition, and macOS system binaries strip the injection variables.

The tfs-native tier: built against the VFS

Note

This tier is shipped.

The binary is compiled against the tebako filesystem interface and carries the VFS inside itself. The patched ruby interpreter is the canonical example: it mounts and serves its own standard library from the env image, with no shim involved.

The choice fits a tool built in-house, where linking the interface is possible. The property is a build-time property, and it cannot be imposed on an arbitrary binary.

The honest limit is that the tier requires source access and the tebako SDK.

The static tier: closure extraction

Note

This tier is shipped.

For statically linked tools, interposition has nothing to attach to. The executable and its declared closure are materialized once into a content-addressed exec cache in the store, verified at install, and run from there. On macOS and Windows this is also the fallback for anything the shim cannot mediate.

The choice fits statically linked binaries and platforms without a syscall-mediation surface.

The honest limits are that such processes see only the host filesystem, with no VFS, and jails do not reach them; that is a platform-API statement, not a trust statement.

Note

This tier is planned.

The mechanism is the same interposition as the dynamic tier, bound at link time instead of launch time, for binaries built in-house that cannot be made fully TFS-native.

The tier is named in the manifest vocabulary today, and the mechanics ship in a later milestone.

The honest limit is that the mechanics are not yet specified.

The hardest case: an image execs an image

The metanorma proof runs exactly this case: a ruby process, inside its mounts, shells out to a JVM and to Inkscape, both of which live inside disk images. Three mechanisms make it work. The runtime mediates process spawning: an exec target held by the mounts is materialized to a host cache and executed there. Before exec, the loader walks the binary’s dependency closure (rpaths, @executable_path, $ORIGIN) and materializes every in-image library it needs, because the platform loader probes for shared libraries with raw syscalls that no userland hook can serve. The spawned child re-enters the namespace through the preload shim with the mount table injected into its environment, so the grandchild of a packaged app sees the same virtual filesystem as its parent.

The result is unglamorous and important: PATH= metanorma compile site.adoc produces a PDF. Nothing on the host participated, so nothing on the host can break it.