Skip to content

system/term: Improve resolution for setting a valid PAM_TTY#1591

Open
3v1n0 wants to merge 5 commits into
trifectatechfoundation:mainfrom
3v1n0:fix-pam-tty
Open

system/term: Improve resolution for setting a valid PAM_TTY#1591
3v1n0 wants to merge 5 commits into
trifectatechfoundation:mainfrom
3v1n0:fix-pam-tty

Conversation

@3v1n0
Copy link
Copy Markdown
Contributor

@3v1n0 3v1n0 commented May 20, 2026

In sudo-rs we're just trying to use stdin as PAM_TTY, but this may not be available if some input is redirected.

For example, using tools such as sshuttle with sudo-rs, PAM_TTY, is currently unset while it's /dev/pts/3 with sudo.ws

Now, the first commit would be enough to handle this in a simple way, but I also implemented a logic (for linux only, although BSDs can be added too), to follow the same logic that sudo is using to pick the TTY in a follow-up commit. Not sure if we have to go that deep though.

A simpler reproducer is (bug visible when uising --close-stdin or --stdin-devnull):

#!/usr/bin/env python3
"""Minimal reproducer for sshuttle-style sudo launch."""

import argparse
import os
import shutil
import socket
import subprocess
import sys


def enable_sshuttle_syslog_fd_mode() -> subprocess.Popen[bytes]:
    """Mimic cmdline.py + ssyslog.py fd setup used by sshuttle --syslog."""
    sink = subprocess.Popen(
        ["cat"],
        stdin=subprocess.PIPE,
        stdout=subprocess.DEVNULL,
        stderr=subprocess.DEVNULL,
    )
    assert sink.stdin is not None
    # ssyslog.close_stdin()
    os.close(0)
    # ssyslog.stdout_to_syslog() + stderr_to_syslog()
    os.dup2(sink.stdin.fileno(), 1)
    os.dup2(sink.stdin.fileno(), 2)
    return sink


def main() -> int:
    parser = argparse.ArgumentParser()
    parser.add_argument(
        "--close-stdin",
        action="store_true",
        help="close stdin before launching sudo (mimics sshuttle --syslog path)",
    )
    parser.add_argument(
        "--stdin-devnull",
        action="store_true",
        help="launch sudo with stdin from /dev/null",
    )
    parser.add_argument(
        "--sshuttle-syslog-fds",
        action="store_true",
        help="mimic sshuttle --syslog fd wiring before spawning sudo",
    )
    args = parser.parse_args()

    if args.sshuttle_syslog_fds and (args.close_stdin or args.stdin_devnull):
        parser.error("--sshuttle-syslog-fds cannot be combined with stdin flags")
    if args.close_stdin and args.stdin_devnull:
        parser.error("--close-stdin and --stdin-devnull are mutually exclusive")

    sudo = shutil.which("sudo") or "sudo"
    argv = [sudo, "-p", "[local sudo] Password: ", "id"]

    s1, s2 = socket.socketpair()

    def setup() -> None:
        # Run in child process.
        s2.close()

    stdin_obj = None
    syslog_sink = None
    if args.sshuttle_syslog_fds:
        syslog_sink = enable_sshuttle_syslog_fd_mode()
    elif args.close_stdin:
        os.close(0)
    elif args.stdin_devnull:
        stdin_obj = open(os.devnull, "rb")

    # We can't rely on stderr being tty-writable in syslog-fd mode.
    debug_fd = open("/dev/tty", "w", buffering=1) if os.path.exists("/dev/tty") else sys.__stderr__
    print(f"Starting command: {argv!r}", file=debug_fd)
    print(
        f"parent isatty: stdin={os.isatty(0)} stdout={os.isatty(1)} stderr={os.isatty(2)}",
        file=debug_fd,
    )
    proc = subprocess.Popen(
        argv,
        stdin=stdin_obj,
        stdout=s1,
        preexec_fn=setup,
    )
    s1.close()
    if stdin_obj is not None:
        stdin_obj.close()

    pfile = s2.makefile("rwb")
    output = pfile.read()
    if output:
        sys.stdout.buffer.write(output)
        sys.stdout.buffer.flush()

    rc = proc.wait()
    pfile.close()
    s2.close()
    if syslog_sink is not None:
        if syslog_sink.stdin is not None:
            syslog_sink.stdin.close()
        syslog_sink.wait()
    if debug_fd is not sys.__stderr__:
        debug_fd.close()
    return rc


if __name__ == "__main__":
    raise SystemExit(main())

Closes: #1593

3v1n0 added 5 commits May 22, 2026 12:48
Resolve the terminal path by checking stdin, stdout, and stderr in
order, instead of only stdin.

This matches sudo's stdio probing fallback behavior and improves PAM_TTY
detection when stdin is redirected but another stdio fd is still
attached to the controlling terminal.

For example when using tools such as sshuttle

Helps with: trifectatechfoundation#1593
Resolve PAM_TTY on Linux by first reading the controlling tty device
from /proc/self/stat, then mapping that device to a path using
sudo.ws-compatible resolution order:
  - /proc/self/fd/{0,1,2} when the fd device matches
  - /dev/console
  - /dev/pts
  - /dev scan fallback

If device-based resolution does not succeed, fall back to probing other
standard tty names (stdin/stdout/stderr) as before.

Actually closes: trifectatechfoundation#1260
Closes: trifectatechfoundation#1593
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

C-pam PAM library

Projects

None yet

Development

Successfully merging this pull request may close these issues.

PAM_TTY is not properly computed and exposed in situations where it could

2 participants