wolfsshd: open PID file with O_NOFOLLOW and a fixed mode#1074
wolfsshd: open PID file with O_NOFOLLOW and a fixed mode#1074yosuke-wolfssl wants to merge 1 commit into
Conversation
wolfSSL-Fenrir-bot
left a comment
There was a problem hiding this comment.
Fenrir Automated Review — PR #1074
Scan targets checked: wolfssh-bugs, wolfssh-src
Findings: 1
1 finding(s) posted as inline comments (see file-level comments below)
This review was generated automatically by Fenrir. Findings are non-blocking.
6053ffc to
1c2bf7a
Compare
1c2bf7a to
db65d64
Compare
wolfSSL-Fenrir-bot
left a comment
There was a problem hiding this comment.
Fenrir Automated Review — PR #1074
Scan targets checked: wolfssh-bugs, wolfssh-src
No new issues found in the changed files. ✅
aidangarske
left a comment
There was a problem hiding this comment.
🐺 Skoll Code Review
Overall recommendation: REQUEST_CHANGES
Findings: 1 total — 1 posted, 0 skipped
Posted findings
- [High] Existing regular PID files keep unsafe ownership or permissions —
apps/wolfsshd/configuration.c:1828-1845
Review generated by Skoll.
| /* No O_TRUNC (truncate only after the fstat check below) and | ||
| * O_NONBLOCK so a planted FIFO fails fast instead of stalling the | ||
| * open before that check runs. */ | ||
| fd = open(conf->pidFile, |
There was a problem hiding this comment.
🟠 [High] Existing regular PID files keep unsafe ownership or permissions
🚫 BLOCK bug
The new POSIX path relies on open(..., O_CREAT, 0644) to ensure the PID file is not writable by other users, but that mode is only applied when the file is newly created. If the PID path already exists as a single-link regular file, including an attacker-created file in the writable PID directory described by the PR or a stale 0666 file from a prior vulnerable run, the new fstat check accepts it and ftruncate rewrites it without normalizing permissions or rejecting unsafe ownership. That leaves the PID file writable by the existing owner or group/world despite the PR's explicit-mode hardening. The new test only covers creation of a fresh PID file, so this bypass is not exercised.
Recommendation: After fstat, reject files not owned by the daemon's effective uid/root and normalize the mode on accepted existing files before truncating, for example by checking st.st_uid and calling fchmod(fd, 0644). Add a regression test that pre-creates the PID path as a regular file with 0666 permissions, and preferably one owned by a different uid where feasible. Fix the existing-file path so the promised fixed permissions apply to reused PID files, or refuse unsafe existing files before truncation.
aidangarske
left a comment
There was a problem hiding this comment.
🐺 Skoll Code Review
Overall recommendation: REQUEST_CHANGES
Findings: 1 total — 1 posted, 0 skipped
Posted findings
- [High] Existing regular PID files keep unsafe ownership or permissions —
apps/wolfsshd/configuration.c:1828-1845
Review generated by Skoll.
| /* No O_TRUNC (truncate only after the fstat check below) and | ||
| * O_NONBLOCK so a planted FIFO fails fast instead of stalling the | ||
| * open before that check runs. */ | ||
| fd = open(conf->pidFile, |
There was a problem hiding this comment.
🟠 [High] Existing regular PID files keep unsafe ownership or permissions
🚫 BLOCK bug
The new POSIX path relies on open(..., O_CREAT, 0644) to ensure the PID file is not writable by other users, but that mode is only applied when the file is newly created. If the PID path already exists as a single-link regular file, including an attacker-created file in the writable PID directory described by the PR or a stale 0666 file from a prior vulnerable run, the new fstat check accepts it and ftruncate rewrites it without normalizing permissions or rejecting unsafe ownership. That leaves the PID file writable by the existing owner or group/world despite the PR's explicit-mode hardening. The new test only covers creation of a fresh PID file, so this bypass is not exercised.
Recommendation: After fstat, reject files not owned by the daemon's effective uid/root and normalize the mode on accepted existing files before truncating, for example by checking st.st_uid and calling fchmod(fd, 0644). Add a regression test that pre-creates the PID path as a regular file with 0666 permissions, and preferably one owned by a different uid where feasible. Fix the existing-file path so the promised fixed permissions apply to reused PID files, or refuse unsafe existing files before truncation.
wolfsshd: harden PID-file write against symlink, hard-link, and FIFO attacks
Summary
wolfSSHD_ConfigSavePIDwrote thePidFilewithWFOPEN(..., "wb")— plainfopen()on POSIX: noO_NOFOLLOW, no explicit mode. The daemon setsumask(0)during daemonization and calls this as root, before privilegedrop. A local user with write access to the
PidFiledirectory could:fopenfollows it and truncatesan arbitrary root-owned file, writing the daemon PID into it; and
umask(0), have the PID file created mode 0666 (world-writable).Exploitability is conditional (the default config leaves
PidFileunset and/var/runis root-owned), so impact is limited to non-defaultPidFilelocations, container/embedded deployments with a non-root-writable PID
directory, or first-install windows.
Addressed by f_5853.
Fix —
apps/wolfsshd/configuration.cThe POSIX path now opens without
O_TRUNC, validates the descriptor, thentruncates and writes:
Defenses, each mapping to a concrete attack:
O_NOFOLLOW(+lstatfallback where unavailable)0644modeumask(0)fstat→S_ISREG && st_nlink == 1O_NOFOLLOWdoes not cover)O_NONBLOCKon opensshdstartup before theS_ISREGcheckO_TRUNC;ftruncateonly after validationWFWRITE/WFCLOSEreturn checked;unlinkon failureOther notes:
ftruncate) only after the descriptor is proven asingle-link regular file, so nothing is clobbered when a target is refused.
_XOPEN_SOURCE/_GNU_SOURCEare defined before the system headers (asauth.cdoes) sofdopen/ftruncate/lstat/S_ISLNKare declared understrict
-std=builds.WFOPENpath, with the sameWFWRITE/WFCLOSEchecks andWREMOVE-on-failure.Supporting change —
wolfssh/port.hThe
WOLFSSH_O_NOFOLLOWportability macro (and its<fcntl.h>include) wasgated behind
(WOLFSSH_SFTP || WOLFSSH_SCP), so it was undefined in an--enable-sshd-only build. It is moved into aWOLFSSH_HAVE_SYMLINKblock witha header-wide
0catch-all, so SFTP, SCP, and SSHD share one definition.Tests —
apps/wolfsshd/test/test_configuration.cNew
test_ConfigSavePID(POSIX) drives the real config loader +wolfSSHD_ConfigSavePID:PidFilewritten with the daemon PID, not group/world writableeven under
umask(0);The symlink-refusal assertion is gated on
WOLFSSH_HAVE_SYMLINKso the test isalso correct under a
WOLFSSH_NO_SYMLINK_CHECKbuild.Scope note
This intentionally does not alter the daemon-wide
umask. The PID file isprotected by its explicit
0644mode, so changing the process umask (whichwould also affect SFTP/SCP client-requested upload modes) was left out to keep
the change scoped to the finding. This matches OpenSSH, which relies on explicit
modes plus a root-owned
PidFiledirectory and does no symlink/hard-link checksitself; operators should still place
PidFilein a root-owned directory.Verification
--enable-all,--enable-scp,--enable-sftp,--enable-scp --disable-sftp,--enable-sftp --disable-scp,and
--enable-sshd(no SFTP/SCP).mode, and hard-link assertions all fail without the fix; a standalone
check confirms
O_NONBLOCKis what prevents the FIFO open from blocking.codespellclean.Files