wolfsshd: fail closed when a per-connection privilege drop fails#1067
wolfsshd: fail closed when a per-connection privilege drop fails#1067yosuke-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 #1067
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: COMMENT
Findings: 1 total — 1 posted, 0 skipped
Posted findings
- [Medium] Add regression coverage for fail-closed subsystem paths —
apps/wolfsshd/wolfsshd.c:595-601
Review generated by Skoll.
301dfb9 to
8f502d4
Compare
wolfSSL-Fenrir-bot
left a comment
There was a problem hiding this comment.
Fenrir Automated Review — PR #1067
Scan targets checked: wolfssh-bugs, wolfssh-src
No new issues found in the changed files. ✅
wolfSSL-Fenrir-bot
left a comment
There was a problem hiding this comment.
Fenrir Automated Review — PR #1067
Error: OperationalError
|
Hi @aidangarske . could you please review this ? |
| #endif | ||
|
|
||
| #if defined(WOLFSSHD_UNIT_TEST) && !defined(_WIN32) | ||
| #ifdef WOLFSSHD_FAULT_INJECT |
There was a problem hiding this comment.
Is there any way we can do this test without adding fault injections into the src?
There was a problem hiding this comment.
Thanks, I dropped the in-source fault injection entirely. auth.c and include.am
are now back to master (no callbacks, no WOLFSSHD_FAULT_INJECT, no faultinj daemon
build).
The test now drives the stock wolfsshd binary with a small LD_PRELOAD interposer
(apps/wolfsshd/test/sshd_privdrop_preload.c) that returns EPERM from
setregid()/setreuid() only when WOLFSSHD_FAULT_PRIVDROP is set, forwarding the real
call otherwise. It's compiled on-demand by the test, Linux-only (SKIPs where
DYLD stripping applies), and never touches the shipped daemon or library — so we're
now testing the actual production binary, not a special build.
8f502d4 to
fb6a9c6
Compare
fb6a9c6 to
b3d1279
Compare
|
Hi @aidangarske , |
wolfsshd: fail closed when a per-connection privilege drop fails
Summary
Fixes a privilege-handling defect in
wolfsshdwhere a failed per-connectiondrop to the authenticated user's uid/gid could leave the connection handler
running at an elevated privilege level (root, or the sshd-daemon uid) instead
of terminating.
Addressed by f_5850.
Background
When servicing a channel request, the subsystem handlers
(
SHELL_Subsystem,SCP_Subsystem,SFTP_Subsystem) drop privileges to theauthenticated user via
wolfSSHD_AuthReducePermissionsUser(). On failure, theold code attempted a fallback to
wolfSSHD_AuthReducePermissions()and thendid
return WS_FATAL_ERROR:Two problems combined into a privilege-management hole:
The fallback is a no-op when privilege separation is off. With
UsePrivilegeSeparationset toWOLFSSHD_PRIV_OFF,wolfSSHD_AuthReducePermissions()performs no uid change and returnsWS_SUCCESS. So after a failed drop-to-user, the fallback "succeeds"without lowering anything.
HandleConnectiondiscarded theSHELL_Subsystemreturn value. TheWS_FATAL_ERRORwas never captured, soretstayedWS_SUCCESS. Thepost-switch error check consults
wolfSSH_get_error(ssh), which a kernelsetreuid()/setregid()failure does not set, sowolfSSH_shutdownand upto ten
wolfSSH_workeriterations would execute while the effective uid wasstill above the authenticated user.
Triggering the drop failure itself requires an environmental condition that
forces
setreuid/setregidto fail (e.g.RLIMIT_NPROCsaturation at thetarget uid, an LSM denial, or user-namespace mapping limits), so this is not
routinely reachable in default deployments — but when it does occur the handler
must fail closed.
Fix
Every per-connection privilege-drop failure path now calls
exit(1)directly instead of attempting the no-op fallback and returning. This is
strictly stronger than propagating an error into the connection handler:
the per-connection process terminates before any work runs at the wrong
privilege level. Applied consistently across
SHELL_Subsystem(the forkedchild branch and the parent branch, plus the
dup2/setgroups/chrootfailure paths),
SCP_Subsystem, andSFTP_Subsystem.HandleConnectionnow captures the return value at both subsystem callsites (
ret = SHELL_Subsystem(...)for the shell and exec sessions), so afailure is reflected in
retas well (defense in depth;exit(1)alreadymakes the path unreachable).
In the
SHELL_Subsystemparent branch theforkptychild has already beenspawned, so it is killed with
kill(childPid, SIGKILL)beforeexit(1)toguarantee deterministic teardown rather than relying on pty/pipe closure,
matching the existing unexpected-error handling later in the same function.
The daemon's own startup drop in
main()is unchanged: it already fails closedby setting
ret = WS_FATAL_ERROR, which prevents the listen/accept loop fromstarting.
Testing
wolfsshdbuilds cleanly with the project configuration.privilege-drop-failure paths are affected, and they now terminate the
per-connection process.