This is an automated email from the ASF dual-hosted git repository. acassis pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/nuttx.git
commit 6d1fa235d69a4f53dab1ff5c70bb46376fa6a11a Author: Marco Casaroli <[email protected]> AuthorDate: Thu Nov 5 18:33:44 2026 +0100 libs/libc/stream: Add a dangerous option for %n in printf(). printf() reads the pointer of %n, stores nothing and prints "?", because a format then cannot write to memory. A program that needs the count can turn on LIBC_PRINTF_PERCENT_N. With it, %n stores the count at the size of its length modifier, and prints nothing. A NULL pointer stores nothing. The option is off in all profiles, also in Full, and its prompt and its help say that it is dangerous. Part of https://github.com/apache/nuttx/issues/21467 Assisted-by: Claude Code:claude-opus-5-5 Signed-off-by: Marco Casaroli <[email protected]> --- libs/libc/stdio/Kconfig | 11 +++++++++ libs/libc/stream/lib_libvsprintf.c | 47 +++++++++++++++++++++++++++++++++++++- 2 files changed, 57 insertions(+), 1 deletion(-) diff --git a/libs/libc/stdio/Kconfig b/libs/libc/stdio/Kconfig index 745977ea366..1ec12282793 100644 --- a/libs/libc/stdio/Kconfig +++ b/libs/libc/stdio/Kconfig @@ -148,6 +148,17 @@ config LIBC_PRINTF_C23 unknown conversions: printf() prints the rest of the format as text and reads no more arguments. +config LIBC_PRINTF_PERCENT_N + bool "Enable %n in printf (dangerous)" + default n + ---help--- + Dangerous. With this option, %n stores the count of the bytes so far + through its pointer, so a format string can write to memory. A format + that an attacker can change then gives a write to any address. Keep it + off unless a program needs it. No profile turns it on. + + Without it, printf() reads the pointer, stores nothing and prints "?". + config LIBC_FLOATINGPOINT bool "Enable floating point in printf" default LIBC_PRINTF_FULL || (!DEFAULT_SMALL && ARCH_FPU && !LIBC_PRINTF_MINIMAL) diff --git a/libs/libc/stream/lib_libvsprintf.c b/libs/libc/stream/lib_libvsprintf.c index eb0d16bf6d3..7c1f789f01a 100644 --- a/libs/libc/stream/lib_libvsprintf.c +++ b/libs/libc/stream/lib_libvsprintf.c @@ -393,6 +393,45 @@ static int vsprintf_putnum(FAR struct lib_outstream_s *stream, return len; } +#ifdef CONFIG_LIBC_PRINTF_PERCENT_N +/* Store the count of %n at the size of its length modifier */ + +static void vsprintf_store(FAR void *ptr, uint16_t flags, size_t count) +{ + if (ptr == NULL) + { + return; + } + + if ((flags & FL_LONG) != 0) + { + if ((flags & FL_REPD_TYPE) != 0) + { + *(FAR long long *)ptr = count; + } + else + { + *(FAR long *)ptr = count; + } + } + else if ((flags & FL_SHORT) != 0) + { + if ((flags & FL_REPD_TYPE) != 0) + { + *(FAR signed char *)ptr = count; + } + else + { + *(FAR short *)ptr = count; + } + } + else + { + *(FAR int *)ptr = count; + } +} +#endif + #ifdef CONFIG_LIBC_PRINTF_C23 /* Parse the rest of wN or wfN, and set the flags of the length that has this * size. Return false for any other N, with fmt after the w. @@ -1567,10 +1606,16 @@ put_unavailable: pnt = va_arg(ap, FAR char *); } - /* %n only takes its pointer: a format never writes to memory */ + /* %n only takes its pointer: a format never writes to memory, + * unless the option says so. + */ if (c == 'n') { +#ifdef CONFIG_LIBC_PRINTF_PERCENT_N + vsprintf_store((FAR void *)pnt, flags, total_len); + continue; +#endif pnt = "?"; size = 1; goto str_lpad;
