Re: [RFC PATCH v2 09/24] unwind_user/eh_frame: Add support for reading .eh_frame section

From
Jens Remus <hidden>
Date
2026-09-14 10:45:09
Also in
linux-s390, sashiko-reviews
Reply to
sashiko-bot@kernel.org, 2026-08-21
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, &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)
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/
Thread (58 messages) 58 messages, 3 authors, last reply 2026-09-14
Keyboard shortcuts
hback out one level
jnext message in thread / next series-diff position
kprevious message in thread / previous series-diff position
ldrill in
Escclose help / fold thread tree
?toggle this help