mirror of
https://github.com/freedomofpress/dangerzone.git
synced 2025-04-28 18:02:38 +02:00

DirectFS is enabled by default in gVisor to improve I/O performance, but comes at the cost of enabling the `openat(2)` syscall (with severe restrictions, but still). As Dangerzone is not performance-sensitive, and that it is desirable to guarantee for the document conversion process to not open any files (to mimic some of what SELinux provides), might as well disable it by default. See #226.
164 lines
4.9 KiB
Python
Executable file
164 lines
4.9 KiB
Python
Executable file
#!/usr/bin/python3
|
|
|
|
import json
|
|
import os
|
|
import shlex
|
|
import subprocess
|
|
import sys
|
|
import typing
|
|
|
|
# This script wraps the command-line arguments passed to it to run as an
|
|
# unprivileged user in a gVisor sandbox.
|
|
# Its behavior can be modified with the following environment variables:
|
|
# RUNSC_DEBUG: If set, print debug messages to stderr, and log all gVisor
|
|
# output to stderr.
|
|
# RUNSC_FLAGS: If set, pass these flags to the `runsc` invocation.
|
|
# These environment variables are not passed on to the sandboxed process.
|
|
|
|
|
|
def log(message: str, *values: typing.Any) -> None:
|
|
"""Helper function to log messages if RUNSC_DEBUG is set."""
|
|
if os.environ.get("RUNSC_DEBUG"):
|
|
print(message.format(*values), file=sys.stderr)
|
|
|
|
|
|
command = sys.argv[1:]
|
|
if len(command) == 0:
|
|
log("Invoked without a command; will execute 'sh'.")
|
|
command = ["sh"]
|
|
else:
|
|
log("Invoked with command: {}", " ".join(shlex.quote(s) for s in command))
|
|
|
|
# Build and write container OCI config.
|
|
oci_config: dict[str, typing.Any] = {
|
|
"ociVersion": "1.0.0",
|
|
"process": {
|
|
"user": {
|
|
# Hardcode the UID/GID of the container image to 1000, since we're in
|
|
# control of the image creation, and we don't expect it to change.
|
|
"uid": 1000,
|
|
"gid": 1000,
|
|
},
|
|
"args": command,
|
|
"env": [
|
|
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
|
|
"PYTHONPATH=/opt/dangerzone",
|
|
"TERM=xterm",
|
|
],
|
|
"cwd": "/",
|
|
"capabilities": {
|
|
"bounding": [],
|
|
"effective": [],
|
|
"inheritable": [],
|
|
"permitted": [],
|
|
},
|
|
"rlimits": [
|
|
{"type": "RLIMIT_NOFILE", "hard": 4096, "soft": 4096},
|
|
],
|
|
},
|
|
"root": {"path": "rootfs", "readonly": True},
|
|
"hostname": "dangerzone",
|
|
"mounts": [
|
|
{
|
|
"destination": "/proc",
|
|
"type": "proc",
|
|
"source": "proc",
|
|
},
|
|
{
|
|
"destination": "/dev",
|
|
"type": "tmpfs",
|
|
"source": "tmpfs",
|
|
"options": ["nosuid", "noexec", "nodev"],
|
|
},
|
|
{
|
|
"destination": "/sys",
|
|
"type": "tmpfs",
|
|
"source": "tmpfs",
|
|
"options": ["nosuid", "noexec", "nodev", "ro"],
|
|
},
|
|
{
|
|
"destination": "/tmp",
|
|
"type": "tmpfs",
|
|
"source": "tmpfs",
|
|
"options": ["nosuid", "noexec", "nodev"],
|
|
},
|
|
# LibreOffice needs a writable home directory, so just mount a tmpfs
|
|
# over it.
|
|
{
|
|
"destination": "/home/dangerzone",
|
|
"type": "tmpfs",
|
|
"source": "tmpfs",
|
|
"options": ["nosuid", "noexec", "nodev"],
|
|
},
|
|
# Used for LibreOffice extensions, which are only conditionally
|
|
# installed depending on which file is being converted.
|
|
{
|
|
"destination": "/usr/lib/libreoffice/share/extensions/",
|
|
"type": "tmpfs",
|
|
"source": "tmpfs",
|
|
"options": ["nosuid", "noexec", "nodev"],
|
|
},
|
|
],
|
|
"linux": {
|
|
"namespaces": [
|
|
{"type": "pid"},
|
|
{"type": "network"},
|
|
{"type": "ipc"},
|
|
{"type": "uts"},
|
|
{"type": "mount"},
|
|
],
|
|
},
|
|
}
|
|
not_forwarded_env = set(
|
|
(
|
|
"PATH",
|
|
"HOME",
|
|
"SHLVL",
|
|
"HOSTNAME",
|
|
"TERM",
|
|
"PWD",
|
|
"RUNSC_FLAGS",
|
|
"RUNSC_DEBUG",
|
|
)
|
|
)
|
|
for key_val in oci_config["process"]["env"]:
|
|
not_forwarded_env.add(key_val[: key_val.index("=")])
|
|
for key, val in os.environ.items():
|
|
if key in not_forwarded_env:
|
|
continue
|
|
oci_config["process"]["env"].append("%s=%s" % (key, val))
|
|
if os.environ.get("RUNSC_DEBUG"):
|
|
log("Command inside gVisor sandbox: {}", command)
|
|
log("OCI config:")
|
|
json.dump(oci_config, sys.stderr, indent=2, sort_keys=True)
|
|
# json.dump doesn't print a trailing newline, so print one here:
|
|
log("")
|
|
with open("/home/dangerzone/dangerzone-image/config.json", "w") as oci_config_out:
|
|
json.dump(oci_config, oci_config_out, indent=2, sort_keys=True)
|
|
|
|
# Run gVisor.
|
|
runsc_argv = [
|
|
"/usr/bin/runsc",
|
|
"--rootless=true",
|
|
"--network=none",
|
|
"--root=/home/dangerzone/.containers",
|
|
# Disable DirectFS for to make the seccomp filter even stricter,
|
|
# at some performance cost.
|
|
"--directfs=false",
|
|
]
|
|
if os.environ.get("RUNSC_DEBUG"):
|
|
runsc_argv += ["--debug=true", "--alsologtostderr=true"]
|
|
if os.environ.get("RUNSC_FLAGS"):
|
|
runsc_argv += [x for x in shlex.split(os.environ.get("RUNSC_FLAGS", "")) if x]
|
|
runsc_argv += ["run", "--bundle=/home/dangerzone/dangerzone-image", "dangerzone"]
|
|
log(
|
|
"Running gVisor with command line: {}", " ".join(shlex.quote(s) for s in runsc_argv)
|
|
)
|
|
runsc_process = subprocess.run(
|
|
runsc_argv,
|
|
check=False,
|
|
)
|
|
log("gVisor quit with exit code: {}", runsc_process.returncode)
|
|
|
|
# We're done.
|
|
sys.exit(runsc_process.returncode)
|