Repository navigation
Stage multi-host launches in a verified per-user directory - #20
Conversation
gmkurtzer
left a comment
There was a problem hiding this comment.
Thanks for addressing the staging race and fail-open digest checks. The security goal is correct, but this implementation needs to be rebased and redesigned around the current multi-host staging pathway before it can merge.
The branch is 137 commits behind main and currently conflicts in all three changed files. More importantly, current main no longer stages launch state under /tmp/waldo-launch-*. It uses a session-scoped directory beneath the configured cache scratch root:
remoteRoot := filepath.Join(cache.Scratch(), "multinode", digest, cluster.RendezvousID)Please preserve that contract and apply the private-directory validation and fail-closed upload within session.remoteRoot. The updated implementation also needs to retain the current cache arguments, delayed worker startup, worker PID/termination handling, and cleanupStaging behavior. The documentation should continue to describe configured scratch storage rather than restoring /tmp/waldo-launch-<uid>.
The new staging tests also fail on macOS because their local SSH shim executes the remote script using the host's BSD tools:
realpath: illegal option -- e
usage: realpath [-q] [path ...]
Five hostfile tests fail at realpath -e; stat -c and GNU find -perm have the same portability issue. It is fine for actual secondary hosts to require Linux/GNU tooling, but the test boundary must not assume those tools are installed on the local test runner. Please provide controlled command shims, run this portion through a Linux-specific harness, or otherwise keep the macOS unit lane passing while retaining meaningful coverage.
Finally, the current private-group exception does not establish the stated security property. Comparing id -gn with id -un does not prove that no other account belongs to the group, and a named ACL may also expose write access through the group permission mask. Either reject all group/other write access for user-owned staging ancestors, or perform a check that actually accounts for group membership and ACL-based access.
What I would expect after the rebase is roughly:
- Validate the existing configured
session.remoteRootand its ancestors. - Upload the binary and checkpoints to unique temporary files within that validated root.
- Verify each digest before an atomic rename.
- Preserve the current session cleanup and worker/cache contracts.
- Exercise successful staging, unsafe-directory rejection, digest mismatch, and cleanup on both Linux and macOS CI.
Local validation performed here:
./testing/vet.sh: passed./testing/docs.sh: passed./testing/unit.sh: the five new staging tests failed on macOS due torealpath -e- The existing local host-memory inspection failures remain unrelated baseline failures
Please rebase and update the implementation along those lines, and I will re-review it.
…match The launcher staged the WALDO binary, worker state, and resume checkpoints beneath the configured scratch path on every secondary host without checking who could modify that path. Another account that could write to the directory or any ancestor, for example by creating /tmp/waldo-<uid> first, could replace the binary before it ran with the launching user's identity. The staging commands were also chained with ';', so a SHA-256 mismatch never stopped the rename that published the binary or checkpoint. Before staging, verify that the session directory, every ancestor, and every ancestor of its resolved path are owned by the remote user or root. Directories owned by the remote user must not be writable by their group or other users; on Linux the group bits carry the POSIX ACL mask, so ACL entries cannot grant write either. Root-owned directories writable by others must be sticky. Uploads go to a unique mktemp file within the verified directory and the script exits before the rename on a digest mismatch. An already staged checkpoint is trusted only inside a verified directory. The session directory, cache arguments, delayed worker startup, worker PID handling, and cleanup are unchanged. The checks use only portable tools (ls -ldn, cd -P, mktemp, sha256sum or shasum), so the staging tests run the real scripts through a local ssh shim on Linux and macOS. Fixes openwaldo#18 Signed-off-by: Cédric Clerget <cedric.clerget@gmail.com>
gmkurtzer
left a comment
There was a problem hiding this comment.
Re-reviewed commit 76f9930 against current main. The requested changes are addressed: staging stays under the configured session remoteRoot, uploads use unique temporary files with fail-closed digest verification and atomic publication, group/other-writable user-owned paths are rejected, root-owned shared ancestors require the sticky bit, resolved paths and ACL exposure are checked, and cleanup/cache/worker contracts remain intact. The portable staging harness now passes on macOS and CI is green on both Ubuntu and macOS; DCO passes.\n\nLocal validation:\n- focused hostfile tests: passed\n- vet: passed\n- docs: passed\n- Python worker tests: passed\n- full Go suite: changed packages passed; only the known host-memory inspection failures reproduced in internal/host and internal/cli
What changed
The multi-host launcher staged the WALDO binary at
/tmp/waldo-launch/<sha256>on every secondary host and never checked who owned that path. Another local user could create it first, replace the binary, and have it executed with the launching user's identity.The staging commands were also chained with
;. As a result, a SHA-256 mismatch never stopped themvthat publishes the binary, and the same was true for resume checkpoints.This PR:
/tmp/waldo-launch-<uid>/<sha256>, where<uid>is the remote user's. The resolved directory is printed back, validated, and recorded per host. Worker invocations and launcher scratch use it.waldo_private_dir, a shell function). The target directory and every ancestor must be owned by the remote user or root. User-owned directories must not be writable by others; group write is allowed only for a user private group, the umask 002 default on RHEL-family systems. Root-owned directories that others can write to, such as/tmp, must be sticky. Staging fails and names the offending path otherwise.mktempfile, so concurrent launches no longer share one temporary name. The script runs underset -eand exits before the rename when the digest does not match.docs/MULTI-NODE-TRAINING.mddocuments the new location and requirements.Fixes #18
Verification
sshshim:775directory is accepted and a777directory is rejected../testing/unit.sh,./testing/vet.sh,./testing/docs.shtesting/e2e/model-torchtitan-multinode.sh(needs 2+ CUDA GPUs and TorchTitan).Contracts
/tmp/waldo-launch/to/tmp/waldo-launch-<uid>/. Staging now fails if a staging or checkpoint directory could be modified by another user.realpath -e,stat -c,find -perm /mode,mktemp).DCO
Signed-off-bytrailer (git commit --signoff).