Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions bash/patchman-client
Original file line number Diff line number Diff line change
Expand Up @@ -1100,8 +1100,7 @@ if ! check_command_exists awk || \
exit 1
fi

os_lock_dir=/var/lock
lock_dir=$(dirname "$(readlink -f ${os_lock_dir})")/patchman
lock_dir=/run/patchman
mkdir -p "${lock_dir}"
if [ ! -d "${lock_dir}" ] ; then
echo "Lock directory does not exist, exiting: ${lock_dir}"
Expand Down
2 changes: 1 addition & 1 deletion contrib/systemd/patchman-client.service
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ NoNewPrivileges=yes
ProtectSystem=strict
ProtectHome=yes
PrivateTmp=yes
ReadOnlyPaths=/
RuntimeDirectory=patchman

[Install]
WantedBy=multi-user.target
2 changes: 1 addition & 1 deletion go/cmd/submit.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ The server URL and API key can be provided via:
}

// Acquire exclusive lock to prevent concurrent runs
lockDir := "/run/lock/patchman"
lockDir := "/run/patchman"
os.MkdirAll(lockDir, 0o755)
lockPath := filepath.Join(lockDir, "patchman.lock")
lockFile, err := os.OpenFile(lockPath, os.O_CREATE|os.O_WRONLY, 0o644)
Expand Down
2 changes: 1 addition & 1 deletion python/patchman_client/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@

__version__ = '2.0.0'
DEFAULT_CONFIG = '/etc/patchman/patchman-client.conf'
LOCK_DIR = '/run/lock/patchman'
LOCK_DIR = '/run/patchman'
LOCK_FILE = os.path.join(LOCK_DIR, 'patchman.lock')


Expand Down
62 changes: 62 additions & 0 deletions tests/test_systemd_lock_consistency.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
"""Guard against #866: hardened unit must carve out a writable lock dir and
all clients must agree on it."""
import os
import re

REPO = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
SERVICE = os.path.join(REPO, 'contrib', 'systemd', 'patchman-client.service')
BASH = os.path.join(REPO, 'bash', 'patchman-client')
PY = os.path.join(REPO, 'python', 'patchman_client', 'client.py')
GO = os.path.join(REPO, 'go', 'cmd', 'submit.go')


def _read(path):
with open(path, encoding='utf-8') as handle:
return handle.read()


def _service_values(key):
values = []
for line in _read(SERVICE).splitlines():
line = line.strip()
if line.startswith('#') or '=' not in line:
continue
name, _, value = line.partition('=')
if name.strip() == key:
values.append(value.strip())
return values


def _runtime_lock_dir():
runtime = _service_values('RuntimeDirectory')
assert runtime, 'patchman-client.service must set RuntimeDirectory'
return '/run/' + runtime[0]


def test_hardened_unit_carves_out_a_writable_runtime_dir():
# if the unit locks the filesystem down, it must provide a writable /run dir
protect = _service_values('ProtectSystem')
if protect and protect[0] == 'strict':
assert _service_values('RuntimeDirectory'), \
'ProtectSystem=strict needs RuntimeDirectory= or /run is read-only (#866)'
# a blanket ReadOnlyPaths=/ re-mounts the runtime dir read-only; forbid it
assert '/' not in _service_values('ReadOnlyPaths'), \
'ReadOnlyPaths=/ makes the lock dir read-only even with RuntimeDirectory (#866)'


def test_bash_client_lock_dir_matches_runtime_dir():
match = re.search(r'^lock_dir=(\S+)', _read(BASH), re.MULTILINE)
assert match, 'could not find lock_dir in bash client'
assert match.group(1) == _runtime_lock_dir()


def test_python_client_lock_dir_matches_runtime_dir():
match = re.search(r"^LOCK_DIR\s*=\s*'([^']+)'", _read(PY), re.MULTILINE)
assert match, 'could not find LOCK_DIR in python client'
assert match.group(1) == _runtime_lock_dir()


def test_go_client_lock_dir_matches_runtime_dir():
match = re.search(r'lockDir\s*:=\s*"([^"]+)"', _read(GO))
assert match, 'could not find lockDir in go client'
assert match.group(1) == _runtime_lock_dir()
Loading