Found a problem with the no screen reader path that could have a user type password when unnecessary and potentially leak the password via speech if key echo is enabled.
This commit is contained in:
@@ -8,9 +8,9 @@ import string
|
|||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
|
import threading
|
||||||
import time
|
import time
|
||||||
import pwd
|
import pwd
|
||||||
import threading
|
|
||||||
|
|
||||||
|
|
||||||
stormuxAdmin = ("storm",)
|
stormuxAdmin = ("storm",)
|
||||||
@@ -24,16 +24,41 @@ pingIntervalSeconds = 180
|
|||||||
pingCount = 5
|
pingCount = 5
|
||||||
maxWormholeFailures = 3
|
maxWormholeFailures = 3
|
||||||
|
|
||||||
|
sudoRefreshIntervalSeconds = 60
|
||||||
sudoKeepaliveThread = None
|
sudoKeepaliveThread = None
|
||||||
sudoKeepaliveStop = threading.Event()
|
sudoKeepaliveStop = threading.Event()
|
||||||
|
sudoSessionPrepared = False
|
||||||
|
sudoAuthenticationError = ""
|
||||||
|
|
||||||
|
|
||||||
def speak_message(message):
|
def speak_message(
|
||||||
|
message,
|
||||||
|
fallbackFile=None,
|
||||||
|
wait=False,
|
||||||
|
applicationName=None,
|
||||||
|
connectionName=None,
|
||||||
|
):
|
||||||
|
command = ["spd-say"]
|
||||||
|
if applicationName:
|
||||||
|
command += ["-N", applicationName]
|
||||||
|
if connectionName:
|
||||||
|
command += ["-n", connectionName]
|
||||||
|
if wait:
|
||||||
|
command.append("-w")
|
||||||
|
command.append(message)
|
||||||
try:
|
try:
|
||||||
subprocess.run(["spd-say", message], check=False)
|
result = subprocess.run(
|
||||||
|
command,
|
||||||
|
check=False,
|
||||||
|
stdout=subprocess.DEVNULL,
|
||||||
|
stderr=subprocess.DEVNULL,
|
||||||
|
start_new_session=wait,
|
||||||
|
)
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
print(message, flush=True)
|
print(message, file=fallbackFile or sys.stdout, flush=True)
|
||||||
|
return
|
||||||
|
if result.returncode != 0:
|
||||||
|
print(message, file=fallbackFile or sys.stdout, flush=True)
|
||||||
|
|
||||||
def say_or_print(message, useSpeech):
|
def say_or_print(message, useSpeech):
|
||||||
if useSpeech:
|
if useSpeech:
|
||||||
@@ -53,16 +78,76 @@ def run_command(command, inputText=None, check=False, env=None):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def run_terminal_sudo_authentication():
|
||||||
|
speak_message(
|
||||||
|
"Your sudo password will be requested next. Do not type your password yet. "
|
||||||
|
"If your microphone can carry computer speech into the support meeting, mute it "
|
||||||
|
"before continuing and unmute it afterward. Press Enter to continue. After you "
|
||||||
|
"press Enter, wait one second, then type your password and press Enter. If you "
|
||||||
|
"hear any part of your password spoken, stop immediately, do not finish it, and "
|
||||||
|
"tell the support assistant.",
|
||||||
|
wait=True,
|
||||||
|
applicationName="sas",
|
||||||
|
connectionName=f"sudo-password-{os.getpid()}",
|
||||||
|
)
|
||||||
|
input()
|
||||||
|
return run_command(["sudo", "-v"])
|
||||||
|
|
||||||
|
|
||||||
|
def authenticate_sudo(useSpeech):
|
||||||
|
global sudoAuthenticationError
|
||||||
|
sudoAuthenticationError = ""
|
||||||
|
command = ["sudo"]
|
||||||
|
if useSpeech:
|
||||||
|
result = run_terminal_sudo_authentication()
|
||||||
|
else:
|
||||||
|
result = run_command(command + ["-v"])
|
||||||
|
if result.returncode != 0:
|
||||||
|
sudoAuthenticationError = concise_command_error(result)
|
||||||
|
return False
|
||||||
|
start_sudo_keepalive()
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def prepare_sudo_session(useSpeech):
|
||||||
|
global sudoAuthenticationError, sudoSessionPrepared
|
||||||
|
if os.geteuid() == 0:
|
||||||
|
sudoSessionPrepared = True
|
||||||
|
return True
|
||||||
|
invalidateResult = run_command(["sudo", "-k"])
|
||||||
|
if invalidateResult.returncode != 0:
|
||||||
|
sudoAuthenticationError = concise_command_error(invalidateResult)
|
||||||
|
return False
|
||||||
|
probeEnv = os.environ.copy()
|
||||||
|
probeEnv["LC_ALL"] = "C"
|
||||||
|
probeEnv["LANG"] = "C"
|
||||||
|
probeResult = run_command(["sudo", "-n", "-v"], env=probeEnv)
|
||||||
|
if probeResult.returncode == 0:
|
||||||
|
start_sudo_keepalive()
|
||||||
|
sudoSessionPrepared = True
|
||||||
|
return True
|
||||||
|
probeError = concise_command_error(probeResult)
|
||||||
|
if "password is required" not in probeError.lower():
|
||||||
|
sudoAuthenticationError = probeError
|
||||||
|
return False
|
||||||
|
if not authenticate_sudo(useSpeech=useSpeech):
|
||||||
|
return False
|
||||||
|
sudoSessionPrepared = True
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
def ensure_sudo(useSpeech):
|
def ensure_sudo(useSpeech):
|
||||||
if os.geteuid() == 0:
|
if os.geteuid() == 0:
|
||||||
return True
|
return True
|
||||||
if useSpeech:
|
if sudoSessionPrepared:
|
||||||
speak_message("Sudo password required. Please enter your password now.")
|
return run_command(["sudo", "-n", "-v"]).returncode == 0
|
||||||
result = run_command(["sudo", "-v"])
|
return authenticate_sudo(useSpeech)
|
||||||
if result.returncode == 0:
|
|
||||||
start_sudo_keepalive()
|
|
||||||
return True
|
def sudo_command_prefix():
|
||||||
return False
|
if sudoSessionPrepared:
|
||||||
|
return ["sudo", "-n"]
|
||||||
|
return ["sudo"]
|
||||||
|
|
||||||
|
|
||||||
def start_sudo_keepalive():
|
def start_sudo_keepalive():
|
||||||
@@ -71,7 +156,7 @@ def start_sudo_keepalive():
|
|||||||
return
|
return
|
||||||
|
|
||||||
def keepalive_loop():
|
def keepalive_loop():
|
||||||
while not sudoKeepaliveStop.wait(240):
|
while not sudoKeepaliveStop.wait(sudoRefreshIntervalSeconds):
|
||||||
run_command(["sudo", "-n", "-v"])
|
run_command(["sudo", "-n", "-v"])
|
||||||
|
|
||||||
sudoKeepaliveThread = threading.Thread(target=keepalive_loop, daemon=True)
|
sudoKeepaliveThread = threading.Thread(target=keepalive_loop, daemon=True)
|
||||||
@@ -84,7 +169,7 @@ def run_privileged(command, useSpeech, inputText=None, check=True):
|
|||||||
else:
|
else:
|
||||||
if not ensure_sudo(useSpeech):
|
if not ensure_sudo(useSpeech):
|
||||||
raise RuntimeError("sudo authentication failed")
|
raise RuntimeError("sudo authentication failed")
|
||||||
fullCommand = ["sudo"] + command
|
fullCommand = sudo_command_prefix() + command
|
||||||
return run_command(fullCommand, inputText=inputText, check=check)
|
return run_command(fullCommand, inputText=inputText, check=check)
|
||||||
|
|
||||||
|
|
||||||
@@ -94,7 +179,7 @@ def run_as_user(userName, command, useSpeech, check=True):
|
|||||||
else:
|
else:
|
||||||
if not ensure_sudo(useSpeech):
|
if not ensure_sudo(useSpeech):
|
||||||
raise RuntimeError("sudo authentication failed")
|
raise RuntimeError("sudo authentication failed")
|
||||||
fullCommand = ["sudo", "-u", userName, "-H"] + command
|
fullCommand = sudo_command_prefix() + ["-u", userName, "-H"] + command
|
||||||
return run_command(fullCommand, check=check)
|
return run_command(fullCommand, check=check)
|
||||||
|
|
||||||
|
|
||||||
@@ -102,6 +187,15 @@ def command_error_text(result):
|
|||||||
return (result.stderr or result.stdout or "").strip()
|
return (result.stderr or result.stdout or "").strip()
|
||||||
|
|
||||||
|
|
||||||
|
def concise_command_error(result):
|
||||||
|
errorText = " ".join(command_error_text(result).split())
|
||||||
|
if not errorText:
|
||||||
|
return "sudo returned no error details"
|
||||||
|
if len(errorText) > 400:
|
||||||
|
return errorText[:400] + "..."
|
||||||
|
return errorText
|
||||||
|
|
||||||
|
|
||||||
def service_is_active(serviceName):
|
def service_is_active(serviceName):
|
||||||
result = run_command(["systemctl", "is-active", "--quiet", serviceName])
|
result = run_command(["systemctl", "is-active", "--quiet", serviceName])
|
||||||
return result.returncode == 0
|
return result.returncode == 0
|
||||||
@@ -373,6 +467,15 @@ def main():
|
|||||||
answer = input().strip().lower()
|
answer = input().strip().lower()
|
||||||
useSpeech = answer in ("n", "no")
|
useSpeech = answer in ("n", "no")
|
||||||
|
|
||||||
|
if useSpeech and not prepare_sudo_session(useSpeech=True):
|
||||||
|
failureMessage = (
|
||||||
|
f"Sudo authentication failed. {sudoAuthenticationError}. "
|
||||||
|
"The support session was not started."
|
||||||
|
)
|
||||||
|
say_or_print(failureMessage, True)
|
||||||
|
print(failureMessage, flush=True)
|
||||||
|
return 1
|
||||||
|
|
||||||
shouldRemoveUser = False
|
shouldRemoveUser = False
|
||||||
cleanupDone = False
|
cleanupDone = False
|
||||||
tempDir = tempfile.mkdtemp(prefix="sas-ii-")
|
tempDir = tempfile.mkdtemp(prefix="sas-ii-")
|
||||||
@@ -425,7 +528,10 @@ def main():
|
|||||||
useSpeech,
|
useSpeech,
|
||||||
)
|
)
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
say_or_print(
|
||||||
|
"Cleanup warning: failed to remove sas user. Please remove it manually.",
|
||||||
|
useSpeech,
|
||||||
|
)
|
||||||
|
|
||||||
sudoKeepaliveStop.set()
|
sudoKeepaliveStop.set()
|
||||||
if sudoKeepaliveThread:
|
if sudoKeepaliveThread:
|
||||||
@@ -615,7 +721,7 @@ def main():
|
|||||||
privateKeyPath,
|
privateKeyPath,
|
||||||
f"{sasUser}@{remoteHost}",
|
f"{sasUser}@{remoteHost}",
|
||||||
]
|
]
|
||||||
sshCommand = ["sudo", "-u", sasUser, "-H"] + sshCommand
|
sshCommand = sudo_command_prefix() + ["-u", sasUser, "-H"] + sshCommand
|
||||||
sshProcess = subprocess.Popen(sshCommand)
|
sshProcess = subprocess.Popen(sshCommand)
|
||||||
sshProcess.wait()
|
sshProcess.wait()
|
||||||
say_or_print("The support connection ended. Cleaning up.", useSpeech)
|
say_or_print("The support connection ended. Cleaning up.", useSpeech)
|
||||||
@@ -649,5 +755,13 @@ def remove_tree(path):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def command_line_main(arguments=None):
|
||||||
|
arguments = sys.argv[1:] if arguments is None else arguments
|
||||||
|
if arguments:
|
||||||
|
print("Usage: sas", file=sys.stderr)
|
||||||
|
return 2
|
||||||
|
return main()
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
sys.exit(main())
|
sys.exit(command_line_main())
|
||||||
|
|||||||
Reference in New Issue
Block a user