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

Reply via email to