]> git.ipfire.org Git - thirdparty/coreutils.git/commitdiff
stat: do not rely on undefined behavior in printf formats
authorPaul Eggert <eggert@cs.ucla.edu>
Sat, 6 Nov 2010 20:57:08 +0000 (13:57 -0700)
committerPaul Eggert <eggert@cs.ucla.edu>
Sun, 7 Nov 2010 01:09:44 +0000 (18:09 -0700)
* src/stat.c (digits, printf_flags): New static vars.
(make_format): New function.
(out_string, out_int, out_uint, out_uint_o, out_uint_x):
(out_minus_zero): Use it to avoid undefined behavior when invoking
printf.
(print_it): Check for invalid conversion specifications such as
%..X and %1-X, which would otherwise rely on undefined behavior
when invoking printf.
* tests/misc/stat-nanoseconds: Check that the "I" printf flag
doesn't mess up in the C locale, as it formerly did on non-GNU
hosts.

src/stat.c
tests/misc/stat-nanoseconds

index 99f115bca602a87c4cc3d3954b5c0a9c53ab587e..ae7ce021f888179d8bb6c25f83b67859b6339c87 100644 (file)
@@ -150,6 +150,16 @@ statfs (char const *filename, struct fs_info *buf)
 #define hextobin(c) ((c) >= 'a' && (c) <= 'f' ? (c) - 'a' + 10 : \
                      (c) >= 'A' && (c) <= 'F' ? (c) - 'A' + 10 : (c) - '0')
 
+static char const digits[] = "0123456789";
+
+/* Flags that are portable for use in printf, for at least one
+   conversion specifier; make_format removes unportable flags as
+   needed for particular specifiers.  The glibc 2.2 extension "I" is
+   listed here; it is removed by make_format because it has undefined
+   behavior elsewhere and because it is incompatible with
+   out_epoch_sec.  */
+static char const printf_flags[] = "'-+ #0I";
+
 #define PROGRAM_NAME "stat"
 
 #define AUTHORS proper_name ("Michael Meskes")
@@ -467,40 +477,59 @@ human_time (struct timespec t)
   return str;
 }
 
+/* PFORMAT points to a '%' followed by a prefix of a format, all of
+   size PREFIX_LEN.  The flags allowed for this format are
+   ALLOWED_FLAGS; remove other printf flags from the prefix, then
+   append SUFFIX.  */
+static void
+make_format (char *pformat, size_t prefix_len, char const *allowed_flags,
+             char const *suffix)
+{
+  char *dst = pformat + 1;
+  char const *src;
+  char const *srclim = pformat + prefix_len;
+  for (src = dst; src < srclim && strchr (printf_flags, *src); src++)
+    if (strchr (allowed_flags, *src))
+      *dst++ = *src;
+  while (src < srclim)
+    *dst++ = *src++;
+  strcpy (dst, suffix);
+}
+
 static void
 out_string (char *pformat, size_t prefix_len, char const *arg)
 {
-  strcpy (pformat + prefix_len, "s");
+  make_format (pformat, prefix_len, "-", "s");
   printf (pformat, arg);
 }
 static int
 out_int (char *pformat, size_t prefix_len, intmax_t arg)
 {
-  strcpy (pformat + prefix_len, PRIdMAX);
+  make_format (pformat, prefix_len, "'-+ 0", PRIdMAX);
   return printf (pformat, arg);
 }
 static int
 out_uint (char *pformat, size_t prefix_len, uintmax_t arg)
 {
-  strcpy (pformat + prefix_len, PRIuMAX);
+  make_format (pformat, prefix_len, "'-0", PRIuMAX);
   return printf (pformat, arg);
 }
 static void
 out_uint_o (char *pformat, size_t prefix_len, uintmax_t arg)
 {
-  strcpy (pformat + prefix_len, PRIoMAX);
+  make_format (pformat, prefix_len, "-#0", PRIoMAX);
   printf (pformat, arg);
 }
 static void
 out_uint_x (char *pformat, size_t prefix_len, uintmax_t arg)
 {
-  strcpy (pformat + prefix_len, PRIxMAX);
+  make_format (pformat, prefix_len, "-#0", PRIxMAX);
   printf (pformat, arg);
 }
 static int
 out_minus_zero (char *pformat, size_t prefix_len)
 {
-  strcpy (pformat + prefix_len, ".0f");
+  make_format (pformat, prefix_len, "'-+ 0", ".0f");
   return printf (pformat, -0.25);
 }
 
@@ -1028,8 +1057,12 @@ print_it (char const *format, char const *filename,
         {
         case '%':
           {
-            size_t len = strspn (b + 1, "#-+.I 0123456789");
+            size_t len = strspn (b + 1, printf_flags);
             char const *fmt_char = b + len + 1;
+            fmt_char += strspn (fmt_char, digits);
+            if (*fmt_char == '.')
+              fmt_char += 1 + strspn (fmt_char + 1, digits);
+            len = fmt_char - (b + 1);
             unsigned int fmt_code = *fmt_char;
             memcpy (dest, b, len + 1);
 
index 0f41eb04521f6cdcce5e217ac8622ab9c680baec..cd215964465c349841b07578d59509e30ac8c643 100755 (executable)
@@ -39,6 +39,7 @@ test "$(stat -c   %13.6X k)" =  ' 67413.023456'       || fail=1
 test "$(stat -c  %013.6X k)" =   067413.023456        || fail=1
 test "$(stat -c  %-13.6X k)" =   '67413.023456 '      || fail=1
 test "$(stat -c  %18.10X k)" = '  67413.0234567890'   || fail=1
+test "$(stat -c %I18.10X k)" = '  67413.0234567890'   || fail=1
 test "$(stat -c %018.10X k)" =  0067413.0234567890    || fail=1
 test "$(stat -c %-18.10X k)" =   '67413.0234567890  ' || fail=1