fix: SHELL_Subsystem returns without restoring raised privileges - #1126
Open
MarkAtwood wants to merge 1 commit into
Open
fix: SHELL_Subsystem returns without restoring raised privileges#1126MarkAtwood wants to merge 1 commit into
MarkAtwood wants to merge 1 commit into
Conversation
Contributor
There was a problem hiding this comment.
Pull request overview
Fixes a privilege-separation correctness issue in SHELL_Subsystem() (wolfsshd) where several early error returns could leave the per-connection handler running with temporarily raised privileges.
Changes:
- Drop raised privileges via
wolfSSHD_AuthReducePermissions(conn->auth)before returning onpipe()failures (stdout/stderr/stdin). - Drop raised privileges via
wolfSSHD_AuthReducePermissions(conn->auth)before returning onforkpty()failure.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
1423
to
+1427
| wolfSSH_Log(WS_LOG_ERROR, "[SSHD] Issue creating new forkpty"); | ||
| if (wolfSSHD_AuthReducePermissions(conn->auth) != WS_SUCCESS) { | ||
| wolfSSH_Log(WS_LOG_ERROR, "[SSHD] Error dropping permissions"); | ||
| exit(1); | ||
| } |
SHELL_Subsystem() raises privileges to look up user information, but the three pipe() failures and the forkpty() failure return WS_FATAL_ERROR without dropping them again. HandleConnection() then runs its teardown -- wolfSSH_shutdown(), up to ten wolfSSH_worker() iterations, and the socket drain -- still elevated when UsePrivilegeSeparation is yes or sandbox. Drop permissions before each of the four returns, reusing the wording the daemon already logs for a failed drop. The forkpty() path also returned with all six pipe descriptors still open. Close them there, as the pipe() failure paths already do, and reset the slots to -1 to keep the child-branch invariant that an entry is either a live descriptor or -1. Issue: F-6980
ejohnstown
force-pushed
the
fix/sshd-restore-priv-on-error
branch
from
July 28, 2026 22:24
6a8e20c to
290ef93
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Bug
SHELL_Subsystem()(apps/wolfsshd/wolfsshd.c) temporarily raises privileges viawolfSSHD_AuthRaisePermissions(conn->auth)to look up user information. Fourerror paths between that raise and the normal privilege drops return
WS_FATAL_ERRORwithout restoring privileges:pipe(stdoutPipe)failurepipe(stderrPipe)failurepipe(stdinPipe)failureforkpty()failureAn earlier revision of this description said the caller discards the return
value. That is wrong --
HandleConnection()does capture it(
ret = SHELL_Subsystem(...)). What actually happens is worse: it falls out ofthe session switch and runs its whole teardown --
wolfSSH_shutdown(), up toten
wolfSSH_worker()iterations with 100 ms sleeps,wolfSSH_free(), then ashutdown()/recv()drain loop on the socket. That is real protocol processingon peer-supplied packets, performed as root.
This only has effect when
UsePrivilegeSeparationisyesorsandbox(otherwise the raise is a no-op), and reaching it requires fd/process
exhaustion, so severity is low -- but the raise should always be paired with a
drop.
Fix
Restore privileges with
wolfSSHD_AuthReducePermissions(conn->auth)before eachof the four early returns, matching the existing
exit(1)-on-drop-failurehandling used on the normal child/parent paths.
wolfSSHD_AuthReducePermissions()is the right call here rather thanwolfSSHD_AuthReducePermissionsUser(): it restores the sshd account viasetegid/seteuidand is a no-op unless privilege separation is on, whichmatches the severity note above.
exit(1)on drop failure is safe -- on POSIXevery connection is a forked child, so it takes down only that connection's
process; on Windows the function body is inside
#ifndef _WIN32and alwaysreturns
WS_SUCCESS, so the branch never fires.Two further items from review are included:
The
forkpty()path leaked six pipe descriptors. When!ptyReq || forcedCmd, all three pipes are already open and none of the sixdescriptors were closed before the return. The three
pipe()failure pathsdo clean up what they opened; this one did not. Since the failure mode this
change exists to handle is fd/process exhaustion, leaking fds on the way out
is the wrong direction. They are now closed and the slots reset to
-1,keeping the invariant the child branch already follows that an entry is
either a live descriptor or
-1.Log wording. The new lines use the daemon's existing phrasing for this
event,
"[SSHD] Error lowering permissions level", rather than introducing athird spelling alongside that and
"[SSHD] Error setting user ID".Verification
--enable-all(incl. wolfsshd) against wolfSSL master on macOS and onUbuntu; compiles clean, no new warnings.
make check: 10 PASS / 1 SKIP / 0 FAIL.sshd_exec_test.sh,sshd_term_size_test.sh,sshd_large_sftp_test.sh,sshd_bad_sftp_test.sh,sshd_scp_fail.shandsshd_term_close_test.shPASSED,ssh_kex_algos.shSKIPPED, host keyownership gate PASSED.
sshd_term_close_test.shis intermittent in that environment. Measured over sixinterleaved iterations per tree on an idle host, unpatched master scored
2 pass / 4 fail and this branch 3 pass / 3 fail, so the flakiness is
pre-existing and not introduced here.
The four new error paths are not covered by any test. The existing
fault-injection harness (
apps/wolfsshd/test/sshd_privdrop_fail_test.shplussshd_privdrop_preload.c) cannot reach them: the interposer overridessetregid/setreuidwhilewolfSSHD_AuthReducePermissions()usessetegid/seteuid, and the test config setsUsePrivilegeSeparation no, whichmakes both the raise and the reduce no-ops. Extending it would need
pipe(),setegid()andseteuid()overrides plus asandboxcase, and is not donehere.
Reported by static analysis (Fenrir finding F-6980).