Hello Everyone, I would like to apologize if you received the same email multiple times from me today.
This is my first time using a mailing list, and I am still learning how it works. I was a bit confused with the process and accidentally sent duplicate messages. Previously, I was mostly working with platforms like GitHub, so this is a new experience for me. Thank you for your patience and understanding. I appreciate your support as I learn and get familiar with these tools. On Mon, Jun 22, 2026 at 8:34 PM Naveed Khan <[email protected]> wrote: > 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 <[email protected]> > --- > >From 3d66e7c14278c08b39f799ce2ef211f79b317276 Mon Sep 17 00:00:00 2001 > From: Naveed Khan <[email protected]> > Date: Mon, 22 Jun 2026 20:29:05 +0530 > Subject: [PATCH v2] 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 <[email protected]> > --- > backends/x86_initreg_sample.c | 16 ++++++++++++++-- > 1 file changed, 14 insertions(+), 2 deletions(-) > > diff --git a/backends/x86_initreg_sample.c b/backends/x86_initreg_sample.c > index 07f0f56..d80e351 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 <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]; > -- > 2.52.0 >
