wolfsshd: base Match blocks on the global config and keep included ones - #1153
wolfsshd: base Match blocks on the global config and keep included ones#1153yosuke-wolfssl wants to merge 1 commit into
Conversation
There was a problem hiding this comment.
Pull request overview
Fixes wolfsshd Match inheritance and Include chaining so that (1) each Match block starts from global defaults rather than inheriting from the immediately preceding Match, and (2) included config files correctly advance the parse cursor, preventing later Match blocks from overwriting/dropping the include chain.
Changes:
- Add a
headpointer toWOLFSSHD_CONFIGand buildMatchnodes from the global config (head) instead of the current node. - Thread the parse cursor through
ConfigLoad()/HandleInclude()viaWOLFSSHD_CONFIG**soIncludebehaves like true textual inclusion and preserves appendedMatchchains. - Add regression tests covering non-inheritance across
Matchblocks and include-chain preservation; update auth comment to reflect new behavior.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| apps/wolfsshd/configuration.c | Reworks Match node creation and Include parsing to use global defaults and a shared cursor. |
| apps/wolfsshd/test/test_configuration.c | Adds focused regression tests and factors file-writing into a helper. |
| apps/wolfsshd/auth.c | Updates commentary in RequestAuthentication to match the new Match resolution semantics. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
a4ffea1 to
4f5de72
Compare
wolfSSL-Fenrir-bot
left a comment
There was a problem hiding this comment.
Fenrir Automated Review — PR #1153
Scan targets checked: wolfssh-bugs, wolfssh-src
Findings: 3
2 finding(s) posted as inline comments (see file-level comments below)
Low (1)
Global-only directives after an Include are silently swallowed by an included Match block
File: apps/wolfsshd/configuration.c:979
Function: HandleInclude
Category: Logic errors
HandleInclude() now advances the caller's cursor, so when an included file ends inside a Match block, later directives in the parent file are applied to that Match node. Port, HostKey, HostCertificate, Banner, and TrustedUserCAKeys are read only from the head node (wolfsshd.c:376-530, 2974) and are therefore dropped with no diagnostic.
Related known finding #8815 (similar but distinct): Both defects concern incorrect configuration state around Match nodes. However, the candidate is HandleInclude propagating an included file's parse cursor into its parent, while #8815 is HandleMatch copying the prior Match node; they have different faulting operations, root causes, and required patches.
Recommendation: Reject or log directives that only take effect on the global node when the parse cursor is a Match node.
This review was generated automatically by Fenrir. Findings are non-blocking.
| } | ||
|
|
||
| if (ret == WS_SUCCESS) { | ||
| heap = (*conf)->heap; |
There was a problem hiding this comment.
🔵 [Low] heap is set but never read on non-unix builds · Dead/unreachable code
Every read of the new heap local sits inside the __unix__/__APPLE__ wildcard block (lines 782-968). On other targets it is only written, producing -Wunused-but-set-variable; the #else arm voids postfix, prefix, and prefixLen but not heap, so -Werror builds break.
Fix: Add (void)heap; to the non-unix #else arm next to the existing (void)postfix; casts.
| newConf->strictModes = conf->strictModes; | ||
| newConf->head = conf->head; | ||
| } | ||
| else { |
There was a problem hiding this comment.
🔵 [Low] Include now exports Match scope to the caller, silently demoting later global directives · Privilege escalation in wolfsshd
Threading the parse cursor through ConfigLoad makes an included file that ends inside a Match block leave the cursor on that Match node, so every directive after the Include in the parent file is scoped to that block. Hardening lines such as PasswordAuthentication no, ForceCommand, ChrootDirectory and AuthorizedKeysFile then never reach the global node, and unmatched users fall back to the built-in defaults (passwordAuth/pubKeyAuth = 1, no chroot, no forced command) with no diagnostic.
Related known finding #8815 (similar but distinct): Both defects cause configuration directives to inherit an unintended Match scope. However, this finding is caused by HandleInclude/ConfigLoad exporting the parse cursor after an included file, while #8815 is caused by HandleMatch copying and chaining preceding Match nodes; they affect different operations and require separate patches.
Fix: Log a warning when an included file returns with the cursor on a Match node different from the one it was entered with.
Problem
HandleMatch()built eachMatchnode withwolfSSHD_ConfigCopy(*conf)— a copy of the preceding Match node rather than the global config. SincewolfSSHD_GetUserConf()returns the first matching node and stops, a user selected by a later block silently inherited every setting changed by earlier blocks that did not apply to them:A
staffuser other than alice resolved toForceCommand /bin/alice. This affected every copied field (forceCmd,chrootDir,authKeysFile,userCAKeysFile,permitRootLogin, …) and is usually fail-open.A second defect in the same linkage:
HandleMatch()linked the new node onto the parse cursor ((*conf)->next = newConf). AnIncluded file's Match nodes are appended to the list but do not move the caller's cursor, so a laterMatchin the parent overwrote the only pointer to them — silently dropping those blocks and leaking them.Fix (
apps/wolfsshd/configuration.c)headpointer added toWOLFSSHD_CONFIG, set inwolfSSHD_ConfigNew()and propagated bywolfSSHD_ConfigCopy().HandleMatch()now copies(*conf)->head, so each block carries the global defaults plus only its own overrides.HandleMatch()walks fromheadto the end of the list before linking, instead of assuming the parse cursor is the tail. Included Match blocks survive and are no longer leaked. Match scope is deliberately unchanged — a directive after anIncludestill belongs to the global config, which is what OpenSSH does.wolfSSHD_ConfigNew()now stores itsheapargument; the previous code discarded it, so allocations and frees could use different heaps.apps/wolfsshd/auth.c: theRequestAuthenticationcomment describing the old inheritance is now false — a user whose ownMatchnever setTrustedUserCAKeyskeeps the global value and is no longer rejected for certificate auth. The fail-closed branch for a genuinely different CA file is unchanged.Closes f_8815.
Tests
test_GetUserConfMatchNoInheritandtest_ConfigIncludeMatchChaininapps/wolfsshd/test/test_configuration.c. The latter also asserts that a directive placed after anIncluderesolves for an unmatched user, pinning the scope behavior.test_IncludeRecursionBound's inline file writing was factored into a sharedWriteConfigFile()helper.Verification
make check: 11 passed, 0 failed;test_configuration55 cases, 255 assertions, 0 failures.Matchscope cross-checked against OpenSSH 10.2p1 usingsshd -T.-Werrorclean across 6 configs, plus a non-unix (-U__unix__ -U__APPLE__)-Wall -Wextrapass; ASan + UBSan clean.