GetOpenSshKey() leaves no keyId for a key-type name it does not know
and returns WS_UNIMPLEMENTED_E itself, so IdentifyOpenSshKey() never
sees ID_UNKNOWN after a failure and passes that result straight on.
- tests/unit.c adds test_IdentifyOpenSshKeyUnknownName() for the
WS_UNIMPLEMENTED_E result on an unknown key-type name.
Issue: F-13330
wolfSSH_SFTP_LSTAT() moves on to the request ID check only once the
reply buffer is allocated, and clears its state when the allocation
fails, so the next STAT or LSTAT starts a new request.
- test LSTAT against a matched, misnumbered and unallocatable reply
Issue: F-17047
SFTP_CheckReqId() matches each reply header against the outstanding
request ID, advances the ID once on a match, and sets
WS_SFTP_BAD_REQ_ID on a mismatch. Every client response handler calls
it before acting on the reply, including the STATUS-only ones.
- DoName and GetHandle set WS_SFTP_BAD_REQ_TYPE on an unexpected
reply type and free their state
- SetSTAT, Close, Remove and RMDIR now check the reply ID
- correct the WS_SFTP_BAD_REQ_TYPE comment in error.h
- test each request against a matched, misnumbered and mistyped reply
Issue: F-10547, F-13909, F-10566
The v3.4.0 leg runs the KBI scenario in 168 to 188 seconds under
QEMU, and one run hit the 200 second limit just short of finishing.
Double the limit on all three scenarios to leave room as tests grow.
The example client prints the keyboard-interactive name, instruction
and prompts through ClientPrintSafe(), which prints a '.' in place of
each control character other than tab and newline, DEL, and each byte
0x80 and up, per RFC 4256 section 3.3 and RFC 4251 section 9.2.
- tests: auth case capturing the printed strings
Issue: F-15534
The password prompt reads stdin with fgets(), and a buffered stdin
keeps its own copy of the line in libc for the life of the process.
The clients make stdin unbuffered before any stdin I/O, so the typed
password lands only in the buffer ClientUserAuth() wipes.
- the wolfssh app, and the example client, sftp client and scp client
whether or not -P was given, as keyboard-interactive reads stdin too
- ClientUnbufferStdin() acts once per process and reports a failure
- scripts/sshclient.test sends the session data right after the
password, with no sleep
Issue: F-15669
ClientUserAuth() hands the library its static password buffer, and the
library sends from it after the callback returns. The clients now wipe
it, and the example's keyboard-interactive responses, as soon as
connect returns instead of holding them for the whole session.
- ClientClearPassword() in both common.c copies, called after connect
in the wolfssh app, the example client and the sftp client
- the password arm wipes before each prompt and on failure, so a retry
keeps no tail of a longer password
- the keyboard arm wipes its scratch copy after each response
- tests: regress and auth cases for the retry tail, the wipe and a
failed read
Issue: F-8846
wsUserAuth parses the peer certificate to print its FASC-N and UUID.
Free the DecodedCert before leaving that block, on success and on
failure alike.
Issue: F-13997
The echoserver and tpmcertserver userauth callbacks compare the stored
digest or password with wolfSSH_ConstantCompare(). The echoserver
callback now wipes its digest buffer on every return.
Issue: F-8858, F-12630
auth.c called the library-internal ConstantCompare(), which does not
resolve when the library is built with --disable-inline. Call
wolfSSH_ConstantCompare() instead.
Issue: F-7514
Userauth callbacks compare passwords and digests outside the library,
where the internal ConstantCompare() is hidden. Export a wrapper, and
add WS_CONSTANTCOMPARE(), which, like WS_FORCEZERO(), maps to
wolfCrypt's wc_ConstantCompare() when the wolfSSL build provides it
(v5.9.4 and later) and to the local loop otherwise.
- misc.c: build the local loop only as the fallback, with a volatile
accumulator
- internal.c: compare MACs and the CBC check block with the macro
- ssh.h: point the userauth callback documentation at the export
- api.c: test match, mismatch, length and argument handling
Issue: F-7513, F-7514
A root-run echoserver with -F now switches to an unprivileged user once
the listener is bound and the keys are loaded, before any peer is
served, so forwarded connects and binds no longer run as root. The new
-u option names the user; the default is nobody, with a warning. A
failed switch ends the server.
- Add DropPrivileges(): setgroups, setgid, setuid, then check root
cannot be regained
- Build it only for the standalone POSIX echoserver with WOLFSSH_FWD,
not for the tests that run echoserver_test() in-process
- Warn on stderr that -u is ignored in a build without it
- Document -u in the usage text and README.md
Issue: F-11935
With -F a peer's direct-tcpip open is set up only for a loopback
destination. The new -L option replaces that default with a list of
allowed host or host:port entries. Any other open is refused, which
the library reports as administratively prohibited.
- Check the destination in the WOLFSSH_FWD_LOCAL_SETUP action
- Document -L in the usage text and README.md
- openssh-interop.test checks a non-loopback destination and one left
out of -L are refused, and that host and host:port entries connect
Issue: F-11935
The forwarding callback connects and binds with the server's own
credentials, so it is registered only with the new -F option. Without
it the library turns down every forwarding request.
- Bind a remote forward for localhost or 127.0.0.1 to the loopback
address, not INADDR_ANY
- Refuse a remote forward to a privileged port or one over 65535
- Pass -F in fwd.test and fwd-bulk.test; document it in README.md
- openssh-interop.test checks a remote forward fails without -F, works
with it, and is refused by the port check for port 80
Issue: F-11935
The forwarded agent socket now lives in a mkdtemp() directory, and the
session's end runs the agent cleanup, which the library never calls on
a server. The relay addresses the agent channel by its own id and
drains each agent reply in full.
- Set agentChannelId from ssh->agent once the agent channel is open
- Drop agent replies when no local agent client is connected
- Close the accepted agent client in WOLFSSH_AGENT_LOCAL_CLEANUP and
remove the socket and its directory
- Print the socket path; openssh-interop.test forwards ssh-agent
through the echoserver, lists a reply larger than the relay buffer,
and checks the directory is private and removed
Issue: F-10553, F-11936, F-14396
SSH_FXP_REMOVE does not take directories and SSH_FXP_RENAME fails on an
existing newpath (draft-ietf-secsh-filexfer-02 6.5). The non-Windows
handlers check with an lstat first, oldpath before newpath, and REMOVE
unlinks. A regular file renames by linkat() and unlink(), which refuses
an existing name atomically; anything else checks then calls rename().
- configure: probe for linkat()
- port.h: WUNLINK on POSIX, WLINK as linkat() when configure finds it
- FatFs, Nucleus: only "/" and "/." stand in for the root
- regress: test both against files, directories, symlinks and a
read-only directory
Issue: F-15535, F-15536
The cppcheck step pipes through tee, so its exit code was lost and the
job passed with findings. Run the step under bash, which sets pipefail,
and upload the results file only when the step fails.
Restructure the two sites that left a redundant check in one build
configuration, and suppress a warning that only appears in a
configuration cppcheck invents.
- wolfsshd.c: set the -h host key file in the else of the cert store
conflict check instead of retesting ret.
- internal.c CompositeEccSign: run wc_ecc_sig_to_rs from an else shared
by the small-stack allocation check and the plain stack arrays.
- wolfsftp.c: suppress constStatement on WWRITE; cppcheck's -DWWRITE
configuration turns the arguments into a comma expression.
SFTP_GetHeader now takes a limit from its caller and refuses a declared
body over it, or one too short to hold the header, with WS_BUFFER_E.
Every client reply handler allocated a buffer of the peer-declared size
first, so one 9-byte header could make the client allocate 2 GiB.
- STATUS, ATTRS and HANDLE replies are held to WOLFSSH_MAX_SFTP_PACKET
- NAME keeps WOLFSSH_MAX_SFTP_NAME; DoName's own check moves into
the call
- a READ reply may also be as large as the caller's buffer plus its
length prefix
- SendWritePacket keeps an error already set instead of
WS_SFTP_BAD_HEADER
- regress test drives all eleven client calls at and past the bound
Issue: F-10598, F-13430, F-14433
The known_hosts search skips an entry whose host matches but whose key
type differs, so a host pinned with one type that presents another was
reported as an unknown host. The check now remembers that the host is
known with another type and says so before offering to add the new one,
as OpenSSH does.
- note the first other-type entry's key type and line in the loop
- print a warning with that entry, the host and the new fingerprint
before the add prompt; a later same-type entry still matches or
rejects as before
- reject a server key type that is not printable ASCII before it is
printed or stored
- regress.c: add TestKnownHostsOtherType
Issue: ZD-22634
The singlethreaded CI test was using macos-14 for reasons, instead of
using macos-latest. The test would always fail on macos-15, which was
latest at the time. Switch the test back to macos-latest.
ConfigLoad() reports a line too long for its buffer and fails the load.
A cut line used to take effect with whatever fitted, since what is left
of it is read as a line of its own and a leftover of two bytes or less
is skipped as blank. The buffer is 1024 bytes now, so a long path or
forced command still loads.
- one byte is read past a full buffer, so a line that ends exactly
there, at a newline or the end of the file, is not taken for a cut one
- the size is WOLFSSHD_MAX_LINE_SIZE in configuration.h, shared with
the test
- test_configuration covers a long line, lines that exactly fill the
buffer, and a line cut to leave a blank-looking remainder
Issue: ZD-22623
A ForceCommand other than a bare "internal-sftp" now denies the SFTP
subsystem, and any ForceCommand denies SCP, as sshd_config(5) describes
it: a forced command applies to shell, command, and subsystem execution.
The session dispatch checks the configured command alongside a
certificate's, so either one on its own confines the session.
- the subsystem arm and both SCP arms consult the configured
ForceCommand, not only a certificate's force-command
- "internal-sftp" followed by options denies SFTP, since no option is
applied, while the shell still refuses it as "internal-sftp"
- every site testing for "internal-sftp" now shares one predicate
- root's transfer refusal no longer points at ForceCommand internal-sftp,
which does not permit SCP
- tests require a ForceCommand to deny SFTP and SCP, read the daemon's
decisions from its log, and pair a configured internal-sftp with a cert
Issue: ZD-22623
Issue: F-10597
An unresolved user took the global methods, so a name a Match User
block covers advertised a different list depending on whether the
account existed. DefaultUserAuthTypes() now composes the blocks that
select on the name alone, with no group list, when the account does
not exist.
- a Match Group block still cannot apply to a name with no account, so
it fails closed to the unmatched methods
- any other failure to resolve the user, or to compose its blocks,
advertises no methods rather than the global ones, so a Match block
that disabled a method is never skipped
- test_configuration stubs the account lookup and covers a lookup
error, an unreadable group list and a resolved account as well as the
Match User and Match Group blocks
Issue: ZD-22617
DefaultUserAuthTypes() resolved the requested user before any
authentication, so a username with no local account produced an empty
"methods that can continue" name-list. It now falls back to the global
configuration's methods when the user does not resolve, so the
USERAUTH_FAILURE reply no longer says which accounts exist.
- an unresolved user still cannot authenticate, since the same failed
lookup rejects every method it tries
- test_configuration covers the advertisement for an unknown user,
comparing against the global mask so it tracks the configuration
Issue: ZD-22617
STAT, LSTAT, OPEN, REMOVE, RENAME and OPENDIR take the status for a
failed call from errno, as OpenSSH's sftp-server does: ENOENT and
ENOTDIR answer SSH_FX_NO_SUCH_FILE, EACCES and EPERM
SSH_FX_PERMISSION_DENIED, anything else SSH_FX_FAILURE. Only the
default POSIX file system sets errno; other ports keep their old status.
- OPENDIR on POSIX no longer reports every failure as NO_SUCH_FILE
Issue: F-16081
RecvOpenDir() allocates the directory node, its name and the HANDLE
reply before linking the node into dirList, so an allocation failure
leaves no handle the peer was never told about. Both the POSIX and the
Windows branch.
Issue: F-10551
RecvReadDir() puts at most WOLFSSH_MAX_SFTP_READDIR entries (default
100) in one NAME reply and keeps the handle's place, so a large
directory takes several requests. The readdir helpers return WS_EOF at
the end of the directory; any other failure, an allocation included,
answers SSH_FX_FAILURE instead of EOF or a short listing.
- free the entry list on every path
- the POSIX helper tells a readdir() error from the end by errno
- an entry whose full path does not fit is listed without attributes,
and on Windows one whose name does not convert is skipped
- a SFTP_Name_readdir hook failure other than WS_MEMORY_E ends the
listing; WS_NEXT_ERROR still marks the last entry
- WOLFSSH_MAX_SFTP_READDIR below 1 is a build error
Issue: F-10552, F-13429
wolfSSH_SFTP_read() answers a request type it has no handler for,
SSH_FXP_EXTENDED among them, with SSH_FX_OP_UNSUPPORTED rather than
the generic SSH_FX_FAILURE (draft-ietf-secsh-filexfer-02 Section 7).
Issue: F-8840
RFC 4253 Section 4.2 bars the minus sign from softwareversion.
ValidateProtoId now scans the body up to the first space and rejects a
'-' there; the comments after the space may still carry one.
- document the rule on wolfSSH_CTX_SetSshProtoIdStr() in ssh.h
- unit vectors for a dash in softwareversion and in the comments
Issue: F-16082
The password backends check the password as a C string, so "\0junk"
was checked as "" while its nonzero length passed the
PermitEmptyPasswords check. RequestAuthentication now refuses any
password with a NUL before the policy check and the backends.
- test_RequestAuth_embeddedNulPasswordRejected puts the NUL first,
last and mid-password, and checks the fake crypt still runs
Issue: F-16096
The -p option of wolfssh, client, wolfscp, wolfsftp and echoserver now
goes through a strtol check, so a value that is not wholly a number in
range exits with an error instead of wrapping into a different port.
- apps/wolfssh: ClientParsePort(), shared with ClientParseDestination(),
which also rejects an empty "ssh://host:" port instead of defaulting
- examples: a ClientParsePort() in examples/client/common.c; port 0 is
rejected in every build, not only with a main driver
- echoserver: accepts 0 to 65535, keeping 0 for the test harness
- add TestClientParsePort and an empty URI port case to regress.test
Issue: F-14763, F-14767, F-14768, F-12552
It was the one channel sender that read ssh->defaultPeerChannelId
without a NULL check. It now returns WS_BAD_ARGUMENT for a NULL ssh and
logs entry and exit like its siblings.
- add TestTerminalResizeNullSsh to regress.test
Issue: F-14769
SHELL_Subsystem copied pw_shell into an 80-byte buffer before execv(),
so a longer path ran whatever its first 79 bytes named. The child now
passes pw_shell to execv() directly, and builds the login argv[0] from
its basename so a long path no longer falls back to "-sh".
- add sshd_long_shell_test.sh, which logs in as a throwaway user whose
shell path is longer than 80 bytes and checks the shell's $0
Issue: F-14757
Where open() cannot refuse a final-component symlink, no pre-check can
stop a link swapped in before the open, and the fstat gate accepts a
root-owned target. wolfSSHD_ConfigSavePID now writes no PID file there
instead of relying on a racy lstat.
- test_ConfigSavePID gets a variant for that build that expects no file
Issue: F-14764
DoKexDhReply() and SendKexDhReply() wipe kexHash after wc_HashFree().
The last input to the exchange hash is mpint(K). The software hashes
already clear the context, but a hardware hash port can leave that
block in the handshake until NEWKEYS.
Issue: F-16110
GenerateKey() wipes its wc_HashAlg after wc_HashFree(). The software
hashes already clear it, but a hardware hash port can leave the last
input block, the derived key, in the stack context.
Issue: F-15670
DoReceive() wipes a packet that fails to decrypt, authenticate or pass
the length checks before it returns. Under CBC the wipe waits until
DiscardPacket() has run its MAC, then covers a fixed MAX_PACKET_SZ, so
it adds nothing that depends on the decrypted length.
- BundlePacket() wipes the aborted packet it rewinds over, for every
sender
- tests: reject and discard paths check the packet is gone; the
BundlePacket() failure test checks the output buffer tail
Issue: F-14437, F-14438
The channel read paths wipe the bytes they hand to the application, and
GrowBuffer() wipes the stale copy its in-place compaction leaves above
the new length. _UpdateChannelWindow() compacts through GrowBuffer()
instead of its own WMEMMOVE, so it gets the same wipe.
- _BufferTake() serves wolfSSH_stream_read(), _ChannelRead() and
_ChannelReadExt()
- tests: short reads, a compacting read, a drained stderr chunk in the
inline buffer, and GrowBuffer() compaction
Issue: F-11677, F-8848, F-13438, F-10593, F-16109
A client refuses shell, exec, subsystem and pty-req channel requests
and direct-tcpip opens before any callback runs (RFC 4254 6.2, 6.5,
7.2). CHANNEL_OPEN_CONFIRMATION and CHANNEL_OPEN_FAILURE are a
protocol error unless the channel's own open is still outstanding.
- SshInit() installs the default WS_TermResize for servers only.
- Drop the Windows clients' raw-HANDLE resize context; nothing uses it.
- Move the channel request builders out of the server-only test block.
Issue: F-10582, F-13923, F-15533, F-15572, F-14434
The POSIX StartSSHD() now returns EXIT_FAILURE when ret holds an error,
and main() passes that on. A config check (-t) of a bad config, a
failed host key load, or a bad -p now exit 1 instead of 0.
- set ret when the accept loop cannot allocate a connection, so that
stop also exits non-zero
- PendingConnection() returns negative on a select() error other than
EINTR, and the accept loop sets ret from it instead of quit
- log "Closing down wolfSSHD" whenever the accept loop exits, not
only on quit, so the error exits still log it
- add sshd_exit_status_test.sh, which checks -D -t exit codes for a
valid config and three startup failures
Issue: F-16053
StartSSHD() no longer stores the NewConnection() result in ret, the
accept loop's condition. A failed fork() or CreateThread() now drops
that one connection, and the daemon goes on listening.
- add sshd_fork_fail_test.sh with an LD_PRELOAD shim that fails the
first connection's fork() with EAGAIN
- run it from run_all_sshd_tests.sh on leased port +7
Issue: F-16052
CheckAlgoList() skips unknown names, but the setters keep the caller's
string and SendKexInit() sends it verbatim. Every name in the list now
has to be at most 64 bytes of printable US-ASCII with no space and at
most one '@', not first or last, per RFC 4251 Section 6 and RFC 4250
Section 4.6, or the setter returns WS_INVALID_ALGO_ID.
- internal.h: add WOLFSSH_MAX_ALGO_NAME_SZ for the 64-byte cap
- api.c: cover the 64/65-byte bound, space, tab, DEL, high bytes, the
at-sign count and an at-sign at either end
- ssh.h: state the name format in the setter contract
Issue: F-15529
Classic DH e and f are mpints (RFC 4251 section 5), so a value with
the top bit set and no leading zero is negative. KeyAgreeDh_client()
and KeyAgreeDh_server() reject one before the range check, and the key
exchange ends with the usual KEY_EXCHANGE_FAILED disconnect.
- add test_KeyAgreeDh_rejectsNegativePeer() for both sides
- pad e in test_DhGexGroup16KeyAgree() the way SendKexDhInit() does
Issue: F-15532
The host key parsers compare the format identifier at the head of the
key blob (RFC 4253 section 6.6) against the expected name and return
WS_INVALID_ALGO_ID on a mismatch, as ParseECCPubKey() already does.
RSA blobs must say "ssh-rsa", Ed25519 "ssh-ed25519", and ML-DSA the
negotiated algorithm name.
- add GetPubKeyFormat() and use it in place of GetSkip()
- add the wolfSSH_TestParseMlDsaPubKey() hook
- test a mismatched name for each key type
Issue: F-15666
ParseRSAPubKey() and ParseRSAPubKeyCert() run the server's host key
through CheckRsaKeyBits(), so a client rejects an RSA host key under
WOLFSSH_RSA_MIN_KEY_BITS the same way a server rejects a short user
auth key. The failure ends the key exchange in DoKexDhReply().
- move CheckRsaKeyBits() above the host key parsers
- ParseRSAPubKey() maps only the decode failure to WS_RSA_E, so a
parse error or a short key keeps its own code
- test 1024 and 2047 bit host keys in test_ParseRSAPubKey()
Issue: F-14687
Build wolfSSH with a C++ compiler as CC and run make check, so a change
that does not compile as C++ fails CI. wolfSSL is built as C.
- g++ and clang++ on Ubuntu, Apple clang++ on macOS
- default and --enable-all configurations, all with -Werror
- clang++ also gets -Wno-deprecated for its .c-as-C++ warning
The four format strings are used only through WLOG(), so they now sit
under the same DEBUG_WOLFSSH/WOLFSSH_SSHD condition that compiles it in.
A namespace-scope const is internal in C++, and clang++ rejected the
unused arrays with -Wunused-const-variable under -Werror.
Run the wolfSSL link probe under AC_CACHE_CHECK with AC_CHECK_LIB's
cache name, so a preset ac_cv_lib_wolfssl_wolfCrypt_Init=yes skips the
link test. The mingw cert-store CI job relies on this, as it has no
cross-built wolfSSL to link against.