From 5cea729c775308b7537b3fd7094fdc0f822b0d1c Mon Sep 17 00:00:00 2001 From: lr00rl Date: Sun, 18 Oct 2026 05:35:23 -0700 Subject: [PATCH 1/3] fix: hand the enrollment token to the sudo re-exec in a file, not argv Run without root, the installer re-executed itself as sudo LATTICE_NODE_TOKEN= ... sh install.sh, and sudo stays alive for the whole install, so any local user could read the token in ps for as long as it ran. The token now goes to the root child in a file only the invoking user can read: mktemp makes it 0600 in TMPDIR, sudo receives only its path in LATTICE_NODE_TOKEN_FILE, and the child reads it and removes it at once. The parent no longer execs sudo, so it removes the file too when sudo returns, fails or is interrupted. A file works whatever sudoers keeps or resets of the environment, which --preserve-env does not. The child reads only a file the re-exec could have made: a regular file, not a symlink, mode 0600 with no ACL, owned by SUDO_UID (or by root without sudo). Anything else dies before any download. Tested: test-install-integrity.sh records the fake sudo's command line and fails when the token is on it (it fails so on the previous installer), checks that the token reaches the service definition and that no token file outlives an install or a failed sudo, and refuses a symlinked, a 0644 and a foreign-owned token file; under macOS sh, dash, Debian bookworm and Alpine 3.20. check-release-workflow.sh passes. --- scripts/install.sh | 47 ++++++++++++++++++++++++++++--- scripts/test-install-integrity.sh | 45 +++++++++++++++++++++++++++-- 2 files changed, 86 insertions(+), 6 deletions(-) diff --git a/scripts/install.sh b/scripts/install.sh index bd3e0a4..39b6956 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -5,7 +5,8 @@ # Everything lives under /opt/lattice (binary, env file, state); nothing is # scattered into system paths. Idempotent: a re-run upgrades in place. Supports # `--uninstall`. The enrollment token is kept OUT of the process argv (it is read -# from the 0600 env file / launchd plist), so it never shows up in `ps`. +# from the 0600 env file / launchd plist), so it never shows up in `ps`; the +# sudo re-exec hands it over in a 0600 file, not on sudo's command line. set -eu LATTICE_HOME="${LATTICE_HOME:-/opt/lattice}" @@ -69,10 +70,26 @@ if [ "$(id -u)" -ne 0 ]; then echo "lattice-agent installer needs root to write $LATTICE_HOME and register a boot service." if have sudo; then log "re-running with sudo ..." - exec sudo \ + # The enrollment token never rides sudo's command line, which any local + # user can read in ps for as long as the install runs. It goes to the root + # child in a file only this user can read (mktemp makes it 0600), which + # the child checks, reads and removes; this shell removes it too when sudo + # returns or is interrupted. A file works whatever sudoers keeps or resets + # of the environment. + token_file="" + if [ -n "$token" ]; then + token_file="$(mktemp "${TMPDIR:-/tmp}/lattice-agent-token.XXXXXX")" || \ + die "cannot create a private file to hand the enrollment token to sudo" + trap 'rm -f "$token_file"' EXIT + trap 'rm -f "$token_file"; exit 130' HUP INT TERM + chmod 0600 "$token_file" || die "cannot make $token_file private" + printf '%s' "$token" >"$token_file" || die "cannot write the enrollment token to $token_file" + fi + sudo_rc=0 + sudo \ LATTICE_HOME="$LATTICE_HOME" LATTICE_AGENT_REPO="$repo" LATTICE_AGENT_VERSION="$version" \ LATTICE_AGENT_SOURCES="${LATTICE_AGENT_SOURCES:-}" LATTICE_AGENT_SHA256="${LATTICE_AGENT_SHA256:-}" \ - LATTICE_SERVER="$server" LATTICE_NODE_ID="$node_id" LATTICE_NODE_TOKEN="$token" \ + LATTICE_SERVER="$server" LATTICE_NODE_ID="$node_id" LATTICE_NODE_TOKEN_FILE="$token_file" \ LATTICE_AGENT_BIN="$bin_path" LATTICE_AGENT_ENV="$env_path" LATTICE_AGENT_STATE="$state_dir" \ LATTICE_AGENT_SERVICE="$service_name" \ LATTICE_AGENT_RUN_USER="${LATTICE_AGENT_RUN_USER:-}" \ @@ -100,11 +117,33 @@ if [ "$(id -u)" -ne 0 ]; then LATTICE_PROXY_USAGE_FILE="${LATTICE_PROXY_USAGE_FILE:-}" LATTICE_PROXY_USAGE_URL="${LATTICE_PROXY_USAGE_URL:-}" \ LATTICE_PROXY_USAGE_XRAY_API="${LATTICE_PROXY_USAGE_XRAY_API:-}" LATTICE_PROXY_USAGE_XRAY_BIN="${LATTICE_PROXY_USAGE_XRAY_BIN:-}" \ LATTICE_PROXY_USAGE_XRAY_PATTERN="${LATTICE_PROXY_USAGE_XRAY_PATTERN:-}" \ - sh "$0" "$@" + sh "$0" "$@" || sudo_rc=$? + [ -z "$token_file" ] || rm -f "$token_file" + exit "$sudo_rc" fi die "please re-run as root (e.g. sudo sh $0)" fi +# ---- the enrollment token handed over by the sudo re-exec --------------------- +# Only a file the re-exec above could have made is read: a regular file, not +# a link, owned by the user who ran sudo (or by root without sudo), and +# readable by that owner alone. It is removed as soon as it is read. +if [ -n "${LATTICE_NODE_TOKEN_FILE:-}" ]; then + token_file="$LATTICE_NODE_TOKEN_FILE" + unset LATTICE_NODE_TOKEN_FILE + token_owner="${SUDO_UID:-0}" + case "$token_owner" in ""|*[!0-9]*) die "SUDO_UID is not a user id: $token_owner" ;; esac + [ -f "$token_file" ] && [ ! -L "$token_file" ] || \ + die "LATTICE_NODE_TOKEN_FILE is not a regular file: $token_file" + token_mode_owner="$(ls -ln "$token_file" 2>/dev/null | awk '{ print $1 " " $3 }')" + case "$token_mode_owner" in + "-rw------- $token_owner"|"-rw-------. $token_owner"|"-rw-------@ $token_owner") ;; + *) die "LATTICE_NODE_TOKEN_FILE must be mode 0600 and owned by uid $token_owner: $token_file" ;; + esac + token="$(cat "$token_file")" || die "cannot read LATTICE_NODE_TOKEN_FILE: $token_file" + rm -f "$token_file" +fi + # ---- existing install adoption --------------------------------------------- # New installs use: # binary: /opt/lattice/lattice-agent diff --git a/scripts/test-install-integrity.sh b/scripts/test-install-integrity.sh index 7fdb035..ad15cab 100755 --- a/scripts/test-install-integrity.sh +++ b/scripts/test-install-integrity.sh @@ -654,19 +654,60 @@ if [ "${1:-}" = "-u" ]; then fi exit 1 SH +# It records its command line, which any local user could read in ps, and +# sets SUDO_UID as sudo does; FAKE_SUDO_FAIL makes it fail as a wrong password +# would. cat >"$SUDO_BIN/sudo" <<'SH' #!/usr/bin/env sh +printf '%s\n' "$*" >>"$FAKE_CALLS.sudo" +[ -z "${FAKE_SUDO_FAIL:-}" ] || exit 1 exec env -i PATH="$PATH" FAKE_ROOT=1 FAKE_DIR="$FAKE_DIR" FAKE_CALLS="$FAKE_CALLS" \ - LATTICE_AGENT_PLIST="$LATTICE_AGENT_PLIST" "$@" + SUDO_UID="$(/usr/bin/id -u)" LATTICE_AGENT_PLIST="$LATTICE_AGENT_PLIST" "$@" SH chmod +x "$SUDO_BIN/id" "$SUDO_BIN/sudo" -SRC_PATH="$SUDO_BIN:/usr/bin:/bin" run_src sudo LATTICE_AGENT_SHA256="$TABLE" \ +mkdir -p "$SRC_TMP/sudo-tmp" "$SRC_TMP/sudo-fail-tmp" +SRC_PATH="$SUDO_BIN:/usr/bin:/bin" run_src sudo TMPDIR="$SRC_TMP/sudo-tmp" LATTICE_AGENT_SHA256="$TABLE" \ LATTICE_AGENT_SOURCES="m=https://mirror-good.example.com/agent $REL" || src_fail sudo "install failed" SRC_PATH="" expect_ok sudo expect_log sudo "re-running with sudo" expect_calls sudo 1 expect_call sudo 1 "https://mirror-good.example.com/$MIRROR_PATH" 0 +# The enrollment token never rides sudo's command line. The root child reads +# it from the 0600 file the re-exec made, installs it, and the file is gone. +[ -s "$SRC_TMP/sudo.calls.sudo" ] || src_fail sudo "the fake sudo recorded no command line" +if grep -Fq node-token-src "$SRC_TMP/sudo.calls.sudo"; then + src_fail sudo "the enrollment token was on sudo's command line: $(cat "$SRC_TMP/sudo.calls.sudo")" +fi +grep -Fq "LATTICE_NODE_TOKEN_FILE=$SRC_TMP/sudo-tmp/lattice-agent-token." "$SRC_TMP/sudo.calls.sudo" || \ + src_fail sudo "the re-exec handed over no token file" +grep -Fq 'LATTICE_NODE_TOKENnode-token-src' "$SRC_TMP/sudo/agent.plist" || \ + src_fail sudo "the token from the file did not reach the service definition" +if ls "$SRC_TMP/sudo-tmp" | grep -q lattice-agent-token; then src_fail sudo "the token file outlived the install"; fi + +# A sudo that fails (a wrong password) leaves no token file behind. +if SRC_PATH="$SUDO_BIN:/usr/bin:/bin" run_src sudo-fail TMPDIR="$SRC_TMP/sudo-fail-tmp" FAKE_SUDO_FAIL=1; then + SRC_PATH="" + src_fail sudo-fail "the installer succeeded without root" +fi +SRC_PATH="" +[ -z "$(ls "$SRC_TMP/sudo-fail-tmp")" ] || src_fail sudo-fail "the token file outlived a failed sudo" + +# The root child reads only a file the re-exec could have made: a regular +# file, not a link, mode 0600, owned by the user who ran sudo. +TOKEN_DIR="$SRC_TMP/token-files" +mkdir -p "$TOKEN_DIR" +MY_UID="$(/usr/bin/id -u)" +printf 'node-token-file' >"$TOKEN_DIR/ok" && chmod 0600 "$TOKEN_DIR/ok" +ln -s "$TOKEN_DIR/ok" "$TOKEN_DIR/link" +printf 'node-token-file' >"$TOKEN_DIR/open" && chmod 0644 "$TOKEN_DIR/open" +expect_refused tokfile-link "LATTICE_NODE_TOKEN_FILE is not a regular file" \ + LATTICE_NODE_TOKEN="" LATTICE_NODE_TOKEN_FILE="$TOKEN_DIR/link" SUDO_UID="$MY_UID" +expect_refused tokfile-mode "LATTICE_NODE_TOKEN_FILE must be mode 0600" \ + LATTICE_NODE_TOKEN="" LATTICE_NODE_TOKEN_FILE="$TOKEN_DIR/open" SUDO_UID="$MY_UID" +expect_refused tokfile-owner "owned by uid 4242" \ + LATTICE_NODE_TOKEN="" LATTICE_NODE_TOKEN_FILE="$TOKEN_DIR/ok" SUDO_UID=4242 +[ -f "$TOKEN_DIR/ok" ] || { echo "a refused token file was removed" >&2; exit 1; } # Malformed tables and sources die before any download. upper_sha="$(printf '%s' "$GOOD_SHA" | tr 'abcdef' 'ABCDEF')" From d5d79c839342d9f3b0f3816a0c33a3cd06d45b5c Mon Sep 17 00:00:00 2001 From: lr00rl Date: Sun, 18 Oct 2026 05:41:32 -0700 Subject: [PATCH 2/3] fix: hand the token over in a private directory the root child verifies From the security review of the file handover. The token file now sits in a 0700 directory that mktemp -d makes, written under umask 077, so nobody else can swap it between the root child's checks and its read, even where TMPDIR lacks the sticky bit. The child checks the directory (mode 0700, owned by SUDO_UID) and the file (regular, one link, mode 0600, the same owner), says so plainly when a default ACL on TMPDIR is the cause, removes only the file and then the directory if it is empty, and never removes a path it was handed. The parent's traps exit with 129, 130 and 143 for HUP, INT and TERM. Tested: test-install-integrity.sh also refuses a hard-linked token file and one in a world-readable directory; under macOS sh, dash, Debian bookworm and Alpine 3.20. check-release-workflow.sh passes. --- scripts/install.sh | 57 ++++++++++++++++++++----------- scripts/test-install-integrity.sh | 17 ++++++--- 2 files changed, 51 insertions(+), 23 deletions(-) diff --git a/scripts/install.sh b/scripts/install.sh index 39b6956..b8526d8 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -71,19 +71,26 @@ if [ "$(id -u)" -ne 0 ]; then if have sudo; then log "re-running with sudo ..." # The enrollment token never rides sudo's command line, which any local - # user can read in ps for as long as the install runs. It goes to the root - # child in a file only this user can read (mktemp makes it 0600), which - # the child checks, reads and removes; this shell removes it too when sudo - # returns or is interrupted. A file works whatever sudoers keeps or resets - # of the environment. + # user can read in ps (and sudo writes to its log) for as long as the + # install runs. It goes to the root child in a 0600 file inside a 0700 + # directory of this user's, which the child checks, reads and removes; + # this shell removes the directory too when sudo returns or is + # interrupted. A file works whatever sudoers keeps or resets of the + # environment. The private directory means nobody else can swap the file + # between the child's checks and its read, even in a TMPDIR without the + # sticky bit. + token_dir="" token_file="" if [ -n "$token" ]; then - token_file="$(mktemp "${TMPDIR:-/tmp}/lattice-agent-token.XXXXXX")" || \ - die "cannot create a private file to hand the enrollment token to sudo" - trap 'rm -f "$token_file"' EXIT - trap 'rm -f "$token_file"; exit 130' HUP INT TERM - chmod 0600 "$token_file" || die "cannot make $token_file private" - printf '%s' "$token" >"$token_file" || die "cannot write the enrollment token to $token_file" + token_dir="$(mktemp -d "${TMPDIR:-/tmp}/lattice-agent-token.XXXXXX")" || \ + die "cannot create a private directory to hand the enrollment token to sudo" + trap 'rm -rf "$token_dir"' EXIT + trap 'rm -rf "$token_dir"; exit 129' HUP + trap 'rm -rf "$token_dir"; exit 130' INT + trap 'rm -rf "$token_dir"; exit 143' TERM + chmod 0700 "$token_dir" || die "cannot make $token_dir private" + token_file="$token_dir/token" + ( umask 077; printf '%s' "$token" >"$token_file" ) || die "cannot write the enrollment token to $token_file" fi sudo_rc=0 sudo \ @@ -118,30 +125,42 @@ if [ "$(id -u)" -ne 0 ]; then LATTICE_PROXY_USAGE_XRAY_API="${LATTICE_PROXY_USAGE_XRAY_API:-}" LATTICE_PROXY_USAGE_XRAY_BIN="${LATTICE_PROXY_USAGE_XRAY_BIN:-}" \ LATTICE_PROXY_USAGE_XRAY_PATTERN="${LATTICE_PROXY_USAGE_XRAY_PATTERN:-}" \ sh "$0" "$@" || sudo_rc=$? - [ -z "$token_file" ] || rm -f "$token_file" + [ -z "$token_dir" ] || rm -rf "$token_dir" exit "$sudo_rc" fi die "please re-run as root (e.g. sudo sh $0)" fi # ---- the enrollment token handed over by the sudo re-exec --------------------- -# Only a file the re-exec above could have made is read: a regular file, not -# a link, owned by the user who ran sudo (or by root without sudo), and -# readable by that owner alone. It is removed as soon as it is read. +# Only a file the re-exec above could have made is read: in a directory only +# the user who ran sudo (or root, without sudo) can enter, a regular file, not +# a link, with one name, mode 0600 and the same owner. Nobody else can then +# swap it between these checks and the read. The file is removed as soon as it +# is read, and its directory only when that leaves it empty. if [ -n "${LATTICE_NODE_TOKEN_FILE:-}" ]; then token_file="$LATTICE_NODE_TOKEN_FILE" unset LATTICE_NODE_TOKEN_FILE token_owner="${SUDO_UID:-0}" case "$token_owner" in ""|*[!0-9]*) die "SUDO_UID is not a user id: $token_owner" ;; esac + token_dir="$(dirname -- "$token_file")" + # mode, link count and numeric owner, as ls -ln prints them everywhere + ls_mode_links_owner() { ls -ldn "$1" 2>/dev/null | awk '{ print $1 " " $2 " " $3 }'; } + [ -d "$token_dir" ] && [ ! -L "$token_dir" ] || \ + die "LATTICE_NODE_TOKEN_FILE is not in a directory: $token_file" + case "$(ls_mode_links_owner "$token_dir")" in + "drwx------ "*" $token_owner"|"drwx------. "*" $token_owner"|"drwx------@ "*" $token_owner") ;; + *) die "LATTICE_NODE_TOKEN_FILE must be in a directory only uid $token_owner can enter (mode 0700): $token_dir" ;; + esac [ -f "$token_file" ] && [ ! -L "$token_file" ] || \ die "LATTICE_NODE_TOKEN_FILE is not a regular file: $token_file" - token_mode_owner="$(ls -ln "$token_file" 2>/dev/null | awk '{ print $1 " " $3 }')" - case "$token_mode_owner" in - "-rw------- $token_owner"|"-rw-------. $token_owner"|"-rw-------@ $token_owner") ;; - *) die "LATTICE_NODE_TOKEN_FILE must be mode 0600 and owned by uid $token_owner: $token_file" ;; + case "$(ls_mode_links_owner "$token_file")" in + "-rw------- 1 $token_owner"|"-rw-------. 1 $token_owner"|"-rw-------@ 1 $token_owner") ;; + "-rw-------+ "*) die "LATTICE_NODE_TOKEN_FILE carries an ACL, likely a default ACL on TMPDIR; set TMPDIR to a directory without one: $token_file" ;; + *) die "LATTICE_NODE_TOKEN_FILE must be mode 0600, with one link, owned by uid $token_owner: $token_file" ;; esac token="$(cat "$token_file")" || die "cannot read LATTICE_NODE_TOKEN_FILE: $token_file" rm -f "$token_file" + rmdir "$token_dir" 2>/dev/null || true fi # ---- existing install adoption --------------------------------------------- diff --git a/scripts/test-install-integrity.sh b/scripts/test-install-integrity.sh index ad15cab..eb73ac1 100755 --- a/scripts/test-install-integrity.sh +++ b/scripts/test-install-integrity.sh @@ -693,19 +693,28 @@ fi SRC_PATH="" [ -z "$(ls "$SRC_TMP/sudo-fail-tmp")" ] || src_fail sudo-fail "the token file outlived a failed sudo" -# The root child reads only a file the re-exec could have made: a regular -# file, not a link, mode 0600, owned by the user who ran sudo. +# The root child reads only a file the re-exec could have made: in a 0700 +# directory of the user who ran sudo, a regular file, not a link, with one +# name, mode 0600 and the same owner. TOKEN_DIR="$SRC_TMP/token-files" -mkdir -p "$TOKEN_DIR" +mkdir -p "$TOKEN_DIR" "$SRC_TMP/token-open-dir" +chmod 0700 "$TOKEN_DIR" +chmod 0755 "$SRC_TMP/token-open-dir" MY_UID="$(/usr/bin/id -u)" printf 'node-token-file' >"$TOKEN_DIR/ok" && chmod 0600 "$TOKEN_DIR/ok" ln -s "$TOKEN_DIR/ok" "$TOKEN_DIR/link" printf 'node-token-file' >"$TOKEN_DIR/open" && chmod 0644 "$TOKEN_DIR/open" +printf 'node-token-file' >"$TOKEN_DIR/twice" && chmod 0600 "$TOKEN_DIR/twice" && ln "$TOKEN_DIR/twice" "$TOKEN_DIR/twice-again" +printf 'node-token-file' >"$SRC_TMP/token-open-dir/ok" && chmod 0600 "$SRC_TMP/token-open-dir/ok" expect_refused tokfile-link "LATTICE_NODE_TOKEN_FILE is not a regular file" \ LATTICE_NODE_TOKEN="" LATTICE_NODE_TOKEN_FILE="$TOKEN_DIR/link" SUDO_UID="$MY_UID" expect_refused tokfile-mode "LATTICE_NODE_TOKEN_FILE must be mode 0600" \ LATTICE_NODE_TOKEN="" LATTICE_NODE_TOKEN_FILE="$TOKEN_DIR/open" SUDO_UID="$MY_UID" -expect_refused tokfile-owner "owned by uid 4242" \ +expect_refused tokfile-links "with one link" \ + LATTICE_NODE_TOKEN="" LATTICE_NODE_TOKEN_FILE="$TOKEN_DIR/twice" SUDO_UID="$MY_UID" +expect_refused tokfile-dir "in a directory only uid $MY_UID can enter" \ + LATTICE_NODE_TOKEN="" LATTICE_NODE_TOKEN_FILE="$SRC_TMP/token-open-dir/ok" SUDO_UID="$MY_UID" +expect_refused tokfile-owner "only uid 4242 can enter" \ LATTICE_NODE_TOKEN="" LATTICE_NODE_TOKEN_FILE="$TOKEN_DIR/ok" SUDO_UID=4242 [ -f "$TOKEN_DIR/ok" ] || { echo "a refused token file was removed" >&2; exit 1; } From f7deddc7acc44cf0071ae00fef93ac2d78f1b528 Mon Sep 17 00:00:00 2001 From: lr00rl Date: Sun, 18 Oct 2026 05:44:33 -0700 Subject: [PATCH 3/3] fix: refuse a TMPDIR anyone can rename in, and name an ACL on the token directory From the re-check of the private-directory handover. In a TMPDIR every user can write to without the sticky bit, another user can rename the token directory away and plant one of their own between the root child's checks and its read. The installer now refuses such a TMPDIR before running sudo, following a symlinked TMPDIR to the directory it names. The comment says what the private directory does close: the file cannot be replaced or linked. A default ACL on TMPDIR, inherited by the token directory, now gets the ACL message at the directory check as well. Tested: test-install-integrity.sh refuses a 0777 TMPDIR before sudo runs and before any token file is written; under macOS sh, dash, Debian bookworm and Alpine 3.20. check-release-workflow.sh passes. --- scripts/install.sh | 13 ++++++++++--- scripts/test-install-integrity.sh | 12 ++++++++++++ 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/scripts/install.sh b/scripts/install.sh index b8526d8..52a62b2 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -76,12 +76,18 @@ if [ "$(id -u)" -ne 0 ]; then # directory of this user's, which the child checks, reads and removes; # this shell removes the directory too when sudo returns or is # interrupted. A file works whatever sudoers keeps or resets of the - # environment. The private directory means nobody else can swap the file - # between the child's checks and its read, even in a TMPDIR without the - # sticky bit. + # environment. The private directory means nobody else can replace or + # link the file between the child's checks and its read. Its own name is + # safe only in a TMPDIR other users cannot write to, or one with the + # sticky bit, so any other TMPDIR is refused. token_dir="" token_file="" if [ -n "$token" ]; then + # The trailing slash makes ls describe the directory a symlink points at. + case "$(ls -ldn "${TMPDIR:-/tmp}/" 2>/dev/null | awk '{ print $1 }')" in + d???????w[tT]*) ;; + d???????w*) die "TMPDIR ${TMPDIR:-/tmp} is writable by every user without the sticky bit; set TMPDIR to a private directory" ;; + esac token_dir="$(mktemp -d "${TMPDIR:-/tmp}/lattice-agent-token.XXXXXX")" || \ die "cannot create a private directory to hand the enrollment token to sudo" trap 'rm -rf "$token_dir"' EXIT @@ -149,6 +155,7 @@ if [ -n "${LATTICE_NODE_TOKEN_FILE:-}" ]; then die "LATTICE_NODE_TOKEN_FILE is not in a directory: $token_file" case "$(ls_mode_links_owner "$token_dir")" in "drwx------ "*" $token_owner"|"drwx------. "*" $token_owner"|"drwx------@ "*" $token_owner") ;; + "drwx------+ "*) die "LATTICE_NODE_TOKEN_FILE is in a directory that carries an ACL, likely a default ACL on TMPDIR; set TMPDIR to a directory without one: $token_dir" ;; *) die "LATTICE_NODE_TOKEN_FILE must be in a directory only uid $token_owner can enter (mode 0700): $token_dir" ;; esac [ -f "$token_file" ] && [ ! -L "$token_file" ] || \ diff --git a/scripts/test-install-integrity.sh b/scripts/test-install-integrity.sh index eb73ac1..e645ec1 100755 --- a/scripts/test-install-integrity.sh +++ b/scripts/test-install-integrity.sh @@ -693,6 +693,18 @@ fi SRC_PATH="" [ -z "$(ls "$SRC_TMP/sudo-fail-tmp")" ] || src_fail sudo-fail "the token file outlived a failed sudo" +# A TMPDIR every user can write to without the sticky bit would let another +# user swap the token directory itself: the installer refuses it before sudo. +mkdir -p "$SRC_TMP/sudo-open-tmp" && chmod 0777 "$SRC_TMP/sudo-open-tmp" +if SRC_PATH="$SUDO_BIN:/usr/bin:/bin" run_src sudo-open TMPDIR="$SRC_TMP/sudo-open-tmp"; then + SRC_PATH="" + src_fail sudo-open "the installer handed the token over through a TMPDIR anyone can rename in" +fi +SRC_PATH="" +expect_log sudo-open "is writable by every user without the sticky bit" +[ ! -s "$SRC_TMP/sudo-open.calls.sudo" ] || src_fail sudo-open "sudo ran before the refusal" +[ -z "$(ls "$SRC_TMP/sudo-open-tmp")" ] || src_fail sudo-open "a token file was written" + # The root child reads only a file the re-exec could have made: in a 0700 # directory of the user who ran sudo, a regular file, not a link, with one # name, mode 0600 and the same owner.