Use short Darwin UDS runtime paths
`platformdirs` can place the runtime dir deep below a pytest temp home, pushing `registry@1616.sock` past Darwin's 104-byte `AF_UNIX` limit. Use a compact `/tmp/<app>-<uid>` root on Darwin and secure it before allocating socket paths: - require a real, current-user-owned dir via `lstat()` - tighten existing roots to mode `0700` - reject symlinks and unsafe nested dirs Cover the path budget, mode, and symlink rejection. Caught-during: review remediation Found-via: `/run-tests` test_macos_rt_dir_fits_uds_path_limit Review: PR #480 (goodboy,copilot-pull-request-reviewer[bot]) https://github.com/goodboy/tractor/pull/480 (this patch was generated in some part by `opencode` using `gpt-5.6-sol` (`openai`))wkt/uds_macos_473
parent
451e0acf8a
commit
634d914161
|
|
@ -3,14 +3,17 @@ Unit-ish tests for specific IPC transport protocol backends.
|
||||||
|
|
||||||
'''
|
'''
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
import os
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
import stat
|
||||||
|
import sys
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
import trio
|
import trio
|
||||||
import tractor
|
import tractor
|
||||||
from tractor import Actor
|
from tractor import Actor
|
||||||
from tractor.runtime import _state
|
|
||||||
from tractor.discovery import _addr
|
from tractor.discovery import _addr
|
||||||
|
from tractor.runtime import _state
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
|
|
@ -31,6 +34,69 @@ def bindspace_dir_str() -> str:
|
||||||
bs_dir.rmdir()
|
bs_dir.rmdir()
|
||||||
|
|
||||||
|
|
||||||
|
def test_macos_rt_dir_fits_uds_path_limit(
|
||||||
|
monkeypatch: pytest.MonkeyPatch,
|
||||||
|
tmp_path: Path,
|
||||||
|
):
|
||||||
|
'''
|
||||||
|
Keep the default Darwin UDS bindpath below its 104-byte limit.
|
||||||
|
|
||||||
|
`platformdirs` normally places the runtime directory below the
|
||||||
|
long `~/Library/Caches/TemporaryItems` path. Pytest also assigns
|
||||||
|
a deeply nested temporary home, so appending a registry socket
|
||||||
|
name made every macOS UDS listener fail with `AF_UNIX path too
|
||||||
|
long`. This test simulates Darwin and an intentionally long
|
||||||
|
platformdirs result, then proves `get_rt_dir()` uses the short
|
||||||
|
system temporary directory and leaves room for the socket name.
|
||||||
|
|
||||||
|
'''
|
||||||
|
long_rt_dir: Path = tmp_path / ('long' * 30)
|
||||||
|
monkeypatch.setattr(sys, 'platform', 'darwin')
|
||||||
|
monkeypatch.setattr(
|
||||||
|
'platformdirs.user_runtime_dir',
|
||||||
|
lambda appname: str(long_rt_dir / appname),
|
||||||
|
)
|
||||||
|
monkeypatch.setattr(_state, '_DARWIN_TMPDIR', tmp_path)
|
||||||
|
rt_dir: Path = _state.get_rt_dir()
|
||||||
|
sockpath: Path = (
|
||||||
|
Path('/tmp')
|
||||||
|
/ f'tractor-{os.getuid()}'
|
||||||
|
/ 'registry@1616.sock'
|
||||||
|
)
|
||||||
|
|
||||||
|
assert rt_dir == tmp_path / f'tractor-{os.getuid()}'
|
||||||
|
assert len(os.fsencode(sockpath)) < 104
|
||||||
|
assert stat.S_IMODE(rt_dir.stat().st_mode) == 0o700
|
||||||
|
|
||||||
|
|
||||||
|
def test_macos_rt_dir_rejects_symlink(
|
||||||
|
monkeypatch: pytest.MonkeyPatch,
|
||||||
|
tmp_path: Path,
|
||||||
|
):
|
||||||
|
'''
|
||||||
|
Reject a pre-created symlink at the Darwin runtime path.
|
||||||
|
|
||||||
|
Darwin uses the predictable `/tmp/tractor-<uid>` path to stay
|
||||||
|
below its `AF_UNIX` limit. A hostile local user could otherwise
|
||||||
|
point that path at a victim-owned directory and make
|
||||||
|
`get_rt_dir()` chmod or place sockets in the symlink target. The
|
||||||
|
test replaces `/tmp` with a controlled directory, installs the
|
||||||
|
malicious link, and proves non-following validation rejects it.
|
||||||
|
|
||||||
|
'''
|
||||||
|
runtime_link: Path = tmp_path / f'tractor-{os.getuid()}'
|
||||||
|
target_dir: Path = tmp_path / 'target'
|
||||||
|
target_dir.mkdir(mode=0o755)
|
||||||
|
runtime_link.symlink_to(target_dir, target_is_directory=True)
|
||||||
|
monkeypatch.setattr(sys, 'platform', 'darwin')
|
||||||
|
monkeypatch.setattr(_state, '_DARWIN_TMPDIR', tmp_path)
|
||||||
|
|
||||||
|
with pytest.raises(PermissionError, match='Unsafe Darwin'):
|
||||||
|
_state.get_rt_dir()
|
||||||
|
|
||||||
|
assert stat.S_IMODE(target_dir.stat().st_mode) == 0o755
|
||||||
|
|
||||||
|
|
||||||
def test_uds_bindspace_created_implicitly(
|
def test_uds_bindspace_created_implicitly(
|
||||||
debug_mode: bool,
|
debug_mode: bool,
|
||||||
bindspace_dir_str: str,
|
bindspace_dir_str: str,
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,10 @@ from __future__ import annotations
|
||||||
from contextvars import (
|
from contextvars import (
|
||||||
ContextVar,
|
ContextVar,
|
||||||
)
|
)
|
||||||
|
import os
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
import stat
|
||||||
|
import sys
|
||||||
from typing import (
|
from typing import (
|
||||||
Any,
|
Any,
|
||||||
Callable,
|
Callable,
|
||||||
|
|
@ -42,6 +45,9 @@ if TYPE_CHECKING:
|
||||||
from .._context import Context
|
from .._context import Context
|
||||||
|
|
||||||
|
|
||||||
|
_DARWIN_TMPDIR: Path = Path('/tmp')
|
||||||
|
|
||||||
|
|
||||||
# default IPC transport protocol settings
|
# default IPC transport protocol settings
|
||||||
TransportProtocolKey = Literal[
|
TransportProtocolKey = Literal[
|
||||||
'tcp',
|
'tcp',
|
||||||
|
|
@ -335,11 +341,44 @@ def get_rt_dir(
|
||||||
# `import tractor` path (gh #470).
|
# `import tractor` path (gh #470).
|
||||||
import platformdirs
|
import platformdirs
|
||||||
|
|
||||||
rt_dir: Path = Path(
|
rt_root: Path|None = None
|
||||||
platformdirs.user_runtime_dir(
|
if sys.platform == 'darwin':
|
||||||
appname=appname,
|
# Darwin's AF_UNIX path limit is 104 bytes. The standard
|
||||||
),
|
# platformdirs path can consume that before the sock name.
|
||||||
)
|
rt_root = (
|
||||||
|
_DARWIN_TMPDIR
|
||||||
|
/ f'{appname}-{os.getuid()}'
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
rt_stat: os.stat_result = rt_root.lstat()
|
||||||
|
except FileNotFoundError:
|
||||||
|
try:
|
||||||
|
rt_root.mkdir(mode=0o700)
|
||||||
|
except FileExistsError:
|
||||||
|
pass
|
||||||
|
rt_stat = rt_root.lstat()
|
||||||
|
|
||||||
|
if (
|
||||||
|
not stat.S_ISDIR(rt_stat.st_mode)
|
||||||
|
or
|
||||||
|
rt_stat.st_uid != os.getuid()
|
||||||
|
):
|
||||||
|
raise PermissionError(
|
||||||
|
f'Unsafe Darwin runtime directory!\n'
|
||||||
|
f'path: {rt_root}\n'
|
||||||
|
f'owner uid: {rt_stat.st_uid}\n'
|
||||||
|
f'mode: {stat.filemode(rt_stat.st_mode)}\n'
|
||||||
|
)
|
||||||
|
if stat.S_IMODE(rt_stat.st_mode) != 0o700:
|
||||||
|
rt_root.chmod(0o700)
|
||||||
|
|
||||||
|
rt_dir: Path = rt_root
|
||||||
|
else:
|
||||||
|
rt_dir = Path(
|
||||||
|
platformdirs.user_runtime_dir(
|
||||||
|
appname=appname,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
# Normalize and validate that `subdir` is a relative path
|
# Normalize and validate that `subdir` is a relative path
|
||||||
# without any parent-directory ("..") components, to prevent
|
# without any parent-directory ("..") components, to prevent
|
||||||
|
|
@ -363,11 +402,30 @@ def get_rt_dir(
|
||||||
|
|
||||||
rt_dir: Path = rt_dir / subdir_path
|
rt_dir: Path = rt_dir / subdir_path
|
||||||
|
|
||||||
if not rt_dir.is_dir():
|
try:
|
||||||
|
dir_stat: os.stat_result = rt_dir.lstat()
|
||||||
|
except FileNotFoundError:
|
||||||
rt_dir.mkdir(
|
rt_dir.mkdir(
|
||||||
|
mode=0o700,
|
||||||
parents=True,
|
parents=True,
|
||||||
exist_ok=True, # avoid `FileExistsError` from conc calls
|
exist_ok=True, # avoid `FileExistsError` from conc calls
|
||||||
)
|
)
|
||||||
|
dir_stat = rt_dir.lstat()
|
||||||
|
|
||||||
|
if rt_root is not None:
|
||||||
|
if (
|
||||||
|
not stat.S_ISDIR(dir_stat.st_mode)
|
||||||
|
or
|
||||||
|
dir_stat.st_uid != os.getuid()
|
||||||
|
):
|
||||||
|
raise PermissionError(
|
||||||
|
f'Unsafe Darwin runtime directory!\n'
|
||||||
|
f'path: {rt_dir}\n'
|
||||||
|
f'owner uid: {dir_stat.st_uid}\n'
|
||||||
|
f'mode: {stat.filemode(dir_stat.st_mode)}\n'
|
||||||
|
)
|
||||||
|
if rt_dir != rt_root:
|
||||||
|
rt_dir.relative_to(rt_root)
|
||||||
|
|
||||||
return rt_dir
|
return rt_dir
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue