[1/8] x86-64 memchr/wmemchr: Properly handle the length parameter [BZ# 24097]

Message ID 20190117165351.25914-2-hjl.tools@gmail.com
State Superseded
Headers

Commit Message

H.J. Lu Jan. 17, 2019, 4:53 p.m. UTC
  On x32, the size_t parameter may be passed in the lower 32 bits of a
64-bit register with the non-zero upper 32 bits.  The string/memory
functions written in assembly can only use the lower 32 bits of a
64-bit register as length or must clear the upper 32 bits before using
the full 64-bit register for length.

This pach fixes memchr/wmemchr for x32.  Tested on x86-64 and x32.  On
x86-64, libc.so is the same with and withou the fix.

	[BZ# 24097]
	* sysdeps/x86_64/memchr.S: Use RDX_LP for length.  Clear the
	upper 32 bits of RDX register.
	* sysdeps/x86_64/multiarch/memchr-avx2.S: Likewise.
	* sysdeps/x86_64/x32/Makefile (tests): Add tst-size_t-memchr and
	tst-size_t-wmemchr.
	* sysdeps/x86_64/x32/test-size_t.h: New file.
	* sysdeps/x86_64/x32/tst-size_t-memchr.c: Likewise.
	* sysdeps/x86_64/x32/tst-size_t-wmemchr.c: Likewise.
---
 sysdeps/x86_64/memchr.S                 |  10 +-
 sysdeps/x86_64/multiarch/memchr-avx2.S  |   8 +-
 sysdeps/x86_64/x32/Makefile             |   8 ++
 sysdeps/x86_64/x32/test-size_t.h        | 170 ++++++++++++++++++++++++
 sysdeps/x86_64/x32/tst-size_t-memchr.c  |  72 ++++++++++
 sysdeps/x86_64/x32/tst-size_t-wmemchr.c |  20 +++
 6 files changed, 283 insertions(+), 5 deletions(-)
 create mode 100644 sysdeps/x86_64/x32/test-size_t.h
 create mode 100644 sysdeps/x86_64/x32/tst-size_t-memchr.c
 create mode 100644 sysdeps/x86_64/x32/tst-size_t-wmemchr.c
  

Comments

H.J. Lu Jan. 18, 2019, 1:25 p.m. UTC | #1
On Thu, Jan 17, 2019 at 8:54 AM H.J. Lu <hjl.tools@gmail.com> wrote:
>
> On x32, the size_t parameter may be passed in the lower 32 bits of a
> 64-bit register with the non-zero upper 32 bits.  The string/memory
> functions written in assembly can only use the lower 32 bits of a
> 64-bit register as length or must clear the upper 32 bits before using
> the full 64-bit register for length.
>
> This pach fixes memchr/wmemchr for x32.  Tested on x86-64 and x32.  On
> x86-64, libc.so is the same with and withou the fix.
>
>         [BZ# 24097]
>         * sysdeps/x86_64/memchr.S: Use RDX_LP for length.  Clear the
>         upper 32 bits of RDX register.
>         * sysdeps/x86_64/multiarch/memchr-avx2.S: Likewise.
>         * sysdeps/x86_64/x32/Makefile (tests): Add tst-size_t-memchr and
>         tst-size_t-wmemchr.
>         * sysdeps/x86_64/x32/test-size_t.h: New file.
>         * sysdeps/x86_64/x32/tst-size_t-memchr.c: Likewise.
>         * sysdeps/x86_64/x32/tst-size_t-wmemchr.c: Likewise.

> diff --git a/sysdeps/x86_64/x32/test-size_t.h b/sysdeps/x86_64/x32/test-size_t.h
> new file mode 100644
> index 0000000000..fbef565bda
> --- /dev/null
> +++ b/sysdeps/x86_64/x32/test-size_t.h
> @@ -0,0 +1,170 @@
> +/* Test string/memory functions with size_t in the lower 32 bits of
> +   64-bit register.
> +   Copyright (C) 2019 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
> +   <http://www.gnu.org/licenses/>.  */
> +
> +#include <stdint.h>
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <string.h>
> +#include <sys/mman.h>
> +#include <sys/param.h>
> +#include <unistd.h>
> +#include <fcntl.h>
> +#include <error.h>
> +#include <errno.h>
> +#include <time.h>
> +#include <ifunc-impl-list.h>
> +
> +/* On x32, parameter_t may be passed in a 64-bit register with the LEN
> +   field in the lower 32 bits.  When the LEN field of 64-bit register
> +   is passed to string/memory function as the size_t parameter, only
> +   the lower 32 bits can be used.  */
> +typedef struct
> +{
> +  union
> +    {
> +      size_t len;
> +      void (*fn) (void);
> +    };
> +  void *p;
> +} parameter_t;
> +
> +typedef struct
> +{
> +  const char *name;
> +  void (*fn) (void);
> +  long test;
> +} impl_t;
> +extern impl_t __start_impls[], __stop_impls[];
> +
> +#define IMPL(name, test) \
> +  impl_t tst_ ## name                                                  \
> +  __attribute__ ((section ("impls"), aligned (sizeof (void *))))       \
> +       = { __STRING (name), (void (*) (void))name, test };
> +
> +#ifndef _GNU_SOURCE
> +#define _GNU_SOURCE
> +#endif
> +
> +#undef __USE_STRING_INLINES
> +
> +/* We are compiled under _ISOMAC, so libc-symbols.h does not do this
> +   for us.  */
> +#include "config.h"
> +#ifdef HAVE_CC_INHIBIT_LOOP_TO_LIBCALL
> +# define inhibit_loop_to_libcall \
> +    __attribute__ ((__optimize__ ("-fno-tree-loop-distribute-patterns")))
> +#else
> +# define inhibit_loop_to_libcall
> +#endif
> +
> +#define GL(x) _##x
> +#define GLRO(x) _##x
> +
> +unsigned char *buf1, *buf2;
> +size_t page_size;
> +
> +#define CALL(parm, ...)        \
> +  (* (proto_t) (parm).fn) (__VA_ARGS__)
> +
> +#ifdef TEST_NAME
> +/* Increase size of FUNC_LIST if assert is triggered at run-time.  */
> +static struct libc_ifunc_impl func_list[32];
> +static int func_count;
> +static int impl_count = -1;
> +static impl_t *impl_array;
> +
> +# define FOR_EACH_IMPL(impl, notall) \
> +  impl_t *impl;                                                                \
> +  int count;                                                           \
> +  if (impl_count == -1)                                                        \
> +    {                                                                  \
> +      impl_count = 0;                                                  \
> +      if (func_count != 0)                                             \
> +       {                                                               \
> +         int f;                                                        \
> +         impl_t *skip = NULL, *a;                                      \
> +         for (impl = __start_impls; impl < __stop_impls; ++impl)       \
> +           if (strcmp (impl->name, TEST_NAME) == 0)                    \
> +             skip = impl;                                              \
> +           else                                                        \
> +             impl_count++;                                             \
> +         a = impl_array = malloc ((impl_count + func_count) *          \
> +                                  sizeof (impl_t));                    \
> +         for (impl = __start_impls; impl < __stop_impls; ++impl)       \
> +           if (impl != skip)                                           \
> +             *a++ = *impl;                                             \
> +         for (f = 0; f < func_count; f++)                              \
> +           if (func_list[f].usable)                                    \
> +             {                                                         \
> +               a->name = func_list[f].name;                            \
> +               a->fn = func_list[f].fn;                                \
> +               a->test = 1;                                            \
> +               a++;                                                    \
> +             }                                                         \
> +         impl_count = a - impl_array;                                  \
> +       }                                                               \
> +      else                                                             \
> +        {                                                              \
> +         impl_count = __stop_impls - __start_impls;                    \
> +         impl_array = __start_impls;                                   \
> +        }                                                              \
> +    }                                                                  \
> +  impl = impl_array;                                                   \
> +  for (count = 0; count < impl_count; ++count, ++impl)                 \
> +    if (!notall || impl->test)
> +#else
> +# define FOR_EACH_IMPL(impl, notall) \
> +  for (impl_t *impl = __start_impls; impl < __stop_impls; ++impl)      \
> +    if (!notall || impl->test)
> +#endif
> +
> +#ifndef BUF1PAGES
> +# define BUF1PAGES 1
> +#endif
> +
> +static void
> +test_init (void)
> +{
> +#ifdef TEST_NAME
> +  func_count = __libc_ifunc_impl_list (TEST_NAME, func_list,
> +                                      (sizeof func_list
> +                                       / sizeof func_list[0]));
> +#endif
> +
> +  page_size = 2 * getpagesize ();
> +#ifdef MIN_PAGE_SIZE
> +  if (page_size < MIN_PAGE_SIZE)
> +    page_size = MIN_PAGE_SIZE;
> +#endif
> +  buf1 = mmap (0, (BUF1PAGES + 1) * page_size, PROT_READ | PROT_WRITE,
> +              MAP_PRIVATE | MAP_ANON, -1, 0);
> +  if (buf1 == MAP_FAILED)
> +    error (EXIT_FAILURE, errno, "mmap failed");
> +  if (mprotect (buf1 + BUF1PAGES * page_size, page_size, PROT_NONE))
> +    error (EXIT_FAILURE, errno, "mprotect failed");
> +  buf2 = mmap (0, 2 * page_size, PROT_READ | PROT_WRITE,
> +              MAP_PRIVATE | MAP_ANON, -1, 0);
> +  if (buf2 == MAP_FAILED)
> +    error (EXIT_FAILURE, errno, "mmap failed");
> +  if (mprotect (buf2 + page_size, page_size, PROT_NONE))
> +    error (EXIT_FAILURE, errno, "mprotect failed");
> +
> +  memset (buf1, 0xa5, BUF1PAGES * page_size);
> +  memset (buf2, 0x5a, page_size);
> +}

We can reuse <string/test-string.h>:

#define TEST_MAIN
#include <string/test-string.h>

/* On x32, parameter_t may be passed in a 64-bit register with the LEN
   field in the lower 32 bits.  When the LEN field of 64-bit register
   is passed to string/memory function as the size_t parameter, only
   the lower 32 bits can be used.  */
typedef struct
{
  union
    {
      size_t len;
      void (*fn) (void);
    };
  void *p;
} parameter_t;
  

Patch

diff --git a/sysdeps/x86_64/memchr.S b/sysdeps/x86_64/memchr.S
index a6f3ebba22..ec96365217 100644
--- a/sysdeps/x86_64/memchr.S
+++ b/sysdeps/x86_64/memchr.S
@@ -34,12 +34,16 @@  ENTRY(MEMCHR)
 	mov	%edi, %ecx
 
 #ifdef USE_AS_WMEMCHR
-	test	%rdx, %rdx
+	test	%RDX_LP, %RDX_LP
 	jz	L(return_null)
-	shl	$2, %rdx
+	shl	$2, %RDX_LP
 #else
+# ifdef __ILP32__
+	/* Clear the upper 32 bits.  */
+	movl	%edx, %edx
+# endif
 	punpcklbw %xmm1, %xmm1
-	test	%rdx, %rdx
+	test	%RDX_LP, %RDX_LP
 	jz	L(return_null)
 	punpcklbw %xmm1, %xmm1
 #endif
diff --git a/sysdeps/x86_64/multiarch/memchr-avx2.S b/sysdeps/x86_64/multiarch/memchr-avx2.S
index 9f98b7ec60..cfec1657b6 100644
--- a/sysdeps/x86_64/multiarch/memchr-avx2.S
+++ b/sysdeps/x86_64/multiarch/memchr-avx2.S
@@ -40,16 +40,20 @@ 
 ENTRY (MEMCHR)
 # ifndef USE_AS_RAWMEMCHR
 	/* Check for zero length.  */
-	testq	%rdx, %rdx
+	test	%RDX_LP, %RDX_LP
 	jz	L(null)
 # endif
 	movl	%edi, %ecx
 	/* Broadcast CHAR to YMM0.  */
 	vmovd	%esi, %xmm0
 # ifdef USE_AS_WMEMCHR
-	shl	$2, %rdx
+	shl	$2, %RDX_LP
 	vpbroadcastd %xmm0, %ymm0
 # else
+#  ifdef __ILP32__
+	/* Clear the upper 32 bits.  */
+	movl	%edx, %edx
+#  endif
 	vpbroadcastb %xmm0, %ymm0
 # endif
 	/* Check if we may cross page boundary with one vector load.  */
diff --git a/sysdeps/x86_64/x32/Makefile b/sysdeps/x86_64/x32/Makefile
index f2ebc24fb0..7d528889c6 100644
--- a/sysdeps/x86_64/x32/Makefile
+++ b/sysdeps/x86_64/x32/Makefile
@@ -4,3 +4,11 @@  ifeq ($(subdir),math)
 # 64-bit llround.  Add -fno-builtin-lround to silence the compiler.
 CFLAGS-s_llround.c += -fno-builtin-lround
 endif
+
+ifeq ($(subdir),string)
+tests += tst-size_t-memchr
+endif
+
+ifeq ($(subdir),wcsmbs)
+tests += tst-size_t-wmemchr
+endif
diff --git a/sysdeps/x86_64/x32/test-size_t.h b/sysdeps/x86_64/x32/test-size_t.h
new file mode 100644
index 0000000000..fbef565bda
--- /dev/null
+++ b/sysdeps/x86_64/x32/test-size_t.h
@@ -0,0 +1,170 @@ 
+/* Test string/memory functions with size_t in the lower 32 bits of
+   64-bit register.
+   Copyright (C) 2019 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
+   <http://www.gnu.org/licenses/>.  */
+
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <sys/param.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <error.h>
+#include <errno.h>
+#include <time.h>
+#include <ifunc-impl-list.h>
+
+/* On x32, parameter_t may be passed in a 64-bit register with the LEN
+   field in the lower 32 bits.  When the LEN field of 64-bit register
+   is passed to string/memory function as the size_t parameter, only
+   the lower 32 bits can be used.  */
+typedef struct
+{
+  union
+    {
+      size_t len;
+      void (*fn) (void);
+    };
+  void *p;
+} parameter_t;
+
+typedef struct
+{
+  const char *name;
+  void (*fn) (void);
+  long test;
+} impl_t;
+extern impl_t __start_impls[], __stop_impls[];
+
+#define IMPL(name, test) \
+  impl_t tst_ ## name							\
+  __attribute__ ((section ("impls"), aligned (sizeof (void *))))	\
+       = { __STRING (name), (void (*) (void))name, test };
+
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
+
+#undef __USE_STRING_INLINES
+
+/* We are compiled under _ISOMAC, so libc-symbols.h does not do this
+   for us.  */
+#include "config.h"
+#ifdef HAVE_CC_INHIBIT_LOOP_TO_LIBCALL
+# define inhibit_loop_to_libcall \
+    __attribute__ ((__optimize__ ("-fno-tree-loop-distribute-patterns")))
+#else
+# define inhibit_loop_to_libcall
+#endif
+
+#define GL(x) _##x
+#define GLRO(x) _##x
+
+unsigned char *buf1, *buf2;
+size_t page_size;
+
+#define CALL(parm, ...)	\
+  (* (proto_t) (parm).fn) (__VA_ARGS__)
+
+#ifdef TEST_NAME
+/* Increase size of FUNC_LIST if assert is triggered at run-time.  */
+static struct libc_ifunc_impl func_list[32];
+static int func_count;
+static int impl_count = -1;
+static impl_t *impl_array;
+
+# define FOR_EACH_IMPL(impl, notall) \
+  impl_t *impl;								\
+  int count;								\
+  if (impl_count == -1)							\
+    {									\
+      impl_count = 0;							\
+      if (func_count != 0)						\
+	{								\
+	  int f;							\
+	  impl_t *skip = NULL, *a;					\
+	  for (impl = __start_impls; impl < __stop_impls; ++impl)	\
+	    if (strcmp (impl->name, TEST_NAME) == 0)			\
+	      skip = impl;						\
+	    else							\
+	      impl_count++;						\
+	  a = impl_array = malloc ((impl_count + func_count) *		\
+				   sizeof (impl_t));			\
+	  for (impl = __start_impls; impl < __stop_impls; ++impl)	\
+	    if (impl != skip)						\
+	      *a++ = *impl;						\
+	  for (f = 0; f < func_count; f++)				\
+	    if (func_list[f].usable)					\
+	      {								\
+		a->name = func_list[f].name;				\
+		a->fn = func_list[f].fn;				\
+		a->test = 1;						\
+		a++;							\
+	      }								\
+	  impl_count = a - impl_array;					\
+	}								\
+      else								\
+        {								\
+	  impl_count = __stop_impls - __start_impls;			\
+	  impl_array = __start_impls;					\
+        }								\
+    }									\
+  impl = impl_array;							\
+  for (count = 0; count < impl_count; ++count, ++impl)			\
+    if (!notall || impl->test)
+#else
+# define FOR_EACH_IMPL(impl, notall) \
+  for (impl_t *impl = __start_impls; impl < __stop_impls; ++impl)	\
+    if (!notall || impl->test)
+#endif
+
+#ifndef BUF1PAGES
+# define BUF1PAGES 1
+#endif
+
+static void
+test_init (void)
+{
+#ifdef TEST_NAME
+  func_count = __libc_ifunc_impl_list (TEST_NAME, func_list,
+				       (sizeof func_list
+					/ sizeof func_list[0]));
+#endif
+
+  page_size = 2 * getpagesize ();
+#ifdef MIN_PAGE_SIZE
+  if (page_size < MIN_PAGE_SIZE)
+    page_size = MIN_PAGE_SIZE;
+#endif
+  buf1 = mmap (0, (BUF1PAGES + 1) * page_size, PROT_READ | PROT_WRITE,
+	       MAP_PRIVATE | MAP_ANON, -1, 0);
+  if (buf1 == MAP_FAILED)
+    error (EXIT_FAILURE, errno, "mmap failed");
+  if (mprotect (buf1 + BUF1PAGES * page_size, page_size, PROT_NONE))
+    error (EXIT_FAILURE, errno, "mprotect failed");
+  buf2 = mmap (0, 2 * page_size, PROT_READ | PROT_WRITE,
+	       MAP_PRIVATE | MAP_ANON, -1, 0);
+  if (buf2 == MAP_FAILED)
+    error (EXIT_FAILURE, errno, "mmap failed");
+  if (mprotect (buf2 + page_size, page_size, PROT_NONE))
+    error (EXIT_FAILURE, errno, "mprotect failed");
+
+  memset (buf1, 0xa5, BUF1PAGES * page_size);
+  memset (buf2, 0x5a, page_size);
+}
diff --git a/sysdeps/x86_64/x32/tst-size_t-memchr.c b/sysdeps/x86_64/x32/tst-size_t-memchr.c
new file mode 100644
index 0000000000..cf13afcb23
--- /dev/null
+++ b/sysdeps/x86_64/x32/tst-size_t-memchr.c
@@ -0,0 +1,72 @@ 
+/* Test memchr with size_t in the lower 32 bits of 64-bit register.
+   Copyright (C) 2019 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
+   <http://www.gnu.org/licenses/>.  */
+
+#ifndef WIDE
+# define TEST_NAME "memchr"
+#else
+# define TEST_NAME "wmemchr"
+#endif /* WIDE */
+#include "test-size_t.h"
+
+#ifndef WIDE
+# define MEMCHR memchr
+# define CHAR char
+# define UCHAR unsigned char
+#else
+# include <wchar.h>
+# define MEMCHR wmemchr
+# define CHAR wchar_t
+# define UCHAR wchar_t
+#endif /* WIDE */
+
+IMPL (MEMCHR, 1)
+
+typedef CHAR * (*proto_t) (const CHAR*, int, size_t);
+
+static CHAR *
+__attribute__ ((noinline, noclone))
+do_memchr (parameter_t a, parameter_t b)
+{
+  return CALL (b, a.p, (uintptr_t) b.p, a.len);
+}
+
+static int
+do_test (void)
+{
+  test_init ();
+
+  parameter_t src = { { page_size / sizeof (CHAR) }, buf2 };
+  parameter_t c = { { 0 }, (void *) (uintptr_t) 0x12 };
+
+  int ret = 0;
+  FOR_EACH_IMPL (impl, 0)
+    {
+      c.fn = impl->fn;
+      CHAR *res = do_memchr (src, c);
+      if (res)
+	{
+	  error (0, 0, "Wrong result in function %s: %p != NULL",
+		 impl->name, res);
+	  ret = 1;
+	}
+    }
+
+  return ret ? EXIT_FAILURE : EXIT_SUCCESS;
+}
+
+#include <support/test-driver.c>
diff --git a/sysdeps/x86_64/x32/tst-size_t-wmemchr.c b/sysdeps/x86_64/x32/tst-size_t-wmemchr.c
new file mode 100644
index 0000000000..877801d646
--- /dev/null
+++ b/sysdeps/x86_64/x32/tst-size_t-wmemchr.c
@@ -0,0 +1,20 @@ 
+/* Test wmemchr with size_t in the lower 32 bits of 64-bit register.
+   Copyright (C) 2019 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
+   <http://www.gnu.org/licenses/>.  */
+
+#define WIDE 1
+#include "tst-size_t-memchr.c"