0d8b67
This is a custom patch for RHEL 7 to fix CVE-2020-29573 and includes
0d8b67
parts of 41290b6e842a2adfbda77a49abfacb0db2d63bfb, and
0d8b67
681900d29683722b1cb0a8e565a0585846ec5a61.
0d8b67
0d8b67
We had a discussion[1] upstream about the treatment of unnormal long
0d8b67
double numbers in glibc and gcc and there is general consensus that
0d8b67
unnormal numbers (pseudos in general) ought to be treated like NaNs
0d8b67
without the guarantee that they will always be treated correctly in
0d8b67
glibc.  That is, there is agreement that we should fix bugs and
0d8b67
security issues arising from such inputs but not guarantee glibc
0d8b67
behaviour with such inputs since the latter would involve extensive
0d8b67
coverage.
0d8b67
0d8b67
Now on to #1869380, this crash in printf manifests itself only in
0d8b67
RHEL-7 and not in any other Red Hat distribution because later
0d8b67
versions of glibc use __builtin_nan from gcc, which always recognizes
0d8b67
pseudos as NaN.  Based on that and the recent consensus, the correct
0d8b67
way to fix #1869380 appears to be to treat unnormals as NaN instead of
0d8b67
fixing the unnormal representation as in this patch[2].
0d8b67
0d8b67
[1] https://sourceware.org/pipermail/libc-alpha/2020-November/119949.html
0d8b67
[2] https://sourceware.org/pipermail/libc-alpha/2020-September/117779.html
0d8b67
0d8b67
Co-authored-by: Siddhesh Poyarekar <siddhesh@redhat.com>
0d8b67
0d8b67
diff --git a/stdio-common/printf_fp.c b/stdio-common/printf_fp.c
0d8b67
index d0e082494af6b0a3..60b143571065a082 100644
0d8b67
--- a/stdio-common/printf_fp.c
0d8b67
+++ b/stdio-common/printf_fp.c
0d8b67
@@ -151,6 +151,28 @@ static wchar_t *group_number (wchar_t *buf, wchar_t *bufend,
0d8b67
 			      wchar_t thousands_sep, int ngroups)
0d8b67
      internal_function;
0d8b67
 
0d8b67
+static __always_inline int
0d8b67
+isnanl_or_pseudo (long double in)
0d8b67
+{
0d8b67
+#if defined __x86_64__ || defined __i386__
0d8b67
+  union
0d8b67
+    {
0d8b67
+      long double f;
0d8b67
+      struct
0d8b67
+	{
0d8b67
+	  uint64_t low;
0d8b67
+	  uint64_t high;
0d8b67
+	} u;
0d8b67
+    } ldouble;
0d8b67
+
0d8b67
+  ldouble.f = in;
0d8b67
+
0d8b67
+  return __isnanl (in) || (ldouble.u.low & 0x8000000000000000) == 0;
0d8b67
+#else
0d8b67
+  return __isnanl (in);
0d8b67
+#endif
0d8b67
+}
0d8b67
+
0d8b67
 
0d8b67
 int
0d8b67
 __printf_fp_l (FILE *fp, locale_t loc,
0d8b67
@@ -335,7 +357,7 @@ __printf_fp_l (FILE *fp, locale_t loc,
0d8b67
 
0d8b67
       /* Check for special values: not a number or infinity.  */
0d8b67
       int res;
0d8b67
-      if (__isnanl (fpnum.ldbl))
0d8b67
+      if (isnanl_or_pseudo (fpnum.ldbl))
0d8b67
 	{
0d8b67
 	  is_neg = signbit (fpnum.ldbl);
0d8b67
 	  if (isupper (info->spec))
0d8b67
diff --git a/sysdeps/x86/Makefile b/sysdeps/x86/Makefile
0d8b67
index c26533245e8a8103..f1da941dbbadadb3 100644
0d8b67
--- a/sysdeps/x86/Makefile
0d8b67
+++ b/sysdeps/x86/Makefile
0d8b67
@@ -18,3 +18,7 @@ sysdep-dl-routines += dl-get-cpu-features
0d8b67
 tests += tst-get-cpu-features
0d8b67
 tests-static += tst-get-cpu-features-static
0d8b67
 endif
0d8b67
+
0d8b67
+ifeq ($(subdir),math)
0d8b67
+tests += tst-ldbl-nonnormal-printf
0d8b67
+endif # $(subdir) == math
0d8b67
diff --git a/sysdeps/x86/tst-ldbl-nonnormal-printf.c b/sysdeps/x86/tst-ldbl-nonnormal-printf.c
0d8b67
new file mode 100644
0d8b67
index 0000000000000000..e4e3e428747488b9
0d8b67
--- /dev/null
0d8b67
+++ b/sysdeps/x86/tst-ldbl-nonnormal-printf.c
0d8b67
@@ -0,0 +1,49 @@
0d8b67
+/* Test printf with x86-specific non-normal long double value.
0d8b67
+   Copyright (C) 2020-2021 Free Software Foundation, Inc.
0d8b67
+
0d8b67
+   This file is part of the GNU C Library.
0d8b67
+
0d8b67
+   The GNU C Library is free software; you can redistribute it and/or
0d8b67
+   modify it under the terms of the GNU Lesser General Public
0d8b67
+   License as published by the Free Software Foundation; either
0d8b67
+   version 2.1 of the License, or (at your option) any later version.
0d8b67
+
0d8b67
+   The GNU C Library is distributed in the hope that it will be useful,
0d8b67
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
0d8b67
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0d8b67
+   Lesser General Public License for more details.
0d8b67
+
0d8b67
+   You should have received a copy of the GNU Lesser General Public
0d8b67
+   License along with the GNU C Library; if not, see
0d8b67
+   <https://www.gnu.org/licenses/>.  */
0d8b67
+
0d8b67
+#include <stdio.h>
0d8b67
+#include <string.h>
0d8b67
+#include <support/check.h>
0d8b67
+
0d8b67
+/* Fill the stack with non-zero values.  This makes a crash in
0d8b67
+   snprintf more likely.  */
0d8b67
+static void __attribute__ ((noinline, noclone))
0d8b67
+fill_stack (void)
0d8b67
+{
0d8b67
+  char buffer[65536];
0d8b67
+  memset (buffer, 0xc0, sizeof (buffer));
0d8b67
+  asm ("" ::: "memory");
0d8b67
+}
0d8b67
+
0d8b67
+static int
0d8b67
+do_test (void)
0d8b67
+{
0d8b67
+  fill_stack ();
0d8b67
+
0d8b67
+  long double value;
0d8b67
+  memcpy (&value, "\x00\x04\x00\x00\x00\x00\x00\x00\x00\x04", 10);
0d8b67
+
0d8b67
+  char buf[30];
0d8b67
+  int ret = snprintf (buf, sizeof (buf), "%Lg", value);
0d8b67
+  TEST_COMPARE (ret, strlen (buf));
0d8b67
+  TEST_COMPARE_STRING (buf, "nan");
0d8b67
+  return 0;
0d8b67
+}
0d8b67
+
0d8b67
+#include <support/test-driver.c>