diff --git a/scripts/entrypoint.sh b/scripts/entrypoint.sh index 58e4317..7e70b2d 100755 --- a/scripts/entrypoint.sh +++ b/scripts/entrypoint.sh @@ -37,12 +37,14 @@ die() { echo "entrypoint: FATAL: $* - refusing to start" >&2 exit 1 } -# Unlink as root: rm on the final component never touches the link target and -# the customer cannot deny it. Still a symlink afterwards -> refuse to start. +# Unlink 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. Still a symlink afterwards -> refuse to +# start (fail closed). 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 @@ -55,9 +57,11 @@ chown -h $user:$user /home/$user # alone, and every root-side operation on it below runs as the customer. drop_symlink /home/$user/logs if [ -d /home/$user/logs ]; then - # nginx opens its logs here as root. find without -L never follows symlinks; - # -delete unlinks the link itself. Re-checked fail-closed before nginx starts. - find /home/$user/logs -xdev -type l \ + # nginx opens its logs here as root. 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. Re-checked + # fail-closed immediately before nginx starts. + as_user find /home/$user/logs -xdev -type l \ -printf 'entrypoint: SECURITY: %p was a symlink to %l - removed\n' -delete >&2 || true fi