From: Naveed Khan Date: Mon, 22 Jun 2026 14:27:22 +0000 (+0530) Subject: x86: Fix out-of-bounds read of register table in sample_perf_regs_mapping X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=b919ce271dec9009344662547b6e0e69cc1f21ff;p=thirdparty%2Felfutils.git x86: Fix out-of-bounds read of register table in sample_perf_regs_mapping 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 Reviewed-by: Serhei Makarov --- diff --git a/backends/x86_initreg_sample.c b/backends/x86_initreg_sample.c index 07f0f563..d80e3513 100644 --- a/backends/x86_initreg_sample.c +++ b/backends/x86_initreg_sample.c @@ -26,6 +26,8 @@ the GNU Lesser General Public License along with this program. If not, see . */ +#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];