[PATCH v2 1/2] cli: Correct several bugs in cli_getch()

This function does not behave as expected when unknown escape sequences are sent to it:
- it fails to store (and thus echo) the last character of the invalid sequence - it fails to set esc_len to 0 when it finishes emitting the invalid sequence, meaning that the following character will appear to be part of a new escape sequence - it processes the first character of the rejected sequence as a valid character, just starting the sequence all over again
The last two bugs conspire to produce an "impossible condition #876" message which is the main symptom of this behaviour.
Fix these bugs and add a test to verify the behaviour.
Signed-off-by: Simon Glass sjg@chromium.org Reported-by: Heinrich Schuchardt xypron.glpk@gmx.de ---
(no changes since v1)
common/cli_getch.c | 5 +++-- test/common/Makefile | 1 + test/common/cread.c | 48 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 52 insertions(+), 2 deletions(-) create mode 100644 test/common/cread.c
diff --git a/common/cli_getch.c b/common/cli_getch.c index 87c23edcf4b..61d4cb261b8 100644 --- a/common/cli_getch.c +++ b/common/cli_getch.c @@ -129,7 +129,7 @@ static int cli_ch_esc(struct cli_ch_state *cch, int ichar,
*actp = act;
- return act == ESC_CONVERTED ? ichar : 0; + return ichar; }
int cli_ch_process(struct cli_ch_state *cch, int ichar) @@ -145,6 +145,7 @@ int cli_ch_process(struct cli_ch_state *cch, int ichar) return cch->esc_save[cch->emit_upto++]; cch->emit_upto = 0; cch->emitting = false; + cch->esc_len = 0; } return 0; } else if (ichar == -ETIMEDOUT) { @@ -185,7 +186,7 @@ int cli_ch_process(struct cli_ch_state *cch, int ichar) cch->esc_save[cch->esc_len++] = ichar; ichar = cch->esc_save[cch->emit_upto++]; cch->emitting = true; - break; + return ichar; case ESC_CONVERTED: /* valid escape sequence, return the resulting char */ cch->esc_len = 0; diff --git a/test/common/Makefile b/test/common/Makefile index cc918f64e54..a5ab10f6f69 100644 --- a/test/common/Makefile +++ b/test/common/Makefile @@ -3,3 +3,4 @@ obj-y += cmd_ut_common.o obj-$(CONFIG_AUTOBOOT) += test_autoboot.o obj-$(CONFIG_CYCLIC) += cyclic.o obj-$(CONFIG_EVENT) += event.o +obj-y += cread.o diff --git a/test/common/cread.c b/test/common/cread.c new file mode 100644 index 00000000000..3dce4bdb0ef --- /dev/null +++ b/test/common/cread.c @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright 2023 Google LLC + */ + +#include <common.h> +#include <cli.h> +#include <test/common.h> +#include <test/test.h> +#include <test/ut.h> + +static int cli_ch_test(struct unit_test_state *uts) +{ + struct cli_ch_state s_cch, *cch = &s_cch; + + cli_ch_init(cch); + + /* should be nothing to return at first */ + ut_asserteq(0, cli_ch_process(cch, 0)); + + /* check normal entry */ + ut_asserteq('a', cli_ch_process(cch, 'a')); + ut_asserteq('b', cli_ch_process(cch, 'b')); + ut_asserteq('c', cli_ch_process(cch, 'c')); + ut_asserteq(0, cli_ch_process(cch, 0)); + + /* send an invalid escape sequence */ + ut_asserteq(0, cli_ch_process(cch, '\e')); + ut_asserteq(0, cli_ch_process(cch, '[')); + + /* + * with the next char it sees that the sequence is invalid, so starts + * emitting it + */ + ut_asserteq('\e', cli_ch_process(cch, 'X')); + + /* now we set 0 bytes to empty the buffer */ + ut_asserteq('[', cli_ch_process(cch, 0)); + ut_asserteq('X', cli_ch_process(cch, 0)); + ut_asserteq(0, cli_ch_process(cch, 0)); + + /* things are normal again */ + ut_asserteq('a', cli_ch_process(cch, 'a')); + ut_asserteq(0, cli_ch_process(cch, 0)); + + return 0; +} +COMMON_TEST(cli_ch_test, 0);

The second call to cli_ch_process() is in the wrong place, meaning that the one of the characters of an invalid escape sequence is swallowed instead of being returned.
Fix the bug and add a test to cover this.
Signed-off-by: Simon Glass sjg@chromium.org Reported-by: Heinrich Schuchardt xypron.glpk@gmx.de ---
Changes in v2: - Fix a failling test
common/cli_readline.c | 3 +-- include/cli.h | 4 ++-- test/common/cread.c | 45 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 48 insertions(+), 4 deletions(-)
diff --git a/common/cli_readline.c b/common/cli_readline.c index 709e9c3d38b..e83743e90c9 100644 --- a/common/cli_readline.c +++ b/common/cli_readline.c @@ -284,10 +284,9 @@ static int cread_line(const char *const prompt, char *buf, unsigned int *len, }
ichar = getcmd_getch(); + ichar = cli_ch_process(cch, ichar); }
- ichar = cli_ch_process(cch, ichar); - /* ichar=0x0 when error occurs in U-Boot getc */ if (!ichar) continue; diff --git a/include/cli.h b/include/cli.h index c777c90313f..094a6602d70 100644 --- a/include/cli.h +++ b/include/cli.h @@ -98,8 +98,8 @@ int cli_readline(const char *const prompt); * * @prompt: Prompt to display * @buffer: Place to put the line that is entered - * @timeout: Timeout in milliseconds, 0 if none - * Return: command line length excluding terminator, or -ve on error: of the + * @timeout: Timeout in seconds, 0 if none + * Return: command line length excluding terminator, or -ve on error: if the * timeout is exceeded (either CONFIG_BOOT_RETRY_TIME or the timeout * parameter), then -2 is returned. If a break is detected (Ctrl-C) then * -1 is returned. diff --git a/test/common/cread.c b/test/common/cread.c index 3dce4bdb0ef..2fdd29a265f 100644 --- a/test/common/cread.c +++ b/test/common/cread.c @@ -46,3 +46,48 @@ static int cli_ch_test(struct unit_test_state *uts) return 0; } COMMON_TEST(cli_ch_test, 0); + +static int cread_test(struct unit_test_state *uts) +{ + int duration; + ulong start; + char buf[10]; + + /* + * useful for debugging + * + * gd->flags &= ~GD_FLG_RECORD; + * print_buffer(0, buf, 1, 7, 0); + */ + + console_record_reset_enable(); + + /* simple input */ + *buf = '\0'; + ut_asserteq(4, console_in_puts("abc\n")); + ut_asserteq(3, cli_readline_into_buffer("-> ", buf, 1)); + ut_asserteq_str("abc", buf); + + /* try an escape sequence (cursor left after the 'c') */ + *buf = '\0'; + ut_asserteq(8, console_in_puts("abc\e[Dx\n")); + ut_asserteq(4, cli_readline_into_buffer("-> ", buf, 1)); + ut_asserteq_str("abxc", buf); + + /* invalid escape sequence */ + *buf = '\0'; + ut_asserteq(8, console_in_puts("abc\e[Xx\n")); + ut_asserteq(7, cli_readline_into_buffer("-> ", buf, 1)); + ut_asserteq_str("abc\e[Xx", buf); + + /* check timeout, should be between 1000 and 1050ms */ + start = get_timer(0); + *buf = '\0'; + ut_asserteq(-2, cli_readline_into_buffer("-> ", buf, 1)); + duration = get_timer(start) - 1000; + ut_assert(duration >= 0); + ut_assert(duration < 50); + + return 0; +} +COMMON_TEST(cread_test, 0);

Am 5. Februar 2023 03:08:54 MEZ schrieb Simon Glass sjg@chromium.org:
This function does not behave as expected when unknown escape sequences are sent to it:
- it fails to store (and thus echo) the last character of the invalid
sequence
What would be the benefit of echoing it? Is there any reason to assume that we want an output for an escape sequence that we fail to parse?
Best regards
Heinrich
- it fails to set esc_len to 0 when it finishes emitting the invalid
sequence, meaning that the following character will appear to be part of a new escape sequence
- it processes the first character of the rejected sequence as a valid
character, just starting the sequence all over again
The last two bugs conspire to produce an "impossible condition #876" message which is the main symptom of this behaviour.
Fix these bugs and add a test to verify the behaviour.
Signed-off-by: Simon Glass sjg@chromium.org Reported-by: Heinrich Schuchardt xypron.glpk@gmx.de
(no changes since v1)
common/cli_getch.c | 5 +++-- test/common/Makefile | 1 + test/common/cread.c | 48 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 52 insertions(+), 2 deletions(-) create mode 100644 test/common/cread.c
diff --git a/common/cli_getch.c b/common/cli_getch.c index 87c23edcf4b..61d4cb261b8 100644 --- a/common/cli_getch.c +++ b/common/cli_getch.c @@ -129,7 +129,7 @@ static int cli_ch_esc(struct cli_ch_state *cch, int ichar,
*actp = act;
- return act == ESC_CONVERTED ? ichar : 0;
- return ichar;
}
int cli_ch_process(struct cli_ch_state *cch, int ichar) @@ -145,6 +145,7 @@ int cli_ch_process(struct cli_ch_state *cch, int ichar) return cch->esc_save[cch->emit_upto++]; cch->emit_upto = 0; cch->emitting = false;
} return 0; } else if (ichar == -ETIMEDOUT) {cch->esc_len = 0;
@@ -185,7 +186,7 @@ int cli_ch_process(struct cli_ch_state *cch, int ichar) cch->esc_save[cch->esc_len++] = ichar; ichar = cch->esc_save[cch->emit_upto++]; cch->emitting = true;
break;
case ESC_CONVERTED: /* valid escape sequence, return the resulting char */ cch->esc_len = 0;return ichar;
diff --git a/test/common/Makefile b/test/common/Makefile index cc918f64e54..a5ab10f6f69 100644 --- a/test/common/Makefile +++ b/test/common/Makefile @@ -3,3 +3,4 @@ obj-y += cmd_ut_common.o obj-$(CONFIG_AUTOBOOT) += test_autoboot.o obj-$(CONFIG_CYCLIC) += cyclic.o obj-$(CONFIG_EVENT) += event.o +obj-y += cread.o diff --git a/test/common/cread.c b/test/common/cread.c new file mode 100644 index 00000000000..3dce4bdb0ef --- /dev/null +++ b/test/common/cread.c @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: GPL-2.0+ +/*
- Copyright 2023 Google LLC
- */
+#include <common.h> +#include <cli.h> +#include <test/common.h> +#include <test/test.h> +#include <test/ut.h>
+static int cli_ch_test(struct unit_test_state *uts) +{
- struct cli_ch_state s_cch, *cch = &s_cch;
- cli_ch_init(cch);
- /* should be nothing to return at first */
- ut_asserteq(0, cli_ch_process(cch, 0));
- /* check normal entry */
- ut_asserteq('a', cli_ch_process(cch, 'a'));
- ut_asserteq('b', cli_ch_process(cch, 'b'));
- ut_asserteq('c', cli_ch_process(cch, 'c'));
- ut_asserteq(0, cli_ch_process(cch, 0));
- /* send an invalid escape sequence */
- ut_asserteq(0, cli_ch_process(cch, '\e'));
- ut_asserteq(0, cli_ch_process(cch, '['));
- /*
* with the next char it sees that the sequence is invalid, so starts
* emitting it
*/
- ut_asserteq('\e', cli_ch_process(cch, 'X'));
- /* now we set 0 bytes to empty the buffer */
- ut_asserteq('[', cli_ch_process(cch, 0));
- ut_asserteq('X', cli_ch_process(cch, 0));
- ut_asserteq(0, cli_ch_process(cch, 0));
- /* things are normal again */
- ut_asserteq('a', cli_ch_process(cch, 'a'));
- ut_asserteq(0, cli_ch_process(cch, 0));
- return 0;
+} +COMMON_TEST(cli_ch_test, 0);

Hi Heinrich,
On Sun, 5 Feb 2023 at 14:29, Heinrich Schuchardt xypron.glpk@gmx.de wrote:
Am 5. Februar 2023 03:08:54 MEZ schrieb Simon Glass sjg@chromium.org:
This function does not behave as expected when unknown escape sequences are sent to it:
- it fails to store (and thus echo) the last character of the invalid
sequence
What would be the benefit of echoing it? Is there any reason to assume that we want an output for an escape sequence that we fail to parse?
Yes. In fact we don't even know if it is invalid or not. It is just that U-Boot doesn't support it. The mimics the behaviour we used to have, I believe.
Regards, Simon
Best regards
Heinrich
- it fails to set esc_len to 0 when it finishes emitting the invalid
sequence, meaning that the following character will appear to be part of a new escape sequence
- it processes the first character of the rejected sequence as a valid
character, just starting the sequence all over again
The last two bugs conspire to produce an "impossible condition #876" message which is the main symptom of this behaviour.
Fix these bugs and add a test to verify the behaviour.
Signed-off-by: Simon Glass sjg@chromium.org Reported-by: Heinrich Schuchardt xypron.glpk@gmx.de
(no changes since v1)
common/cli_getch.c | 5 +++-- test/common/Makefile | 1 + test/common/cread.c | 48 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 52 insertions(+), 2 deletions(-) create mode 100644 test/common/cread.c
diff --git a/common/cli_getch.c b/common/cli_getch.c index 87c23edcf4b..61d4cb261b8 100644 --- a/common/cli_getch.c +++ b/common/cli_getch.c @@ -129,7 +129,7 @@ static int cli_ch_esc(struct cli_ch_state *cch, int ichar,
*actp = act;
return act == ESC_CONVERTED ? ichar : 0;
return ichar;
}
int cli_ch_process(struct cli_ch_state *cch, int ichar) @@ -145,6 +145,7 @@ int cli_ch_process(struct cli_ch_state *cch, int ichar) return cch->esc_save[cch->emit_upto++]; cch->emit_upto = 0; cch->emitting = false;
cch->esc_len = 0; } return 0; } else if (ichar == -ETIMEDOUT) {
@@ -185,7 +186,7 @@ int cli_ch_process(struct cli_ch_state *cch, int ichar) cch->esc_save[cch->esc_len++] = ichar; ichar = cch->esc_save[cch->emit_upto++]; cch->emitting = true;
break;
return ichar; case ESC_CONVERTED: /* valid escape sequence, return the resulting char */ cch->esc_len = 0;
diff --git a/test/common/Makefile b/test/common/Makefile index cc918f64e54..a5ab10f6f69 100644 --- a/test/common/Makefile +++ b/test/common/Makefile @@ -3,3 +3,4 @@ obj-y += cmd_ut_common.o obj-$(CONFIG_AUTOBOOT) += test_autoboot.o obj-$(CONFIG_CYCLIC) += cyclic.o obj-$(CONFIG_EVENT) += event.o +obj-y += cread.o diff --git a/test/common/cread.c b/test/common/cread.c new file mode 100644 index 00000000000..3dce4bdb0ef --- /dev/null +++ b/test/common/cread.c @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: GPL-2.0+ +/*
- Copyright 2023 Google LLC
- */
+#include <common.h> +#include <cli.h> +#include <test/common.h> +#include <test/test.h> +#include <test/ut.h>
+static int cli_ch_test(struct unit_test_state *uts) +{
struct cli_ch_state s_cch, *cch = &s_cch;
cli_ch_init(cch);
/* should be nothing to return at first */
ut_asserteq(0, cli_ch_process(cch, 0));
/* check normal entry */
ut_asserteq('a', cli_ch_process(cch, 'a'));
ut_asserteq('b', cli_ch_process(cch, 'b'));
ut_asserteq('c', cli_ch_process(cch, 'c'));
ut_asserteq(0, cli_ch_process(cch, 0));
/* send an invalid escape sequence */
ut_asserteq(0, cli_ch_process(cch, '\e'));
ut_asserteq(0, cli_ch_process(cch, '['));
/*
* with the next char it sees that the sequence is invalid, so starts
* emitting it
*/
ut_asserteq('\e', cli_ch_process(cch, 'X'));
/* now we set 0 bytes to empty the buffer */
ut_asserteq('[', cli_ch_process(cch, 0));
ut_asserteq('X', cli_ch_process(cch, 0));
ut_asserteq(0, cli_ch_process(cch, 0));
/* things are normal again */
ut_asserteq('a', cli_ch_process(cch, 'a'));
ut_asserteq(0, cli_ch_process(cch, 0));
return 0;
+} +COMMON_TEST(cli_ch_test, 0);

On 2/6/23 18:12, Simon Glass wrote:
Hi Heinrich,
On Sun, 5 Feb 2023 at 14:29, Heinrich Schuchardt xypron.glpk@gmx.de wrote:
Am 5. Februar 2023 03:08:54 MEZ schrieb Simon Glass sjg@chromium.org:
This function does not behave as expected when unknown escape sequences are sent to it:
- it fails to store (and thus echo) the last character of the invalid sequence
What would be the benefit of echoing it? Is there any reason to assume that we want an output for an escape sequence that we fail to parse?
Yes. In fact we don't even know if it is invalid or not. It is just that U-Boot doesn't support it. The mimics the behaviour we used to have, I believe.
We should assume that escape sequences are valid as they are either coming from our drivers or passed from a terminal emulation to our serial console.
When I open the eficonfig command and in the main menu I press <PG-UP> the program returns to the command line and a ~ is printed. The expected behavior is that <PG-UP> is ignored.
This is the escape sequence: 1b 5b 35 7e
cli_ch_process(0x1b) returns 0. cli_ch_process(0x5b) returns 0. cli_ch_process(0x35) returns 0x1b.
This 0x1b is converted to BKEY_QUIT in bootmenu_conv_key(). For unknown escape sequences cli_ch_process() should return 0 and set cch->esc_len = 0 to minimize interference with the caller.
Mapping control sequences to characters 0x01-0x1b in cli_ch_esc() is not viable: F1-F12 with combined with SHIFT, ALT, CTRL, META already require 192 different values for representation. But that is stuff for future patches.
Best regards
Heinrich
Regards, Simon
Best regards
Heinrich
- it fails to set esc_len to 0 when it finishes emitting the invalid sequence, meaning that the following character will appear to be part of a new escape sequence
- it processes the first character of the rejected sequence as a valid character, just starting the sequence all over again
The last two bugs conspire to produce an "impossible condition #876" message which is the main symptom of this behaviour.
Fix these bugs and add a test to verify the behaviour.
Signed-off-by: Simon Glass sjg@chromium.org Reported-by: Heinrich Schuchardt xypron.glpk@gmx.de
(no changes since v1)
common/cli_getch.c | 5 +++-- test/common/Makefile | 1 + test/common/cread.c | 48 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 52 insertions(+), 2 deletions(-) create mode 100644 test/common/cread.c
diff --git a/common/cli_getch.c b/common/cli_getch.c index 87c23edcf4b..61d4cb261b8 100644 --- a/common/cli_getch.c +++ b/common/cli_getch.c @@ -129,7 +129,7 @@ static int cli_ch_esc(struct cli_ch_state *cch, int ichar,
*actp = act;
return act == ESC_CONVERTED ? ichar : 0;
return ichar;
}
int cli_ch_process(struct cli_ch_state *cch, int ichar) @@ -145,6 +145,7 @@ int cli_ch_process(struct cli_ch_state *cch, int ichar) return cch->esc_save[cch->emit_upto++]; cch->emit_upto = 0; cch->emitting = false;
cch->esc_len = 0; } return 0; } else if (ichar == -ETIMEDOUT) {
@@ -185,7 +186,7 @@ int cli_ch_process(struct cli_ch_state *cch, int ichar) cch->esc_save[cch->esc_len++] = ichar; ichar = cch->esc_save[cch->emit_upto++]; cch->emitting = true;
break;
return ichar; case ESC_CONVERTED: /* valid escape sequence, return the resulting char */ cch->esc_len = 0;
diff --git a/test/common/Makefile b/test/common/Makefile index cc918f64e54..a5ab10f6f69 100644 --- a/test/common/Makefile +++ b/test/common/Makefile @@ -3,3 +3,4 @@ obj-y += cmd_ut_common.o obj-$(CONFIG_AUTOBOOT) += test_autoboot.o obj-$(CONFIG_CYCLIC) += cyclic.o obj-$(CONFIG_EVENT) += event.o +obj-y += cread.o diff --git a/test/common/cread.c b/test/common/cread.c new file mode 100644 index 00000000000..3dce4bdb0ef --- /dev/null +++ b/test/common/cread.c @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: GPL-2.0+ +/*
- Copyright 2023 Google LLC
- */
+#include <common.h> +#include <cli.h> +#include <test/common.h> +#include <test/test.h> +#include <test/ut.h>
+static int cli_ch_test(struct unit_test_state *uts) +{
struct cli_ch_state s_cch, *cch = &s_cch;
cli_ch_init(cch);
/* should be nothing to return at first */
ut_asserteq(0, cli_ch_process(cch, 0));
/* check normal entry */
ut_asserteq('a', cli_ch_process(cch, 'a'));
ut_asserteq('b', cli_ch_process(cch, 'b'));
ut_asserteq('c', cli_ch_process(cch, 'c'));
ut_asserteq(0, cli_ch_process(cch, 0));
/* send an invalid escape sequence */
ut_asserteq(0, cli_ch_process(cch, '\e'));
ut_asserteq(0, cli_ch_process(cch, '['));
/*
* with the next char it sees that the sequence is invalid, so starts
* emitting it
*/
ut_asserteq('\e', cli_ch_process(cch, 'X'));
/* now we set 0 bytes to empty the buffer */
ut_asserteq('[', cli_ch_process(cch, 0));
ut_asserteq('X', cli_ch_process(cch, 0));
ut_asserteq(0, cli_ch_process(cch, 0));
/* things are normal again */
ut_asserteq('a', cli_ch_process(cch, 'a'));
ut_asserteq(0, cli_ch_process(cch, 0));
return 0;
+} +COMMON_TEST(cli_ch_test, 0);

Hi Heinrich,
On Mon, 6 Feb 2023 at 15:18, Heinrich Schuchardt xypron.glpk@gmx.de wrote:
On 2/6/23 18:12, Simon Glass wrote:
Hi Heinrich,
On Sun, 5 Feb 2023 at 14:29, Heinrich Schuchardt xypron.glpk@gmx.de wrote:
Am 5. Februar 2023 03:08:54 MEZ schrieb Simon Glass sjg@chromium.org:
This function does not behave as expected when unknown escape sequences are sent to it:
- it fails to store (and thus echo) the last character of the invalid sequence
What would be the benefit of echoing it? Is there any reason to assume that we want an output for an escape sequence that we fail to parse?
Yes. In fact we don't even know if it is invalid or not. It is just that U-Boot doesn't support it. The mimics the behaviour we used to have, I believe.
We should assume that escape sequences are valid as they are either coming from our drivers or passed from a terminal emulation to our serial console.
When I open the eficonfig command and in the main menu I press <PG-UP> the program returns to the command line and a ~ is printed. The expected behavior is that <PG-UP> is ignored.
This is the escape sequence: 1b 5b 35 7e
cli_ch_process(0x1b) returns 0. cli_ch_process(0x5b) returns 0. cli_ch_process(0x35) returns 0x1b.
This 0x1b is converted to BKEY_QUIT in bootmenu_conv_key(). For unknown escape sequences cli_ch_process() should return 0 and set cch->esc_len = 0 to minimize interference with the caller.
Hmmm...is that the behaviour we had before my patches? If so, I have made a mistake.
Mapping control sequences to characters 0x01-0x1b in cli_ch_esc() is not viable: F1-F12 with combined with SHIFT, ALT, CTRL, META already require 192 different values for representation. But that is stuff for future patches.
Indeed.
Regards, Simon [..]
participants (2)
-
Heinrich Schuchardt
-
Simon Glass