]> git.ipfire.org Git - thirdparty/elfutils.git/commitdiff
Fix leb128 reading
authorTom Tromey <tom@tromey.com>
Wed, 28 Oct 2020 23:26:42 +0000 (17:26 -0600)
committerMark Wielaard <mark@klomp.org>
Thu, 29 Oct 2020 22:59:25 +0000 (23:59 +0100)
PR 26773 points out that some sleb128 values are decoded incorrectly.

This version of the fix only examines the sleb128 conversion.
Overlong encodings are not handled, and the uleb128 decoders are not
touched.  The approach taken here is to do the work in an unsigned
type, and then rely on an implementation-defined cast to convert to
signed.

Signed-off-by: Tom Tromey <tom@tromey.com>
.gitignore
ChangeLog
libdw/ChangeLog
libdw/dwarf_getlocation.c
libdw/memory-access.h
tests/ChangeLog
tests/Makefile.am
tests/leb128.c [new file with mode: 0644]

index c9790941504f77a65148e938e8ac7454bc68f5fb..d737b14d9b13155e76059f6bdb4111382000f8b9 100644 (file)
@@ -151,6 +151,7 @@ Makefile.in
 /tests/get-units-invalid
 /tests/get-units-split
 /tests/hash
+/tests/leb128
 /tests/line2addr
 /tests/low_high_pc
 /tests/msg_tst
index 72e8397cc43f28b9cd90d0dd708d62a61e1b10ba..4c8699f8628a9be95e6e6479c19b26baccba6472 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,7 @@
+2020-10-28  Tom Tromey  <tom@tromey.com>
+
+       * .gitignore: Add /tests/leb128.
+
 2020-10-01  Frank Ch. Eigler  <fche@redhat.com>
 
        PR25461
index 731c7e79be2dc6319090a9ffc23e918d7e484e49..289bb4c9faff7f456f4daec99f31004ccf4a63ff 100644 (file)
@@ -1,3 +1,13 @@
+2020-10-28  Tom Tromey  <tom@tromey.com>
+
+       PR26773
+       * dwarf_getlocation.c (store_implicit_value): Use
+       __libdw_get_uleb128_unchecked.
+       * memory-access.h (get_sleb128_step): Assume unsigned type for
+       'var'.
+       (__libdw_get_sleb128, __libdw_get_sleb128_unchecked): Work in
+       unsigned type.  Handle final byte.
+
 2020-10-19  Mark Wielaard  <mark@klomp.org>
 
        * dwarf_frame_register.c (dwarf_frame_register): Declare ops_mem
index 4617f9e9e623bc923552a713d3ef9b8bde35b25d..f2bad5a99f57a9c706277a96bc8381943d5d09c1 100644 (file)
@@ -130,9 +130,8 @@ store_implicit_value (Dwarf *dbg, void **cache, Dwarf_Op *op)
   struct loc_block_s *block = libdw_alloc (dbg, struct loc_block_s,
                                           sizeof (struct loc_block_s), 1);
   const unsigned char *data = (const unsigned char *) (uintptr_t) op->number2;
-  uint64_t len = __libdw_get_uleb128 (&data, data + len_leb128 (Dwarf_Word));
-  if (unlikely (len != op->number))
-    return -1;
+  /* Skip the block length.  */
+  __libdw_get_uleb128_unchecked (&data);
   block->addr = op;
   block->data = (unsigned char *) data;
   block->length = op->number;
index 14436a714b80c1e656502ad72359db2d664b481f..8b2386ee67deb50c796bd75e39954c0d2225fa54 100644 (file)
@@ -113,19 +113,22 @@ __libdw_get_uleb128_unchecked (const unsigned char **addrp)
 #define get_sleb128_step(var, addr, nth)                                     \
   do {                                                                       \
     unsigned char __b = *(addr)++;                                           \
+    (var) |= (typeof (var)) (__b & 0x7f) << ((nth) * 7);                     \
     if (likely ((__b & 0x80) == 0))                                          \
       {                                                                              \
-       struct { signed int i:7; } __s = { .i = __b };                        \
-       (var) |= (typeof (var)) __s.i * ((typeof (var)) 1 << ((nth) * 7));    \
+       if ((__b & 0x40) != 0)                                                \
+         (var) |= - ((typeof (var)) 1 << (((nth) + 1) * 7));                 \
        return (var);                                                         \
       }                                                                              \
-    (var) |= (typeof (var)) (__b & 0x7f) << ((nth) * 7);                     \
   } while (0)
 
 static inline int64_t
 __libdw_get_sleb128 (const unsigned char **addrp, const unsigned char *end)
 {
-  int64_t acc = 0;
+  /* Do the work in an unsigned type, but use implementation-defined
+     behavior to cast to signed on return.  This avoids some undefined
+     behavior when shifting.  */
+  uint64_t acc = 0;
 
   /* Unroll the first step to help the compiler optimize
      for the common single-byte case.  */
@@ -134,6 +137,20 @@ __libdw_get_sleb128 (const unsigned char **addrp, const unsigned char *end)
   const size_t max = __libdw_max_len_sleb128 (*addrp - 1, end);
   for (size_t i = 1; i < max; ++i)
     get_sleb128_step (acc, *addrp, i);
+  if (*addrp == end)
+    return INT64_MAX;
+
+  /* There might be one extra byte.  */
+  unsigned char b = **addrp;
+  ++*addrp;
+  if (likely ((b & 0x80) == 0))
+    {
+      /* We only need the low bit of the final byte, and as it is the
+        sign bit, we don't need to do anything else here.  */
+      acc |= ((typeof (acc)) b) << 7 * max;
+      return acc;
+    }
+
   /* Other implementations set VALUE to INT_MAX in this
      case.  So we better do this as well.  */
   return INT64_MAX;
@@ -142,7 +159,10 @@ __libdw_get_sleb128 (const unsigned char **addrp, const unsigned char *end)
 static inline int64_t
 __libdw_get_sleb128_unchecked (const unsigned char **addrp)
 {
-  int64_t acc = 0;
+  /* Do the work in an unsigned type, but use implementation-defined
+     behavior to cast to signed on return.  This avoids some undefined
+     behavior when shifting.  */
+  uint64_t acc = 0;
 
   /* Unroll the first step to help the compiler optimize
      for the common single-byte case.  */
@@ -152,6 +172,18 @@ __libdw_get_sleb128_unchecked (const unsigned char **addrp)
   const size_t max = len_leb128 (int64_t) - 1;
   for (size_t i = 1; i < max; ++i)
     get_sleb128_step (acc, *addrp, i);
+
+  /* There might be one extra byte.  */
+  unsigned char b = **addrp;
+  ++*addrp;
+  if (likely ((b & 0x80) == 0))
+    {
+      /* We only need the low bit of the final byte, and as it is the
+        sign bit, we don't need to do anything else here.  */
+      acc |= ((typeof (acc)) b) << 7 * max;
+      return acc;
+    }
+
   /* Other implementations set VALUE to INT_MAX in this
      case.  So we better do this as well.  */
   return INT64_MAX;
index e9d1e2606d680ab8d313a2223ba50adf02434bfb..91aeadafd953215f1a5f63c037f319bc84702745 100644 (file)
@@ -1,3 +1,10 @@
+2020-10-28  Tom Tromey  <tom@tromey.com>
+
+       PR26773
+       * Makefile.am (check_PROGRAMS, TESTS): Add leb128.
+       (leb128_LDADD): New variable.
+       * leb128.c: New file.
+
 2020-10-19  Mark Wielaard  <mark@klomp.org>
 
        * addrcfi.c (print_register): Make ops_mem 3 elements.
index bc5d034f0821d41c2b3e6213f5f313e8403992d2..1b51ab8da853ea161ed71313eef9a4b29fa5a43f 100644 (file)
@@ -63,7 +63,7 @@ check_PROGRAMS = arextract arsymtest newfile saridx scnnames sectiondump \
                  all-dwarf-ranges unit-info next_cfi \
                  elfcopy addsections xlate_notes elfrdwrnop \
                  dwelf_elf_e_machine_string \
-                 getphdrnum
+                 getphdrnum leb128
 
 asm_TESTS = asm-tst1 asm-tst2 asm-tst3 asm-tst4 asm-tst5 \
            asm-tst6 asm-tst7 asm-tst8 asm-tst9
@@ -185,7 +185,8 @@ TESTS = run-arextract.sh run-arsymtest.sh run-ar.sh newfile test-nlist \
        run-elfclassify.sh run-elfclassify-self.sh \
        run-disasm-riscv64.sh \
        run-pt_gnu_prop-tests.sh \
-       run-getphdrnum.sh run-test-includes.sh
+       run-getphdrnum.sh run-test-includes.sh \
+       leb128
 
 if !BIARCH
 export ELFUTILS_DISABLE_BIARCH = 1
@@ -694,6 +695,7 @@ xlate_notes_LDADD = $(libelf)
 elfrdwrnop_LDADD = $(libelf)
 dwelf_elf_e_machine_string_LDADD = $(libelf) $(libdw)
 getphdrnum_LDADD = $(libelf) $(libdw)
+leb128_LDADD = $(libelf) $(libdw)
 
 # We want to test the libelf header against the system elf.h header.
 # Don't include any -I CPPFLAGS. Except when we install our own elf.h.
diff --git a/tests/leb128.c b/tests/leb128.c
new file mode 100644 (file)
index 0000000..47b57c0
--- /dev/null
@@ -0,0 +1,173 @@
+/* Test program for leb128
+   Copyright (C) 2020 Tom Tromey
+   This file is part of elfutils.
+
+   This file is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   elfutils 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 General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+#include <config.h>
+#include <stddef.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <libdw.h>
+#include "../libdw/libdwP.h"
+#include "../libdw/memory-access.h"
+
+#define OK 0
+#define FAIL 1
+
+static const unsigned char v0[] = { 0x0 };
+static const unsigned char v1[] = { 0x1 };
+static const unsigned char v23[] = { 23 };
+static const unsigned char vm_1[] = { 0x7f };
+static const unsigned char vm_2[] = { 0x7e };
+static const unsigned char s127[] = { 0xff, 0x00 };
+static const unsigned char v128[] = { 0x80, 0x01 };
+static const unsigned char v129[] = { 0x81, 0x01 };
+static const unsigned char vm_127[] = { 0x81, 0x7f };
+static const unsigned char vm_128[] = { 0x80, 0x7f };
+static const unsigned char vm_129[] = { 0xff, 0x7e };
+static const unsigned char vhuge[] =
+  {
+    0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0x0
+  };
+static const unsigned char most_positive[] =
+  {
+    0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0x3f
+  };
+static const unsigned char most_negative[] =
+  {
+    0x80, 0x80, 0x80, 0x80, 0x80,
+    0x80, 0x80, 0x80, 0x40
+  };
+static const unsigned char minus_one[] =
+  {
+    0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0x7f
+  };
+static const unsigned char int64_max_m1[] =
+  {
+    0xfe, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0x00
+  };
+static const unsigned char int64_min_p1[] =
+  {
+    0x81, 0x80, 0x80, 0x80, 0x80,
+    0x80, 0x80, 0x80, 0x80, 0x7f
+  };
+
+static int
+test_one_sleb (const unsigned char *data, size_t len, int64_t expect)
+{
+  int64_t value;
+  const unsigned char *p;
+
+  p = data;
+  get_sleb128 (value, p, p + len);
+  if (value != expect || p != data + len)
+    return FAIL;
+
+  p = data;
+  get_sleb128_unchecked (value, p);
+  if (value != expect || p != data + len)
+    return FAIL;
+
+  return OK;
+}
+
+static int
+test_sleb (void)
+{
+#define TEST(ARRAY, V)                               \
+  if (test_one_sleb (ARRAY, sizeof (ARRAY), V) != OK) \
+    return FAIL;
+
+  TEST (v0, 0);
+  TEST (v1, 1);
+  TEST (v23, 23);
+  TEST (vm_1, -1);
+  TEST (vm_2, -2);
+  TEST (s127, 127);
+  TEST (v128, 128);
+  TEST (v129, 129);
+  TEST (vm_127, -127);
+  TEST (vm_128, -128);
+  TEST (vm_129, -129);
+  TEST (vhuge, 9223372036854775807ll);
+  TEST (most_positive, 4611686018427387903ll);
+  TEST (most_negative, -4611686018427387904ll);
+  TEST (minus_one, -1);
+  TEST (int64_max_m1, INT64_MAX - 1);
+  TEST (int64_min_p1, INT64_MIN + 1);
+
+#undef TEST
+
+  return OK;
+}
+
+static int
+test_one_uleb (const unsigned char *data, size_t len, uint64_t expect)
+{
+  uint64_t value;
+  const unsigned char *p;
+
+  p = data;
+  get_uleb128 (value, p, p + len);
+  if (value != expect || p != data + len)
+    return FAIL;
+
+  p = data;
+  get_uleb128_unchecked (value, p);
+  if (value != expect || p != data + len)
+    return FAIL;
+
+  return OK;
+}
+
+static int
+test_uleb (void)
+{
+#define TEST(ARRAY, V)                               \
+  if (test_one_uleb (ARRAY, sizeof (ARRAY), V) != OK) \
+    return FAIL;
+
+  TEST (v0, 0);
+  TEST (v1, 1);
+  TEST (v23, 23);
+  TEST (vm_1, 127);
+  TEST (vm_2, 126);
+  TEST (s127, 127);
+  TEST (v128, 128);
+  TEST (v129, 129);
+  TEST (vm_127, 16257);
+  TEST (vm_128, 16256);
+  TEST (vm_129, 16255);
+  TEST (vhuge, 9223372036854775807ull);
+  TEST (most_positive, 4611686018427387903ull);
+  TEST (most_negative, 4611686018427387904ull);
+  TEST (minus_one, 9223372036854775807ull);
+  TEST (int64_max_m1, INT64_MAX - 1);
+  TEST (int64_min_p1, INT64_MIN + 1);
+
+#undef TEST
+
+  return OK;
+}
+
+int
+main (void)
+{
+  return test_sleb () || test_uleb ();
+}