[PATCH] arm: CVE-2020-6096: fix memcpy and memmove for negative length [BZ #25620]
authorEvgeny Eremin <e.eremin@omprussia.ru>
Wed, 8 Jul 2020 12:18:19 +0000 (14:18 +0200)
committerHelmut Grohne <helmut@subdivi.de>
Sat, 8 Oct 2022 15:53:16 +0000 (16:53 +0100)
Unsigned branch instructions could be used for r2 to fix the wrong
behavior when a negative length is passed to memcpy and memmove.
This commit fixes the generic arm implementation of memcpy amd memmove.

From beea361050728138b82c57dda0c4810402d342b9 Mon Sep 17 00:00:00 2001
From: Alexander Anisimov <a.anisimov@omprussia.ru>
Date: Wed, 8 Jul 2020 14:18:31 +0200
Subject: [PATCH] arm: CVE-2020-6096: Fix multiarch memcpy for negative length
 [BZ #25620]

Unsigned branch instructions could be used for r2 to fix the wrong
behavior when a negative length is passed to memcpy.
This commit fixes the armv7 version.

From eec0f4218cda936a6ab8f543e90b96b196df3fc2 Mon Sep 17 00:00:00 2001
From: Florian Weimer <fweimer@redhat.com>
Date: Tue, 12 May 2020 19:02:08 +0200
Subject: [PATCH] string: Add string/tst-memmove-overflow, a test case for bug
 25620

Reviewed-by: Carlos O'Donell <carlos@redhat.com>
Tested-by: Carlos O'Donell <carlos@redhat.com>
From 0e28cfff9dfdb71352151054e0d38816856182d5 Mon Sep 17 00:00:00 2001
From: Florian Weimer <fweimer@redhat.com>
Date: Tue, 12 May 2020 19:01:49 +0200
Subject: [PATCH] support: Add support_blob_repeat_allocate_shared

Reviewed-by: Carlos O'Donell <carlos@redhat.com>
Gbp-Pq: Topic arm
Gbp-Pq: Name git-CVE-2020-6096-fix-memcpy-and-memmove-for-negative-size.diff

string/Makefile
string/tst-memmove-overflow.c [new file with mode: 0644]
support/blob_repeat.c
support/blob_repeat.h
support/tst-support_blob_repeat.c
sysdeps/arm/armv7/multiarch/memcpy_impl.S
sysdeps/arm/memcpy.S
sysdeps/arm/memmove.S

index aa2da9ca72262886d2854a2c4bd16132e8f53841..3e30b0adb88944801abecab0957add5255de34d5 100644 (file)
@@ -59,7 +59,7 @@ tests         := tester inl-tester noinl-tester testcopy test-ffs     \
                   bug-envz1 tst-strxfrm2 tst-endian tst-svc2           \
                   tst-strtok_r bug-strcoll2 tst-cmp tst-xbzero-opt     \
                   test-endian-types test-endian-file-scope             \
-                  test-endian-sign-conversion
+                  test-endian-sign-conversion tst-memmove-overflow
 
 # This test allocates a lot of memory and can run for a long time.
 xtests = tst-strcoll-overflow
diff --git a/string/tst-memmove-overflow.c b/string/tst-memmove-overflow.c
new file mode 100644 (file)
index 0000000..b744679
--- /dev/null
@@ -0,0 +1,155 @@
+/* Test for signed comparision bug in memmove (bug 25620).
+   Copyright (C) 2020 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/>.  */
+
+/* This test shifts a memory region which is a bit larger than 2 GiB
+   by one byte.  In order to make it more likely that the memory
+   allocation succeeds on 32-bit systems, most of the allocation
+   consists of shared pages.  Only a portion at the start and end of
+   the allocation are unshared, and contain a specific non-repeating
+   bit pattern.  */
+
+#include <array_length.h>
+#include <libc-diag.h>
+#include <stdint.h>
+#include <string.h>
+#include <support/blob_repeat.h>
+#include <support/check.h>
+#include <support/xunistd.h>
+#include <sys/mman.h>
+#include <unistd.h>
+
+#define TEST_MAIN
+#define TEST_NAME "memmove"
+#include "test-string.h"
+#include <support/test-driver.h>
+
+IMPL (memmove, 1)
+
+/* Size of the part of the allocation which is not shared, at the
+   start and the end of the overall allocation.  4 MiB.  */
+static const size_t unshared_size = 4U << 20;
+
+/* The allocation is 2 GiB plus 8 MiB.  This should work with all page
+   sizes that occur in practice.  */
+static const size_t allocation_size = (2U << 30) + 2 * unshared_size;
+
+/* Compute the expected byte at the given index.  This is used to
+   produce a non-repeating pattern.  */
+static inline unsigned char
+expected_value (size_t index)
+{
+  uint32_t randomized = 0x9e3779b9 * index; /* Based on golden ratio.  */
+  return randomized >> 25;     /* Result is in the range [0, 127].  */
+}
+
+static int
+test_main (void)
+{
+  test_init ();
+
+  FOR_EACH_IMPL (impl, 0)
+    {
+      printf ("info: testing %s\n", impl->name);
+
+      /* Check that the allocation sizes are multiples of the page
+        size.  */
+      TEST_COMPARE (allocation_size % xsysconf (_SC_PAGESIZE), 0);
+      TEST_COMPARE (unshared_size % xsysconf (_SC_PAGESIZE), 0);
+
+      /* The repeating pattern has the MSB set in all bytes.  */
+      unsigned char repeating_pattern[128];
+      for (unsigned int i = 0; i < array_length (repeating_pattern); ++i)
+       repeating_pattern[i] = 0x80 | i;
+
+      struct support_blob_repeat repeat
+       = support_blob_repeat_allocate_shared (repeating_pattern,
+                                              sizeof (repeating_pattern),
+                                              (allocation_size
+                                               / sizeof (repeating_pattern)));
+      if (repeat.start == NULL)
+       FAIL_UNSUPPORTED ("repeated blob allocation failed: %m");
+      TEST_COMPARE (repeat.size, allocation_size);
+
+      /* Unshared the start and the end of the allocation.  */
+      unsigned char *start = repeat.start;
+      xmmap (start, unshared_size,
+            PROT_READ | PROT_WRITE,
+            MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1);
+      xmmap (start + allocation_size - unshared_size, unshared_size,
+            PROT_READ | PROT_WRITE,
+            MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1);
+
+      /* Initialize the non-repeating pattern.  */
+      for (size_t i = 0; i < unshared_size; ++i)
+       start[i] = expected_value (i);
+      for (size_t i = allocation_size - unshared_size; i < allocation_size;
+          ++i)
+       start[i] = expected_value (i);
+
+      /* Make sure that there was really no sharing.  */
+      asm volatile ("" ::: "memory");
+      for (size_t i = 0; i < unshared_size; ++i)
+       TEST_COMPARE (start[i], expected_value (i));
+      for (size_t i = allocation_size - unshared_size; i < allocation_size;
+          ++i)
+       TEST_COMPARE (start[i], expected_value (i));
+
+      /* Used for a nicer error diagnostic using
+        TEST_COMPARE_BLOB.  */
+      unsigned char expected_start[128];
+      memcpy (expected_start, start + 1, sizeof (expected_start));
+      unsigned char expected_end[128];
+      memcpy (expected_end,
+             start + allocation_size - sizeof (expected_end),
+             sizeof (expected_end));
+
+      /* Move the entire allocation forward by one byte.  */
+      DIAG_PUSH_NEEDS_COMMENT;
+#if __GNUC_PREREQ (8, 0)
+      /* GCC 8 warns about string function argument overflows.  */
+      DIAG_IGNORE_NEEDS_COMMENT (8, "-Warray-bounds");
+      DIAG_IGNORE_NEEDS_COMMENT (8, "-Wstringop-overflow");
+#endif
+      memmove (start, start + 1, allocation_size - 1);
+      DIAG_POP_NEEDS_COMMENT;
+
+      /* Check that the unshared of the memory region have been
+        shifted as expected.  The TEST_COMPARE_BLOB checks are
+        redundant, but produce nicer diagnostics.  */
+      asm volatile ("" ::: "memory");
+      TEST_COMPARE_BLOB (expected_start, sizeof (expected_start),
+                        start, sizeof (expected_start));
+      TEST_COMPARE_BLOB (expected_end, sizeof (expected_end),
+                        start + allocation_size - sizeof (expected_end) - 1,
+                        sizeof (expected_end));
+      for (size_t i = 0; i < unshared_size - 1; ++i)
+       TEST_COMPARE (start[i], expected_value (i + 1));
+      /* The gap between the checked start and end area of the mapping
+        has shared mappings at unspecified boundaries, so do not
+        check the expected values in the middle.  */
+      for (size_t i = allocation_size - unshared_size; i < allocation_size - 1;
+          ++i)
+       TEST_COMPARE (start[i], expected_value (i + 1));
+
+      support_blob_repeat_free (&repeat);
+    }
+
+  return 0;
+}
+
+#include <support/test-driver.c>
index 718846d81d75b70aef9bfa1f9955e2d584b1af3b..473015b4a86eda9bb4db04b52d66bd21992add20 100644 (file)
@@ -145,10 +145,11 @@ minimum_stride_size (size_t page_size, size_t element_size)
 }
 
 /* Allocations larger than maximum_small_size potentially use mmap
-   with alias mappings.  */
+   with alias mappings.  If SHARED, the alias mappings are created
+   using MAP_SHARED instead of MAP_PRIVATE.  */
 static struct support_blob_repeat
 allocate_big (size_t total_size, const void *element, size_t element_size,
-              size_t count)
+              size_t count, bool shared)
 {
   unsigned long page_size = xsysconf (_SC_PAGESIZE);
   size_t stride_size = minimum_stride_size (page_size, element_size);
@@ -233,7 +234,11 @@ allocate_big (size_t total_size, const void *element, size_t element_size,
   {
     size_t remaining_size = total_size;
     char *current = target;
-    int flags = MAP_FIXED | MAP_FILE | MAP_PRIVATE;
+    int flags = MAP_FIXED | MAP_FILE;
+    if (shared)
+      flags |= MAP_SHARED;
+    else
+      flags |= MAP_PRIVATE;
 #ifdef MAP_NORESERVE
     flags |= MAP_NORESERVE;
 #endif
@@ -271,8 +276,8 @@ allocate_big (size_t total_size, const void *element, size_t element_size,
 }
 
 struct support_blob_repeat
-support_blob_repeat_allocate (const void *element, size_t element_size,
-                              size_t count)
+repeat_allocate (const void *element, size_t element_size,
+                size_t count, bool shared)
 {
   size_t total_size;
   if (check_mul_overflow_size_t (element_size, count, &total_size))
@@ -283,7 +288,21 @@ support_blob_repeat_allocate (const void *element, size_t element_size,
   if (total_size <= maximum_small_size)
     return allocate_malloc (total_size, element, element_size, count);
   else
-    return allocate_big (total_size, element, element_size, count);
+    return allocate_big (total_size, element, element_size, count, shared);
+}
+
+struct support_blob_repeat
+support_blob_repeat_allocate (const void *element, size_t element_size,
+                              size_t count)
+{
+  return repeat_allocate (element, element_size, count, false);
+}
+
+struct support_blob_repeat
+support_blob_repeat_allocate_shared (const void *element, size_t element_size,
+                                    size_t count)
+{
+  return repeat_allocate (element, element_size, count, true);
 }
 
 void
index 8e9d7ff5f1e01f667ef1c110105b31419d4adf78..c4eb3697bb3cdeeb3f0d12bc050458ce438663bc 100644 (file)
@@ -38,7 +38,17 @@ struct support_blob_repeat support_blob_repeat_allocate (const void *element,
                                                          size_t element_size,
                                                          size_t count);
 
-/* Deallocate the blob created by support_blob_repeat_allocate.  */
+/* Like support_blob_repeat_allocate, except that copy-on-write
+   semantics are disabled.  This means writing to one part of the blob
+   can affect other parts.  It is possible to map non-shared memory
+   over parts of the resulting blob using MAP_ANONYMOUS | MAP_FIXED
+   | MAP_PRIVATE, so that writes to these parts do not affect
+   others.  */
+struct support_blob_repeat support_blob_repeat_allocate_shared
+  (const void *element, size_t element_size, size_t count);
+
+/* Deallocate the blob created by support_blob_repeat_allocate or
+   support_blob_repeat_allocate_shared.  */
 void support_blob_repeat_free (struct support_blob_repeat *);
 
 #endif /* SUPPORT_BLOB_REPEAT_H */
index 1978c14488106ff295fb9d2913f86d96e7485a00..a06aaf903526b9e007753bc2c7150accf2bbcfff 100644 (file)
@@ -17,6 +17,7 @@
    <http://www.gnu.org/licenses/>.  */
 
 #include <stdio.h>
+#include <string.h>
 #include <support/blob_repeat.h>
 #include <support/check.h>
 
@@ -63,21 +64,39 @@ do_test (void)
     }
   support_blob_repeat_free (&repeat);
 
-  repeat = support_blob_repeat_allocate ("012345678", 9, 10 * 1000 * 1000);
-  if (repeat.start == NULL)
-    puts ("warning: not enough memory for large mapping");
-  else
+  for (int do_shared = 0; do_shared < 2; ++do_shared)
     {
-      unsigned char *p = repeat.start;
-      for (int i = 0; i < 10 * 1000 * 1000; ++i)
-        for (int j = 0; j <= 8; ++j)
-          if (p[i * 9 + j] != '0' + j)
-            {
-              printf ("error: element %d index %d\n", i, j);
-              TEST_COMPARE (p[i * 9 + j], '0' + j);
-            }
+      if (do_shared)
+        repeat = support_blob_repeat_allocate_shared ("012345678", 9,
+                                                      10 * 1000 * 1000);
+      else
+        repeat = support_blob_repeat_allocate ("012345678", 9,
+                                               10 * 1000 * 1000);
+      if (repeat.start == NULL)
+        puts ("warning: not enough memory for large mapping");
+      else
+        {
+          unsigned char *p = repeat.start;
+          for (int i = 0; i < 10 * 1000 * 1000; ++i)
+            for (int j = 0; j <= 8; ++j)
+              if (p[i * 9 + j] != '0' + j)
+                {
+                  printf ("error: element %d index %d\n", i, j);
+                  TEST_COMPARE (p[i * 9 + j], '0' + j);
+                }
+
+          enum { total_size = 9 * 10 * 1000 * 1000 };
+          p[total_size - 1] = '\0';
+          asm ("" ::: "memory");
+          if (do_shared)
+            /* The write is repeated in multiple places earlier in the
+               string due to page sharing.  */
+            TEST_VERIFY (strlen (repeat.start) < total_size - 1);
+          else
+            TEST_COMPARE (strlen (repeat.start), total_size - 1);
+        }
+      support_blob_repeat_free (&repeat);
     }
-  support_blob_repeat_free (&repeat);
 
   return 0;
 }
index 2de172635c2099056c12644008da4115fe40532c..802c310f3ec720f49453f4fc42f89f1feb207ecb 100644 (file)
@@ -268,7 +268,7 @@ ENTRY(memcpy)
 
        mov     dst, dstin      /* Preserve dstin, we need to return it.  */
        cmp     count, #64
-       bge     .Lcpy_not_short
+       bhs     .Lcpy_not_short
        /* Deal with small copies quickly by dropping straight into the
           exit block.  */
 
@@ -351,10 +351,10 @@ ENTRY(memcpy)
 
 1:
        subs    tmp2, count, #64        /* Use tmp2 for count.  */
-       blt     .Ltail63aligned
+       blo     .Ltail63aligned
 
        cmp     tmp2, #512
-       bge     .Lcpy_body_long
+       bhs     .Lcpy_body_long
 
 .Lcpy_body_medium:                     /* Count in tmp2.  */
 #ifdef USE_VFP
@@ -378,7 +378,7 @@ ENTRY(memcpy)
        add     src, src, #64
        vstr    d1, [dst, #56]
        add     dst, dst, #64
-       bge     1b
+       bhs     1b
        tst     tmp2, #0x3f
        beq     .Ldone
 
@@ -412,7 +412,7 @@ ENTRY(memcpy)
        ldrd    A_l, A_h, [src, #64]!
        strd    A_l, A_h, [dst, #64]!
        subs    tmp2, tmp2, #64
-       bge     1b
+       bhs     1b
        tst     tmp2, #0x3f
        bne     1f
        ldr     tmp2,[sp], #FRAME_SIZE
@@ -482,7 +482,7 @@ ENTRY(memcpy)
        add     src, src, #32
 
        subs    tmp2, tmp2, #prefetch_lines * 64 * 2
-       blt     2f
+       blo     2f
 1:
        cpy_line_vfp    d3, 0
        cpy_line_vfp    d4, 64
@@ -494,7 +494,7 @@ ENTRY(memcpy)
        add     dst, dst, #2 * 64
        add     src, src, #2 * 64
        subs    tmp2, tmp2, #prefetch_lines * 64
-       bge     1b
+       bhs     1b
 
 2:
        cpy_tail_vfp    d3, 0
@@ -615,8 +615,8 @@ ENTRY(memcpy)
 1:
        pld     [src, #(3 * 64)]
        subs    count, count, #64
-       ldrmi   tmp2, [sp], #FRAME_SIZE
-       bmi     .Ltail63unaligned
+       ldrlo   tmp2, [sp], #FRAME_SIZE
+       blo     .Ltail63unaligned
        pld     [src, #(4 * 64)]
 
 #ifdef USE_NEON
@@ -633,7 +633,7 @@ ENTRY(memcpy)
        neon_load_multi d0-d3, src
        neon_load_multi d4-d7, src
        subs    count, count, #64
-       bmi     2f
+       blo     2f
 1:
        pld     [src, #(4 * 64)]
        neon_store_multi d0-d3, dst
@@ -641,7 +641,7 @@ ENTRY(memcpy)
        neon_store_multi d4-d7, dst
        neon_load_multi d4-d7, src
        subs    count, count, #64
-       bpl     1b
+       bhs     1b
 2:
        neon_store_multi d0-d3, dst
        neon_store_multi d4-d7, dst
index cba8609813c4cf0ff6f28ca5ff28c8d0e91d3c30..7b4214aae9275cf7dd0cb701768314a36a240a3f 100644 (file)
@@ -68,7 +68,7 @@ ENTRY(memcpy)
                cfi_remember_state
 
                subs    r2, r2, #4
-               blt     8f
+               blo     8f
                ands    ip, r0, #3
        PLD(    pld     [r1, #0]                )
                bne     9f
@@ -82,7 +82,7 @@ ENTRY(memcpy)
                cfi_rel_offset (r6, 4)
                cfi_rel_offset (r7, 8)
                cfi_rel_offset (r8, 12)
-               blt     5f
+               blo     5f
 
        CALGN(  ands    ip, r1, #31             )
        CALGN(  rsb     r3, ip, #32             )
@@ -98,9 +98,9 @@ ENTRY(memcpy)
 #endif
 
        PLD(    pld     [r1, #0]                )
-2:     PLD(    subs    r2, r2, #96             )
+2:     PLD(    cmp     r2, #96                 )
        PLD(    pld     [r1, #28]               )
-       PLD(    blt     4f                      )
+       PLD(    blo     4f                      )
        PLD(    pld     [r1, #60]               )
        PLD(    pld     [r1, #92]               )
 
@@ -108,9 +108,7 @@ ENTRY(memcpy)
 4:             ldmia   r1!, {r3, r4, r5, r6, r7, r8, ip, lr}
                subs    r2, r2, #32
                stmia   r0!, {r3, r4, r5, r6, r7, r8, ip, lr}
-               bge     3b
-       PLD(    cmn     r2, #96                 )
-       PLD(    bge     4b                      )
+               bhs     3b
 
 5:             ands    ip, r2, #28
                rsb     ip, ip, #32
@@ -222,7 +220,7 @@ ENTRY(memcpy)
                strbge  r4, [r0], #1
                subs    r2, r2, ip
                strb    lr, [r0], #1
-               blt     8b
+               blo     8b
                ands    ip, r1, #3
                beq     1b
 
@@ -236,7 +234,7 @@ ENTRY(memcpy)
                .macro  forward_copy_shift pull push
 
                subs    r2, r2, #28
-               blt     14f
+               blo     14f
 
        CALGN(  ands    ip, r1, #31             )
        CALGN(  rsb     ip, ip, #32             )
@@ -253,9 +251,9 @@ ENTRY(memcpy)
                cfi_rel_offset (r10, 16)
 
        PLD(    pld     [r1, #0]                )
-       PLD(    subs    r2, r2, #96             )
+       PLD(    cmp     r2, #96                 )
        PLD(    pld     [r1, #28]               )
-       PLD(    blt     13f                     )
+       PLD(    blo     13f                     )
        PLD(    pld     [r1, #60]               )
        PLD(    pld     [r1, #92]               )
 
@@ -280,9 +278,7 @@ ENTRY(memcpy)
                mov     ip, ip, PULL #\pull
                orr     ip, ip, lr, PUSH #\push
                stmia   r0!, {r3, r4, r5, r6, r7, r8, r10, ip}
-               bge     12b
-       PLD(    cmn     r2, #96                 )
-       PLD(    bge     13b                     )
+               bhs     12b
 
                pop     {r5 - r8, r10}
                cfi_adjust_cfa_offset (-20)
index 74d3042043b3b25b33a7b7e57c9ef10e767f52fd..b28a81191288b4a0851627507ad8cd1058c8c7aa 100644 (file)
@@ -85,7 +85,7 @@ ENTRY(memmove)
                add     r1, r1, r2
                add     r0, r0, r2
                subs    r2, r2, #4
-               blt     8f
+               blo     8f
                ands    ip, r0, #3
        PLD(    pld     [r1, #-4]               )
                bne     9f
@@ -99,7 +99,7 @@ ENTRY(memmove)
                cfi_rel_offset (r6, 4)
                cfi_rel_offset (r7, 8)
                cfi_rel_offset (r8, 12)
-               blt     5f
+               blo     5f
 
        CALGN(  ands    ip, r1, #31             )
        CALGN(  sbcsne  r4, ip, r2              )  @ C is always set here
@@ -114,9 +114,9 @@ ENTRY(memmove)
 #endif
 
        PLD(    pld     [r1, #-4]               )
-2:     PLD(    subs    r2, r2, #96             )
+2:     PLD(    cmp     r2, #96                 )
        PLD(    pld     [r1, #-32]              )
-       PLD(    blt     4f                      )
+       PLD(    blo     4f                      )
        PLD(    pld     [r1, #-64]              )
        PLD(    pld     [r1, #-96]              )
 
@@ -124,9 +124,7 @@ ENTRY(memmove)
 4:             ldmdb   r1!, {r3, r4, r5, r6, r7, r8, ip, lr}
                subs    r2, r2, #32
                stmdb   r0!, {r3, r4, r5, r6, r7, r8, ip, lr}
-               bge     3b
-       PLD(    cmn     r2, #96                 )
-       PLD(    bge     4b                      )
+               bhs     3b
 
 5:             ands    ip, r2, #28
                rsb     ip, ip, #32
@@ -237,7 +235,7 @@ ENTRY(memmove)
                strbge  r4, [r0, #-1]!
                subs    r2, r2, ip
                strb    lr, [r0, #-1]!
-               blt     8b
+               blo     8b
                ands    ip, r1, #3
                beq     1b
 
@@ -251,7 +249,7 @@ ENTRY(memmove)
                .macro  backward_copy_shift push pull
 
                subs    r2, r2, #28
-               blt     14f
+               blo     14f
 
        CALGN(  ands    ip, r1, #31             )
        CALGN(  rsb     ip, ip, #32             )
@@ -268,9 +266,9 @@ ENTRY(memmove)
                cfi_rel_offset (r10, 16)
 
        PLD(    pld     [r1, #-4]               )
-       PLD(    subs    r2, r2, #96             )
+       PLD(    cmp     r2, #96                 )
        PLD(    pld     [r1, #-32]              )
-       PLD(    blt     13f                     )
+       PLD(    blo     13f                     )
        PLD(    pld     [r1, #-64]              )
        PLD(    pld     [r1, #-96]              )
 
@@ -295,9 +293,7 @@ ENTRY(memmove)
                mov     r4, r4, PUSH #\push
                orr     r4, r4, r3, PULL #\pull
                stmdb   r0!, {r4 - r8, r10, ip, lr}
-               bge     12b
-       PLD(    cmn     r2, #96                 )
-       PLD(    bge     13b                     )
+               bhs     12b
 
                pop     {r5 - r8, r10}
                cfi_adjust_cfa_offset (-20)