detect root by uid, not by the name root

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) <noreply@anthropic.com>
This commit is contained in:
2026-08-12 18:36:16 +00:00
co-authored by Claude Opus 5
parent d9d4085033
commit b1cc916258
+30 -8
View File
@@ -199,14 +199,25 @@ ask_username() {
echo " of hand. sudo gives you the same power when you ask for it, and" 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." 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 # Whether this was started FROM a root session, which usually means root is
# usually means root is how they are logged in. That is exactly the situation # how they log in. That is exactly the situation the advice above is for, and
# the advice above is for, so it is worth saying plainly rather than leaving # the one where general advice is easiest to assume is aimed at somebody else.
# as general guidance they can assume is aimed at somebody else. #
if [[ -z "${SUDO_USER:-}" ]]; then # 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 "" echo ""
warn "You are running this as root directly, not through sudo." if [[ -n "${SUDO_USER:-}" ]]; then
echo " If root is how you normally log into this machine, now is the" 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." echo " moment to make an account and stop doing that."
fi fi
echo "" echo ""
@@ -225,7 +236,18 @@ ask_username() {
# answered against a name that was never going to work. # answered against a name that was never going to work.
[[ "$answer" =~ ^[a-z_][a-z0-9_-]*\$?$ && ${#answer} -le 32 ]] || [[ "$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." 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" USERNAME="$answer"