Files
aiturk-hermes-ide/tests/hermes_cli/test_linux_sandbox_fixup.py

164 lines
7.0 KiB
Python

"""Tests for the Linux desktop sandbox-helper fixup and the userns probe.
``_desktop_linux_sandbox_fixup`` historically demanded a root-owned 4755
``chrome-sandbox`` on every Linux host and shelled out to ``sudo`` to get it
— which fails silently when the desktop entry launches ``hermes desktop``
without a TTY (#88032, #51327), and blocked the updater's relaunch gate
(#58593). On hosts where unprivileged user namespaces work, Chromium uses
its namespace sandbox and never consults the setuid helper, so the fixup now
probes for that capability first and skips the sudo path entirely.
"""
from __future__ import annotations
import stat
import subprocess
import sys
from unittest.mock import patch
from hermes_cli import main as cli_main
class TestDesktopLinuxUsernsSandboxAvailable:
def test_false_on_non_linux(self, monkeypatch):
monkeypatch.setattr(sys, "platform", "darwin")
assert cli_main._desktop_linux_userns_sandbox_available() is False
def test_false_when_unshare_is_missing(self, monkeypatch):
monkeypatch.setattr(sys, "platform", "linux")
with patch.object(cli_main.shutil, "which", return_value=None):
assert cli_main._desktop_linux_userns_sandbox_available() is False
def test_true_when_probe_succeeds(self, monkeypatch):
monkeypatch.setattr(sys, "platform", "linux")
with patch.object(cli_main.shutil, "which", return_value="/usr/bin/unshare"), \
patch.object(cli_main.subprocess, "run") as run:
run.return_value.returncode = 0
assert cli_main._desktop_linux_userns_sandbox_available() is True
probe = run.call_args.args[0]
assert probe[0] == "/usr/bin/unshare"
assert "--user" in probe
def test_false_when_probe_fails(self, monkeypatch):
"""EPERM from the kernel (userns disabled or AppArmor-restricted)."""
monkeypatch.setattr(sys, "platform", "linux")
with patch.object(cli_main.shutil, "which", return_value="/usr/bin/unshare"), \
patch.object(cli_main.subprocess, "run") as run:
run.return_value.returncode = 1
assert cli_main._desktop_linux_userns_sandbox_available() is False
def test_false_when_probe_raises(self, monkeypatch):
monkeypatch.setattr(sys, "platform", "linux")
with patch.object(cli_main.shutil, "which", return_value="/usr/bin/unshare"), \
patch.object(
cli_main.subprocess,
"run",
side_effect=subprocess.TimeoutExpired(cmd="unshare", timeout=5),
):
assert cli_main._desktop_linux_userns_sandbox_available() is False
class TestDesktopLinuxSandboxFixup:
def _fake_packaged_app(self, tmp_path):
"""Unpacked-app layout with a non-root, non-setuid chrome-sandbox."""
unpacked = tmp_path / "linux-unpacked"
unpacked.mkdir()
exe = unpacked / "Hermes"
exe.write_text("", encoding="utf-8")
sandbox = unpacked / "chrome-sandbox"
sandbox.write_text("", encoding="utf-8")
sandbox.chmod(0o755)
return exe
def test_userns_host_skips_sudo_and_succeeds(self, monkeypatch, tmp_path):
"""A user-owned helper must not trigger sudo when userns works.
This is the .desktop-launch regression: no TTY means sudo cannot
prompt, so reaching the sudo path at all kills the launch.
"""
monkeypatch.setattr(sys, "platform", "linux")
exe = self._fake_packaged_app(tmp_path)
with patch.object(
cli_main, "_desktop_linux_userns_sandbox_available", return_value=True
), \
patch.object(cli_main.subprocess, "run") as run:
assert cli_main._desktop_linux_sandbox_fixup(exe) is True
run.assert_not_called()
def test_restricted_host_without_sudo_still_fails(self, monkeypatch, tmp_path):
"""The pre-existing strict path is preserved when userns is unusable."""
monkeypatch.setattr(sys, "platform", "linux")
exe = self._fake_packaged_app(tmp_path)
with patch.object(
cli_main, "_desktop_linux_userns_sandbox_available", return_value=False
), \
patch.object(cli_main.shutil, "which", return_value=None):
assert cli_main._desktop_linux_sandbox_fixup(exe) is False
def test_root_owned_setuid_helper_short_circuits(self, monkeypatch, tmp_path):
"""A correctly configured helper wins before the userns probe runs."""
monkeypatch.setattr(sys, "platform", "linux")
exe = self._fake_packaged_app(tmp_path)
real_lstat = (exe.parent / "chrome-sandbox").lstat()
class _RootSetuidStat:
st_mode = stat.S_IFREG | 0o4755
st_uid = 0
def __getattr__(self, name):
return getattr(real_lstat, name)
with patch.object(cli_main.Path, "lstat", return_value=_RootSetuidStat()), \
patch.object(
cli_main, "_desktop_linux_userns_sandbox_available"
) as probe:
assert cli_main._desktop_linux_sandbox_fixup(exe) is True
probe.assert_not_called()
class TestDesktopLinuxNeedsDisableSetuidSandbox:
def _fake_packaged_app(self, tmp_path):
unpacked = tmp_path / "linux-unpacked"
unpacked.mkdir()
exe = unpacked / "Hermes"
exe.write_text("", encoding="utf-8")
sandbox = unpacked / "chrome-sandbox"
sandbox.write_text("", encoding="utf-8")
sandbox.chmod(0o755)
return exe
def test_true_for_user_owned_helper_when_userns_works(self, monkeypatch, tmp_path):
monkeypatch.setattr(sys, "platform", "linux")
exe = self._fake_packaged_app(tmp_path)
with patch.object(
cli_main, "_desktop_linux_userns_sandbox_available", return_value=True
):
assert cli_main._desktop_linux_needs_disable_setuid_sandbox(exe) is True
def test_false_for_root_owned_setuid_helper(self, monkeypatch, tmp_path):
monkeypatch.setattr(sys, "platform", "linux")
exe = self._fake_packaged_app(tmp_path)
real_lstat = (exe.parent / "chrome-sandbox").lstat()
class _RootSetuidStat:
st_mode = stat.S_IFREG | 0o4755
st_uid = 0
def __getattr__(self, name):
return getattr(real_lstat, name)
with patch.object(cli_main.Path, "lstat", return_value=_RootSetuidStat()), \
patch.object(
cli_main, "_desktop_linux_userns_sandbox_available", return_value=True
) as probe:
assert cli_main._desktop_linux_needs_disable_setuid_sandbox(exe) is False
probe.assert_not_called()
def test_false_when_helper_missing(self, monkeypatch, tmp_path):
monkeypatch.setattr(sys, "platform", "linux")
unpacked = tmp_path / "linux-unpacked"
unpacked.mkdir()
exe = unpacked / "Hermes"
exe.write_text("", encoding="utf-8")
assert cli_main._desktop_linux_needs_disable_setuid_sandbox(exe) is False