Files
DroidDeck/tools/tests/test_bwrap_lock.py
T

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()