summaryrefslogtreecommitdiff
path: root/libmat2/bubblewrap.py
diff options
context:
space:
mode:
authorjvoisin2019-10-12 16:13:49 -0700
committerjvoisin2019-10-12 16:13:49 -0700
commit5f0b3beb46d09af26107fe5f80e63ddccb127a59 (patch)
treef3d46e6e9dac60daa304d212bed62b17c019f7eb /libmat2/bubblewrap.py
parent3cef7fe7fc81c1495a461a8594b1df69467536ea (diff)
Add a way to disable the sandbox
Due to bubblewrap's pickiness, mat2 can now be run without a sandbox, even if bubblewrap is installed.
Diffstat (limited to 'libmat2/bubblewrap.py')
-rw-r--r--libmat2/bubblewrap.py113
1 files changed, 113 insertions, 0 deletions
diff --git a/libmat2/bubblewrap.py b/libmat2/bubblewrap.py
new file mode 100644
index 0000000..fb6fc9d
--- /dev/null
+++ b/libmat2/bubblewrap.py
@@ -0,0 +1,113 @@
1"""
2Wrapper around a subset of the subprocess module,
3that uses bwrap (bubblewrap) when it is available.
4
5Instead of importing subprocess, other modules should use this as follows:
6
7 from . import subprocess
8"""
9
10import os
11import shutil
12import subprocess
13import tempfile
14from typing import List, Optional
15
16
17__all__ = ['PIPE', 'run', 'CalledProcessError']
18PIPE = subprocess.PIPE
19CalledProcessError = subprocess.CalledProcessError
20
21
22def _get_bwrap_path() -> str:
23 bwrap_path = '/usr/bin/bwrap'
24 if os.path.isfile(bwrap_path):
25 if os.access(bwrap_path, os.X_OK):
26 return bwrap_path
27
28 raise RuntimeError("Unable to find bwrap") # pragma: no cover
29
30
31# pylint: disable=bad-whitespace
32def _get_bwrap_args(tempdir: str,
33 input_filename: str,
34 output_filename: Optional[str] = None) -> List[str]:
35 ro_bind_args = []
36 cwd = os.getcwd()
37
38 # XXX: use --ro-bind-try once all supported platforms
39 # have a bubblewrap recent enough to support it.
40 ro_bind_dirs = ['/usr', '/lib', '/lib64', '/bin', '/sbin', cwd]
41 for bind_dir in ro_bind_dirs:
42 if os.path.isdir(bind_dir): # pragma: no cover
43 ro_bind_args.extend(['--ro-bind', bind_dir, bind_dir])
44
45 ro_bind_files = ['/etc/ld.so.cache']
46 for bind_file in ro_bind_files:
47 if os.path.isfile(bind_file): # pragma: no cover
48 ro_bind_args.extend(['--ro-bind', bind_file, bind_file])
49
50 args = ro_bind_args + \
51 ['--dev', '/dev',
52 '--proc', '/proc',
53 '--chdir', cwd,
54 '--tmpfs', '/tmp',
55 '--unshare-user-try',
56 '--unshare-ipc',
57 '--unshare-pid',
58 '--unshare-net',
59 '--unshare-uts',
60 '--unshare-cgroup-try',
61 '--new-session',
62 '--cap-drop', 'all',
63 # XXX: enable --die-with-parent once all supported platforms have
64 # a bubblewrap recent enough to support it.
65 # '--die-with-parent',
66 ]
67
68 if output_filename:
69 # Mount an empty temporary directory where the sandboxed
70 # process will create its output file
71 output_dirname = os.path.dirname(os.path.abspath(output_filename))
72 args.extend(['--bind', tempdir, output_dirname])
73
74 absolute_input_filename = os.path.abspath(input_filename)
75 args.extend(['--ro-bind', absolute_input_filename, absolute_input_filename])
76
77 return args
78
79
80# pylint: disable=bad-whitespace
81def run(args: List[str],
82 input_filename: str,
83 output_filename: Optional[str] = None,
84 **kwargs) -> subprocess.CompletedProcess:
85 """Wrapper around `subprocess.run`, that uses bwrap (bubblewrap) if it
86 is available.
87
88 Extra supported keyword arguments:
89
90 - `input_filename`, made available read-only in the sandbox
91 - `output_filename`, where the file created by the sandboxed process
92 is copied upon successful completion; an empty temporary directory
93 is made visible as the parent directory of this file in the sandbox.
94 Optional: one valid use case is to invoke an external process
95 to inspect metadata present in a file.
96 """
97 try:
98 bwrap_path = _get_bwrap_path()
99 except RuntimeError: # pragma: no cover
100 # bubblewrap is not installed ⇒ short-circuit
101 return subprocess.run(args, **kwargs)
102
103 with tempfile.TemporaryDirectory() as tempdir:
104 prefix_args = [bwrap_path] + \
105 _get_bwrap_args(input_filename=input_filename,
106 output_filename=output_filename,
107 tempdir=tempdir)
108 completed_process = subprocess.run(prefix_args + args, **kwargs)
109 if output_filename and completed_process.returncode == 0:
110 shutil.copy(os.path.join(tempdir, os.path.basename(output_filename)),
111 output_filename)
112
113 return completed_process