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"]):
|
2022-06-12 08:03:24 -04:00
|
|
|
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",
|
2021-11-21 12:07:09 -05:00
|
|
|
"--bindmount", f"{self.path}:/run/user/{os.getuid()}",
|
2023-02-24 12:40:31 +00:00
|
|
|
"--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",
|
2023-02-24 12:40:31 +00:00
|
|
|
"--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",
|
2021-10-14 12:52:28 +01:00
|
|
|
"--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",
|
2021-10-17 09:09:38 -04:00
|
|
|
"--cwd", "/tmp",
|
2021-12-29 02:03:35 -05:00
|
|
|
"--rlimit_fsize", "soft",
|
2023-01-20 18:04:27 +00:00
|
|
|
"--rlimit_nofile", "soft",
|
2022-08-09 06:46:19 +09:00
|
|
|
# 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",
|
2022-08-09 06:46:19 +09:00
|
|
|
"--env", "WINEDEBUG=-all",
|
|
|
|
|
"--env", "WINEPREFIX=/wine",
|
2021-08-10 22:44:39 -04:00
|
|
|
]
|
|
|
|
|
# fmt: on
|
2022-08-08 17:11:27 +01:00
|
|
|
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,
|
2023-01-20 18:04:27 +00:00
|
|
|
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 {}
|
2023-01-20 18:04:27 +00:00
|
|
|
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(
|
2021-10-17 09:09:38 -04:00
|
|
|
command,
|
|
|
|
|
text=True,
|
2023-04-10 03:25:00 +02:00
|
|
|
errors="backslashreplace",
|
2021-10-17 09:09:38 -04:00
|
|
|
env=env,
|
|
|
|
|
cwd=self.path,
|
|
|
|
|
check=True,
|
|
|
|
|
shell=False,
|
2022-08-11 01:19:38 -07:00
|
|
|
stdout=subprocess.PIPE,
|
|
|
|
|
stderr=subprocess.STDOUT,
|
2023-01-20 18:04:27 +00:00
|
|
|
timeout=timeout,
|
2021-08-10 22:44:39 -04:00
|
|
|
)
|