Files

147 lines
4.9 KiB
Python
Raw Permalink Normal View History

2022-01-25 11:02:08 -05:00
import contextlib
2024-08-06 07:40:13 +01:00
import getpass
2021-08-10 22:44:39 -04:00
import logging
import os
import shlex
import subprocess
2022-01-25 11:02:08 -05:00
from pathlib import Path
2021-08-10 22:44:39 -04:00
from tempfile import TemporaryDirectory
2022-07-03 10:43:59 -04:00
from typing import Any, Dict, List, Optional, Union
2022-01-25 11:02:08 -05:00
from django.conf import settings
2021-08-10 22:44:39 -04:00
2022-03-13 13:33:19 -04:00
from coreapp.error import SandboxError
2021-08-10 22:44:39 -04:00
logger = logging.getLogger(__name__)
2021-08-11 13:37:24 -04:00
class Sandbox(contextlib.AbstractContextManager["Sandbox"]):
def __enter__(self) -> "Sandbox":
2021-08-11 13:37:24 -04:00
self.use_jail = settings.USE_SANDBOX_JAIL
2021-08-10 22:44:39 -04:00
tmpdir: Optional[str] = None
2021-08-11 13:37:24 -04:00
if self.use_jail:
# Only use SANDBOX_TMP_PATH if USE_SANDBOX_JAIL is enabled,
# otherwise use the system default
settings.SANDBOX_TMP_PATH.mkdir(parents=True, exist_ok=True)
tmpdir = str(settings.SANDBOX_TMP_PATH)
2021-08-10 22:44:39 -04:00
self.temp_dir = TemporaryDirectory(dir=tmpdir)
self.path = Path(self.temp_dir.name)
return self
2022-07-03 10:43:59 -04:00
def __exit__(self, *exc: Any) -> None:
2021-08-10 22:44:39 -04:00
self.temp_dir.cleanup()
@staticmethod
def quote_options(opts: str) -> str:
return shlex.join(shlex.split(opts))
def rewrite_path(self, path: Path) -> str:
2021-08-11 13:37:24 -04:00
if self.use_jail and path.is_relative_to(self.path):
2021-08-10 22:44:39 -04:00
path = Path("/tmp") / path.relative_to(self.path)
2021-08-11 13:37:24 -04:00
return str(path)
2021-08-10 22:44:39 -04:00
2021-08-11 13:37:24 -04:00
def sandbox_command(self, mounts: List[Path], env: Dict[str, str]) -> List[str]:
if not self.use_jail:
2021-08-10 22:44:39 -04:00
return []
2021-08-11 13:37:24 -04:00
settings.SANDBOX_CHROOT_PATH.mkdir(parents=True, exist_ok=True)
2022-07-29 11:41:20 -04:00
settings.WINEPREFIX.mkdir(parents=True, exist_ok=True)
2021-11-21 14:23:39 +00:00
2021-08-10 22:44:39 -04:00
assert ":" not in str(self.path)
2022-07-29 11:41:20 -04:00
assert ":" not in str(settings.WINEPREFIX)
2024-08-06 07:40:13 +01:00
# wine-specific hacks
user = getpass.getuser()
(self.path / "Temp").mkdir(parents=True, exist_ok=True)
2021-08-10 22:44:39 -04:00
# fmt: off
wrapper = [
2021-08-11 13:37:24 -04:00
str(settings.SANDBOX_NSJAIL_BIN_PATH),
2021-08-10 22:44:39 -04:00
"--mode", "o",
"--chroot", str(settings.SANDBOX_CHROOT_PATH),
"--bindmount", f"{self.path}:/tmp",
"--bindmount", f"{self.path}:/run/user/{os.getuid()}",
"--bindmount_ro", "/dev",
2021-08-10 22:44:39 -04:00
"--bindmount_ro", "/bin",
2021-10-17 17:54:03 +01:00
"--bindmount_ro", "/etc/alternatives",
2021-11-21 14:23:39 +00:00
"--bindmount_ro", "/etc/fonts",
2022-06-12 02:00:15 +09:00
"--bindmount_ro", "/etc/passwd",
2021-08-10 22:44:39 -04:00
"--bindmount_ro", "/lib",
2025-01-21 14:32:30 +00:00
"--bindmount_ro", "/lib32",
2021-08-10 22:44:39 -04:00
"--bindmount_ro", "/lib64",
"--bindmount_ro", "/usr",
"--bindmount_ro", "/proc",
2025-02-10 10:01:41 +00:00
"--bindmount_ro", "/sys",
2023-03-21 21:08:55 -05:00
"--bindmount", f"{self.path}:/var/tmp",
"--bindmount_ro", str(settings.COMPILER_BASE_PATH),
2023-09-30 07:54:40 +02:00
"--bindmount_ro", str(settings.LIBRARY_BASE_PATH),
2021-08-10 22:44:39 -04:00
"--env", "PATH=/usr/bin:/bin",
"--cwd", "/tmp",
"--rlimit_fsize", "soft",
"--rlimit_nofile", "soft",
# the following are settings that can be removed once we are done with wine
"--bindmount_ro", f"{settings.WINEPREFIX}:/wine",
2024-08-06 07:40:13 +01:00
"--bindmount", f"{self.path}/Temp:/wine/drive_c/users/{user}/Temp",
"--env", "WINEDEBUG=-all",
"--env", "WINEPREFIX=/wine",
2021-08-10 22:44:39 -04:00
]
# fmt: on
if settings.SANDBOX_DISABLE_PROC:
wrapper.append("--disable_proc") # needed for running inside Docker
2021-08-10 22:44:39 -04:00
2021-08-11 14:04:06 -04:00
if not settings.DEBUG:
wrapper.append("--really_quiet")
2021-08-10 22:44:39 -04:00
for mount in mounts:
wrapper.extend(["--bindmount_ro", str(mount)])
for key in env:
wrapper.extend(["--env", key])
wrapper.append("--")
return wrapper
def run_subprocess(
self,
args: Union[str, List[str]],
*,
mounts: Optional[List[Path]] = None,
env: Optional[Dict[str, str]] = None,
shell: bool = False,
timeout: Optional[float] = None,
2021-08-10 22:44:39 -04:00
) -> subprocess.CompletedProcess[str]:
mounts = mounts if mounts is not None else []
env = env if env is not None else {}
timeout = None if timeout == 0 else timeout
2021-08-10 22:44:39 -04:00
2022-01-25 11:02:08 -05:00
try:
wrapper = self.sandbox_command(mounts, env)
except Exception as e:
raise SandboxError(f"Failed to initialize sandbox command: {e}")
2021-08-10 22:44:39 -04:00
if shell:
2021-08-26 14:05:06 +01:00
if isinstance(args, list):
args = " ".join(args)
2021-08-10 22:44:39 -04:00
command = wrapper + ["/bin/bash", "-euo", "pipefail", "-c", args]
else:
assert isinstance(args, list)
command = wrapper + args
2021-08-11 13:37:24 -04:00
debug_env_str = " ".join(
2023-01-10 08:04:57 -05:00
f"{key}={shlex.quote(value)}" for key, value in env.items() if key != "PATH"
2021-08-11 13:37:24 -04:00
)
logger.debug(f"Sandbox Command: {debug_env_str} {shlex.join(command)}")
2021-08-10 22:44:39 -04:00
return subprocess.run(
command,
text=True,
errors="backslashreplace",
env=env,
cwd=self.path,
check=True,
shell=False,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
timeout=timeout,
2021-08-10 22:44:39 -04:00
)