Skip to content

wolfsshd: bound the shell child reap and flush the held backlog - #1276

Open
yosuke-wolfssl wants to merge 1 commit into
wolfSSL:masterfrom
yosuke-wolfssl:fix/wolfsshd-loop
Open

yosuke-wolfssl wants to merge 1 commit into
wolfSSL:masterfrom
yosuke-wolfssl:fix/wolfsshd-loop

Conversation

@yosuke-wolfssl

Copy link
Copy Markdown
Contributor

Problem

Follow-up to #1217. Two bugs on the path from a break in POSIX SHELL_Subsystem() to its end:

  • Pinned connection process. The post-loop reap was a blocking waitpid(). When a client closes its channel while wolfsshd holds output, the loop sends SIGINT and breaks. A command that ignores SIGINT, including any interactive shell on a pty, keeps the connection process, its socket and the child alive until the client drops the whole transport. Reproduced with paramiko (exec_command or invoke_shell, stop reading, channel.close()): the command is still running after 10 s.
  • Lost output. A break can leave unsent output in shellBuffer, and the post-loop pipe drain then read()s over it.

Fix (apps/wolfsshd/wolfsshd.c)

  • Pty master closed before the reap, so the shell gets a hangup and exits on its own.
  • Bounded reap: waitpid(WNOHANG) for WOLFSSHD_CHILD_REAP_TRIES x WOLFSSHD_CHILD_REAP_WAIT_US (0.5 s by default), then SIGKILL and a blocking wait. A normal exit is reaped on the first poll. A failed kill() is logged and ends the wait.
  • Held output flushed before the drain with SHELL_FlushOut(). A SHELL_SEND_NEVER backlog is dropped, and the drain is skipped whenever the backlog is not sent, so the stream never has a gap. The channel close drops its backlog; the flush covers the select() and child-stdin write breaks.

Tests

New apps/wolfsshd/test/sshd_channel_close_test.sh, using paramiko (the OpenSSH client never closes a channel early):

Case Asserts
exec command trapping INT and HUP gone within 5 s (the SIGKILL fallback)
interactive shell on a pty gone within 5 s, through its HUP/EXIT trap rather than SIGKILL
exit 3, with and without a pty client receives exit status 3

It skips (77) without python3 or paramiko, on a non-loopback host, or when paramiko cannot connect. sshd-test.yml and code-coverage.yml install python3-paramiko from apt, because the suite runs as root and cannot see a per-user pip install.

Verification

  • ubuntu:24.04 with sshd-test.yml's configure lines, suite run with sudo as a sudoer: 31 passed, 3 skipped (the kex debug test, the UPN negative on FPKI builds, ML-DSA); make check 13/13.
  • On master the new test fails: exec and pty commands are still running after 5 s. With the bounded reap but without the pty close, the pty case fails.
  • ASan + UBSan with leak detection: clean. gcc-13 -Werror across 6 configs: clean.

Not in this PR

  • A child killed by a signal is reported as exit-status 0 (WEXITSTATUS without WIFEXITED). Fixing that changes what goes on the wire, so it is separate.
  • Closing the channel of an idle pty shell still leaves the shell running until the transport drops: nothing is held, so the loop never breaks.
  • Resending stderr on the list-head channel is a separate PR.

- The pty master is closed before the reap, so the shell gets
  SIGHUP and can exit on its own.
- The post-loop reap polls waitpid() with WNOHANG for
  WOLFSSHD_CHILD_REAP_TRIES x WOLFSSHD_CHILD_REAP_WAIT_US, then
  sends SIGKILL and waits. A failed kill() is logged with its
  errno and ends the wait.
- Output held in the shell backlog when the loop breaks is sent
  with SHELL_FlushOut() before the pipe drain reads into
  shellBuffer. A SHELL_SEND_NEVER backlog is dropped unsent, and
  the drain is skipped when the backlog is dropped or its send
  fails.
- sshd_channel_close_test.sh closes a session channel with
  paramiko while wolfsshd holds output, for an exec command that
  traps SIGINT and SIGHUP and for an interactive shell on a pty,
  and checks each command is gone within 5s and that the shell
  left through its HUP/EXIT trap rather than SIGKILL. It also
  checks that an exit status of 3 reaches the client with and
  without a pty. It exits 77 when python3 or paramiko is missing,
  the host is not a loopback address, or paramiko cannot connect.
- run_all_sshd_tests.sh runs it after sshd_stdin_stall_test.sh.
- sshd-test.yml and code-coverage.yml install python3-paramiko
  before the wolfSSHd tests.
@yosuke-wolfssl yosuke-wolfssl self-assigned this Sep 28, 2026
Copilot AI lite review requested due to automatic review settings September 28, 2026 02:09

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Process descendants may survive cleanup, and the ESRCH race can leave children unreaped.

Review effort: Lite
Findings: 1 High severity

Open (1)
What changed in this PR

This PR bounds wolfsshd shell cleanup after channel closure and preserves pending output.

Changes:

  • Adds PTY closure, bounded reaping, and forced termination.
  • Flushes held shell output before pipe draining.
  • Adds Paramiko regression tests and CI dependencies.
File Description
apps/​wolfsshd/​wolfsshd.c Implements bounded reaping and backlog flushing.
apps/​wolfsshd/​test/​sshd_channel_close_test.sh Tests channel-close cleanup and exit statuses.
apps/​wolfsshd/​test/​run_all_sshd_tests.sh Registers the new test.
.github/​workflows/​sshd-test.yml Installs Paramiko for SSHD tests.
.github/​workflows/​code-coverage.yml Installs Paramiko for coverage runs.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread apps/wolfsshd/wolfsshd.c

@wolfSSL-Fenrir-bot wolfSSL-Fenrir-bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fenrir Automated Review — PR #1276

Scan targets checked: wolfssh-src, wolfssh-bugs
Coverage: 1 of 1 in-scope changed file(s) opened by the reviewer

Fenrir result: Approved ✅

No new issues found in the changed files.

Advisory only — this automated result does not count as a GitHub approval.

Review tier: Lite

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants