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;

Reply via email to