Blob Blame History Raw
This is a custom patch for RHEL 7 to fix CVE-2020-29573 and includes
parts of 41290b6e842a2adfbda77a49abfacb0db2d63bfb, and
681900d29683722b1cb0a8e565a0585846ec5a61.

We had a discussion[1] upstream about the treatment of unnormal long
double numbers in glibc and gcc and there is general consensus that
unnormal numbers (pseudos in general) ought to be treated like NaNs
without the guarantee that they will always be treated correctly in
glibc.  That is, there is agreement that we should fix bugs and
security issues arising from such inputs but not guarantee glibc
behaviour with such inputs since the latter would involve extensive
coverage.

Now on to #1869380, this crash in printf manifests itself only in
RHEL-7 and not in any other Red Hat distribution because later
versions of glibc use __builtin_nan from gcc, which always recognizes
pseudos as NaN.  Based on that and the recent consensus, the correct
way to fix #1869380 appears to be to treat unnormals as NaN instead of
fixing the unnormal representation as in this patch[2].

[1] https://sourceware.org/pipermail/libc-alpha/2020-November/119949.html
[2] https://sourceware.org/pipermail/libc-alpha/2020-September/117779.html

Co-authored-by: Siddhesh Poyarekar <siddhesh@redhat.com>

diff --git a/stdio-common/printf_fp.c b/stdio-common/printf_fp.c
index d0e082494af6b0a3..60b143571065a082 100644
--- a/stdio-common/printf_fp.c
+++ b/stdio-common/printf_fp.c
@@ -151,6 +151,28 @@ static wchar_t *group_number (wchar_t *buf, wchar_t *bufend,
 			      wchar_t thousands_sep, int ngroups)
      internal_function;
 
+static __always_inline int
+isnanl_or_pseudo (long double in)
+{
+#if defined __x86_64__ || defined __i386__
+  union
+    {
+      long double f;
+      struct
+	{
+	  uint64_t low;
+	  uint64_t high;
+	} u;
+    } ldouble;
+
+  ldouble.f = in;
+
+  return __isnanl (in) || (ldouble.u.low & 0x8000000000000000) == 0;
+#else
+  return __isnanl (in);
+#endif
+}
+
 
 int
 __printf_fp_l (FILE *fp, locale_t loc,
@@ -335,7 +357,7 @@ __printf_fp_l (FILE *fp, locale_t loc,
 
       /* Check for special values: not a number or infinity.  */
       int res;
-      if (__isnanl (fpnum.ldbl))
+      if (isnanl_or_pseudo (fpnum.ldbl))
 	{
 	  is_neg = signbit (fpnum.ldbl);
 	  if (isupper (info->spec))
diff --git a/sysdeps/x86/Makefile b/sysdeps/x86/Makefile
index c26533245e8a8103..f1da941dbbadadb3 100644
--- a/sysdeps/x86/Makefile
+++ b/sysdeps/x86/Makefile
@@ -18,3 +18,7 @@ sysdep-dl-routines += dl-get-cpu-features
 tests += tst-get-cpu-features
 tests-static += tst-get-cpu-features-static
 endif
+
+ifeq ($(subdir),math)
+tests += tst-ldbl-nonnormal-printf
+endif # $(subdir) == math
diff --git a/sysdeps/x86/tst-ldbl-nonnormal-printf.c b/sysdeps/x86/tst-ldbl-nonnormal-printf.c
new file mode 100644
index 0000000000000000..e4e3e428747488b9
--- /dev/null
+++ b/sysdeps/x86/tst-ldbl-nonnormal-printf.c
@@ -0,0 +1,49 @@
+/* Test printf with x86-specific non-normal long double value.
+   Copyright (C) 2020-2021 Free Software Foundation, Inc.
+
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+#include <stdio.h>
+#include <string.h>
+#include <support/check.h>
+
+/* Fill the stack with non-zero values.  This makes a crash in
+   snprintf more likely.  */
+static void __attribute__ ((noinline, noclone))
+fill_stack (void)
+{
+  char buffer[65536];
+  memset (buffer, 0xc0, sizeof (buffer));
+  asm ("" ::: "memory");
+}
+
+static int
+do_test (void)
+{
+  fill_stack ();
+
+  long double value;
+  memcpy (&value, "\x00\x04\x00\x00\x00\x00\x00\x00\x00\x04", 10);
+
+  char buf[30];
+  int ret = snprintf (buf, sizeof (buf), "%Lg", value);
+  TEST_COMPARE (ret, strlen (buf));
+  TEST_COMPARE_STRING (buf, "nan");
+  return 0;
+}
+
+#include <support/test-driver.c>