Skip to content

Releases

hisn 0.4.3

Choose a tag to compare

@github-actions github-actions released this 11 Nov 08:11
76758aa

0.4.3 adds no features. It locks in the 0.4.2 fixes with regression tests,
adds a release gate, fixes smaller audit findings, and restructures
session startup so the coming egress proxy, cgroup v2 limits and
multi-seat work can be added safely.

Host evidence (rubin): make release-gate all ok (fmt, clippy, make
check, make test), leak suite 333 passed, 0 failed, 9 skipped, 1 weak;
with HISN_TEST_ROOTFS and HISN_BIG_TESTS the leak suite reports 346
passed, 0 failed, 5 skipped, 1 weak; make leaktest on a terminal also
reports 333 passed, 0 failed, 9 skipped, 1 weak. The gate's
pseudo-terminal step was skipped because script is not installed; the
direct terminal run covers it. No unit-test count for rubin is recorded;
no sessions, leak or integration tests were run in the build sandbox.

Changed

  • Host services run in their own process group. Each host service is
    a process-group leader with stdin /dev/null (stdout and stderr too).
    Stopping a session signals the whole group, then SIGKILLs it after the
    leader is reaped, so a TERM-ignoring child does not survive.
  • Startup fails at once when a host service exits before readiness
    completes
    , with host_service <name>: exited with status <n> before readiness completed.
  • A service shows starting in ls --json until readiness passes,
    then ready. exit stays null for in-sandbox services.
  • SSH agent proxy timeouts. The host agent socket now has a 30 s
    read timeout alongside the existing 30 s write timeout.
  • Release gate. make release-gate runs fmt, clippy, make check,
    make test and the leak suite (pipe, then pseudo-terminal when
    script is present), stopping at the first failure.

Fixed

  • recv_with_fd treats a truncated control message as an error.
  • rootfs.rs dup failures now return the OS error instead of wrapping -1.
  • stdio_to_dev_null closes the opened fd only above fd 2 on error.
  • The SSH agent client counter is decremented on every exit path via a
    guard inside the worker thread.
  • sessionmeta::rewrite removes a stale temp of the same name before
    creating a new one.
  • Leak tests L77/L78 take the helper socket path from argv[1]; the
    release gate pipes the leak suite and reports a skipped pseudo-terminal
    run honestly; dead-service detection, the cleanup guard's group kill,
    and the ExecTable insert panic are fixed.

Hardening

  • run_session is split into named phases behind one cleanup guard; a new
    src/services.rs holds the service code; host and in-sandbox stops
    share one stop_and_reap path; one profile load; one exec table.
  • Leak cases L76–L81: exec cancel on early disconnect, hung and slow
    readiness probes, load-time rejections, host-service group stop, and
    readiness failing for an already-dead host service.

Leak suite: the portable subset runs in CI; host-only checks (user namespaces, rootfs, attach, leases) are verified on a real host before tagging — see the release notes for the host result. The aarch64 binary is built and started (--version) but has not been leak-tested. The glibc binaries (hisn-x86_64-linux-gnu, hisn-aarch64-linux-gnu) are dynamically linked and built natively on Ubuntu 22.04 (glibc 2.35 or newer required). The musl binary (hisn-x86_64-linux-musl) is statically linked. Verify with sha256sum -c SHA256SUMS. See README, "Verifying a build".

hisn 0.4.2

Choose a tag to compare

@github-actions github-actions released this 09 Nov 23:13
5f7bba2

hisn v0.4.2

0.4.2 closes the findings of a code audit of 0.4.1. It adds no features. Profiles that used to panic or hang the supervisor at run time are now rejected when they load, readiness probes can no longer outlive their timeout, a cancelled exec no longer keeps running, and session.json updates are safe across processes.

Upgrade notes

  • A profile that loaded under 0.4.1 can now be rejected. This affects stop.grace or a readiness timeout above 86400 s, NUL bytes in exec strings, and any use of ready.tcp.
  • ready.tcp was accepted before but never became ready, so a profile using it always timed out. It now fails at load with E_NOT_SUPPORTED.
  • A ready.command probe that hangs is no longer retried. It is killed when ready.timeout expires and startup fails.
  • The control-socket protocol, ls --json output, the supervisor.pid format and exit codes are unchanged.

Changed

  • Profile validation. stop.grace and readiness timeout are capped at 86400 s. NUL bytes in host_services.exec, ready.command and service argv are rejected. ready.tcp is rejected as not implemented.
  • Readiness probes are bounded by ready.timeout. A ready.command probe gets the time remaining until the readiness deadline. At the deadline the probe and its process group are killed and startup fails. A slow probe that finishes within the timeout now succeeds.
  • session.json is written under a lock. Every read-modify-write (set_state, record_member, touch_lease) takes an exclusive flock on session.json.lock in the session directory, in the supervisor and in hisn stop.

Fixed

  • Cancelled exec kept running. If a client disconnected before hisn sent started, the program was never signalled and the session could not idle out while it ran. The exec is now cancelled.
  • Supervisor panics from profile values. A very large grace or timeout overflowed the deadline arithmetic, and a NUL byte in an exec string panicked at start. Both are now load-time errors.
  • Host-service stop race. SIGKILL is never sent to a pid that was already reaped, and an interrupted wait no longer cuts the stop grace short.
  • Readiness reported too early. An interrupted wait on a ready.command probe was treated as success. The wait result is now checked.
  • Lost session.json updates. The supervisor and hisn stop could overwrite each other's changes.

Hardening

  • Host-service and readiness-probe children do no allocation between fork and exec. Readiness probes close inherited descriptors and die with the supervisor.
  • The SSH agent proxy computes key fingerprints with the sha2 crate. Its output is unchanged.

Internal

  • Removed dead code: session::mode_of, ServiceHandle.name, init::raw_fd, a stale #[allow(clippy::too_many_arguments)] and an unused parameter.
  • Leak test L33 passes --no-tty, so the suite also passes when run directly on a terminal.

Verified

On the release host (Fedora 45, x86_64): unit tests 406 passed, 0 failed. Leak suite 307 passed, 0 failed, 9 skipped, 1 weak. The exec-cancel and readiness-timeout checks pass end to end.

The portable subset of the leak suite runs in CI. The host-only checks (user namespaces, rootfs, attach, leases) are covered by the host result above. The aarch64 binary is built and started (--version) but has not been leak-tested.

Binaries

The glibc binaries (hisn-x86_64-linux-gnu, hisn-aarch64-linux-gnu) are dynamically linked and built natively on Ubuntu 22.04, so they need glibc 2.35 or newer. The musl binary (hisn-x86_64-linux-musl) is statically linked. Verify with sha256sum -c SHA256SUMS. See README, "Verifying a build".

hisn 0.4.1

Choose a tag to compare

@github-actions github-actions released this 08 Nov 19:13
9b44908

hisn v0.4.1

0.4.1 makes run --detach return, completes the ls --json client view, removes the fixed 100 ms waits on session start and stop, and hardens the 0.4.0 seat model.

Highlights

  • hisn run --detach forks the supervisor, waits for readiness, prints <id> <name|-> ready and exits 0.
  • A failed start exits 125, with or without --detach.
  • Session readiness and hisn stop are event-driven. No fixed 100 ms poll remains on either path.
  • hisn ls --json now reports live clients and execs (hisn-ls/v2, additive).
  • hisn ls --prune gains --name-prefix and --dry-run.
  • Observer queries (ls, inspect, sessioninfo) no longer count as clients, so they cannot keep an idle session alive.

Added

  • hisn run --detach returns. The child calls setsid and writes stdio to <session-dir>/supervisor.log (mode 0600). The parent prints RISK lines before forking, because the child's stderr is not the caller's.
  • hisn ls --prune --name-prefix <prefix> prunes only stopped or crashed sessions whose name starts with <prefix>. An unnamed session never matches. Plain --prune is unchanged.
  • hisn ls --prune --dry-run lists the sessions that would be pruned and removes nothing.
  • hisn ls --json additions (hisn-ls/v2, same schema name):
    • clients: live control connections (uid, pid, label, label_source, connected_since).
    • execs: a live attach appears with attach: true.
    • services, rootfs, members, lease, and dir fields.
    • For a stopped or crashed session, clients is [] and CLIENTS is 0.
  • A sessioninfo control op. The supervisor uses it to supply the live client and exec view, which session.json on disk cannot.
  • hisn exec --client LABEL, the same flag as attach.
  • hisn run --env-file PATH. It reads KEY=VALUE lines (blank lines and # comments ignored; validated like --env). Use it for secret-shaped values so they stay out of /proc/<pid>/cmdline.
  • Leak tests L73 (ls --prune --name-prefix), L74 (ls --json clients) and L75 (run --detach returns).
  • Tests tests/latency.rs, tests/detach_pipes.rs and tests/observers.rs.

Changed

  • Readiness polls the control socket with a nonblocking connect and exponential backoff, 1 ms up to 20 ms. A stalled listener cannot block the waiter.
  • hisn stop waits on the supervisor's pidfd. Only the no-pidfd fallback polls, with bounded exponential backoff.
  • A stopped session keeps its name. The name is reusable once hisn ls --prune removes the directory and repairs the index.
  • Name reservation checks the index, so concurrent runs are race-free. release drops a name only if it still maps to the releasing session's id.
  • hisn stop on a stopped session prints already stopped. On a crashed one it prints crashed, no supervisor. Both exit 0.
  • hisn inspect redacts [env.set] values whose names match looks_secret.
  • touch_lease writes session.json only when the session has a lease.

Fixed

  • A failed run --detach start returned 1 instead of 125 (refused profile, missing risk_ack, home.persist refusal, service readiness timeout). It now exits with the named constant EXIT_START_FAILED (125) in every case. A running session's command status is unchanged.
  • A detached supervisor inherited the parent's stdout, stderr and ready-pipe write ends. A caller that held those pipes never saw EOF. Go's exec reported WaitDelay expired before I/O complete and treated a live session as dead. The child now closes every inherited fd except the ready pipe.
  • RISK lines never reached the caller under --detach. The parent now prints them before the fork.
  • Client records were keyed by file descriptor number. The kernel reuses a closed fd at once, so a later connection could replace a live record and a close could remove the wrong one. clients stayed at 1 after the connection closed and idle never fired. Connections now have a process-unique id, and the count and records live in one registry.
  • Observers and bare probes could register as clients and bump last_activity. Registration now happens only after a request is admitted. A refused request never registers. A disconnect bumps activity only for a counted connection.

Hardening

  • redact_env_set redacts [env.set] values by parsing the policy TOML.
  • The host environment is read with vars_os(). A non-UTF-8 variable no longer panics the supervisor.
  • Client connect and exec registration take lease_lock, the same lock the idle check holds. An exec admitted during the idle check is not stopped under it.
  • is_loopback_ns also rejects 0.0.0.0, ::, ::ffff:127.0.0.0/8 and ::ffff:0.0.0.0.
  • build_resolv_conf refuses a [net] nameservers override that is empty or loopback-only. Before, only hisn profile validate did.
  • Detached-child fd hygiene: all inherited fds of 3 and above are closed, and 0 to 2 are reopened from /dev/null until stdio moves to supervisor.log.
  • RISK lines are printed before the fork under --detach.

Compatibility

  • The ls --json schema name stays hisn-ls/v2. The changes are additive, and 0.4.0 fields are kept.
  • hisn ls --prune without the new flags behaves as in 0.4.0. run without --detach is unchanged.
  • If the sessioninfo request fails, ls shows empty clients, execs and services for that session. It does not fail.
  • 0.4.1-only: run --detach returning, --env-file, exec --client, --name-prefix, --dry-run, the sessioninfo op, and the live clients, execs, services and rootfs data in ls --json.
  • Not tested in the tree: a 0.4.1 client against a running 0.4.0 supervisor.

Known issue carried forward: ttyname() inside an attach still fails. The fix needs a protocol change and a host to verify it. See CHANGELOG and DESIGN section 20.1.

Verified

  • Unit tests and tests/leak/test_l8.py were run in the build sandbox, per the CHANGELOG.
  • The build sandbox blocks user namespaces. The leak suite and the --detach and attach host steps could not run there.
  • Latency is asserted in tests/latency.rs: a socket that appears after 8 ms must be observed ready in under 60 ms, an already-ready socket in under 30 ms, and a stop in under 80 ms. A fallback test asserts no fixed 100 ms sleep. The tree holds no trace output. The test prints the measured times, but no trace output is committed, so this note quotes no measured figure. The earlier CHANGELOG figures in microseconds were removed: they cannot describe the 8 ms case, which takes at least 8 ms by construction.
  • No leak suite result for 0.4.1 is committed. The last recorded one is 308 passed, 0 failed, 6 skipped, 1 weak, for 0.4.0. This note gives no 0.4.1 count. The same applies to two-host seat and audit archival runs: nothing in the tree records them for 0.4.1.
  • Host steps to run on rubin: cargo test --release, HISN_TEST_ROOTFS="$(hisn rootfs path wakil-dev)" make leaktest, hisn run --name t --detach, hisn ls --json, hisn ls --prune --name-prefix wakil-.

hisn 0.4.0

Choose a tag to compare

@github-actions github-actions released this 07 Nov 20:49
161a70c

0.4.0 adds seats: a hisn session is a named place that several clients join,
locally or over SSH (ssh -t host hisn attach --session <name>), sharing one
sandbox, one pid namespace, one session env snapshot and one audit log, with
idle leases, an explicit hisn stop that keeps the session directory, and
archival of the audit on prune.

Host evidence: make leaktest on rubin: 308 passed, 0 failed, 6 skipped,
1 weak; remote attach verified on smaragd (Fedora 44, kernel 7.2.13).

Security: equal access per uid in 0.4.0; the client record, the single
authorize() seam and the per-client audit ship now, so roles are a policy
change later.

Added

  • Named sessions: hisn run --name <name> (unique per user); --session
    accepts a name or id everywhere; hisn ls shows NAME, ID, STATE, CWD,
    ROOTFS, NET, AGE, CLIENTS, LEASE. A per-user index in the /tmp/hisn-$UID
    root is authoritative; a fallback scan repairs it (ADR 0014).
  • Attach: hisn attach --session <name|id> — an interactive shell
    ($SHELL from the env snapshot, else /bin/sh) in the session cwd. Multiple
    attaches are peers. The detach key Ctrl-] d closes the client; the shell
    is killed on disconnect (no --detach in 0.4.0) (ADR 0015).
  • Client identity and the authorization seam: every control request
    carries a ClientRecord (uid, pid, label, req_id, seq, principal). One
    authorize() function gates every op; in 0.4.0 it allows iff
    uid == owner.uid. The Authorized token cannot be forged outside the
    authz module. Every audit line carries client, seq, authz.
    Members are append-only in session.json (ADR 0016).
  • Session env snapshot: --env K=V at run declares a session env;
    precedence: scrub < declared < computed; a request naming a declared key
    gets E_ENV_LOCKED (ADR 0017).
  • Leases, stop, archival: [session] idle_timeout (duration string,
    default none). hisn stop --session X sets state stopped and keeps the
    dir. ls --prune archives grants.jsonl + session.json to
    $XDG_STATE_HOME/hisn/audit/<id>.jsonl (0600) before removing; --purge
    skips archival (ADR 0019). Idle expiry runs the same stop function as
    hisn stop (dir kept as stopped, audit lease/expired); a foreground
    hisn run then exits 143 with hisn: session <name|id> stopped: idle for <d> (idle_timeout <d>). A stop racing the session end no longer removes
    the dir. hisn ls --json gains dir.
  • Lifecycle state: session.json carries state ∈ {creating, running, stopping, stopped, crashed}.

Known issues (0.4.1)

  • hisn run --detach stays in the foreground instead of returning once the
    session is ready.
  • Inside an attach, ttyname() fails (tty: no such device): the client's
    pty slave is not visible in the session's private /dev/pts. isatty is
    true (L20), so interactive programs work; programs that need the tty's
    name do not.
  • hisn ls --json lacks the clients array and does not list a live
    attach under execs.
  • CLIENTS is not zeroed on a stopped session.
  • L56 and L58 remain host-only / documented checks (not automated in the
    leak suite).

Changed

  • Repository cleanup (no code change): process documents (plans, patches,
    host-run logs, the verification record) are removed from the tree; their
    decisions are in docs/adr/. The policy schema is regenerated from the
    struct (adds landlock, shm, shm_size, tmp_size). Example profiles use
    /tmp/hisn-out as grantable_rw, so they validate on a fresh host. CI builds
    the musl binary and validates the example profiles.

Leak suite: the portable subset runs in CI; host-only checks (user namespaces, rootfs, attach, leases) are verified on a real host before tagging — see the release notes for the host result. The aarch64 binary is built and started (--version) but has not been leak-tested. The glibc binaries (hisn-x86_64-linux-gnu, hisn-aarch64-linux-gnu) are dynamically linked and built natively on Ubuntu 22.04 (glibc 2.35 or newer required). The musl binary (hisn-x86_64-linux-musl) is statically linked. Verify with sha256sum -c SHA256SUMS. See README, "Verifying a build".

hisn 0.3.3

Choose a tag to compare

@github-actions github-actions released this 06 Nov 23:34
235be78

Fixed

  • Session DNS on systemd-resolved hosts. The session's /etc/resolv.conf
    is now built from the host's non-loopback nameservers, else the
    systemd-resolved upstream file /run/systemd/resolve/resolv.conf, else the
    launch fails with no usable DNS resolver …. Loopback nameservers are never
    passed in, and an empty resolv.conf is never written. Evidence reported from a
    Fedora 45 host (hisn 0.3.2, wakil-dev rootfs): the session's resolv.conf was
    empty, getent hosts example.com returned rc=2, while curl -sI https://1.1.1.1
    returned HTTP/2 301. The host resolv.conf was the systemd stub (127.0.0.53)
    and the real upstreams were in /run/systemd/resolve/resolv.conf. The Fedora
    host itself was not re-run for this release. On the build host (non-loopback
    host resolv.conf) a network = "host" session showed the host nameserver
    with loopback dropped, and getent hosts example.com returned rc=0.
  • Rootfs sessions on network = "host" no longer fail with no usable DNS resolver when the image's own etc/resolv.conf is empty (the wakil-dev
    image). The first version of this fix read only the image file for a rootfs
    session, so such a launch failed closed while the host had working
    resolvers. The precedence is now: [net] nameservers, then the host
    /etc/resolv.conf, then /run/systemd/resolve/resolv.conf, and the image's
    etc/resolv.conf only when neither host source has a usable nameserver. A
    populated image never shadows a working host resolver. The image file is read
    with openat2 (RESOLVE_BENEATH | RESOLVE_NO_SYMLINKS), so a symlink in any
    component, including etc, is refused, and the upstream file is read
    from the real host, not from inside the image. Regression check: leak test
    L50 (runs when HISN_TEST_ROOTFS is set).

Added

  • [net] nameservers = ["ip", ...] (profile): replaces the derived resolver
    list. Empty, non-IP and loopback entries are rejected at hisn profile validate.
  • hisn exec --no-tty: non-interactive exec with no pty. Stdin is
    /dev/null; stdout and stderr are client-owned pipes that the client copies to
    its own stdout and stderr, so the sandbox never receives the caller's terminal.
    --tty --no-tty is a usage error. The refusal for a terminal without either
    flag still applies and now names both options.
  • mounts.ro accepts src:dst (ADR 0012): the host path src is bound
    read-only (ro,nosuid,nodev) at the in-session path dst. Same-path entries
    are unchanged. dst under /run/hisn or a runtime root, dst containing
    .., and dst colliding with another session mount are rejected at validate,
    naming the path. A symlinked src directory is resolved on the host.
  • policy validate and profile validate now run the mount plan, so mount
    conflicts are refused at validate, not only at session start.
  • Leak tests L47–L49 (src:dst mount, validate refusals, symlinked source).

The leak suite (L1-L45) passed on Ubuntu 22.04 and 24.04 x86_64 CI runners for this exact commit before these binaries were built; the aarch64 binary is built and started (--version) but has not been leak-tested. The glibc binaries (hisn-x86_64-linux-gnu, hisn-aarch64-linux-gnu) are dynamically linked and built natively on Ubuntu 22.04 (glibc 2.35 or newer required). The musl binary (hisn-x86_64-linux-musl) is statically linked. Verify with sha256sum -c SHA256SUMS. See README, "Verifying a build".

hisn 0.3.2

Choose a tag to compare

@github-actions github-actions released this 05 Nov 00:39
3b9b9f2

Hardening before the tag (same version):

  • rootfs import moved to rootfs_import: streaming tar crate extraction with
    explicit hardening, unique staging, no-clobber / rename-aside --replace,
    manifest.json written without following archive links, option-like
    --from-image values refused.
  • [rootfs] name that cannot be resolved is an error (it used to fall back to the
    host toolchain); a rootfs overlapping a writable mount, or with a symlinked
    /etc, is refused.
  • [home] persist refuses fixed host trees, shared dirs, hisn's own state dirs and
    directories owned by someone else.
  • ssh proxy rejects malformed SIGN_REQUESTs locally, caps clients, times out idle
    ones.

Added

  • Persistent $HOME ([home] persist, --home-persist): a profile may
    declare a persistent home directory that survives across sessions, bind-mounted
    rw at the same in-sandbox path. Refuse list (real $HOME, ancestors of
    $HOME, /, cwd, ancestors of cwd) enforced before any namespace exists. Risk
    label home-persist (Data). ADR 0009. Leak test L42.
  • Read-only rootfs ([rootfs] name|path, --rootfs-path): a profile may
    declare a rootfs tree that replaces the host toolchain dirs (/usr, /bin,
    /sbin, /lib, /lib32, /lib64, /opt, /nix, /etc) inside the sandbox.
    All rootfs mounts are ro, nosuid, nodev. Host /usr, /opt, /nix are not
    visible when a rootfs is set. hisn rootfs import|list|path|rm subcommand with
    streaming extraction (via the tar crate — handles ustar prefix, GNU
    LongLink, and PAX extended headers) and hardening (strip setuid, reject
    traversal, reject hard links outside, reject symlink-then-write, skip
    devices, atomic staging). hisn check reports the rootfs store. Risk label
    rootfs (Trust). ADR 0010. Leak tests L43, L44, L46.
  • SSH agent T1 ([ssh] agent = true, keys = [...]): a filtering proxy that
    forwards only REQUEST_IDENTITIES and SIGN_REQUEST, rejects all mutations
    (ADD, REMOVE, LOCK, UNLOCK, EXTENSION), filters by fingerprint
    allowlist, and injects SSH_AUTH_SOCK into the scrubbed env. The host
    SSH_AUTH_SOCK is never mounted. Every forwarded sign request is logged
    (fingerprint, session id) — allowlist or not. The accept loop retries on
    EMFILE/ENFILE/EAGAIN. Risk label ssh-agent (Signing). ADR 0011.
    Leak test L45.

Changed

  • Cargo.toml version bumped to 0.3.2.
  • CI: musl target (x86_64-unknown-linux-musl) added to the release workflow
    alongside the glibc build, with a -musl suffix in the asset name. Glibc
    remains the default download.

Tests

  • L42 (home-persist): persist dir created, cross-session persistence (file
    written in session A exists in B), symlink escape blocked, pre-0.3.2 home
    location fails, each refuse-list case (real $HOME, ancestor of $HOME,
    /, cwd, ancestor of cwd) rejected before any namespace exists, HOME env
    is the sandbox path, persist ≠ real $HOME.
  • L43 (rootfs, with HISN_TEST_ROOTFS): host /opt marker not visible,
    host binary not executable, /usr mount shows ro,nosuid,nodev in
    mountinfo, remount cannot drop ro, username matches, nosuid enforced.
  • L44 (rootfs import hardening, always runs): path traversal, absolute names,
    hard link escape, setuid stripping on a synthetic tar.
  • L46 (rootfs symlink traps, with HISN_TEST_ROOTFS): symlink traps in
    /usr/local/bin and absolute symlinks to host $HOME do not reach host
    data.
  • L45 (ssh-agent): two ed25519 keys; with keys=[fp1]: ssh-add -l shows
    only key1, key2 filtered, ssh-add (add) and ssh-add -D (remove all) from
    inside session fail, host agent still lists both keys, proxy socket absent
    after teardown; without keys=: both keys listed, add/remove still fail
    (skip if ssh-agent or ssh-add not found).

The leak suite (L1-L45) passed on Ubuntu 22.04 and 24.04 x86_64 CI runners for this exact commit before these binaries were built; the aarch64 binary is built and started (--version) but has not been leak-tested. The glibc binaries (hisn-x86_64-linux-gnu, hisn-aarch64-linux-gnu) are dynamically linked and built natively on Ubuntu 22.04 (glibc 2.35 or newer required). The musl binary (hisn-x86_64-linux-musl) is statically linked. Verify with sha256sum -c SHA256SUMS. See README, "Verifying a build".

hisn 0.3.1

Choose a tag to compare

@github-actions github-actions released this 03 Nov 23:00
v0.3.1
009a5bd

Fixed

  • L17 signal race (7b162c7, 809da11): the control thread left SIGCHLD,
    SIGTERM, SIGINT, SIGHUP and SIGWINCH unblocked, so the kernel could
    deliver them to it and the main thread's sigsuspend was never woken (a
    detached supervisor hung after its init exited; seen on ubuntu-22.04 CI).
    The control thread now blocks them. 7b162c7 also made the L29 test and
    profiles/docker-socket.toml use network = "none" (host mode needs
    Landlock ABI >= 6, absent on ubuntu-22.04).
  • Verification rows for CI and the host run: see docs/VERIFICATION.txt.
    The 0.3.0 tag points at afdccd0 and does not contain these fixes.

The leak suite (L1-L24) passed on Ubuntu 22.04 and 24.04 x86_64 CI runners for this exact commit before these binaries were built; the aarch64 binary is built and started () but has not been leak-tested. Binaries are dynamically linked against glibc and built natively on Ubuntu 22.04 (glibc 2.35 or newer required). Verify with sha256sum -c SHA256SUMS. See README, "Verifying a build".

hisn 0.2.2

Choose a tag to compare

@github-actions github-actions released this 02 Nov 22:04
1197119

Fixed

  • User name inside the sandbox. 0.2.1 took the name for /etc/passwd and
    /etc/group from $USER, which a scrubbed launch environment (Wakil,
    env -i) does not have, so id -un and echo $USER showed user. The name
    now comes from getpwuid_r(geteuid()) (fallback user only if the lookup
    fails or the name is unusable; hisn run -v logs why).

Changed

  • USER and LOGNAME are set in the sandbox environment, computed from that
    name, like HOME: never copied from the host environment, and env.pass,
    env.set and exec requests cannot override them (an exec request naming
    them is refused as a computed name). POLICY-SPEC and DESIGN §8 updated.
    A 0.2.1 policy yields the same mount plan. Differences: the two new
    variables; the content of the generated /etc/passwd / /etc/group (host
    login name instead of the placeholder user under a scrubbed launch
    environment); a policy that set USER/LOGNAME now loses to the computed
    value. Session start now queries the host user database once
    (getpwuid_r), which can be slow with an unreachable network backend.

Tests

  • Leak test L1c (id -un, $USER, $LOGNAME, getent passwd show the host
    user, also for a session started under env -i); unit tests for the lookup
    and fallback, stub generation and the computed names.

The leak suite (L1-L24) passed on Ubuntu 22.04 and 24.04 x86_64 CI runners for this exact commit before these binaries were built; the aarch64 binary is built and started () but has not been leak-tested. Binaries are dynamically linked against glibc and built natively on Ubuntu 22.04 (glibc 2.35 or newer required). Verify with sha256sum -c SHA256SUMS. See README, "Verifying a build".

hisn 0.2.1

Choose a tag to compare

@github-actions github-actions released this 02 Nov 13:06
a478d5d

Added

  • /dev/shm (session.shm, default false): optional tmpfs at /dev/shm
    with configurable size (session.shm_size, MiB, default 64). Default off =
    no /dev/shm (0.2 behavior).
  • /tmp size (session.tmp_size, MiB, default 1024, range 16..=65536):
    replaces the hardcoded 1 GiB constant with a policy key.
  • --ready-fd N on hisn run: after the session is live, writes "ready\n"
    to fd N and closes it. For programmatic readiness (Wakil).
  • Opaque exec ids: every exec gets exec_id = "e-<n>" (monotonic, never
    reused). signal accepts exec_id (preferred) or pid (deprecated, kept
    for 0.2 clients); a recycled pid is rejected with E_NOT_FOUND.
  • Frozen --json schemas: hisn ls --json and hisn inspect --json emit
    {"schema":"hisn-ls/v1",…} / {"schema":"hisn-inspect/v1",…}. cwd,
    hostname, network are included in ls --json.
  • hisn ls --prune: remove stale session directories (no live
    supervisor). Stale = control socket refuses AND supervisor pid is gone or
    has a different starttime. Removal is fd-relative (openat2 with
    NO_SYMLINKS|NO_XDEV|BENEATH), so a planted symlink is unlinked and never
    followed and a planted bind mount is never entered; it is owner-checked and
    confined to the one session directory.
  • Stale session cleanup on hisn run --session-id X: if the dir is
    stale, remove it and proceed; if live, E_EXISTS stands.
  • Stale session cleanup on hisn kill --session X: if stale, remove the
    dir and exit 0 with "session X was stale; removed".
  • supervisor.pid file in the session dir: written at start with the
    supervisor pid and starttime for stale-detection.
  • R5 ancestry retry (OPEN-POINTS P16): is_descendant_of retries the
    whole /proc/<pid>/status walk on ENOENT/ESRCH/EINTR (3 attempts,
    50 ms apart). The retry targets the sandbox init (a specific pid), not
    "any pid 1".

Changed

  • passwd/group stub verified to use the host username (HostFacts.username
    from $USER); id -un inside the sandbox prints the host username.
  • init argv scrub: after START, init overwrites its argv memory in place
    with "hisn-init" so the session id, policy path, and session dir do not
    appear in /proc/1/cmdline inside the sandbox. Best-effort.
  • musl ioctl casts (OPEN-POINTS P22): ioctl request constants cast with
    as _ so x86_64/aarch64-unknown-linux-musl compile. The musl target
    has not been leak-tested (stdlib not installed in the build sandbox).

Known issues

  • Pending from the external §14 review (OPEN-POINTS): align DESIGN §4.5 with §7
    on root-tmpfs renames; stop a continuous stream of spawns from starving a
    hard revoke.

The leak suite (L1-L24) passed on Ubuntu 22.04 and 24.04 x86_64 CI runners for this exact commit before these binaries were built; the aarch64 binary is built and started () but has not been leak-tested. Binaries are dynamically linked against glibc and built natively on Ubuntu 22.04 (glibc 2.35 or newer required). Verify with sha256sum -c SHA256SUMS. See README, "Verifying a build".

hisn 0.2.0

Choose a tag to compare

@github-actions github-actions released this 01 Nov 20:20
v0.2.0
57d194b

First release.

Added

  • Sandbox sessions (hisn run): user, mount, pid, ipc, uts and net
    namespaces; an empty tmpfs root entered with pivot_root and the old root
    detached; the working directory at the identical host path (rw), a read-only
    toolchain, private tmpfs $HOME, /tmp and /run. Anything else does not
    exist (ENOENT).
  • Confinement inside the sandbox: all capabilities dropped, no_new_privs,
    a seccomp denylist (mount, unshare, setns, open_tree, ptrace, bpf,
    io_uring, clone with CLONE_NEW*, clone3 answered with ENOSYS, ...),
    Landlock as a second write-restricting layer, a non-dumpable capless PID 1,
    rlimits, and an environment rebuilt from scratch (env.pass allowlist).
  • Grants (hisn grant, hisn revoke, hisn ls, hisn inspect,
    hisn kill): the host-side supervisor adds and removes bind mounts in a
    running session through a control socket the sandbox cannot name or use.
    Grants end with the session. Soft revoke guarantees only that the path no
    longer resolves; revoke --hard kills every sandbox process.
  • hisn exec (v0.2): run further commands in a running session, in the same
    namespaces, with the same confinement and the same grants, optionally on a
    pty (--tty, with resize). hisn run --detach starts a session that is only
    exec'd into. Confinement is re-applied on every exec (DESIGN §14).
  • Policy (hisn-policy/v1 TOML, hisn policy validate, JSON schema in
    schemas/), including [landlock] scoped (below).
  • hisn check: host capability report (kernel, user namespaces, Landlock,
    seccomp, mount API); exit 2 when a required capability is missing.
  • Leak suite (make leaktest, L1-L24): one PASS|FAIL|SKIP|WEAK line per
    check and a RESULT: line; non-zero exit on any FAIL. make leaktest-log
    writes a dated log with a header (commit, tree state, date, host, kernel,
    hisn version). docs/VERIFICATION.txt records what was run, where.
  • Documentation: DESIGN, POLICY-SPEC, OPEN-POINTS, ROADMAP, WAKIL-INTEGRATION.

Changed

  • Landlock signal and abstract_unix_socket scoping is off by default
    ([landlock] scoped = []) so that one exec can signal processes started by an
    earlier exec of the same session (OPEN-POINTS P17). The filesystem ruleset,
    capabilities, no_new_privs, seccomp and environment scrubbing are
    unchanged. Opt in with scoped = ["signal"] and/or
    scoped = ["abstract_unix_socket"].
  • With network = "host" the abstract-unix-socket scope is always applied; only
    --allow-host-abstract-sockets removes it, and that is audited (found by the
    external §14 review; already enforced, now covered by leak test L24).

Security

  • hisn exec verifies fail-closed, before running anything, that the root it
    entered is the sandbox root (DESIGN D25).

The leak suite (L1-L24) passed on Ubuntu 22.04 and 24.04 x86_64 CI runners for this exact commit before these binaries were built; the aarch64 binary is built and started () but has not been leak-tested. Binaries are dynamically linked against glibc and built natively on Ubuntu 22.04 (glibc 2.35 or newer required). Verify with sha256sum -c SHA256SUMS. See README, "Verifying a build".