From b00bbc8f8b74110b95509cc024c0d0405b217622 Mon Sep 17 00:00:00 2001 From: Yosuke Shimizu Date: Tue, 30 Jun 2026 13:28:07 +0900 Subject: [PATCH] Bound CSI continuation parse and clear esc state on completion --- src/wolfterm.c | 20 ++++++++++++++++++++ tests/api.c | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+) diff --git a/src/wolfterm.c b/src/wolfterm.c index 7ddd5d117..cf7d05786 100644 --- a/src/wolfterm.c +++ b/src/wolfterm.c @@ -473,6 +473,18 @@ static int wolfSSH_DoControlSeq(WOLFSSH* ssh, WOLFSSH_HANDLE handle, byte* buf, } else { numArgs = getArgs(buf, bufSz, &i, args); + + if (i >= bufSz) { + /* args ran to end of buffer with no command char yet, save + * left overs for next call rather than reading past buf */ + 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; + } c = buf[i]; i++; } } @@ -482,6 +494,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; @@ -669,6 +685,9 @@ int wolfSSH_ConvertConsole(WOLFSSH* ssh, WOLFSSH_HANDLE handle, byte* buf, if (ret == WS_WANT_READ) { return ret; } + /* sequence is finished, clear state so the next call starts + * fresh and does not re-enter CSI parsing on plain bytes */ + ssh->escState = WC_ESC_NONE; ssh->escBufSz = 0; break; @@ -677,6 +696,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; diff --git a/tests/api.c b/tests/api.c index d511419ca..601bfa73f 100644 --- a/tests/api.c +++ b/tests/api.c @@ -3020,6 +3020,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 */ @@ -3067,6 +3079,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 */