sbase

suckless unix tools
git clone git://git.suckless.org/sbase
Log | Files | Refs | README | LICENSE

commit d77328ca4d1a7aab1905a85faea8bacdcb0cb293
parent 4593bfb3596df8e69a178329283c9d07ed0eddbd
Author: Santtu Lakkala <inz@inz.fi>
Date:   Wed, 12 Nov 2025 17:49:54 +0200

printf: Fix multiple flags and read overflow

Support having multiple flags for a single conversion; at least '+'/'#' and
'0' are not exclusive.

Further use strspn() instead of inlined version for correct handling of
string ending with allowed characters.

Diffstat:
Mprintf.c | 37+++++++++++++++++++------------------
Atests/0002-printf.sh | 44++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 63 insertions(+), 18 deletions(-)

diff --git a/printf.c b/printf.c @@ -19,11 +19,11 @@ int main(int argc, char *argv[]) { Rune *rarg; - size_t i, j, argi, lastargi, formatlen, blen; + size_t i, j, f, argi, lastargi, formatlen, blen, nflags; long long num; double dou; int cooldown = 0, width, precision, ret = 0; - char *format, *tmp, *arg, *fmt, flag; + char *format, *tmp, *arg, *fmt; argv0 = argv[0]; if (argc < 2) @@ -44,15 +44,19 @@ main(int argc, char *argv[]) break; lastargi = argi; } + if (format[i] != '%') { putchar(format[i]); continue; } /* flag */ - for (flag = '\0', i++; strchr("#-+ 0", format[i]); i++) { - flag = format[i]; - } + f = ++i; + nflags = strspn(&format[f], "#-+ 0"); + i += nflags; + + if (nflags > INT_MAX) + eprintf("Too many flags in format\n"); /* field width */ width = -1; @@ -64,7 +68,7 @@ main(int argc, char *argv[]) i++; } else { j = i; - for (; strchr("+-0123456789", format[i]); i++); + i += strspn(&format[i], "+-0123456789"); if (j != i) { tmp = estrndup(format + j, i - j); width = estrtonum(tmp, 0, INT_MAX); @@ -85,7 +89,7 @@ main(int argc, char *argv[]) i++; } else { j = i; - for (; strchr("+-0123456789", format[i]); i++); + i += strspn(&format[i], "+-0123456789"); if (j != i) { tmp = estrndup(format + j, i - j); precision = estrtonum(tmp, 0, INT_MAX); @@ -127,9 +131,8 @@ main(int argc, char *argv[]) free(rarg); break; case 's': - fmt = estrdup(flag ? "%#*.*s" : "%*.*s"); - if (flag) - fmt[1] = flag; + fmt = emalloc(sizeof("%*.*s" + nflags)); + sprintf(fmt, "%%%.*s*.*s", (int)nflags, &format[f]); printf(fmt, width, precision, arg); free(fmt); break; @@ -161,22 +164,20 @@ main(int argc, char *argv[]) } else { num = 0; } - fmt = estrdup(flag ? "%#*.*ll#" : "%*.*ll#"); - if (flag) - fmt[1] = flag; - fmt[flag ? 7 : 6] = format[i]; + fmt = emalloc(sizeof("%*.*ll#") + nflags); + sprintf(fmt, "%%%.*s*.*ll%c", (int)nflags, &format[f], format[i]); printf(fmt, width, precision, num); free(fmt); break; case 'a': case 'A': case 'e': case 'E': case 'f': case 'F': case 'g': case 'G': - fmt = estrdup(flag ? "%#*.*#" : "%*.*#"); - if (flag) - fmt[1] = flag; - fmt[flag ? 5 : 4] = format[i]; + fmt = emalloc(sizeof("%*.*#") + nflags); + sprintf(fmt, "%%%.*s*.*%c", (int)nflags, &format[f], format[i]); dou = (strlen(arg) > 0) ? estrtod(arg) : 0; printf(fmt, width, precision, dou); free(fmt); break; + case '\0': + eprintf("Missing format specifier.\n"); default: eprintf("Invalid format specifier '%c'.\n", format[i]); } diff --git a/tests/0002-printf.sh b/tests/0002-printf.sh @@ -0,0 +1,44 @@ +#!/bin/sh + +set -e + +exp1=exp1.$$ +exp2=exp2.$$ +res1=res1.$$ +res2=res2.$$ + +cleanup() +{ + st=$? + rm -f $exp1 $exp2 $res1 $res2 + exit $st +} + +trap cleanup EXIT HUP INT TERM + +cat <<'EOF' > $exp1 +123 +0 +foo +bar ++001 +2 +003 -400 +Expected failure +EOF + +cat <<'EOF' > $exp2 +../printf: Missing format specifier. +EOF + +( + ../printf '123\n' + ../printf '%d\n' + ../printf '%b' 'foo\nbar\n' + + # Two flags used simulatenously, + and 0 + ../printf '%+04d %+4d ' 1 2 3 -400; ../printf "\n" + # Missing format specifier; should have sane error message + ../printf '%000' FOO || echo "Expected failure" +) > $res1 2> $res2 + +diff -u $exp1 $res1 +diff -u $exp2 $res2