On 8/21/2026 10:16 PM, sashiko-bot@kernel.org 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.
quoted
diff --git a/kernel/unwind/eh_frame.c b/kernel/unwind/eh_frame.c
quoted
+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;
[ ... ]
quoted
+ 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?
quoted
+ if (cie_id != EH_FRAME_CIE_ID)
+ return -EINVAL;
quoted
+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)
+{
[ ... ]
quoted
+ 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) {
quoted
+ 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];
}
quoted
+ 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.
[ ... ]
quoted
+ 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.
quoted
+ ip += offset;
if (check_add_overflow(loc, offset, &loc))
return -EINVAL;
Likewise for all other DW_CFA_advance_loc* flavors.
quoted
+ break;
+ }
[ ... ]
quoted
+ case DW_CFA_restore_extended: {
+ unsigned long reg;
+ int idx;
+
+ if (ctx->cie)
+ return -EINVAL;
+
+ ret = read_uleb128(&cur, end, ®);
+ 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)
jremus@de.ibm.com / jremus@linux.ibm.com
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/