Traditional sscanf ignores trailing unmatched characters,
because it derives from design of streaming fscanf().

Without extra care this leads to confusing parsing results:
"%d" successfully parses "0x1" as 0 and "1M" as 1.

Silent ignore of trailing input also could hide bugs and
unexpectedly break backward compatibility at any change.

This patch adds end of text matching into sscanf format by
ASCII End-Of-Transfer character '\004' (EOT aka ^D).

EOT stops matching and if input text haven't ended and adds
flag SCANF_MORE to the result value. Result stays positive.

EOT allows to rephrase common end of text checks like:

int end;
if (sscanf(buf, fmt "%n", ..., &end) == NR && !buf[end])

char dummy;
if (sscanf(buf, fmt "%c", ..., &dummy) == NR - 1)

purely in format string without extra code or arguments:

if (sscanf(buf, fmt KERN_EOT, ...) == NR)

This checks that text ends strictly after previous matched character.
For skipping trailing white spaces simply add space before EOT.

Signed-off-by: Konstantin Khlebnikov <khlebni...@yandex-team.ru>
---
 include/linux/kernel.h |    6 ++++++
 lib/vsprintf.c         |   11 ++++++++++-
 2 files changed, 16 insertions(+), 1 deletion(-)

diff --git a/include/linux/kernel.h b/include/linux/kernel.h
index 34a5036debd3..32726b25eaa5 100644
--- a/include/linux/kernel.h
+++ b/include/linux/kernel.h
@@ -476,6 +476,12 @@ char *kvasprintf(gfp_t gfp, const char *fmt, va_list args);
 extern __printf(2, 0)
 const char *kvasprintf_const(gfp_t gfp, const char *fmt, va_list args);
 
+#define KERN_EOT       "\004"  /* ASCII End Of Transfer */
+#define KERN_EOT_ASCII '\004'
+
+/* sscanf adds this to the result if EOT does not match end of text */
+#define SCANF_MORE     (1 << 30)
+
 extern __scanf(2, 3)
 int sscanf(const char *, const char *, ...);
 extern __scanf(2, 0)
diff --git a/lib/vsprintf.c b/lib/vsprintf.c
index ada0501f1525..66debf42387a 100644
--- a/lib/vsprintf.c
+++ b/lib/vsprintf.c
@@ -3005,7 +3005,8 @@ EXPORT_SYMBOL_GPL(bprintf);
  * @fmt:       format of buffer
  * @args:      arguments
  *
- * Returns number of successfully matched arguments.
+ * Returns number of successfully matched arguments,
+ * plus SCANF_MORE bit flag if EOT does not matched end of text.
  *
  * This function implements only basic vsscanf features:
  *
@@ -3027,6 +3028,8 @@ EXPORT_SYMBOL_GPL(bprintf);
  * - %s without field width limited with SHRT_MAX
  * - "%*..." simply skips non white-space characters without conversion
  * - integer overflows are handled as matching failure
+ * - KERN_EOT ("\004") matches end of string otherwise stops parsing and
+ *   returns count matched arguments plus SCANF_MORE bit flag,
  */
 int vsscanf(const char *buf, const char *fmt, va_list args)
 {
@@ -3055,6 +3058,12 @@ int vsscanf(const char *buf, const char *fmt, va_list 
args)
 
                /* anything that is not a conversion must match exactly */
                if (*fmt != '%' && *fmt) {
+                       /* EOT in format string: text must end here */
+                       if (*fmt == KERN_EOT_ASCII) {
+                               if (*str)
+                                       num |= SCANF_MORE;
+                               break;
+                       }
                        if (*fmt++ != *str++)
                                break;
                        continue;

Reply via email to