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.
| @@ -595,12 +595,10 @@ static int SCP_Subsystem(WOLFSSHD_CONNECTION* conn, WOLFSSH* ssh, | |||
| if (wolfSSHD_AuthReducePermissionsUser(conn->auth, pPasswd->pw_uid, | |||
There was a problem hiding this comment.
🟡 [Medium] Add regression coverage for fail-closed subsystem paths
💡 SUGGEST test
The PR changes several privilege-drop failure paths from returning WS_FATAL_ERROR after a fallback attempt to calling exit(1), and it changes HandleConnection to retain SHELL_Subsystem return values. Existing tests cover wolfSSHD_AuthReducePermissionsUser() itself, but I did not find tests that exercise the subsystem call sites or verify that HandleConnection no longer discards shell/exec subsystem failures. Without a regression test around these changed paths, a future edit could accidentally reintroduce a return path into the connection handler after a failed per-connection privilege drop.
Suggestion:
| if (wolfSSHD_AuthReducePermissionsUser(conn->auth, pPasswd->pw_uid, | |
| Add a focused regression test using the existing `WOLFSSHD_UNIT_TEST` privilege-drop stubs. Run the subsystem failure case in a child process and assert that it exits non-zero, and add coverage that a `SHELL_Subsystem` failure is assigned into `ret` at the shell and exec call sites. |
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.