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
|
||||
import os
|
||||
from pathlib import Path
|
||||
import stat
|
||||
import sys
|
||||
|
||||
import pytest
|
||||
import trio
|
||||
import tractor
|
||||
from tractor import Actor
|
||||
from tractor.runtime import _state
|
||||
from tractor.discovery import _addr
|
||||
from tractor.runtime import _state
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
|
|
@ -31,6 +34,69 @@ def bindspace_dir_str() -> str:
|
|||
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(
|
||||
debug_mode: bool,
|
||||
bindspace_dir_str: str,
|
||||
|
|
|
|||
|
|
@ -22,7 +22,10 @@ from __future__ import annotations
|
|||
from contextvars import (
|
||||
ContextVar,
|
||||
)
|
||||
import os
|
||||
from pathlib import Path
|
||||
import stat
|
||||
import sys
|
||||
from typing import (
|
||||
Any,
|
||||
Callable,
|
||||
|
|
@ -42,6 +45,9 @@ if TYPE_CHECKING:
|
|||
from .._context import Context
|
||||
|
||||
|
||||
_DARWIN_TMPDIR: Path = Path('/tmp')
|
||||
|
||||
|
||||
# default IPC transport protocol settings
|
||||
TransportProtocolKey = Literal[
|
||||
'tcp',
|
||||
|
|
@ -335,7 +341,40 @@ def get_rt_dir(
|
|||
# `import tractor` path (gh #470).
|
||||
import platformdirs
|
||||
|
||||
rt_dir: Path = Path(
|
||||
rt_root: Path|None = None
|
||||
if sys.platform == 'darwin':
|
||||
# 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,
|
||||
),
|
||||
|
|
@ -363,11 +402,30 @@ def get_rt_dir(
|
|||
|
||||
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(
|
||||
mode=0o700,
|
||||
parents=True,
|
||||
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
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue