@@ -1033,6 +1033,17 @@ static int SHELL_Subsystem(WOLFSSHD_CONNECTION* conn, WOLFSSH* ssh,
10331033 size_t sz = 0 ;
10341034 WCHAR h [MAX_PATH ];
10351035 char * forcedCmd ;
1036+ WOLFSSH_CHANNEL * shellChannel ;
1037+
1038+ /* Off the channel list, not DEFAULT_NEXT_CHANNEL, which a build can
1039+ * override. Same as the POSIX copy; this loop routes its reads and sends
1040+ * off the same id. */
1041+ shellChannel = wolfSSH_ChannelNext (ssh , NULL );
1042+ if (shellChannel == NULL || wolfSSH_ChannelGetId (shellChannel ,
1043+ & shellChannelId , WS_CHANNEL_ID_SELF ) != WS_SUCCESS ) {
1044+ wolfSSH_Log (WS_LOG_ERROR , "[SSHD] No session channel to service" );
1045+ return WS_FATAL_ERROR ;
1046+ }
10361047
10371048 forcedCmd = wolfSSHD_ConfigGetForcedCmd (usrConf );
10381049
@@ -1811,6 +1822,23 @@ static int SHELL_Subsystem(WOLFSSHD_CONNECTION* conn, WOLFSSH* ssh,
18111822
18121823 struct termios tios ;
18131824 word32 shellChannelId = 0 ;
1825+ WOLFSSH_CHANNEL * shellChannel ;
1826+
1827+ /* Name the session channel off the channel list rather than trusting
1828+ * DEFAULT_NEXT_CHANNEL to be 0, which a build can override. It is the
1829+ * only channel open at this point; the agent channel comes later. The
1830+ * loop below closes the child's stdin off this channel, so a wrong id
1831+ * there drops the peer's input instead of handing it over. */
1832+ shellChannel = wolfSSH_ChannelNext (ssh , NULL );
1833+ if (shellChannel == NULL || wolfSSH_ChannelGetId (shellChannel ,
1834+ & shellChannelId , WS_CHANNEL_ID_SELF ) != WS_SUCCESS ) {
1835+ /* The shell child is already forked, and nothing below can reach the
1836+ * peer without an id to address, so take it down with us. */
1837+ wolfSSH_Log (WS_LOG_ERROR , "[SSHD] No session channel to service" );
1838+ kill (childPid , SIGKILL );
1839+ return WS_FATAL_ERROR ;
1840+ }
1841+
18141842 signal (SIGCHLD , ChildSig );
18151843 signal (SIGINT , SIG_DFL );
18161844
@@ -1853,6 +1881,7 @@ static int SHELL_Subsystem(WOLFSSHD_CONNECTION* conn, WOLFSSH* ssh,
18531881 WS_SOCKET_T maxFd ;
18541882 int cnt_r ;
18551883 int cnt_w ;
1884+ WOLFSSH_CHANNEL * current ;
18561885 int pending = 0 ;
18571886
18581887 FD_ZERO (& readFds );
@@ -1864,7 +1893,17 @@ static int SHELL_Subsystem(WOLFSSHD_CONNECTION* conn, WOLFSSH* ssh,
18641893 FD_SET (sshFd , & writeFds );
18651894 }
18661895
1867- if (wolfSSH_stream_peek (ssh , tmp , 1 ) <= 0 ) {
1896+ /* Buffered shell input is a wake condition in its own right.
1897+ * wolfSSH_stream_peek() goes blind once the channel is at EOF, so
1898+ * leaning on it alone would park undrained bytes in a select() that
1899+ * nothing else can wake. */
1900+ current = wolfSSH_ChannelFind (ssh , shellChannelId , WS_CHANNEL_ID_SELF );
1901+ if (current != NULL
1902+ && current -> inputBuffer .length > current -> inputBuffer .idx ) {
1903+ pending = 1 ;
1904+ }
1905+
1906+ if (!pending && wolfSSH_stream_peek (ssh , tmp , 1 ) <= 0 ) {
18681907 /* select on stdout/stderr pipes with forced commands */
18691908 if (!ptyReq || forcedCmd ) {
18701909 FD_SET (stdoutPipe [0 ], & readFds );
@@ -1896,42 +1935,28 @@ static int SHELL_Subsystem(WOLFSSHD_CONNECTION* conn, WOLFSSH* ssh,
18961935 }
18971936
18981937 if (wantWrite || windowFull || pending || FD_ISSET (sshFd , & readFds )) {
1899- word32 lastChannel = 0 ;
1938+ word32 avail ;
19001939
19011940 wantWrite = 0 ;
1902- /* The following tries to read from the first channel inside
1903- the stream. If the pending data in the socket is for
1904- another channel, this will return an error with id
1905- WS_CHAN_RXD. That means the agent has pending data in its
1906- channel. The additional channel is only used with the
1907- agent. */
1908- cnt_r = wolfSSH_worker (ssh , & lastChannel );
1941+ /* The worker services the transport. What lands on the shell
1942+ channel is handed to the child by the drain below, which names
1943+ the channel itself, so the id the worker would report here is
1944+ not needed. */
1945+ cnt_r = wolfSSH_worker (ssh , NULL );
19091946 if (cnt_r < 0 ) {
19101947 rc = wolfSSH_get_error (ssh );
19111948 if (rc == WS_CHAN_RXD ) {
1912- if (!windowFull ) { /* don't rewrite channeldBuffer if full
1913- * of windowFull left overs */
1914- if (lastChannel == shellChannelId ) {
1915- cnt_r = wolfSSH_ChannelIdRead (ssh , shellChannelId ,
1916- channelBuffer ,
1917- sizeof channelBuffer );
1918- if (cnt_r <= 0 )
1919- break ;
1920-
1921- if (!ptyReq || forcedCmd ) {
1922- cnt_w = (int )write (stdinPipe [1 ], channelBuffer ,
1923- cnt_r );
1924- }
1925- else {
1926- cnt_w = (int )write (childFd , channelBuffer ,
1927- cnt_r );
1928- }
1929- if (cnt_w <= 0 )
1930- break ;
1931- }
1932- }
1949+ /* Arrival only; the drain below owns the read. */
19331950 }
19341951 else if (rc == WS_CHANNEL_CLOSED ) {
1952+ /* The channel is retired, so nothing more can reach the
1953+ * child and the drain below is skipped on this pass.
1954+ * Close its stdin here or it blocks forever on input
1955+ * that cannot come. */
1956+ if (stdinPipe [1 ] != -1 && (!ptyReq || forcedCmd )) {
1957+ close (stdinPipe [1 ]);
1958+ stdinPipe [1 ] = -1 ;
1959+ }
19351960 peerConnected = 0 ;
19361961 continue ;
19371962 }
@@ -1950,15 +1975,72 @@ static int SHELL_Subsystem(WOLFSSHD_CONNECTION* conn, WOLFSSH* ssh,
19501975 }
19511976 }
19521977
1953- /* did the channel just receive an EOF? */
1954- if (cnt_r == 0 ) {
1955- int eof ;
1956- WOLFSSH_CHANNEL * current ;
1978+ /* The shell channel's own buffer, looked up again because the
1979+ * worker above can retire it. */
1980+ current = wolfSSH_ChannelFind (ssh , shellChannelId ,
1981+ WS_CHANNEL_ID_SELF );
1982+ avail = (current != NULL ) ?
1983+ current -> inputBuffer .length - current -> inputBuffer .idx : 0 ;
1984+
1985+ /* Hand over what the peer sent, this pass or left buffered
1986+ * while the window was full; this is the only copy. Gated on
1987+ * windowFull, and not because the buffers overlap -- shellBuffer
1988+ * and channelBuffer are disjoint. While the peer will not take
1989+ * the child's output, writing to the child's stdin deadlocks it:
1990+ * it blocks on a full stdout pipe, stops reading stdin, and this
1991+ * write never returns. The backlog clears as soon as the peer
1992+ * reads. One buffer per pass, since the write can block. */
1993+ if (avail > 0 && !windowFull ) {
1994+ int off = 0 ;
1995+
1996+ cnt_r = wolfSSH_ChannelIdRead (ssh , shellChannelId ,
1997+ channelBuffer , sizeof channelBuffer );
1998+ if (cnt_r <= 0 )
1999+ break ;
2000+
2001+ /* Data behind the peer's EOF, RFC 4254 section 5.3. Stdin
2002+ * is gone, so drop it rather than write to fd -1 and end the
2003+ * session on an EBADF. */
2004+ if ((!ptyReq || forcedCmd ) && stdinPipe [1 ] == -1 )
2005+ off = cnt_r ;
2006+
2007+ /* The read took the bytes off the channel, so this is the
2008+ * only copy: a short write has to be finished, not dropped.
2009+ * A PTY master goes short whenever the line discipline fills,
2010+ * and a signal can cut a transfer already under way. */
2011+ while (off < cnt_r ) {
2012+ if (!ptyReq || forcedCmd ) {
2013+ cnt_w = (int )write (stdinPipe [1 ], channelBuffer + off ,
2014+ cnt_r - off );
2015+ }
2016+ else {
2017+ cnt_w = (int )write (childFd , channelBuffer + off ,
2018+ cnt_r - off );
2019+ }
2020+ if (cnt_w <= 0 ) {
2021+ /* errno only speaks for a -1 return. */
2022+ if (cnt_w < 0 && errno == EINTR )
2023+ continue ;
2024+ break ;
2025+ }
2026+ off += cnt_w ;
2027+ }
2028+ if (off < cnt_r )
2029+ break ;
2030+
2031+ avail = current -> inputBuffer .length - current -> inputBuffer .idx ;
2032+ }
19572033
1958- current = wolfSSH_ChannelFind (ssh , lastChannel ,
1959- WS_CHANNEL_ID_SELF );
1960- eof = wolfSSH_ChannelGetEof (current );
1961- if (eof && (!ptyReq || forcedCmd )) {
2034+ /* Peer done sending: close the child's stdin, but only once what
2035+ * it already sent has been handed over. Closing early drops it
2036+ * and the next write lands on fd -1. A channel that is gone is
2037+ * the peer being done too -- DoChannelClose() retires it, and the
2038+ * id was validated at entry, so a miss here cannot mean a wrong
2039+ * id. Leaving the pipe open then would block the child forever on
2040+ * a stdin nothing will ever close. */
2041+ if (stdinPipe [1 ] != -1 && (!ptyReq || forcedCmd )) {
2042+ if (current == NULL
2043+ || (wolfSSH_ChannelGetEof (current ) && avail == 0 )) {
19622044 /* SSH is done, close stdin pipe to child process */
19632045 close (stdinPipe [1 ]);
19642046 stdinPipe [1 ] = -1 ;
0 commit comments