From: Thomas Walker Lynch Date: Thu, 25 Sep 2025 05:52:48 +0000 (+0000) Subject: new diag_perm X-Git-Url: https://git.reasoningtechnology.com/usr/lib/python2.7/encodings/cp862.py?a=commitdiff_plain;h=5fd52978675caae64879e961079e613315be6231;p=Man-In-Grey new diag_perm --- diff --git a/developer/source/diag_perm b/developer/source/diag_perm index d40f495..ba10d90 100644 --- a/developer/source/diag_perm +++ b/developer/source/diag_perm @@ -1,105 +1,194 @@ #!/usr/bin/env bash -# Diagnose Man_In_Grey privilege flow: user -> setuid gasket -> python inner set -euo pipefail -# --- locate release base from this script's real path --- -_this="$(readlink -f "$0")" -_shell_dir="$(cd -- "$(dirname -- "$_this")" && pwd -P)" # .../release/shell -_relbase="$(dirname "$_shell_dir")" # .../release - -# --- arch normalize --- -_arch_raw=$(uname -m | tr '[:upper:]' '[:lower:]') -case "$_arch_raw" in - amd64|x64) _arch="x86_64" ;; - x86_64) _arch="x86_64" ;; - i386|i486|i586|i686) _arch="i686" ;; - arm64|aarch64) _arch="aarch64" ;; - armv7l) _arch="armv7l" ;; - armv6l) _arch="armv6l" ;; - riscv64) _arch="riscv64" ;; - ppc64le|powerpc64le) _arch="ppc64le" ;; - s390x) _arch="s390x" ;; - *) _arch="$_arch_raw" ;; -esac +# --- tiny helpers ------------------------------------------------------------- +say(){ printf "%s\n" "$*"; } +have(){ command -v "$1" >/dev/null 2>&1; } -# Allow explicit overrides via args -GASKET="${1:-"${_relbase}/${_arch}/man_in_grey_apply"}" -WRAP="${2:-"${_relbase}/shell/Man_In_Grey"}" +longest_mount_covering() { + # Arg: absolute path + # Output: "MOUNTPOINT FSTYPE OPTIONS" (best effort) + local target="$1" + # Parse /proc/self/mountinfo to find the longest mountpoint prefix + # Fields: ... 5:mountpoint 6:opts ... " - " 9:fstype ... + awk -v P="$target" ' + BEGIN{best=""; bestlen=-1} + { + # split into pre/post " - " + line=$0 + split(line,parts," - ") + pre=parts[1]; post=parts[2]; + n=split(pre,a," ") + mp=a[5]; opts=a[6]; + nf=split(post,b," ") + fstype=b[1]; -echo "== who/where ==" -echo "user: $(id -un) (uid=$(id -u)) groups: $(id -nG)" -echo "pwd : $(pwd)" -echo + # mountpoints in mountinfo use escape sequences for spaces; safe for our use + if (index(P, mp)==1) { + l=length(mp) + if (l>bestlen) { best=mp; bestlen=l; BO=opts; FT=fstype; } + } + } + END{ + if (bestlen>=0) { print best " " FT " " BO; } + }' /proc/self/mountinfo +} -echo "== paths ==" -echo "wrapper: $WRAP" -echo "gasket : $GASKET" -echo +# --- locate repo bits --------------------------------------------------------- +script_afp="$(realpath "${BASH_SOURCE[0]}")" +RELEASE_DIR="$(cd "$(dirname "$script_afp")/.." && pwd -P)" +WRAP="$RELEASE_DIR/shell/Man_In_Grey" -echo "== wrapper sanity ==" -if [[ -x "$WRAP" ]]; then - head -n 1 "$WRAP" | sed 's/^/shebang: /' - echo "exec path: $(command -v "$WRAP" || echo '(not in PATH)')" +# Compute arch dir like the rest of the tools +arch_raw="$(uname -m | tr '[:upper:]' '[:lower:]')" +case "$arch_raw" in + amd64|x64) arch="x86_64" ;; + x86_64) arch="x86_64" ;; + i386|i486|i586|i686) arch="i686" ;; + arm64|aarch64) arch="aarch64" ;; + armv7l) arch="armv7l" ;; + armv6l) arch="armv6l" ;; + riscv64) arch="riscv64" ;; + ppc64le|powerpc64le) arch="ppc64le" ;; + s390x) arch="s390x" ;; + *) arch="$arch_raw" ;; +esac +GASKET="$RELEASE_DIR/$arch/man_in_grey_apply" + +# --- who/where ---------------------------------------------------------------- +u="$(id -un)"; uid="$(id -u)" +groups="$(id -nG 2>/dev/null || true)" +pwd="$(pwd -P)" +say "== who/where ==" +say "user: $u (uid=$uid) groups: ${groups:-unknown}" +say "pwd : $pwd" +say +say "== paths ==" +say "wrapper: $WRAP" +say "gasket : $GASKET" +say + +# --- wrapper sanity ------------------------------------------------------------ +say "== wrapper sanity ==" +if [[ -f "$WRAP" && -x "$WRAP" ]]; then + read -r firstline <"$WRAP" || true + say "shebang: ${firstline:-}" + say "exec path: $WRAP" else - echo "!! wrapper missing/non-exec" + say "!! wrapper missing/non-exec" fi -echo +say -echo "== gasket file sanity ==" +# --- gasket file sanity & mount flags ----------------------------------------- +say "== gasket file sanity ==" if [[ -e "$GASKET" ]]; then ls -l "$GASKET" - if [[ -u "$GASKET" ]]; then echo "setuid: YES"; else echo "setuid: NO"; fi - mp="$(dirname "$(readlink -f "$GASKET")")" - echo "mount: $(findmnt -no TARGET,FSTYPE,OPTIONS "$mp" 2>/dev/null || echo 'findmnt not available')" + if [[ -u "$GASKET" ]]; then + say "setuid: YES" + else + say "setuid: NO" + fi + + # try to find findmnt robustly + FINDMNT_BIN="" + for c in "${FINDMNT_BIN:-}" "$(command -v findmnt 2>/dev/null || true)" /usr/bin/findmnt /bin/findmnt /usr/sbin/findmnt; do + [[ -n "$c" && -x "$c" ]] && { FINDMNT_BIN="$c"; break; } + done + + gasket_abs="$(realpath "$GASKET")" + if [[ -n "$FINDMNT_BIN" ]]; then + # prefer findmnt -T if supported + if "$FINDMNT_BIN" -T "$gasket_abs" >/dev/null 2>&1; then + out="$("$FINDMNT_BIN" -no TARGET,FSTYPE,OPTIONS -T "$gasket_abs" || true)" + else + # fallback: search by path in the tree + out="$("$FINDMNT_BIN" -no TARGET,FSTYPE,OPTIONS "$gasket_abs" 2>/dev/null || true)" + fi + if [[ -n "${out:-}" ]]; then + say "mount: $out" + mp="$(awk '{print $1}' <<<"$out")" + fstype="$(awk '{print $2}' <<<"$out")" + opts="$(awk '{$1="";$2=""; sub(/^ */,""); print}' <<<"$out")" + else + say "mount: findmnt returned no match, falling back to /proc/self/mountinfo" + fi + else + say "mount: findmnt not found, falling back to /proc/self/mountinfo" + fi + + if [[ -z "${opts:-}" ]]; then + parsed="$(longest_mount_covering "$gasket_abs" || true)" + if [[ -n "$parsed" ]]; then + mp="$(awk '{print $1}' <<<"$parsed")" + fstype="$(awk '{print $2}' <<<"$parsed")" + opts="$(awk '{$1="";$2=""; sub(/^ */,""); print}' <<<"$parsed")" + say "mount: $mp $fstype $opts (from /proc/self/mountinfo)" + else + say "mount: could not determine mount options (even via mountinfo)" + fi + fi + + if [[ -n "${opts:-}" ]]; then + if grep -qw nosuid <<<"$opts"; then + say "‼ detected nosuid on mount → setuid will NOT take effect here" + else + say "✓ nosuid NOT present on mount" + fi + if ! grep -qw exec <<<"$opts"; then + say "‼ mount appears noexec (or exec not set) → binary execution may fail" + fi + fi else - echo "!! gasket not found" + say "!! gasket not found" fi -echo +say -echo "== gasket self-report (--print-flags) ==" +# --- gasket self-report -------------------------------------------------------- +say "== gasket self-report (--print-flags) ==" if [[ -x "$GASKET" ]]; then "$GASKET" --print-flags || true else - echo "skip (no gasket)" + say "skip (no gasket)" fi -echo +say -echo "== python inner EUID test ==" -PYTEST="/tmp/mig_ids_$$.py" -cat >"$PYTEST"<<'PY' +# --- python inner EUID test ---------------------------------------------------- +say "== python inner EUID test ==" +if [[ -x "$GASKET" ]]; then + tmp_py="$(mktemp /tmp/mig_ids_XXXXXX.py)" + cat >"$tmp_py" <<'PY' import os, pwd -u = os.getuid(); e = os.geteuid() -name = pwd.getpwuid(u).pw_name if u>=0 else "?" -print(f"py.real_uid={u} ({name})") -print(f"py.effective_uid={e}") -print("py.groups=", os.getgroups()) +uid = os.getuid() +euid = os.geteuid() +name = pwd.getpwuid(uid).pw_name +print(f"py.real_uid={uid} ({name})") +print(f"py.effective_uid={euid}") +print("py.groups=", sorted(os.getgroups())) PY -chmod 0644 "$PYTEST" - -if [[ -x "$GASKET" ]]; then - echo "running: $GASKET --inner $PYTEST" - "$GASKET" --inner "$PYTEST" || true + say "running: $GASKET --inner $tmp_py" + "$GASKET" --inner "$tmp_py" || true + rm -f "$tmp_py" else - echo "skip (no gasket)" + say "skip (no gasket)" fi -rm -f "$PYTEST" -echo +say -echo "== wrapper execution trace (dry) ==" +# --- wrapper execution trace (dry) -------------------------------------------- +say "== wrapper execution trace (dry) ==" if [[ -x "$WRAP" ]]; then - echo "+ bash -x \"$WRAP\" --phase-2-sanity2-then-stop --stage dummy (trace only)" set +e - bash -x "$WRAP" --phase-2-sanity2-then-stop --stage dummy 1>/dev/null 2>&1 | sed 's/^/TRACE: /' || true + bash -x "$WRAP" --phase-2-sanity2-then-stop --stage dummy >/dev/null 2>&1 \ + && say "+ bash -x \"$WRAP\" --phase-2-sanity2-then-stop --stage dummy (trace only)" \ + || say "+ bash -x \"$WRAP\" --phase-2-sanity2-then-stop --stage dummy (trace only)" set -e else - echo "skip (no wrapper)" + say "skip (no wrapper)" fi +say -echo -echo "== verdict (rules of thumb) ==" -cat <<'EOT' -- If --print-flags shows flag.this_process_privileged=0: setuid isn’t taking effect (check nosuid mount, bit not set, or wrong binary). -- If that flag is 1 and the Python test shows euid=0: privileges flow is OK; if you still get EPERM to /etc, the wrapper may bypass the gasket sometimes. -- If the wrapper trace never execs the gasket: replace the wrapper so it always runs the gasket. -- If root is refused for “not in sudo”, update gasket policy to allow UID 0 (you already have that patch). -EOT +# --- verdict ------------------------------------------------------------------- +say "== verdict (rules of thumb) ==" +say "- If --print-flags shows flag.this_process_privileged=0: setuid isn’t taking effect (check nosuid mount, bit not set, or wrong binary)." +say "- If that flag is 1 and the Python test shows euid=0: privileges flow is OK; if you still get EPERM to /etc, the wrapper may bypass the gasket sometimes." +say "- If the wrapper trace never execs the gasket: replace the wrapper so it always runs the gasket." +say "- If root is refused for “not in sudo”, update gasket policy to allow UID 0 (you have that patch)." diff --git a/release/shell/diag_perm b/release/shell/diag_perm index d40f495..ba10d90 100755 --- a/release/shell/diag_perm +++ b/release/shell/diag_perm @@ -1,105 +1,194 @@ #!/usr/bin/env bash -# Diagnose Man_In_Grey privilege flow: user -> setuid gasket -> python inner set -euo pipefail -# --- locate release base from this script's real path --- -_this="$(readlink -f "$0")" -_shell_dir="$(cd -- "$(dirname -- "$_this")" && pwd -P)" # .../release/shell -_relbase="$(dirname "$_shell_dir")" # .../release - -# --- arch normalize --- -_arch_raw=$(uname -m | tr '[:upper:]' '[:lower:]') -case "$_arch_raw" in - amd64|x64) _arch="x86_64" ;; - x86_64) _arch="x86_64" ;; - i386|i486|i586|i686) _arch="i686" ;; - arm64|aarch64) _arch="aarch64" ;; - armv7l) _arch="armv7l" ;; - armv6l) _arch="armv6l" ;; - riscv64) _arch="riscv64" ;; - ppc64le|powerpc64le) _arch="ppc64le" ;; - s390x) _arch="s390x" ;; - *) _arch="$_arch_raw" ;; -esac +# --- tiny helpers ------------------------------------------------------------- +say(){ printf "%s\n" "$*"; } +have(){ command -v "$1" >/dev/null 2>&1; } -# Allow explicit overrides via args -GASKET="${1:-"${_relbase}/${_arch}/man_in_grey_apply"}" -WRAP="${2:-"${_relbase}/shell/Man_In_Grey"}" +longest_mount_covering() { + # Arg: absolute path + # Output: "MOUNTPOINT FSTYPE OPTIONS" (best effort) + local target="$1" + # Parse /proc/self/mountinfo to find the longest mountpoint prefix + # Fields: ... 5:mountpoint 6:opts ... " - " 9:fstype ... + awk -v P="$target" ' + BEGIN{best=""; bestlen=-1} + { + # split into pre/post " - " + line=$0 + split(line,parts," - ") + pre=parts[1]; post=parts[2]; + n=split(pre,a," ") + mp=a[5]; opts=a[6]; + nf=split(post,b," ") + fstype=b[1]; -echo "== who/where ==" -echo "user: $(id -un) (uid=$(id -u)) groups: $(id -nG)" -echo "pwd : $(pwd)" -echo + # mountpoints in mountinfo use escape sequences for spaces; safe for our use + if (index(P, mp)==1) { + l=length(mp) + if (l>bestlen) { best=mp; bestlen=l; BO=opts; FT=fstype; } + } + } + END{ + if (bestlen>=0) { print best " " FT " " BO; } + }' /proc/self/mountinfo +} -echo "== paths ==" -echo "wrapper: $WRAP" -echo "gasket : $GASKET" -echo +# --- locate repo bits --------------------------------------------------------- +script_afp="$(realpath "${BASH_SOURCE[0]}")" +RELEASE_DIR="$(cd "$(dirname "$script_afp")/.." && pwd -P)" +WRAP="$RELEASE_DIR/shell/Man_In_Grey" -echo "== wrapper sanity ==" -if [[ -x "$WRAP" ]]; then - head -n 1 "$WRAP" | sed 's/^/shebang: /' - echo "exec path: $(command -v "$WRAP" || echo '(not in PATH)')" +# Compute arch dir like the rest of the tools +arch_raw="$(uname -m | tr '[:upper:]' '[:lower:]')" +case "$arch_raw" in + amd64|x64) arch="x86_64" ;; + x86_64) arch="x86_64" ;; + i386|i486|i586|i686) arch="i686" ;; + arm64|aarch64) arch="aarch64" ;; + armv7l) arch="armv7l" ;; + armv6l) arch="armv6l" ;; + riscv64) arch="riscv64" ;; + ppc64le|powerpc64le) arch="ppc64le" ;; + s390x) arch="s390x" ;; + *) arch="$arch_raw" ;; +esac +GASKET="$RELEASE_DIR/$arch/man_in_grey_apply" + +# --- who/where ---------------------------------------------------------------- +u="$(id -un)"; uid="$(id -u)" +groups="$(id -nG 2>/dev/null || true)" +pwd="$(pwd -P)" +say "== who/where ==" +say "user: $u (uid=$uid) groups: ${groups:-unknown}" +say "pwd : $pwd" +say +say "== paths ==" +say "wrapper: $WRAP" +say "gasket : $GASKET" +say + +# --- wrapper sanity ------------------------------------------------------------ +say "== wrapper sanity ==" +if [[ -f "$WRAP" && -x "$WRAP" ]]; then + read -r firstline <"$WRAP" || true + say "shebang: ${firstline:-}" + say "exec path: $WRAP" else - echo "!! wrapper missing/non-exec" + say "!! wrapper missing/non-exec" fi -echo +say -echo "== gasket file sanity ==" +# --- gasket file sanity & mount flags ----------------------------------------- +say "== gasket file sanity ==" if [[ -e "$GASKET" ]]; then ls -l "$GASKET" - if [[ -u "$GASKET" ]]; then echo "setuid: YES"; else echo "setuid: NO"; fi - mp="$(dirname "$(readlink -f "$GASKET")")" - echo "mount: $(findmnt -no TARGET,FSTYPE,OPTIONS "$mp" 2>/dev/null || echo 'findmnt not available')" + if [[ -u "$GASKET" ]]; then + say "setuid: YES" + else + say "setuid: NO" + fi + + # try to find findmnt robustly + FINDMNT_BIN="" + for c in "${FINDMNT_BIN:-}" "$(command -v findmnt 2>/dev/null || true)" /usr/bin/findmnt /bin/findmnt /usr/sbin/findmnt; do + [[ -n "$c" && -x "$c" ]] && { FINDMNT_BIN="$c"; break; } + done + + gasket_abs="$(realpath "$GASKET")" + if [[ -n "$FINDMNT_BIN" ]]; then + # prefer findmnt -T if supported + if "$FINDMNT_BIN" -T "$gasket_abs" >/dev/null 2>&1; then + out="$("$FINDMNT_BIN" -no TARGET,FSTYPE,OPTIONS -T "$gasket_abs" || true)" + else + # fallback: search by path in the tree + out="$("$FINDMNT_BIN" -no TARGET,FSTYPE,OPTIONS "$gasket_abs" 2>/dev/null || true)" + fi + if [[ -n "${out:-}" ]]; then + say "mount: $out" + mp="$(awk '{print $1}' <<<"$out")" + fstype="$(awk '{print $2}' <<<"$out")" + opts="$(awk '{$1="";$2=""; sub(/^ */,""); print}' <<<"$out")" + else + say "mount: findmnt returned no match, falling back to /proc/self/mountinfo" + fi + else + say "mount: findmnt not found, falling back to /proc/self/mountinfo" + fi + + if [[ -z "${opts:-}" ]]; then + parsed="$(longest_mount_covering "$gasket_abs" || true)" + if [[ -n "$parsed" ]]; then + mp="$(awk '{print $1}' <<<"$parsed")" + fstype="$(awk '{print $2}' <<<"$parsed")" + opts="$(awk '{$1="";$2=""; sub(/^ */,""); print}' <<<"$parsed")" + say "mount: $mp $fstype $opts (from /proc/self/mountinfo)" + else + say "mount: could not determine mount options (even via mountinfo)" + fi + fi + + if [[ -n "${opts:-}" ]]; then + if grep -qw nosuid <<<"$opts"; then + say "‼ detected nosuid on mount → setuid will NOT take effect here" + else + say "✓ nosuid NOT present on mount" + fi + if ! grep -qw exec <<<"$opts"; then + say "‼ mount appears noexec (or exec not set) → binary execution may fail" + fi + fi else - echo "!! gasket not found" + say "!! gasket not found" fi -echo +say -echo "== gasket self-report (--print-flags) ==" +# --- gasket self-report -------------------------------------------------------- +say "== gasket self-report (--print-flags) ==" if [[ -x "$GASKET" ]]; then "$GASKET" --print-flags || true else - echo "skip (no gasket)" + say "skip (no gasket)" fi -echo +say -echo "== python inner EUID test ==" -PYTEST="/tmp/mig_ids_$$.py" -cat >"$PYTEST"<<'PY' +# --- python inner EUID test ---------------------------------------------------- +say "== python inner EUID test ==" +if [[ -x "$GASKET" ]]; then + tmp_py="$(mktemp /tmp/mig_ids_XXXXXX.py)" + cat >"$tmp_py" <<'PY' import os, pwd -u = os.getuid(); e = os.geteuid() -name = pwd.getpwuid(u).pw_name if u>=0 else "?" -print(f"py.real_uid={u} ({name})") -print(f"py.effective_uid={e}") -print("py.groups=", os.getgroups()) +uid = os.getuid() +euid = os.geteuid() +name = pwd.getpwuid(uid).pw_name +print(f"py.real_uid={uid} ({name})") +print(f"py.effective_uid={euid}") +print("py.groups=", sorted(os.getgroups())) PY -chmod 0644 "$PYTEST" - -if [[ -x "$GASKET" ]]; then - echo "running: $GASKET --inner $PYTEST" - "$GASKET" --inner "$PYTEST" || true + say "running: $GASKET --inner $tmp_py" + "$GASKET" --inner "$tmp_py" || true + rm -f "$tmp_py" else - echo "skip (no gasket)" + say "skip (no gasket)" fi -rm -f "$PYTEST" -echo +say -echo "== wrapper execution trace (dry) ==" +# --- wrapper execution trace (dry) -------------------------------------------- +say "== wrapper execution trace (dry) ==" if [[ -x "$WRAP" ]]; then - echo "+ bash -x \"$WRAP\" --phase-2-sanity2-then-stop --stage dummy (trace only)" set +e - bash -x "$WRAP" --phase-2-sanity2-then-stop --stage dummy 1>/dev/null 2>&1 | sed 's/^/TRACE: /' || true + bash -x "$WRAP" --phase-2-sanity2-then-stop --stage dummy >/dev/null 2>&1 \ + && say "+ bash -x \"$WRAP\" --phase-2-sanity2-then-stop --stage dummy (trace only)" \ + || say "+ bash -x \"$WRAP\" --phase-2-sanity2-then-stop --stage dummy (trace only)" set -e else - echo "skip (no wrapper)" + say "skip (no wrapper)" fi +say -echo -echo "== verdict (rules of thumb) ==" -cat <<'EOT' -- If --print-flags shows flag.this_process_privileged=0: setuid isn’t taking effect (check nosuid mount, bit not set, or wrong binary). -- If that flag is 1 and the Python test shows euid=0: privileges flow is OK; if you still get EPERM to /etc, the wrapper may bypass the gasket sometimes. -- If the wrapper trace never execs the gasket: replace the wrapper so it always runs the gasket. -- If root is refused for “not in sudo”, update gasket policy to allow UID 0 (you already have that patch). -EOT +# --- verdict ------------------------------------------------------------------- +say "== verdict (rules of thumb) ==" +say "- If --print-flags shows flag.this_process_privileged=0: setuid isn’t taking effect (check nosuid mount, bit not set, or wrong binary)." +say "- If that flag is 1 and the Python test shows euid=0: privileges flow is OK; if you still get EPERM to /etc, the wrapper may bypass the gasket sometimes." +say "- If the wrapper trace never execs the gasket: replace the wrapper so it always runs the gasket." +say "- If root is refused for “not in sudo”, update gasket policy to allow UID 0 (you have that patch)."