-
Notifications
You must be signed in to change notification settings - Fork 171
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Sandbox all Dangerzone document processing within gVisor.
This wraps the existing container image inside a gVisor-based sandbox. gVisor is an open-source OCI-compliant container runtime. It is a userspace reimplementation of the Linux kernel in a memory-safe language. It works by creating a sandboxed environment in which regular Linux applications run, but their system calls are intercepted by gVisor. gVisor then redirects these system calls and reinterprets them in its own kernel. This means the host Linux kernel is isolated from the sandboxed application, thereby providing protection against Linux container escape attacks. It also uses `seccomp-bpf` to provide a secondary layer of defense against container escapes. Even if its userspace kernel gets compromised, attackers would have to additionally have a Linux container escape vector, and that exploit would have to fit within the restricted `seccomp-bpf` rules that gVisor adds on itself. Fixes #126 Fixes #224 Fixes #225 Fixes #228
- Loading branch information
1 parent
e005ea3
commit f03bc71
Showing
6 changed files
with
298 additions
and
28 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,161 @@ | ||
#!/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", | ||
] | ||
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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.