Commit Graph

1606 Commits (399c83b1b6ea17f58ebc0638b82da9e6a6c222be)

Author SHA1 Message Date
John Safranek 65802d438c internal: commit a session only once accepted
A shell, exec or subsystem request changes the channel only once the
callback accepts it. The session type and command are set for the
callback to read and put back if it refuses, and CLIENT_DONE follows
acceptance alone, so wolfSSH_accept() stays where it is rather than
reporting a session it answered CHANNEL_FAILURE as established.

- DoChannelRequestSession() carries the three arms, which differed only
  in the type and the callback consulted
- a refusal puts the type and command back, so a grant an earlier
  request won still stands
- FreeChannelCommand() wipes and releases a command line for both
  ChannelDelete() and the refusal path
- unit.c drives a refused shell, exec and subsystem request through
  DoChannelRequest() and checks nothing was committed, and that a
  refusal after a grant puts the earlier command back whole
- regress.c checks accept() stays at ACCEPT_SERVER_CHANNEL_ACCEPT_SENT
  on a refused shell, and that the sftp gate and both diverts ask for
  the grant alone

Issue: F-8852
2026-09-14 14:48:15 -07:00
Yosuke Shimizu a14e6d0f3c ssh, apps, examples: take the worker's status from its return
- wolfSSH_OutputPending() moves from wolfssh/internal.h to
  wolfssh/ssh.h as a WOLFSSH_API taking a const WOLFSSH*, defined
  in src/ssh.c beside the other public calls.
- wolfSSH_worker() puts the send's code in the return in place of
  an event when the flush fails outright, and gates its flush on
  wolfSSH_OutputPending(). wolfssh/ssh.h states both.
- Both wolfsshd shell loops, both echoservers and
  ReceiveScpMessage() dispatch on wolfSSH_worker()'s return, and
  call wolfSSH_get_error() only to tell a transient failure from a
  terminal one. The POSIX wolfsshd loop sets wantWrite from
  wolfSSH_OutputPending().
- Five worker tests expect the send's code where they expected the
  event, and tests/testsuite.c calls wolfSSH_OutputPending().
2026-09-14 10:40:48 -07:00
Yosuke Shimizu 5ea9b736de scp: drain extended data in the SCP read path
- ScpStreamRead() calls _DumpExtendedData() and reads again when
  wolfSSH_stream_read() returns WS_EXTDATA, reporting the drain's
  own failure when it has one.
- ReceiveScpConfirmation() drops its WS_EXTDATA arm and reports a
  negative read directly.
- ReceiveScpMessage() returns _DumpExtendedData()'s failure in place
  of discarding it.
2026-09-14 10:40:48 -07:00
John Safranek 2a15157cf6 scp, apps: correct three ssh->error reads
ReceiveScpConfirmation() takes a WS_EXTDATA off ScpStreamRead()'s
return, the rule ssh.h now states for reading an event.
FlushQueuedSend() samples the owed write with the session still
locked, since the peer reader thread writes ssh->error too, and
reports a send that failed there over the event it rode in on.

- A pass that delivers stderr while its flush short-writes returns
  WS_EXTDATA with WS_WANT_WRITE in ssh->error, so the confirmation
  read aborted the transfer with the stderr undrained.
- wolfSSH_stream_read() on the reader thread clears ssh->error with
  this thread's packet still queued, which ended the flush early and
  reported it a success.
- An event return with a hard send error latched collapsed to
  WS_SUCCESS, telling the caller a queued packet reached a socket
  that was gone.
2026-09-14 10:40:48 -07:00
Yosuke Shimizu 21deb724fe scp, test.h, apps, examples: wait to write and route on the return
- tcp_select_write() joins tcp_select(), with WS_SELECT_SEND_READY
  at the end of the enum. tcp_select() passes NULL for writefds and
  cannot wait on the write side.
- The Windows wolfsshd window-change drain and sftp_worker()'s
  handshake flush retry wait on it. The drain records a give-up in
  ret, and sftp_worker() breaks only on WS_SELECT_ERROR_READY.
- The echoserver, Espressif and both wolfsshd shell loops, and
  ReceiveScpMessage(), take the event from wolfSSH_worker()'s
  return when it carries one, in place of reading ssh->error alone.
  The two echoservers cover WS_CHAN_RXD, WS_REKEYING,
  WS_CHANNEL_CLOSED and WS_EOF; the wolfsshd loops and
  ReceiveScpMessage() cover the ones they have arms for.
- The POSIX wolfsshd loop takes an owed write into wantWrite before
  the event overwrites rc, and its WS_WANT_WRITE arm runs the
  channel drain below in place of skipping it.
- FlushQueuedSend() folds the receive's own statuses into
  WS_SUCCESS inside its loop and keeps flushing while
  wolfSSH_get_error() reports WS_WANT_WRITE within the deadline, in
  place of looping on the worker's return. It reports
  WS_WANT_WRITE when the deadline leaves the packet queued.
2026-09-14 10:40:48 -07:00
Yosuke Shimizu 586b697b18 ssh, internal: always flush the worker's output
- wolfSSH_worker() calls wolfSSH_SendPacket() whenever
  ssh->outputBuffer holds bytes and the session is not
  disconnected. ssh->error keeps the receive's code when the
  receive failed, and the close's when a WS_CHANNEL_CLOSED pass
  hard-failed its flush; WS_REKEYING is withheld on a failed
  flush. Drops the second DoReceive(), the WOLFSSH_TEST_BLOCK
  fork and the separate WS_CHANNEL_CLOSED flush.
- BundlePacket() resets ssh->outputBuffer.length to
  ssh->packetStartIdx when the framing fails. wolfSSH_shutdown()
  reports WS_WANT_WRITE when its close-read leaves output queued,
  and the send's own error in place of it when that send failed.
  SendPacketFlush() records its code in ssh->error on every
  transport failure path, and wolfSSH_TriggerKeyExchange() writes
  it only when SendKexInit() fails.
- portfwd, client and scpclient accept WS_WANT_WRITE from
  wolfSSH_shutdown(); in scpclient the close-message drain runs
  on it.
- wolfssh/ssh.h drops WS_WINDOW_FULL from wolfSSH_worker() and says
  to read the return and wolfSSH_get_error() as independent channels
  on every pass.
- Twenty unit tests and the extended TestWorkerReportsDisconnect
  cover what ret and ssh->error hold after a receive, send, buffer,
  callback or framing failure.
2026-09-14 10:40:48 -07:00
John Safranek ed633eac0c wolfSSH_get_fd: return socket sentinel on NULL
- Return -1 on both (Windows build and not), the sentinel wolfSSH_new()
  uses for rfd/wfd.
2026-09-10 18:03:59 -07:00
John Safranek ae81d4d857 ssh: divert only into a granted session
wolfSSH_accept() hands a session to the built-in SCP or SFTP server
only when the request naming it was answered CHANNEL_SUCCESS. A
callback that refuses one sends CHANNEL_FAILURE, yet sessionType and
command are recorded ahead of that answer and stay set, so the diverts
read a refused session as a served one.

- gate both diverts on channel->sessionGranted, as
  wolfSSH_SFTP_accept() already gates the app-channels path
- the SCP divert tests the channel list itself, having no command
  lookup ahead of it to do that
- cover each divert with a refused request and a granted control
2026-09-10 17:14:23 -07:00
John Safranek 3437d6f2cc internal: drop an always-true guard
DoChannelRequest() returns early when the header parse fails, so the
ret == WS_SUCCESS test that followed it could never be false. The
channel lookup moves into the else, which is the only way the function
reaches it.

Issue: F-11657
2026-09-10 17:14:23 -07:00
John Safranek 0a9b44a962 internal: scrub the channel command on free
The peer's exec or subsystem command line is wiped ahead of both frees
that release it, ChannelDelete() and the GetStringAlloc() that replaces
it on a repeat request, the way ChannelDelete() already wipes the
decrypted inputBuffer just above. A command line can carry a password
or a token among its arguments.

- ScrubChannelCommand() leaves the free to GetStringAlloc(), so a parse
  that fails behind it holds no dangling pointer
- cover both wipes with the retain-on-free allocator, the replacement
  through wolfSSH_TestDoChannelRequest()
- release the test's hand-built channel on a setup failure

Issue: F-8850
2026-09-10 17:14:23 -07:00
John Safranek ff1f82f547 internal: act on a session request only if parsed
DoChannelRequest() records the session type and asks the exec and
subsystem callbacks whether to grant a session only when the command
string parsed. A failed parse is refused on ret alone, and
channel->command still holds an earlier request's value rather than the
one being answered.

- cover a command length header running past the end of the packet, on
  exec and on subsystem

Issue: F-11674
2026-09-10 17:14:23 -07:00
John Safranek 9c8caea8b5 ssh: expose the session command length
An application vetting an exec or subsystem request in its channel
request callback is handed the command as a C string, which stops at an
embedded NUL. wolfSSH_ChannelGetSessionCommandSz() and
wolfSSH_GetSessionCommandSz() report the parsed wire length, so a
callback can match a name whole the way DoChannelRequest() does.

- both accessors report 0 for a NULL channel or session
- wolfSSH_GetSessionCommand() defers to the channel accessor
- the sftp divert in wolfSSH_accept() asks the accessor for the length
- correct the trace name in wolfSSH_ChannelGetSessionCommand()
- cover a callback seeing "sftp\0evil" through exec and subsystem
2026-09-10 17:14:23 -07:00
John Safranek 3516fb00a0 ssh, sftp: match the sftp subsystem name exactly
The built-in SFTP server takes a session only when the subsystem name
is sftp, matched whole. DoChannelRequest() keeps the parsed length in
channel->commandSz, so neither wolfSSH_SFTP_accept()'s grant gate nor
wolfSSH_accept()'s divert serves "sftpx" or "sftp\0evil".

- cover a granted name longer than sftp, one of its length, and one
  running past an embedded NUL
- cover the divert with those three names and a control that diverts
- exec keeps its command length too

Issue: F-11665
2026-09-10 17:14:23 -07:00
John Safranek 7adf524f6f sftp: require a granted subsystem to serve
wolfSSH_SFTP_accept() serves an application-driven session only on a
channel whose subsystem request was answered CHANNEL_SUCCESS.
DoChannelRequest() records the session type and command before it
decides, and leaves both set on a refusal, so they cannot say by
themselves whether anything was granted.

- add channel->sessionGranted, set from the answer a shell, exec or
  subsystem request gets rather than from the request arriving
- look the channel up again before recording it: a callback may close
  its own channel, and wolfSSH_ChannelFree() frees it
- log a request's strings where they are known good: once the parse
  has succeeded, and ahead of a callback that may free the channel
- gate the app-channels path on that flag alongside the session type
  and the command
- cover a refusal from both sides, no callback registered and a
  callback that rejects, and a callback that frees its channel
2026-09-10 17:14:23 -07:00
John Safranek 742f327ed4 sftp: serve app-channels only on a granted sftp
In application-driven mode wolfSSH_accept() parks at userauth, so the
sftp test its divert applies never runs. wolfSSH_SFTP_accept() applies
it itself: the session channel must be a subsystem the application's
callback granted sftp on, or the call returns WS_INVALID_STATE_E and
leaves the wire alone without recording an error.

- gate the app-channels branch on wolfSSH_GetSessionType() and
  wolfSSH_GetSessionCommand(), the same test accept() makes
- ask for that grant in every accept state: below the user-auth stop
  accept() returns with no channel open, and past the stop there is no
  accept() left that could have checked anything
- say in ssh.h that the mode serves SFTP through that grant and never
  reaches the SCP entry point
- regress.c refuses the call with no channel, ahead of accept(), on a
  granted shell and on an established one, and serves an INIT on a
  granted sftp subsystem
2026-09-10 17:14:23 -07:00
John Safranek b723519312 ssh: add opt-in application-driven channels
A server that wants to own its channels had no way to get them: accept()
ran the session state machine to the end, and a shell, exec or subsystem
request with no callback registered was granted regardless.

- add wolfSSH_CTX_SetAppChannels() and wolfSSH_SetAppChannels(), off by
  default, a byte on the context copied into the session
- on, accept() returns once the user is authenticated, and a session
  request with no callback behind it is refused: nothing is left to serve
- keep the stop state out of the pending-send advance, so a re-entry with
  queued output cannot step over where this call is meant to stop
- stop early only while the session is short of that state, so turning the
  mode on afterward cannot leave the loop hunting a state it went past
- teach wolfSSH_SFTP_accept() that the mode parks accept() short of an
  established session, so it stops redoing the handshake on every poll
2026-09-10 17:14:23 -07:00
Yosuke Shimizu c039486e52 scp: return directory entries from the Zephyr entry walk
- The WOLFSSH_ZEPHYR branch of FindNextDirEntry() loops while the
  entry name is "." or "..", matching the POSIX and Windows
  branches, in place of while (1).
- tests/api.c gains test_wolfSSH_SCP_SendRecursiveEntry(), staging a
  directory holding one file and driving three
  WOLFSSH_SCP_RECURSIVE_REQUEST calls through wsScpSendCallback(),
  checking the entry name, size, and bytes placed in buf.  dirPath
  is rooted at CONFIG_WOLFSSH_SFTP_DEFAULT_DIR under WOLFSSH_ZEPHYR
  and at "./scp_recur_entry" otherwise.
- The test is gated on WOLFSSH_SCP, with WOLFSSH_SCP_USER_CALLBACKS,
  NO_FILESYSTEM and NO_WOLFSSH_DIR unset, carries an empty stub
  otherwise, and is called from wolfSSH_ApiTest().
- scpStageRecurFile() writes that fixture file.

Issue: F-13315
2026-09-10 15:28:17 -07:00
Yosuke Shimizu d3ad9fa5ed internal: gate auth-agent channel opens on the client's request
- DoChannelOpen refuses an auth-agent open on a server endpoint, and on
  a client with the agent disabled or connectState below
  CONNECT_CLIENT_CHANNEL_AGENT_REQUEST_SENT.
- Both answer OPEN_ADMINISTRATIVELY_PROHIBITED; the ssh->agent check
  stays as the resource check behind them.
- Cover the refusals before the request, with the agent disabled, over
  an accepting channelOpenCb, and on a server, plus the accepted open
  past the request; each refusal asserts ssh->error stays clean.
- Move TestAgentChannelNullAgentSendsOpenFail to a client harness, and
  have the agent tests set the agent flag explicitly.

Issue: F-13389
2026-09-10 15:28:04 -07:00
Hideki Miyazaki 6d163a87d9 Fix remaining MinGW build errors in the regress/unit test build
apps/wolfssh/common.c declared a CONSOLE_SCREEN_BUFFER_INFO local in
ClientSetEcho that nothing ever read; the Windows echo toggling never
grew the code that would have used it. Drop the unused declaration.

wolfssh/port.h defined WSTRSEP as strsep(), a BSD extension MSVCRT
and MinGW do not provide. Add a portable wstrsep() in src/port.c,
matching the wstrnstr/wstrncat/wstrdup pattern already used for other
missing string functions, and route WSTRSEP through it under
USE_WINDOWS_API.

tests/regress.c called the two argument POSIX mkdir(path, mode) and
setenv()/unsetenv() directly in TestKnownHostsLastEntry. Use the
existing WMKDIR macro for the directory creation, and add small
TEST_SETENV/TEST_UNSETENV macros backed by _putenv_s() on Windows so
the HOME juggling this test does still works there.

src/wolfsftp.c had three separate issues in code paths that had never
been compiled before this job existed. wolfSSH_SFTP_RecvOpen declared
a flagsAndAttrs DWORD that nothing read, since WS_CreateFileA is
called with a hardcoded FILE_ATTRIBUTE_NORMAL instead.
wolfSSH_SFTP_RecvOpenDir compared a signed loop counter against a
sizeof expression while building ssh->driveList, so make the counter
word32. wolfSSH_SFTP_Put passed &state->rSz, an int, to ReadFile()'s
DWORD* output parameter; read into a local DWORD and copy it into
state->rSz afterward, since that field is also assigned from
WFREAD() on non-Windows builds.

src/wolfterm.c's wolfSSH_DoOSC never used its handle parameter. Mark
it with WOLFSSH_UNUSED rather than removing it, since the parameter
matches the signature its two call sites already pass and future OSC
handling such as window titles is a natural use for it.

Verified against a real x86_64-w64-mingw32 cross compiler with a
config.h edited to match the sizes and header availability the
actual Windows CI run reported (SIZEOF_LONG 4, HAVE_SYS_IOCTL_H
undefined, and so on): every file this job compiles builds cleanly
under the same -Werror flag set. Also reconfirmed a clean, unmodified
Linux build still passes both tests/regress.test and tests/unit.test.
2026-09-10 15:27:50 -07:00
Hideki Miyazaki 83a2246471 Fix three MinGW -Werror build failures in the regress/unit test build
MinGW-w64 does not ship arpa/inet.h; guard tests/regress.c's include
the same way apps/wolfsshd/auth.c already does, since htonl/ntohl end
up declared via the winsock2.h wolfSSL's headers pull in later in the
same translation unit.

wolfssh/test.h guarded its MSVC #pragma warning(disable:4996) with
USE_WINDOWS_API alone, which is also true for MinGW's gcc; gcc treats
the unrecognized pragma as an error under -Werror. Require _MSC_VER
too, matching the existing ALIGN16 pragma guard in wolfssh/internal.h.

wolfSSH_CleanPath's Windows/Nucleus drive-letter cleanup re-declared
`i` in a nested scope, shadowing the function's own `i` used by every
other loop in the function. Hoist `j` to the function's declarations
(guarded by the same #if so non-Windows/Nucleus builds don't get an
unused-variable warning) and drop the now-unnecessary block so the
loop reuses the outer `i`.

Verified locally: autoreconf + configure + make tests/regress.test
tests/unit.test builds clean and both binaries pass on Linux.
2026-09-10 15:27:50 -07:00
Aidan Keefe 4553b3c659 Skoll review and Github review
Added more test vectors as well as locking in behavior of VerifyProtoId
Added round trip test for proto id
Comment fixes
New define for proto id min size
2026-09-10 15:27:30 -07:00
Aidan Keefe 155a4e1c47 Validate a custom SSH proto ID string
wolfSSH_CTX_SetSshProtoIdStr() now rejects a string that is not
CRLF-terminated, exceeds 255 bytes counting the terminator, or
carries a CR or LF in the body. DoKexInit() subtracts the two
terminator bytes from the length when hashing it, so an
unterminated string underflowed the hash length.

Issue: F-10571

partial progress
2026-09-10 15:27:30 -07:00
Yosuke Shimizu 29762a64c6 port, scp: distinguish end of directory from enumeration failure
- WS_FindNextFileA_ex() takes a lastError out-parameter and carries
  the body of WS_FindNextFileA(), which now calls it with NULL. A
  failing return reports the FindNextFileW() error, or
  ERROR_NO_UNICODE_TRANSLATION when the name would not convert.
- FindNextDirEntry() calls WS_FindNextFileA_ex() on Windows. An
  ERROR_NO_MORE_FILES result frees ctx->entry, sets it to NULL and
  returns WS_NEXT_ERROR so the caller pops the directory; every other
  error still returns WS_FATAL_ERROR.

Issue: F-13316
2026-09-08 15:44:54 -07:00
John Safranek e7a05229ad agent: commit the open on a completed flush
wolfSSH_AGENT_ChannelOpen() settles its channel and agent on whether
the open reached the peer, which SendPacketDelivered() answers from the
flush count. The highwater callback runs after the last byte is out, so
its failure arrives as this send's return, and the rollback read that
as a send that never left.

- give SendPacketDelivered() external linkage, contract in internal.h
- note in agent.h that an error raised once the open is on the wire
  leaves the channel open
- test a failing highwater callback: the channel and the agent stand,
  and the poll after it is the idempotent one
2026-09-08 11:50:40 -05:00
John Safranek 6164ec39d5 agent: refuse a channel open after a disconnect
wolfSSH_AGENT_ChannelOpen() answers a poll on a session that is over
with WS_FATAL_ERROR and WS_DISCONNECT in ssh->error, the shape every
other public sender uses: no channel opened, nothing on the wire,
RFC 4253 section 11.1. wolfSSH_accept() gates the open it drives, so
the new public entry point is the only way in.

- promote SendAfterDisconnect() to WOLFSSH_LOCAL so agent.c uses the
  same helper as every other public sender
- leave an open queued before the disconnect unflushed, the rule
  wolfSSH_shutdown() applies to all but its own disconnect
- keep WS_DISCONNECT in ssh->error at the accept() call site, which
  used to overwrite it with the status the open returns
2026-09-08 11:50:40 -05:00
John Safranek 41f171d982 agent: let the application open the agent channel
The one server-side site that opens auth-agent@openssh.com sits inside
wolfSSH_accept(), so an application driving its own channels cannot
reach it: the session records the request and no channel follows.

- add wolfSSH_AGENT_ChannelOpen(), the same open lifted out of accept(),
  which still calls it
- it reports WS_BAD_ARGUMENT until the peer asks and on a client
  session, and is idempotent after, so an application can poll it
- publish the agent on a queued open too, so a retry after WS_WANT_WRITE
  finds it rather than opening a second channel and leaking the first
- flush what is left of a queued open on the next call, rather than
  reporting a success the peer never saw
- record ssh->error from the send alone, so neither a poll ahead of the
  request nor a failed allocation stops accept() continuing
2026-09-08 11:50:40 -05:00
John Safranek 85f7978eb4 SCP: report only a read want from SCP_accept
wolfSSH_SCP_accept() reported any want held in ssh->error in place of
the DoScpRequest() result. A short send that SendChannelData() accepts
leaves WS_WANT_WRITE there with nothing clearing it on a later flush,
so a terminal result came back as retryable and the retry re-entered
the state machine, re-sending an abort confirmation or reading past a
bad message.

- Substitute the want only when the result is WS_FATAL_ERROR and the
  want is WS_WANT_READ, the one case GetInputData() hides by value.
- Leave write wants alone; the SCP state machine already returns them.
2026-09-08 10:17:42 -05:00
John Safranek 13b9e39f90 SCP: let the application start the transfer
An application that binds an "scp ..." command to a channel itself has no
way to run the transfer; wolfSSH_accept() did it through a WS_SCP_INIT
re-entry only that state machine can drive.

- add wolfSSH_SCP_accept(), a wrapper over DoScpRequest() reporting
  WS_SCP_COMPLETE for any non-negative result, as accept() does
- a receive-side want reaches the wrapper as a generic error with the
  want in ssh->error, so report the want itself and let the caller retry
- state that resume contract beside the prototype, and clear a stale want
  on entry the way the other re-entrant entry points do
2026-09-08 10:17:42 -05:00
JacobBarthelmeh 161c5ae24f expand CI test for non-fpki build, fix check for error state with CertEnumCertificatesInStore, add extended key usage type check if present, fix for EKU checks with X509 use and add test case 2026-09-05 01:32:02 -07:00
JacobBarthelmeh 0be0fae009 use wolfSSH_ prefix on wolfSSH specific config's HostKeyStore, HostKeyStoreSubject, HostKeyStoreFlags 2026-09-05 01:32:02 -07:00
JacobBarthelmeh ed9bf644d0 use proper free for cert store version, fix echoserver on mac os 2026-09-05 01:32:02 -07:00
JacobBarthelmeh 4baba585d5 fix for flags handling, ocsp case, macro guards, unused variable, changed default from MY to required to be set
enable SHA1 with windows cert store test case

expand test cases, adjust to authorized key file, minor dead code adjustments

add more documentation, refactor duplicate code sections, clean up test cases, more adjustments to logging spamming protections
2026-09-05 01:32:02 -07:00
JacobBarthelmeh 13c2adf34b add Windows cert store use with signing and add example arguments
add Windows cert store test case

make windows cert feature default disabled and simplify macro guard

additional unit tests, advertise x509 and pubkey, use CN to match username, build check for WOLFSSL_SYS_CA_CERTS, fix for CM ref count

additional build test, uniform enum name, fail on unkown cert store ecc curve, tie in of loading whole cert store for sys CA's
2026-09-05 01:32:02 -07:00
JacobBarthelmeh 37e75d4aa0 add TrustedSystemCAKeys sshd option for system CA load 2026-09-05 01:32:02 -07:00
John Safranek 6485062d3d internal: send LOCAL_CLEANUP on forward delete
WOLFSSH_FWD_LOCAL_CLEANUP is part of the public WS_FwdCbAction contract
and the library never sent it, so an application's handler never ran and
every peer-opened forward leaked what its setup callback allocated.
ChannelDelete() now sends it, so a peer close, an open refused after the
setup ran, wolfSSH_ChannelFree(), and freeing the session all report it.

- record the setup in a new fwdSetupTxd bit on WOLFSSH_CHANNEL: a
  locally opened forward gets no LOCAL_SETUP, so the channel type alone
  cannot say whether the application holds anything
- clear the bit when the cleanup goes out, so a channel reports it once
- pass the channel's id in the port parameter, the way
  WOLFSSH_FWD_CHANNEL_ID does, so an application with two forwards can
  tell which one ended
- TestDirectTcpipFwdCbRejectsChannelId now counts three callback calls
2026-09-03 21:43:30 -05:00
John Safranek ee7fa04bec ssh: flush any queued output at teardown
wolfSSH_shutdown() flushes whatever a short send left in the output
buffer, not just a queued disconnect. A rejected auth's
USERAUTH_FAILURE has no channel, and DoChannelClose() retires the
channel as soon as the close is bundled, so neither had anything
left to carry the retry.

- The gate still refuses a flush once the peer has disconnected,
  unless our own disconnect is the thing queued.
- A flush that finishes on a live session clears the WS_WANT_WRITE
  the short send latched, as the disconnected path already did.
- ssh.h documents the widened flush and the WS_WANT_WRITE a short
  one leaves wolfSSH_shutdown() returning.
- Tests cover the rejected auth with no channel and the close whose
  channel DoChannelClose() already retired.
- A flush that short-sends on a live channel leaves the teardown
  queued behind it. The retry adds no second EOF or close, and skips
  the close wait while its own flush is still short.
- A teardown send that drained the leftovers settles the flush; one
  that failed outranks it, since a reset leaves the buffer intact.
2026-09-03 16:12:17 -05:00
John Safranek 79456c2002 Refuse a session channel open on a client
RFC 4254 6.1 has a session open travelling client-to-server, so
DoChannelOpen refuses one on a client endpoint in the type switch,
ahead of ChannelNew and any channelOpenCb. OpenSSH and Dropbear
refuse it outright too, with no application opt-in.

- Answer OPEN_ADMINISTRATIVELY_PROHIBITED, as the forwarded-tcpip
  direction gate below it does.
- Widen the client harness guard in regress.c so it builds without
  WOLFSSH_FWD, and hoist AcceptChannelOpenCb out of the forwarding
  block.
- Cover the refusal, and the gate outranking an accepting
  channelOpenCb.

Issue: F-8836
2026-09-03 10:40:16 -05:00
John Safranek b13704af1d Cover and document the client KEX role check
TestClientOnlyKexMsgsBlocked now asserts that 31 and 33, the ids a
client does receive, stay allowed where the handshake expects them, so
widening the role check into a 30-34 range fails the suite. The
IsMessageAllowed() comment records the receive-only policy and the
per-KEX-method id namespace.

- assert id 33 is allowed once expectMsgId is MSGID_KEXDH_GEX_REPLY
- assert expectMsgId starts at MSGID_NONE, so the role check is what
  rejects the blocked ids
- recheck all three blocked ids during a rekey on an established session
2026-09-03 10:32:18 -05:00
Aidan Keefe 557f3df42d Validate a custom SSH proto ID string
wolfSSH_CTX_SetSshProtoIdStr() now rejects a string that is not
CRLF-terminated, exceeds 255 bytes counting the terminator, or
carries a CR or LF in the body. DoKexInit() subtracts the two
terminator bytes from the length when hashing it, so an
unterminated string underflowed the hash length.

Issue: F-10571
2026-09-02 15:36:18 -07:00
Emma Stensland ea530fe05d wolfsftp: Improve POSIX and Windows write/append handling
Ensures EOF appending on all POSIX systems and fails short writes on Windows.
Consolidates the Windows SFTP flag matrix testing with shared helpers.
2026-09-02 15:35:09 -07:00
Emma Stensland 6a71292401 internal: Refactor and gate ML-DSA composite key handling
Gates ML-DSA composites behind WOLFSSH_NO_MLDSA_COMPOSITES.
Deduplicates key handling and uses heap allocation for
composite buffers when compiling for small stacks.
2026-09-02 15:35:09 -07:00
Emma Stensland a513f69f1d internal: Share one bounded ECDSA r/s mpint encoder 2026-09-02 15:35:09 -07:00
John Safranek 5e72c76683 Rebind a request in flight to the folded forward
FwdRemoteSettle() folds a port-0 forward the peer bound onto a
registration already standing at that port. A request still in its
send window resolved its forward before the send, so it names the
survivor from here on, the same as the queued replies do.

- Add FwdPendingRebind() and call it ahead of the unlink, so a cancel
  whose send pumped in the answer commits naming a forward rather
  than nothing.
- Regression test drives the port-0 answer in from the cancel's send
  callback.
2026-09-02 15:18:32 -05:00
John Safranek cb211506e2 Fold a forward the send settled onto its bind
FwdPendingCommit() looks for a registration of the bind its entry names
before linking it. An answer pumped in from the send can settle a
port-pending forward onto that port, and the fold in FwdRemoteSettle()
runs before the committing entry joins the list.

- replies queued against the folded entry move to the one that stands
  at the bind
- test drives that answer from the IO send callback; the bind takes two
  registrations without the fold, and one cancel leaves one matching
2026-09-02 15:18:32 -05:00
John Safranek e53c157049 Resolve a forward request once, before its send
A request looked its registration up again at commit, after the send had
run the application's send and highwater callbacks. A callback that
reenters the library sends behind the request that ran it, so the lookup
found what the callback did rather than what the request asked for: a
cancel from a first setup's send found nothing to take, and that setup's
commit then registered the forward the peer had just been told to drop.

- Requests in flight hang off ssh->fwdPendingHead for the length of
  their send, so a reentrant request resolves to the forward the request
  it interrupted is registering, not to nothing.
- FwdRemotePrepare() settles what the request names and holds it on the
  pending; FwdPendingCommit() uses that instead of a fresh lookup, which
  also drops the dedupe branch the double lookup needed.
- FwdRemoteUnlink() voids the pending pointers along with the queued
  slots, so freeing a forward mid-send leaves no commit naming it, and
  FwdPendingDiscard() goes through it rather than freeing raw.
- FwdRemoteMatch() reads the pending list under the rules the scan over
  registered forwards uses: a setup mid-send is on the wire and can feed
  channels; a cancel behind it or a refusal on its slot stops matching.
- Drop pend->bindAddr and pend->bindPort, which nothing reads now.
- Tests drive a cancel, a setup, an inbound forwarded-tcpip open and a
  mid-send REQUEST_FAILURE from the IO send callback, the window the
  highwater tests no longer reach.

Issue: ZD-22195
2026-09-02 15:18:32 -05:00
John Safranek 79a7f29933 Match forwarded-tcpip on every client session
The match only ran once wolfSSH_FwdRemoteSetup() had registered
something, so a client that never called it had every forwarded-tcpip
open go unchecked -- the case RFC 4254 7.2 is clearest about, since a
session that asked for no forward has nothing an open could answer for.

- Refuse the open whenever it names no registration, empty list
  included. wolfSSH_SetFwdRemoteMatch(WOLFSSH_FWD_MATCH_OFF) is the
  opt-out for an application that keeps its own bind list.
- Drop fwdRemoteTracked, which nothing reads now.
- wolfSSH_global_request() cannot carry the bind address and port RFC
  4254 7.1 puts after the want-reply boolean, so no request that
  registers a forward can be framed outside wolfSSH_FwdRemoteSetup().

Issue: ZD-22195
2026-09-02 15:18:32 -05:00
Reda Chouk 6802df0c52 Reject the KEX requests a client never receives
This is just a follow-up PR to (#1221), that mirrors pretty much the same changes for the client side too.
2026-09-02 11:20:06 -07:00
Hideki Miyazaki 4ee445dd07 Addressed comments 2026-09-02 09:17:30 -07:00
Hideki Miyazaki 88c0e34b7e Addressed review comments 2026-09-02 09:17:30 -07:00
Hideki Miyazaki 1c5fbfe0b5 Addressed review comments 2026-09-02 09:17:30 -07:00