A few issues with handling of the new %ls, %lc and %Q conversion specifications:
The %ls and %lc conversion specifications dereference a null pointer if used without an argument: $ (printf %ls) Segmentation fault: 11 $ (printf %lc) Segmentation fault: 11 The Q conversion specifier's precision handling applies when precision is provided as part of the format string, but not when it comes form an argument: $ printf '%.1Q\n' '**' \* $ printf '%.*Q\n' 1 '**' \*\* There is code to handle integer overflow for precision values by capping values to INT_MAX but it's broken for the %Q conversion specifier due to reading the value into an int rather than an intmax_t prior to overflow checks being applied: $ INT_MAX=$(getconf INT_MAX) OVERFLOW=$((INT_MAX*2 + 3)) $ printf "%.${OVERFLOW}s" XY XY $ printf "%.${OVERFLOW}Q" XY X A precision value of 0 is ignored for the %ls conversion specification: $ printf '[%.0s][%.0ls]' X Y [][Y] A null argument to the %lc conversion specifier does not produce a null byte. Per discussion in [1], Bash's behavior matches the standard as it was written, but this is now considered a defect. $ printf '[%c][%lc]' '' '' | cat -v [^@][] [1] https://austingroupbugs.net/view.php?id=1647 --- builtins/printf.def | 38 ++++++++++++++++++++++++++------------ 1 file changed, 26 insertions(+), 12 deletions(-) diff --git a/builtins/printf.def b/builtins/printf.def index ae6c75c5..6d3fd9a4 100644 --- a/builtins/printf.def +++ b/builtins/printf.def @@ -252,12 +252,13 @@ static size_t conv_bufsize; static inline int decodeprec (char *ps) { - int mpr; + intmax_t mpr; mpr = *ps++ - '0'; while (DIGIT (*ps)) mpr = (mpr * 10) + (*ps++ - '0'); - return mpr; + /* Error if precision > INT_MAX here? */ + return (mpr < 0 || mpr > INT_MAX) ? INT_MAX : mpr; } int @@ -490,7 +491,10 @@ printf_builtin (WORD_LIST *list) ws[0] = wc; ws[1] = L'\0'; - r = printwidestr (start, ws, 1, fieldwidth, precision); + if (wc == L'\0') + r = printstr (start, ws, 1, fieldwidth, precision); + else + r = printwidestr (start, ws, 1, fieldwidth, precision); if (r < 0) PRETURN (EXECUTION_FAILURE); break; @@ -514,7 +518,7 @@ printf_builtin (WORD_LIST *list) wp = getwidestr (&slen); r = printwidestr (start, wp, slen, fieldwidth, precision); - free (wp); + FREE (wp); if (r < 0) PRETURN (EXECUTION_FAILURE); break; @@ -647,20 +651,19 @@ printf_builtin (WORD_LIST *list) case 'Q': { char *p, *xp; - int r, mpr; + int r; size_t slen; r = 0; p = getstr (); /* Decode precision and apply it to the unquoted string. */ - if (convch == 'Q' && precstart) + if (convch == 'Q' && (have_precision || precstart)) { - mpr = decodeprec (precstart); - /* Error if precision > INT_MAX here? */ - precision = (mpr < 0 || mpr > INT_MAX) ? INT_MAX : mpr; + if (precstart) + precision = decodeprec (precstart); slen = strlen (p); /* printf precision works in bytes. */ - if (precision < slen) + if (precision >= 0 && precision < slen) p[precision] = '\0'; } if (p && *p == 0) /* XXX - getstr never returns null */ @@ -1517,6 +1520,14 @@ getwidestr (size_t *lenp) wchar_t *ws; const char *mbs; size_t slen, mblength; + + if (garglist == 0) + { + if (lenp) + *lenp = 0; + return NULL; + } + DECLARE_MBSTATE; mbs = garglist->word->word; @@ -1545,8 +1556,11 @@ getwidechar (void) { wchar_t wc; size_t slen, mblength; - DECLARE_MBSTATE; + if (garglist == 0) + return L'\0'; + + DECLARE_MBSTATE; wc = 0; mblength = mbrtowc (&wc, garglist->word->word, locale_mb_cur_max, &state); if (MB_INVALIDCH (mblength)) @@ -1571,7 +1585,7 @@ convwidestr (wchar_t *ws, int prec) ts = (const wchar_t *)ws; - if (prec > 0) + if (prec >= 0) { rsize = prec * MB_CUR_MAX; ret = (char *)xmalloc (rsize + 1); -- 2.42.0