mirror of
https://github.com/lucannez64/darling-lol.git
synced 2026-10-26 19:47:42 +00:00
Close inherited host file descriptors before starting the guest
A terminal, IDE or service manager can leave descriptors open in the launcher, and the guest inherited all of them. tools/exec-guest.py closes every descriptor above stderr and then execs the guest command. A shell loop over /proc/self/fd cannot do this portably: dash only parses single-digit descriptors in redirections, so "exec 10>&-" runs a command named 10 and ends the launcher on Debian and Ubuntu. The launcher offline check now leaks descriptors 3, 4, 12 and 200 into the launcher and asserts the guest sees only 0, 1 and 2. It runs in CI. Split from #61. Co-authored-by: DevWebeloper <[email protected]> Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
603b5a168e
commit
6b753e87c5
5 files changed
+59
-5
No files matched your search
Executable
+30
@@ -0,0 +1,30 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Run a command without the host file descriptors this launcher inherited (anything above stderr)."""
|
||||
import os
|
||||
import sys
|
||||
|
||||
|
||||
def close_inherited_fds():
|
||||
"""A terminal, IDE or service manager can leak descriptors; the guest must not inherit them."""
|
||||
try:
|
||||
names = os.listdir('/proc/self/fd')
|
||||
except OSError:
|
||||
os.closerange(3, 65536)
|
||||
return
|
||||
for name in names:
|
||||
if name.isdigit() and int(name) > 2:
|
||||
try:
|
||||
os.close(int(name))
|
||||
except OSError:
|
||||
pass # the descriptor listdir used is already closed
|
||||
|
||||
|
||||
def main(argv):
|
||||
if len(argv) < 2:
|
||||
sys.exit('usage: exec-guest.py COMMAND [ARGUMENT]...')
|
||||
close_inherited_fds()
|
||||
os.execvp(argv[1], argv[1:])
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main(sys.argv)
|
||||
@@ -16,13 +16,25 @@ def check():
|
||||
folder = Path(directory)
|
||||
launcher = folder / "launch.sh"
|
||||
launcher.write_text((ROOT / "tools/run-riot-client.sh").read_text().replace("@GUEST_HOME@", "/Users/offline-check"))
|
||||
(folder / "exec-guest.py").write_text((ROOT / "tools/exec-guest.py").read_text())
|
||||
binary_folder = folder / "bin"
|
||||
binary_folder.mkdir()
|
||||
log = folder / "calls.jsonl"
|
||||
stub = f'''#!{sys.executable}
|
||||
import json, os, sys
|
||||
record = {{"command": os.path.basename(sys.argv[0]), "args": sys.argv[1:]}}
|
||||
# The launcher starts the guest through exec-guest.py; run it for real.
|
||||
if record["command"] == "python3" and sys.argv[1:] and os.path.basename(sys.argv[1]) == "exec-guest.py":
|
||||
os.execv(sys.executable, [sys.executable] + sys.argv[1:])
|
||||
if record["command"] == "darling":
|
||||
# Descriptors open now were inherited; the one listdir used is closed again before this check.
|
||||
def is_open(fd):
|
||||
try:
|
||||
os.fstat(fd)
|
||||
return True
|
||||
except OSError:
|
||||
return False
|
||||
record["fds"] = sorted(fd for fd in map(int, os.listdir("/proc/self/fd")) if is_open(fd))
|
||||
record["flags"] = {{key: os.environ.get(key) for key in
|
||||
("RCLOL_APP_ARG3", "RCLOL_APP_ARG3_SLOT", "RCLOL_UX_ARG2", "RCLOL_UX_ARG2_SLOT", "RCLOL_UX_ARG1", "RCLOL_APP_ARGS", "RCLOL_APP_ARG2", "DYLD_INSERT_LIBRARIES", "DYLD_LIBRARY_PATH", "DYLD_FRAMEWORK_PATH", "DARLING_XSHM_LIBRARY")}}
|
||||
with open({str(log)!r}, "a") as output:
|
||||
@@ -60,7 +72,15 @@ with open({str(log)!r}, "a") as output:
|
||||
"RCLOL_OPEN_URL_BRIDGE": "1",
|
||||
"RCLOL_DISABLE_GPU_VSYNC": "1",
|
||||
})
|
||||
subprocess.run(["sh", str(launcher)], env=env, check=True, capture_output=True, text=True)
|
||||
# Leaked host descriptors, including multi-digit ones, must not reach the guest.
|
||||
leaked = list(os.pipe())
|
||||
for target in (12, 200):
|
||||
leaked.append(os.dup2(leaked[0], target, inheritable=False))
|
||||
try:
|
||||
subprocess.run(["sh", str(launcher)], env=env, check=True, capture_output=True, text=True, pass_fds=leaked)
|
||||
finally:
|
||||
for fd in leaked:
|
||||
os.close(fd)
|
||||
records = [json.loads(line) for line in log.read_text().splitlines()]
|
||||
maintenance = [r for r in records if r["command"] == "python3"]
|
||||
assert [Path(r["args"][0]).name for r in maintenance] == [
|
||||
@@ -70,6 +90,7 @@ with open({str(log)!r}, "a") as output:
|
||||
launches = [r for r in records if r["command"] == "darling"]
|
||||
assert len(launches) == 1, "unexpected launch count"
|
||||
launch = launches[0]
|
||||
assert launch["fds"] == [0, 1, 2], f"inherited descriptors reached the guest: {launch['fds']}"
|
||||
assert all(launch["flags"][key] is None for key in (
|
||||
"RCLOL_APP_ARG3", "RCLOL_APP_ARG3_SLOT", "RCLOL_UX_ARG2", "RCLOL_UX_ARG2_SLOT"
|
||||
)), "inherited inspector/unlimited-frame argument survived"
|
||||
@@ -95,7 +116,7 @@ with open({str(log)!r}, "a") as output:
|
||||
subprocess.run(["sh", str(launcher)], env=env, check=True, capture_output=True, text=True)
|
||||
clean = json.loads(log.read_text().splitlines()[-1])
|
||||
assert clean["flags"]["DYLD_INSERT_LIBRARIES"] is None
|
||||
print("LAUNCHER_OFFLINE pass=1 installed=PASS installer=PASS inherited_injection=absent runtime=preserved")
|
||||
print("LAUNCHER_OFFLINE pass=1 installed=PASS installer=PASS inherited_injection=absent runtime=preserved inherited_fds=closed")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -104,7 +104,9 @@ fi
|
||||
# On affected setups Chromium's default temp directory is unusable. Electron
|
||||
# can copy app.asar entries to temporary files for async reads and report a
|
||||
# failed copy as "not found". Use a writable guest directory by default.
|
||||
exec env -u ELECTRON_RUN_AS_NODE -u DYLD_INSERT_LIBRARIES EGL_PLATFORM=x11 \
|
||||
# exec-guest.py closes host file descriptors above stderr before starting the guest. A shell
|
||||
# loop cannot: dash only accepts single-digit descriptors in redirections.
|
||||
exec python3 "$runner_dir/exec-guest.py" env -u ELECTRON_RUN_AS_NODE -u DYLD_INSERT_LIBRARIES EGL_PLATFORM=x11 \
|
||||
MAC_CHROMIUM_TMPDIR="${MAC_CHROMIUM_TMPDIR:-/private/tmp}" \
|
||||
LANG="${RCLOL_GUEST_LOCALE:-en_US.UTF-8}" LC_ALL="${RCLOL_GUEST_LOCALE:-en_US.UTF-8}" \
|
||||
mesa_glthread="${RCLOL_MESA_GLTHREAD-${mesa_glthread-false}}" \
|
||||
|
||||
Reference in new issue
Block a user