]> git.ipfire.org Git - thirdparty/elfutils.git/commitdiff
x86: Fix out-of-bounds read of register table in sample_perf_regs_mapping
authorNaveed Khan <naveed@digiscrypt.com>
Mon, 22 Jun 2026 14:27:22 +0000 (19:57 +0530)
committerMark Wielaard <mark@klomp.org>
Thu, 25 Jun 2026 16:55:46 +0000 (18:55 +0200)
x86_sample_perf_regs_mapping selects dwarf_to_perf from one of two
tables, regs_i386[] (9 entries) or regs_x86_64[] (17 entries), depending
on the sample ABI.  The final mapping loop was bounded by
ebl->frame_nregs and indexed dwarf_to_perf[i].  For the x86_64 backend
frame_nregs is 17, so when a sample carries PERF_SAMPLE_REGS_ABI_32
(is_abi32 true) the loop reads regs_i386[9..16], eight ints past the end
of the 9-element array.  The resulting out-of-bounds values are then used
to index perf_to_regs[].

The abi value originates from a perf sample and is passed unvalidated
through the public dwflst_perf_sample_getframes() API; it is never
checked against the backend ELF class.  Commit a532f8d hardened the
perf_to_regs[] write side but left this read overflow in place.

Bound the loop by the minimum of frame_nregs and the selected table
length.  Neither bound dominates: an i386 backend (frame_nregs 9)
handling an x86_64-ABI sample has frame_nregs < dwarf_to_perf_len, the
reverse of the overflow above, so MIN() is required rather than the table
length alone.  Computing the limit once also keeps the loop bound
invariant.

* backends/x86_initreg_sample.c: Include system.h for MIN.
(x86_sample_perf_regs_mapping): Compute dwarf_to_perf_len for the
selected table and bound the mapping loop by
MIN (ebl->frame_nregs, dwarf_to_perf_len).

Signed-off-by: Naveed Khan <naveed@digiscrypt.com>
Reviewed-by: Serhei Makarov <serhei@serhei.io>
backends/x86_initreg_sample.c

index 07f0f5633b09d880160eee399604ebbc210f0938..d80e351355bcd55b0edc76b30a046371fe98cc0e 100644 (file)
@@ -26,6 +26,8 @@
    the GNU Lesser General Public License along with this program.  If
    not, see <http://www.gnu.org/licenses/>.  */
 
+#include "system.h"
+
 static inline bool
 x86_sample_perf_regs_mapping (Ebl *ebl,
                              uint64_t perf_regs_mask, uint32_t abi,
@@ -63,6 +65,11 @@ x86_sample_perf_regs_mapping (Ebl *ebl,
                                    16/*r8 after flags+segment*/, 17, 18, 19, 20, 21, 22, 23,
                                    8/*ip*/};
   const int *dwarf_to_perf = is_abi32 ? regs_i386 : regs_x86_64;
+  /* regs_i386 and regs_x86_64 have different lengths; the mapping loop
+     below must not index dwarf_to_perf beyond the selected table.  */
+  size_t dwarf_to_perf_len = is_abi32 ?
+    sizeof (regs_i386) / sizeof (regs_i386[0]) :
+    sizeof (regs_x86_64) / sizeof (regs_x86_64[0]);
 
   /* Count bits and allocate regs_mapping:  */
   int j, k, count; uint64_t bit;
@@ -105,8 +112,13 @@ x86_sample_perf_regs_mapping (Ebl *ebl,
       return false;
 
   /* Locations of perf_regs in the dwarf_regs array, according to
-     perf_regs_mask and perf_to_regs[]:  */
-  for (size_t i = 0; i < ebl->frame_nregs; i++)
+     perf_regs_mask and perf_to_regs[].  Bound by both frame_nregs and
+     the selected table length: when abi does not match the backend
+     either one can be the smaller (an x86_64 ebl with an ABI_32 sample
+     has frame_nregs 17 > 9, an i386 ebl with a 64-bit sample has
+     frame_nregs 9 < 17), so take the minimum once, up front.  */
+  size_t max_reg = MIN (ebl->frame_nregs, dwarf_to_perf_len);
+  for (size_t i = 0; i < max_reg; i++)
     {
       k = dwarf_to_perf[i];
       j = perf_to_regs[k];