wolfterm: bound escBuf saves and clear esc state on OSC resume

Harden the partial-sequence reassembly in wolfSSH_DoControlSeq and fix an
escape-state leak in the wolfSSH_ConvertConsole OSC resume path
(USE_WINDOWS_API console handling).

- Tighten both escBuf partial-save branches to reject bufSz - *idx >=
  WOLFSSL_MAX_ESCBUF before the WMEMCPY and log via WLOG. escBuf is
  WOLFSSL_MAX_ESCBUF bytes, so a save of exactly that length fills it with
  no room for later state; rejecting the boundary keeps the two branches
  consistent and matches the resume check.

- Add the capacity guard to the initial-parse CSI save branch, which
  previously copied into escBuf with no bound. In the default build getArgs
  caps the advance below WOLFSSL_MAX_ESCBUF so the copy was already safe;
  the guard prevents a custom WOLFSSH_MAX_CONSOLE_ARGS > WOLFSSL_MAX_ESCBUF
  build from overflowing escBuf.

- Clear escState to WC_ESC_NONE when a resumed OSC sequence completes, not
  just escBufSz. Without this escState stayed WS_ESC_OSC and the next call
  re-entered OSC parsing on plain bytes instead of printing them.

Extend test_wolfSSH_ConvertConsole with a CSI sequence split across three
calls (ESC[ | args | command char), a trailing plain byte that must be
printed once state is cleared, and a single buffer whose CSI args run to
the end and complete on the next byte.
pull/1113/head
Yosuke Shimizu 2026-06-30 13:28:07 +09:00 committed by John Safranek
parent 9702235f91
commit 0b819b0b48
2 changed files with 42 additions and 1 deletions

View File

@ -475,7 +475,8 @@ static int wolfSSH_DoControlSeq(WOLFSSH* ssh, WOLFSSH_HANDLE handle, byte* buf,
numArgs = getArgs(buf, bufSz, &i, args);
if (i >= bufSz) {
/* save left overs for next call */
if (bufSz - *idx > WOLFSSL_MAX_ESCBUF) {
if (bufSz - *idx >= WOLFSSL_MAX_ESCBUF) {
WLOG(WS_LOG_ERROR, "escBuf state exceeds capacity");
return WS_FATAL_ERROR;
}
WMEMCPY(ssh->escBuf, buf + *idx, bufSz - *idx);
@ -491,6 +492,10 @@ static int wolfSSH_DoControlSeq(WOLFSSH* ssh, WOLFSSH_HANDLE handle, byte* buf,
if (i >= bufSz) {
/* save left overs for next call */
if (bufSz - *idx >= WOLFSSL_MAX_ESCBUF) {
WLOG(WS_LOG_ERROR, "escBuf state exceeds capacity");
return WS_FATAL_ERROR;
}
WMEMCPY(ssh->escBuf, buf + *idx, bufSz - *idx);
ssh->escBufSz = bufSz - *idx;
return WS_WANT_READ;
@ -687,6 +692,7 @@ int wolfSSH_ConvertConsole(WOLFSSH* ssh, WOLFSSH_HANDLE handle, byte* buf,
if (ret == WS_WANT_READ) {
return ret;
}
ssh->escState = WC_ESC_NONE;
ssh->escBufSz = 0;
break;

View File

@ -3370,6 +3370,18 @@ static byte osc_trunc_str[] = { /* "ESC ] 0 ; title" with no BEL terminator */
static byte osc_full[] = { /* well formed "ESC ] 0 ; hi BEL" */
0x1B, 0x5D, 0x30, 0x3B, 0x68, 0x69, 0x07
};
static byte csi_open[] = { /* "ESC [" with no args yet, escBufSz left at 0 */
0x1B, 0x5B
};
static byte csi_args[] = { /* pure args "12" with no command char */
0x31, 0x32
};
static byte csi_cmd[] = { /* command char 'm' completes the sequence */
0x6D
};
static byte csi_inline[] = { /* "ESC [ 1 2" args run to end of one buffer */
0x1B, 0x5B, 0x31, 0x32
};
#endif /* USE_WINDOWS_API */
@ -3417,6 +3429,29 @@ static void test_wolfSSH_ConvertConsole(void)
AssertIntEQ(wolfSSH_ConvertConsole(ssh, stdoutHandle, osc_full,
sizeof(osc_full)), WS_SUCCESS);
/* a CSI sequence split so the first packet ends right after "ESC [" and
* the second carries only argument bytes must not read past the buffer
* while waiting for the command char */
AssertIntEQ(wolfSSH_ConvertConsole(ssh, stdoutHandle, csi_open,
sizeof(csi_open)), WS_WANT_READ);
AssertIntEQ(wolfSSH_ConvertConsole(ssh, stdoutHandle, csi_args,
sizeof(csi_args)), WS_WANT_READ);
/* the trailing command char completes the reassembled sequence */
AssertIntEQ(wolfSSH_ConvertConsole(ssh, stdoutHandle, csi_cmd,
sizeof(csi_cmd)), WS_SUCCESS);
/* after the split sequence completes the esc state must be cleared, so a
* following plain argument byte is printed rather than swallowed back into
* CSI parsing (which would return WS_WANT_READ) */
AssertIntEQ(wolfSSH_ConvertConsole(ssh, stdoutHandle, csi_args, 1),
WS_SUCCESS);
/* a single buffer whose CSI arguments run to the end with no command char
* must save the partial args and wait, then complete on the next byte */
AssertIntEQ(wolfSSH_ConvertConsole(ssh, stdoutHandle, csi_inline,
sizeof(csi_inline)), WS_WANT_READ);
AssertIntEQ(wolfSSH_ConvertConsole(ssh, stdoutHandle, csi_cmd,
sizeof(csi_cmd)), WS_SUCCESS);
wolfSSH_free(ssh);
wolfSSH_CTX_free(ctx);
#endif /* USE_WINDOWS_API */