|
7 | 7 |
|
8 | 8 | import logging |
9 | 9 | import os |
| 10 | +import tempfile |
10 | 11 | import time |
11 | 12 | from functools import partial |
| 13 | +from pathlib import Path |
12 | 14 | from typing import Any, cast |
13 | 15 |
|
14 | 16 | import torch |
|
48 | 50 | # in by exporting VLLM_USE_DEEP_GEMM=1. |
49 | 51 | os.environ.setdefault("VLLM_USE_DEEP_GEMM", "0") |
50 | 52 |
|
| 53 | +# CVE-2025-69872: diskcache (pulled in transitively by outlines and used by |
| 54 | +# vLLM's optional on-disk outlines cache) deserializes cached values with |
| 55 | +# pickle/cloudpickle and is therefore RCE-vulnerable if another principal can |
| 56 | +# write into the cache directory. Neither library exposes a way to swap the |
| 57 | +# serializer, so we mitigate at the boundary: |
| 58 | +# 1. Keep vLLM's opt-in diskcache off (its default is an in-memory LRUCache). |
| 59 | +# Hard-set (not setdefault) so a user env can't silently flip on a |
| 60 | +# pickle-deserializing code path. |
| 61 | +# 2. Pin OUTLINES_CACHE_DIR to a per-user path and chmod it to 0700, since |
| 62 | +# outlines always uses diskcache for its FSM/index cache. |
| 63 | +os.environ["VLLM_V1_USE_OUTLINES_CACHE"] = "0" |
| 64 | + |
| 65 | + |
| 66 | +def _secure_outlines_cache_dir() -> None: |
| 67 | + """Pin ``OUTLINES_CACHE_DIR`` to a per-user path and tighten permissions. |
| 68 | +
|
| 69 | + Respects an explicit ``OUTLINES_CACHE_DIR`` set by the operator (so CI and |
| 70 | + multi-tenant deployments can choose their own private location), but always |
| 71 | + creates the directory with 0700 permissions to prevent co-tenants from |
| 72 | + poisoning the diskcache (CVE-2025-69872). |
| 73 | +
|
| 74 | + When unset, picks a per-user path under ``$XDG_CACHE_HOME`` or |
| 75 | + ``$HOME/.cache`` and falls back to a UID-scoped subdir of the system temp |
| 76 | + dir for distroless/rootless containers where ``$HOME`` is ``/``. |
| 77 | + """ |
| 78 | + cache_dir_env = os.environ.get("OUTLINES_CACHE_DIR") |
| 79 | + if cache_dir_env: |
| 80 | + cache_dir = Path(cache_dir_env) |
| 81 | + else: |
| 82 | + xdg_cache_home = os.environ.get("XDG_CACHE_HOME") |
| 83 | + home_dir = os.path.normpath(os.path.expanduser("~")) |
| 84 | + if xdg_cache_home: |
| 85 | + cache_root = Path(xdg_cache_home) |
| 86 | + elif home_dir != "/" and Path(home_dir).is_dir(): |
| 87 | + cache_root = Path(home_dir) / ".cache" |
| 88 | + else: |
| 89 | + uid = getattr(os, "getuid", lambda: "default")() |
| 90 | + cache_root = Path(tempfile.gettempdir()) / f".cache-{uid}" |
| 91 | + cache_dir = cache_root / "nemo-safe-synthesizer" / "outlines" |
| 92 | + os.environ["OUTLINES_CACHE_DIR"] = str(cache_dir) |
| 93 | + |
| 94 | + try: |
| 95 | + # Set the umask to 077 to prevent other principals from writing to the |
| 96 | + # cache directory between the mkdir and chmod calls. |
| 97 | + old_umask = os.umask(0o077) |
| 98 | + try: |
| 99 | + cache_dir.mkdir(parents=True, exist_ok=True) |
| 100 | + finally: |
| 101 | + os.umask(old_umask) |
| 102 | + # Also explicitly set permissions to 0700 for the situation where the |
| 103 | + # directory already exists and is not 0700. |
| 104 | + cache_dir.chmod(0o700) |
| 105 | + except OSError as exc: |
| 106 | + logger.warning( |
| 107 | + "Could not enforce 0700 permissions on outlines cache dir %s: %s. " |
| 108 | + "If this path is shared with other principals, set OUTLINES_CACHE_DIR " |
| 109 | + "to a private location (CVE-2025-69872).", |
| 110 | + cache_dir, |
| 111 | + exc, |
| 112 | + ) |
| 113 | + |
| 114 | + |
| 115 | +_secure_outlines_cache_dir() |
| 116 | + |
51 | 117 |
|
52 | 118 | def _is_redis_available() -> bool: |
53 | 119 | """Return True if the ``redis`` package is importable.""" |
|
0 commit comments