Hi Sayed,
On Mon, Jul 06, 2026 at 12:42:35PM +0530, Sayed Kaif wrote:
> execute_cfi walks a CFI instruction stream delimited by program and end.
> The outer loop condition (program < end) only guarantees that the opcode
> byte is present. The DW_CFA_advance_loc1 case then reads a one-byte
> operand with
>
> operand = *program++;
>
> without first checking that another byte is available. The sibling
> cases DW_CFA_advance_loc2/4/8 all guard their fixed-size reads with
> cfi_assert (program + N <= end), and readelf's own CFI printer guards
> advance_loc1 with an equivalent check, but this path was missing it.
Nice find. Interesting we somehow forgot this one place.
> A crafted or truncated FDE/CIE whose instruction stream ends exactly on
> a DW_CFA_advance_loc1 opcode therefore causes a one-byte read past the
> end of the CFI data buffer. Add the matching bounds check so malformed
> input is rejected with DWARF_E_INVALID_CFI instead.
Right. Although it is called cfi_assert, it doesn't actually assert
(which would be bad in a library). cfi_assert is a macro that does:
#define cfi_assert(ok) do { \
if (likely (ok)) break; \
result = DWARF_E_INVALID_CFI; \
goto out; \
} while (0)
> Signed-off-by: Sayed Kaif <[email protected]>
> ---
> diff --git a/libdw/cfi.c b/libdw/cfi.c
> index 743bfc0..a960724 100644
> --- a/libdw/cfi.c
> +++ b/libdw/cfi.c
> @@ -146,6 +146,7 @@ execute_cfi (Dwarf_CFI *cache,
> /* These cases move LOC, i.e. "create a new table row". */
>
> case DW_CFA_advance_loc1:
> + cfi_assert (program + 1 <= end);
> operand = *program++;
> FALLTHROUGH;
> case DW_CFA_advance_loc + 0 ... DW_CFA_advance_loc + CFI_PRIMARY_MAX:
Obvious correct guard.
Pushed as: https://sourceware.org/cgit/elfutils/commit/?id=77156c031116
Thanks,
Mark