From 377709664480a30fa5acdd11c7ca8c16669678ce Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Wed, 6 Sep 2023 14:59:59 -0400 Subject: bfstd: Fix an OOB string index in xmbrtowc() This bug could be reproduced with something like $ bfs -samefile $'\xFA\xFA' bfs: error: bfs: dstrnescat@src/dstring.c:252: wordesc() result truncated or worse, with -DNDEBUG, $ bfs -samefile $'.....................\xFA\xFA' bfs: error: bfs -samefile $'.....................\xFA\xFA\x00\x55\x53\x45\x52\x3D\x74\x61\x76\x69\x61\x6E\x61\x74\x6F\x72 bfs: error: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ bfs: error: No such file or directory. which prints the memory after the end of the string (in this case, the environment variable USER=tavianator). The bug was caused by the line `*i += len`, which was intended to be `*i = len`. But actually, the right behaviour seems to be `*i += 1`. Fixes: 19c96abe0a1ee56cf206fd5e87defb1fd3e0daa5 --- src/bfstd.c | 8 ++------ tests/bfstd.c | 18 ++++++++++++++++++ tests/common/samefile_wordesc.sh | 4 ++++ 3 files changed, 24 insertions(+), 6 deletions(-) create mode 100644 tests/common/samefile_wordesc.sh diff --git a/src/bfstd.c b/src/bfstd.c index e546a47..2d9f60a 100644 --- a/src/bfstd.c +++ b/src/bfstd.c @@ -546,15 +546,11 @@ int xstrtofflags(const char **str, unsigned long long *set, unsigned long long * static int xmbrtowc(wchar_t *wc, size_t *i, const char *str, size_t len, mbstate_t *mb) { size_t mblen = mbrtowc(wc, str + *i, len - *i, mb); switch (mblen) { - case -1: - // Invalid byte sequence + case -1: // Invalid byte sequence + case -2: // Incomplete byte sequence *i += 1; memset(mb, 0, sizeof(*mb)); return -1; - case -2: - // Incomplete byte sequence - *i += len; - return -1; default: *i += mblen; return 0; diff --git a/tests/bfstd.c b/tests/bfstd.c index fa854a8..2db084a 100644 --- a/tests/bfstd.c +++ b/tests/bfstd.c @@ -23,6 +23,15 @@ static void check_base_dir(const char *path, const char *dir, const char *base) free(xbase); } +/** Check the result of wordesc(). */ +static void check_wordesc(const char *str, const char *exp, enum wesc_flags flags) { + char buf[256]; + char *end = buf + sizeof(buf); + char *ret = wordesc(buf, end, str, flags); + bfs_verify(ret != end); + bfs_verify(strcmp(buf, exp) == 0, "wordesc(%s) == %s (!= %s)", str, buf, exp); +} + int main(void) { // From man 3p basename check_base_dir("usr", ".", "usr"); @@ -36,5 +45,14 @@ int main(void) { check_base_dir("//usr//lib//", "//usr", "lib"); check_base_dir("/home//dwc//test", "/home//dwc", "test"); + check_wordesc("", "\"\"", WESC_SHELL); + check_wordesc("word", "word", WESC_SHELL); + check_wordesc("two words", "\"two words\"", WESC_SHELL); + check_wordesc("word's", "\"word's\"", WESC_SHELL); + check_wordesc("\"word\"", "'\"word\"'", WESC_SHELL); + check_wordesc("\"word's\"", "'\"word'\\''s\"'", WESC_SHELL); + check_wordesc("\033[1mbold's\033[0m", "$'\\e[1mbold\\'s\\e[0m'", WESC_SHELL | WESC_TTY); + check_wordesc("\x7F", "$'\\x7F'", WESC_SHELL | WESC_TTY); + return EXIT_SUCCESS; } diff --git a/tests/common/samefile_wordesc.sh b/tests/common/samefile_wordesc.sh new file mode 100644 index 0000000..b5d158f --- /dev/null +++ b/tests/common/samefile_wordesc.sh @@ -0,0 +1,4 @@ +# Regression test: don't abort on incomplete UTF-8 sequences +export LC_ALL=$(locale -a | grep -Ei 'utf-?8$' | head -n1) +test -n "$LC_ALL" || skip +! invoke_bfs -samefile $'\xFA\xFA' -- cgit v1.2.3