On 8/21/2026 10:16 PM, [email protected] wrote:

> In preparation for using .eh_frame to unwind user space stacks, add an
> eh_frame_find() interface for finding the .eh_frame[_hdr] information
> associated with a given text address.

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

>> +static __always_inline int __read_cie(struct eh_frame_section *sec,
>> +                                  unsigned long cie_addr,
>> +                                  struct eh_frame_cie *cie)
>> +{
>> +    void __user *cie_ptr = (void __user *)cie_addr;
>> +    unsigned long cur = cie_addr, end;
> 
> [ ... ]
> 
>> +    scoped_user_read_access_size(cie_ptr, 4 + length, Efault) {
>> +            /* Read CIE_ID (must be 0 for CIE; FDE otherwise) */
>> +            UNSAFE_GET_USER_INC(cie_id, cur, end, Efault);
> 
> [Severity: High]
> Are we bypassing the pointer masking mitigation here?

True.  Fixed in next version likewise as for eh_frame_read_header() in
preceeding "[RFC PATCH v2 07/24] unwind_user/eh_frame: Add support for
reading .eh_frame_hdr section".

> 
> The scoped_user_read_access_size() macro shadows the passed pointer (cie_ptr)
> with a masked version to prevent speculative kernel accesses. However, the
> memory read via UNSAFE_GET_USER_INC() uses the separate unmasked cur variable.
> 
> Could this allow speculative out-of-bounds reads to access kernel memory?
> 
>> +            if (cie_id != EH_FRAME_CIE_ID)
>> +                    return -EINVAL;

>> +static __always_inline int __do_cfi_insn(struct eh_frame_section *sec,
>> +                                     struct eh_frame_fde *fde,
>> +                                     unsigned long *cur_ptr,
>> +                                     unsigned long end,
>> +                                     unsigned long *ip_ptr,
>> +                                     unsigned long target_ip,
>> +                                     struct eh_frame_cfi_context *ctx)
>> +{
> 
> [ ... ]
> 
>> +    case DW_CFA_restore: {
>> +            u8 reg = DW_CFA_operand(opcode);
>> +            int idx;
>> +
>> +            if (ctx->cie)
>> +                    return -EINVAL;
>> +
>> +            idx = reg_to_index(reg);
>> +            if (idx >= 0)

                if (idx >= 0) {

>> +                    ctx->state.reg_rule[idx] = ctx->cie_state.reg_rule[idx];

                        ctx->state.reg_regnum[idx] = 
ctx->cie_state.reg_regnum[idx];
                        ctx->state.reg_offset[idx] = 
ctx->cie_state.reg_offset[idx];
                }

>> +            break;
>> +    }
> 
> [Severity: High]
> Does this properly restore the complete register state?
> 
> It appears only the rule type is restored from ctx->cie_state, but the
> offset (reg_offset) and register number (reg_regnum) are not restored and
> will remain at their previously modified values.

Valid.  I was in a rush to get all of the RFC v1 Sashiko AI review
resolved and was used to the struct unwind_user_rule_data approach,
where a single assignment is sufficient.  I will do likewise here
and introduce struct eh_frame_{cfa|reg}_rule_data:

struct eh_frame_cfa_rule_data {
        enum eh_frame_cfa_rule rule;
        unsigned long regnum;
        long offset;
};

struct eh_frame_reg_rule_data {
        enum eh_frame_reg_rule rule;
        unsigned long regnum;
        long offset;
};

struct eh_frame_state {
        struct eh_frame_cfa_rule_data cfa;
        struct eh_frame_reg_rule_data reg[NR_REGS];
};

struct eh_frame_cfi_context {
        struct eh_frame_state state;
        struct eh_frame_state state_stack[EH_FRAME_MAX_STATE_STACK];
        struct eh_frame_state cie_state;
        ...

Another option would be to use struct unwind_user[_cfa]_rule_data.
For that purpose those would need to be updated to represent the
offset and register number as (unsigned) long.  The question is
how to deal with UNWIND_USER_CFA_RULE_{SP|FP}_OFFSET then.
eh_frame could internally use UNWIND_USER_CFA_RULE_REG_OFFSET and
only before returning the info to unwind user convert to
UNWIND_USER_CFA_RULE_{SP|FP}_OFFSET if appropriate.

I will explore that in a future version.

> 
> [ ... ]
> 
>> +            case DW_CFA_advance_loc4: {
>> +                    unsigned long offset;
>> +                    u32 delta;
>> +
>> +                    UNSAFE_GET_USER_INC(delta, cur, end, Efault);
>> +                    offset = delta * fde->cie.code_align;

                        if (check_mul_overflow(delta, fde->cie.code_align, 
&offset))
                                return -EINVAL;

> 
> [Severity: Medium]
> Can this calculation overflow?

Valid.  Will add a check in next version.

> 
> The multiplication of delta (a u32) and fde->cie.code_align (an unsigned int)
> evaluates as a 32-bit unsigned integer before being assigned to the 64-bit
> unsigned long offset.
> 
> If the product exceeds 32 bits, the high bits would be truncated, which might
> cause the unwinder to desync from the actual PC.
> 
>> +                    ip += offset;

                        if (check_add_overflow(loc, offset, &loc))
                                return -EINVAL;

Likewise for all other DW_CFA_advance_loc* flavors.

>> +                    break;
>> +            }
> 
> [ ... ]
> 
>> +            case DW_CFA_restore_extended: {
>> +                    unsigned long reg;
>> +                    int idx;
>> +
>> +                    if (ctx->cie)
>> +                            return -EINVAL;
>> +
>> +                    ret = read_uleb128(&cur, end, &reg);
>> +                    if (ret)
>> +                            return ret;
>> +
>> +                    idx = reg_to_index(reg);
>> +                    if (idx >= 0)
>> +                            ctx->state.reg_rule[idx] = 
>> ctx->cie_state.reg_rule[idx];
>> +                    break;
>> +            }
> 
> [Severity: High]
> This seems to have the same register state restoration issue as 
> DW_CFA_restore.
> Should reg_offset and reg_regnum also be restored here alongside the rule 
> type?

Same as for DW_CFA_restore above.

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