test(mosaic): make #791 PR2 Part 7 symlink control busybox-portable (#791 PR2)
All checks were successful
ci/woodpecker/pr/ci Pipeline was successful
All checks were successful
ci/woodpecker/pr/ci Pipeline was successful
CI #1881 failed at head8bee1b65: the durable-snapshot gate was 40/41, the sole failure being the Part 7 NEGATIVE control ("secret leaked through the symlink"). Root cause is a test-harness portability gap, not a code defect: node:24-alpine runs busybox cp as root, and busybox cp REPLACES a symlinked destination instead of following it, whereas GNU/BSD cp — what a real operator runs `mosaic update` under — follows the link and leaks. So under the CI harness the CWE-59 leak vector the control asserts simply cannot occur, and the negative control can't reproduce. Fix is test-only; install.sh (independently approved at8bee1b65) and the real security assertions are untouched. make_symlink_leaf_shim now emulates GNU cp's follow-through-dest-symlink behavior portably: when the destination is a symlink it writes the source bytes through the link via redirection (which follows symlinks on every coreutils, busybox included); otherwise it delegates to the host cp unchanged. Both the shipped-case and the control run through this single shim, so the ONLY difference between them remains the SYMLINK-LEAF-GUARD — the control stays load-bearing and non-tautological, now on busybox too. With the guard present the symlinked leaf is dropped before this cp runs, so the shipped path is unchanged. Verified in the exact CI image (node:24-alpine, busybox, root, apk add bash rsync): durable-snapshot 41/41, manifest-guard 193, rollback 28. GNU host 41/41, shellcheck clean. Sole tracked delta vs8bee1b65= this test file. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -95,17 +95,38 @@ seed_home_v1() {
|
|||||||
# sync), so the verify net must restore a REAL file in place WITHOUT following the
|
# sync), so the verify net must restore a REAL file in place WITHOUT following the
|
||||||
# link (which would write the snapshot's secret out through it). $EXFIL_TARGET is
|
# link (which would write the snapshot's secret out through it). $EXFIL_TARGET is
|
||||||
# expanded at shim-write time from the caller's environment.
|
# expanded at shim-write time from the caller's environment.
|
||||||
|
#
|
||||||
|
# PORTABILITY (why this shim, not the real `cp`): the CWE-59 leak this exercises is
|
||||||
|
# `cp` writing THROUGH a symlinked destination. GNU/BSD cp — what a real operator
|
||||||
|
# runs `mosaic update` under — follows the dest symlink and leaks. busybox cp (the
|
||||||
|
# Alpine CI image) REPLACES a symlinked dest instead of following it, so under the
|
||||||
|
# CI harness the leak vector simply does not exist and the negative control could
|
||||||
|
# never reproduce it. This shim therefore emulates the real-target GNU cp behavior
|
||||||
|
# PORTABLY: when the destination is a symlink it writes the source bytes through the
|
||||||
|
# link via redirection (which follows symlinks on every coreutils, busybox included);
|
||||||
|
# otherwise it delegates to the host's real cp unchanged. Both the shipped-case and
|
||||||
|
# the negative control run through this identical shim, so the ONLY difference
|
||||||
|
# between them remains the SYMLINK-LEAF-GUARD — the control stays load-bearing and
|
||||||
|
# non-tautological. It does NOT touch install.sh (approved) or the real assertions:
|
||||||
|
# with the guard present the symlinked leaf is dropped BEFORE this cp runs, so the
|
||||||
|
# dest is a real file and the delegate path is taken exactly as on a GNU host.
|
||||||
make_symlink_leaf_shim() {
|
make_symlink_leaf_shim() {
|
||||||
local dir="$1" home="$2"
|
local dir="$1" home="$2"
|
||||||
cat > "$dir/cp" <<SHIM
|
cat > "$dir/cp" <<SHIM
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
dest="\${@: -1}"
|
dest="\${@: -1}"
|
||||||
|
src="\${@:(-2):1}"
|
||||||
case "\$dest" in
|
case "\$dest" in
|
||||||
*/$POISON_REL)
|
*/$POISON_REL)
|
||||||
rm -f "$home/tools/_lib/credentials.json"
|
rm -f "$home/tools/_lib/credentials.json"
|
||||||
ln -s "$EXFIL_TARGET" "$home/tools/_lib/credentials.json"
|
ln -s "$EXFIL_TARGET" "$home/tools/_lib/credentials.json"
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
# Coreutils-agnostic emulation of GNU cp's follow-through-dest-symlink behavior.
|
||||||
|
if [[ -L "\$dest" && -f "\$src" ]]; then
|
||||||
|
cat "\$src" > "\$dest"
|
||||||
|
exit \$?
|
||||||
|
fi
|
||||||
exec env PATH="$ORIG_PATH" cp "\$@"
|
exec env PATH="$ORIG_PATH" cp "\$@"
|
||||||
SHIM
|
SHIM
|
||||||
chmod +x "$dir/cp"
|
chmod +x "$dir/cp"
|
||||||
|
|||||||
Reference in New Issue
Block a user