Files

108 lines
4.1 KiB
Python

import json
import os
import runpy
import socket
import struct
import subprocess
import sys
import tempfile
import threading
import unittest
from pathlib import Path
BWRAP = Path(__file__).resolve().parents[1] / "linuxfs/overlay/usr/local/bin/droiddeck-bwrap"
PROBE = """
import fcntl, os, sys
fd = os.open(sys.argv[1], os.O_RDWR)
try:
fcntl.lockf(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
except OSError:
print("held")
else:
print("free")
"""
class BwrapLockFileTest(unittest.TestCase):
def setUp(self):
self.shim = runpy.run_path(str(BWRAP))
self.temp = tempfile.TemporaryDirectory()
self.addCleanup(self.temp.cleanup)
self.instance = Path(self.temp.name) / "instance"
self.instance.mkdir()
(self.instance / ".ref").write_text("")
def spec(self, *extra):
argv = ["--ro-bind", str(self.instance), "/run/user/1000/.flatpak/7", *extra, "--", "true"]
return self.shim["parse"](argv)
def probe(self):
return subprocess.run([sys.executable, "-c", PROBE, str(self.instance / ".ref")],
capture_output=True, text=True, timeout=30, check=True).stdout.strip()
def test_sandbox_paths_resolve_through_the_binds(self):
spec = self.spec()
self.assertEqual(str(self.instance / ".ref"), self.shim["host_path"](spec, "/run/user/1000/.flatpak/7/.ref"))
self.assertEqual("/etc/hosts", self.shim["host_path"](spec, "/etc/hosts"))
def test_the_instance_lock_is_held_while_the_sandbox_runs(self):
spec = self.spec("--lock-file", "/run/user/1000/.flatpak/7/.ref")
self.assertEqual("free", self.probe())
self.shim["hold_locks"](spec)
self.assertEqual("held", self.probe())
def test_without_a_lock_file_nothing_is_locked(self):
self.shim["hold_locks"](self.spec())
self.assertEqual("free", self.probe())
def test_the_lock_survives_exec(self):
script = (
"import runpy, sys; shim = runpy.run_path(sys.argv[1]); "
"spec = shim['parse'](['--ro-bind', sys.argv[2], '/run/user/1000/.flatpak/7', "
"'--lock-file', '/run/user/1000/.flatpak/7/.ref', '--', 'true']); "
"shim['hold_locks'](spec); "
"import os; os.execv(sys.executable, [sys.executable, '-c', "
"'import subprocess, sys; print(subprocess.run([sys.executable, \"-c\", sys.argv[1], sys.argv[2]], "
"capture_output=True, text=True).stdout.strip())', sys.argv[3], sys.argv[4]])"
)
out = subprocess.run([sys.executable, "-c", script, str(BWRAP), str(self.instance), PROBE,
str(self.instance / ".ref")], capture_output=True, text=True, timeout=30, check=True)
self.assertEqual("held", out.stdout.strip())
class BwrapReportedPidTest(unittest.TestCase):
def test_flatpak_is_given_the_wrapper_so_killing_it_ends_the_whole_sandbox(self):
name = "droiddeck-bwrap-test-%d" % os.getpid()
server = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
server.bind("\0" + name)
server.listen(1)
self.addCleanup(server.close)
def answer():
conn, _ = server.accept()
with conn:
head = conn.recv(5)
size = struct.unpack(">I", head[1:])[0]
while size > 0:
size -= len(conn.recv(size))
for kind, payload in ((b"P", b"424242"), (b"X", b"0")):
conn.sendall(kind + struct.pack(">I", len(payload)) + payload)
thread = threading.Thread(target=answer, daemon=True)
thread.start()
read_end, write_end = os.pipe()
env = dict(os.environ, DROIDDECK_BWRAP_SOCKET=name)
proc = subprocess.Popen([sys.executable, str(BWRAP), "--ro-bind", "/", "/elsewhere", "--info-fd",
str(write_end), "--", "true"], env=env, pass_fds=[write_end])
os.close(write_end)
info = os.read(read_end, 4096)
os.close(read_end)
self.assertEqual(0, proc.wait(30))
thread.join(5)
self.assertEqual({"child-pid": proc.pid}, json.loads(info))
if __name__ == "__main__":
unittest.main()