diff --git a/scripts/safe-paths.sh b/scripts/safe-paths.sh index ceab00e..f453d30 100644 --- a/scripts/safe-paths.sh +++ b/scripts/safe-paths.sh @@ -34,13 +34,14 @@ die() { exit 1 } -# drop_symlink PATH : if PATH is a symlink, unlink it (as root: rm on the final -# component never touches the link target, and the customer cannot deny it) and -# log it. Fails closed: if it is still a symlink afterwards, refuse to start. +# drop_symlink PATH : if PATH is a symlink, unlink it AS THE CUSTOMER (rm on +# the final component never touches the link target, and if a parent dir is +# swapped for a symlink mid-way the customer can only delete what they already +# could) and log it. Fails closed: still a symlink afterwards -> refuse to start. drop_symlink() { if [ -L "$1" ]; then echo "entrypoint: SECURITY: $1 was a symlink to '$(readlink "$1")' - removing" >&2 - rm -f -- "$1" + as_user rm -f -- "$1" [ -L "$1" ] && die "could not remove symlink $1" fi return 0 @@ -59,9 +60,11 @@ secure_home() { # public_html may legitimately be a symlink (release-dir deploys); it is left # alone and every root-side operation on it runs as the customer instead. if [ -d "$h/logs" ]; then - # find without -L never follows symlinks; -delete unlinks the link itself. - # Errors are not fatal HERE: require_safe_logs re-checks and fails closed. - find "$h/logs" -xdev -type l \ + # As the customer: find without -L never follows symlinks, -delete unlinks + # the link itself, and a raced parent swap can only reach what the customer + # could already delete. Errors are not fatal HERE: require_safe_logs + # re-checks immediately before the daemons start and fails closed. + as_user find "$h/logs" -xdev -type l \ -printf 'entrypoint: SECURITY: %p was a symlink to %l - removed\n' -delete >&2 || true fi }