On 8/18/2026 5:02 PM, [email protected] wrote:

>> diff --git a/kernel/unwind/eh_frame.c b/kernel/unwind/eh_frame.c

>> +static __always_inline int read_encoded_pointer(struct eh_frame_section 
>> *sec,
>> +                                            unsigned long *addr,
>> +                                            unsigned long end,
>> +                                            u8 encoding,
>> +                                            unsigned long *value)
>> +{
>> +    unsigned long cur = *addr;
>> +    u8 format = DW_EH_PE_format(encoding);
>> +    u8 application = DW_EH_PE_application(encoding);
>> +    unsigned long result = 0;
>> +    int ret;
>> +
>> +    if (encoding == DW_EH_PE_omit)
>> +            return -EINVAL;
>> +
>> +    /* Determine base address based on application */
>> +    switch (application) {
>> +    case 0:
>> +            /* Absolute */
>> +            break;
>> +    case DW_EH_PE_pcrel:
>> +            result = *addr;
>> +            break;
>> +    case DW_EH_PE_datarel:
>> +            result = sec->eh_frame_hdr_start;
>> +            break;
>> +    case DW_EH_PE_textrel:
>> +            result = sec->text_start;
>> +            break;
>> +    case DW_EH_PE_funcrel:
>> +    case DW_EH_PE_aligned:
>> +            return -EOPNOTSUPP;
>> +    default:
>> +            return -EINVAL;
>> +    }
>> +
>> +    /* Read value based on format */
>> +    switch (format) {
>> +    case DW_EH_PE_absptr:
>> +            UNSAFE_GET_USER_INC(result, cur, end, Efault);

                unsigned long tmp;
                UNSAFE_GET_USER_INC(tmp, cur, end, Efault);
                result += tmp;

>> +            break;
> 
> [Severity: Medium]
> Does this overwrite the base address that was just computed for relative
> encodings?
> 
> When handling DW_EH_PE_absptr in combination with relative application flags
> like DW_EH_PE_pcrel, the base address is stored in the result variable above.
> However, UNSAFE_GET_USER_INC directly assigns the read value to result
> instead of adding it, which will discard the base address.
> 
> Should this read into a temporary variable and add it to result, similar to
> how the other formats are handled?

Yes.  I wrongly assumed that DW_EH_PE_absptr would be treated as-is,
similar as DW_EH_PE_omit, and would therefore not be allowed in
combination with any application flags as well as DW_EH_PE_indirect.

The LSB spec, section DWARF EH encoding [1] does read as any combination
would be allowed, as it is also implemented in libgcc's unwinder.

[1]: LSB 5.0.0, 10.5.1. DWARF Exception Header Encoding
     
https://refspecs.linuxfoundation.org/LSB_5.0.0/LSB-Core-generic/LSB-Core-generic/dwarfext.html#DWARFEHENCODING>
 
>> +    case DW_EH_PE_uleb128: {
>> +            unsigned long tmp;
>> +            ret = read_uleb128(&cur, end, &tmp);
>> +            if (ret)
>> +                    return ret;
>> +            result += tmp;
>> +            break;
>> +    }

>> +static int eh_frame_read_header(struct eh_frame_section *sec)
>> +{
>> +    struct mm_struct *mm = current->mm;
>> +    void __user *eh_frame_hdr = (void __user *)sec->eh_frame_hdr_start;
>> +    unsigned long cur = sec->eh_frame_hdr_start, end = 
>> sec->eh_frame_hdr_end;
>> +    unsigned long eh_frame_start, eh_frame_vma_end, table_start, table_end;
>> +    u8 version, eh_frame_ptr_enc, fde_count_enc, table_enc;
>> +    unsigned long fde_count;
>> +    int entry_size;
>> +    int ret;
> [ ... ]
>> +            /* Read FDE count */
>> +            ret = read_encoded_pointer(sec, &cur, end,
>> +                                       fde_count_enc, &fde_count);
>> +            if (ret)
>> +                    return ret;
>> +
>> +            /* Determine binary search table start and end */
>> +            table_start = cur;
>> +            entry_size = 2 * encoded_pointer_size(table_enc);
>> +            if (!entry_size)
>> +                    return -EINVAL;

Check that the FDE count is within the remaining .eh_frame_hdr bounds:

                if (fde_count > (end - table_start) / entry_size)
                        return -EINVAL;

>> +            table_end = table_start + fde_count * entry_size;
>> +            if (table_end > sec->eh_frame_hdr_end)
>> +                    return -EINVAL;

Drop this check in favor of the new one above.

> 
> [Severity: Medium]
> Can a maliciously large fde_count cause an integer overflow here?
> 
> Since fde_count is parsed from the untrusted user eh_frame_hdr section,
> the multiplication of fde_count * entry_size could wrap around to a small
> positive value.
> 
> This would bypass the bounds check against sec->eh_frame_hdr_end and
> potentially allow the kernel to trust an arbitrarily large fde_count,
> leading to out-of-bounds kernel memory reads when performing binary searches.
> 
> Would it be safer to use check_mul_overflow() and check_add_overflow() for
> these bounds calculations?
> 
>> +    }
>> +
>> +end:
> 

Regards,
Jens
-- 
Jens Remus
Linux on Z Development (D3303)
[email protected] / [email protected]

IBM Deutschland Research & Development GmbH; Vorsitzender des Aufsichtsrats: 
Wolfgang Wendt; Geschäftsführung: David Faller; Sitz der Gesellschaft: 
Ehningen; Registergericht: Amtsgericht Stuttgart, HRB 243294
IBM Data Privacy Statement: https://www.ibm.com/privacy/


Reply via email to