fix(entrypoint): no recursive chmod on the customer home; root never follows customer symlinks #3

Merged
jknapp merged 5 commits from fix/no-recursive-chmod-on-start into main 2026-10-21 00:01:32 +00:00
Showing only changes of commit 4064d9d6aa - Show all commits
+10 -6
View File
@@ -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