[hackers] [sbase][PATCH v2] printf: Fix multiple flags and read overflow

From: Santtu Lakkala <inz_AT_inz.fi>
Date: Wed, 12 Nov 2025 17:49:54 +0200

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.
---
 printf.c             | 37 +++++++++++++++++++------------------
 tests/0002-printf.sh | 44 ++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 63 insertions(+), 18 deletions(-)
 create mode 100755 tests/0002-printf.sh
diff --git a/printf.c b/printf.c
index 039dac7..e36a14f 100644
--- a/printf.c
+++ b/printf.c
_AT_@ -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)
_AT_@ -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;
_AT_@ -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);
_AT_@ -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);
_AT_@ -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;
_AT_@ -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
new file mode 100755
index 0000000..6384fe2
--- /dev/null
+++ b/tests/0002-printf.sh
_AT_@ -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
-- 
2.42.0
Received on Wed Nov 12 2025 - 16:49:54 CET

This archive was generated by hypermail 2.3.0 : Wed Nov 12 2025 - 17:00:37 CET