From b1cc916258d1b59383647d052a9f5b29956fb795 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Padez?= Date: Wed, 12 Aug 2026 18:36:16 +0000 Subject: [PATCH] detect root by uid, not by the name root MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A provider whose image logs you in as "ubuntu" at uid 0 would have walked straight past the previous check, which compared the string. What makes an account root is uid 0; "root" is only the usual label for it. Two places now ask id -u rather than comparing names: the answer — an account at uid 0 is refused whatever it is called, and says which case it is rather than a bare "not root" the invoker — the warning about working as root fires when SUDO_USER is unset OR when SUDO_USER is itself uid 0. The second is the one that hides: sudo from a uid-0 account sets SUDO_USER to something that reads like an ordinary user and is not. The EUID check that requires the script to run as root was already uid-based and is unchanged. Verified by creating a real uid-0 account named ubuntu on this box: refused with the uid named, where the name check accepted it. That account has been removed — userdel refused it at first because it matches by uid and saw PID 1 running as uid 0, so -f was needed, and deliberately not -r, since its home was /root. Confirmed afterwards that root, /root, root's shadow entry and sudo are all intact and that root is once again the only uid-0 account. Co-Authored-By: Claude Opus 5 (1M context) --- scripts/setup/machine-setup/lib/base.sh | 38 +++++++++++++++++++------ 1 file changed, 30 insertions(+), 8 deletions(-) diff --git a/scripts/setup/machine-setup/lib/base.sh b/scripts/setup/machine-setup/lib/base.sh index 9e135f92..eaaab9c5 100644 --- a/scripts/setup/machine-setup/lib/base.sh +++ b/scripts/setup/machine-setup/lib/base.sh @@ -199,14 +199,25 @@ ask_username() { echo " of hand. sudo gives you the same power when you ask for it, and" echo " only then — which is why root is not accepted as an answer here." - # No SUDO_USER means this was started as root rather than through sudo, which - # usually means root is how they are logged in. That is exactly the situation - # the advice above is for, so it is worth saying plainly rather than leaving - # as general guidance they can assume is aimed at somebody else. - if [[ -z "${SUDO_USER:-}" ]]; then + # Whether this was started FROM a root session, which usually means root is + # how they log in. That is exactly the situation the advice above is for, and + # the one where general advice is easiest to assume is aimed at somebody else. + # + # Two ways to be in it, and the second is the one that hides: no SUDO_USER at + # all, or a SUDO_USER that is itself uid 0. Some providers ship an image whose + # default account is uid 0 under an ordinary-looking name, so `sudo` from it + # sets SUDO_USER to something that looks like a normal user and is not. + local invoker_uid="" + [[ -n "${SUDO_USER:-}" ]] && invoker_uid="$(id -u "$SUDO_USER" 2>/dev/null || true)" + + if [[ -z "${SUDO_USER:-}" || "$invoker_uid" == "0" ]]; then echo "" - warn "You are running this as root directly, not through sudo." - echo " If root is how you normally log into this machine, now is the" + if [[ -n "${SUDO_USER:-}" ]]; then + warn "You are running this from '${SUDO_USER}', which is uid 0 — the root account." + else + warn "You are running this as root directly, not through sudo." + fi + echo " If that is how you normally log into this machine, now is the" echo " moment to make an account and stop doing that." fi echo "" @@ -225,7 +236,18 @@ ask_username() { # answered against a name that was never going to work. [[ "$answer" =~ ^[a-z_][a-z0-9_-]*\$?$ && ${#answer} -le 32 ]] || fail "'${answer}' is not a usable Linux username — lower case, starting with a letter or underscore." - [[ "$answer" == "root" ]] && fail "root is not the account to set up here." + # By uid, not by name. "root" is a label — what makes an account root is uid 0, + # and some providers ship an image whose default login is uid 0 under a + # friendlier name. Refusing only the string would let exactly that case through, + # which is the one worth catching. + local answer_uid + answer_uid="$(id -u "$answer" 2>/dev/null || true)" + if [[ "$answer_uid" == "0" ]]; then + if [[ "$answer" == "root" ]]; then + fail "root is not the account to set up here — see the warning above." + fi + fail "'${answer}' is uid 0 — the root account under another name, and not what to set up here." + fi USERNAME="$answer"