[PATCH] fixup! unwind_user/sframe: Add support for reading .sframe contents

Jens Remus posted 1 patch 6 months, 3 weeks ago
kernel/unwind/sframe.c | 38 +++++++++++++++++++++++++++-----------
1 file changed, 27 insertions(+), 11 deletions(-)
[PATCH] fixup! unwind_user/sframe: Add support for reading .sframe contents
Posted by Jens Remus 6 months, 3 weeks ago
---

Notes (jremus):
    Link: https://lore.kernel.org/all/b35ca3a3-8de5-4d32-8d30-d4e562f6b0de@linux.ibm.com/
    
    The struct sframe_fre field ip_off must be u32, as the SFrame FRE start
    address (sfre_start_address) is unsigned 8-bit, 16-bit, or 32-bit:
    https://sourceware.org/binutils/docs/sframe-spec.html#SFrame-Frame-Row-Entries
    
    When searching for a FRE of a FDE for an IP, the IP offset from function
    start address (ip_off = ip - (sec->sframe_start + fde->start_addr)) is
    always positive, as the search for a FDE for the same IP returned a
    FDE with:  sec->sframe_start + fde->start_addr <= ip
    This enables comparison against the unsigned FDE ip_off.
    
    This fixup includes a proposed fix from Josh (with minor modification
    due to duplicate macro names) to correctly perform sign extension when
    reading (un-)signed SFrame FDE/FRE fields:
    https://lore.kernel.org/all/20250207210614.nks6bxad4jhdulwg@jpoimboe/

 kernel/unwind/sframe.c | 38 +++++++++++++++++++++++++++-----------
 1 file changed, 27 insertions(+), 11 deletions(-)

diff --git a/kernel/unwind/sframe.c b/kernel/unwind/sframe.c
index 3f7cc9bc27eb..8804ac59edfa 100644
--- a/kernel/unwind/sframe.c
+++ b/kernel/unwind/sframe.c
@@ -19,7 +19,7 @@
 
 struct sframe_fre {
 	unsigned int	size;
-	s32		ip_off;
+	u32		ip_off;
 	s32		cfa_off;
 	s32		ra_off;
 	s32		fp_off;
@@ -129,33 +129,48 @@ static __always_inline int __find_fde(struct sframe_section *sec,
 	return -EFAULT;
 }
 
-#define __UNSAFE_GET_USER_INC(to, from, type, label)			\
+#define ____UNSAFE_GET_USER_INC(to, from, type, label)			\
 ({									\
 	type __to;							\
 	unsafe_get_user(__to, (type __user *)from, label);		\
 	from += sizeof(__to);						\
-	to = (typeof(to))__to;							\
+	to = __to;							\
 })
 
-#define UNSAFE_GET_USER_INC(to, from, size, label)			\
+#define __UNSAFE_GET_USER_INC(to, from, size, label, u_or_s)		\
 ({									\
 	switch (size) {							\
 	case 1:								\
-		__UNSAFE_GET_USER_INC(to, from, u8, label);		\
+		____UNSAFE_GET_USER_INC(to, from, u_or_s##8, label);	\
 		break;							\
 	case 2:								\
-		__UNSAFE_GET_USER_INC(to, from, u16, label);		\
+		____UNSAFE_GET_USER_INC(to, from, u_or_s##16, label);	\
 		break;							\
 	case 4:								\
-		__UNSAFE_GET_USER_INC(to, from, u32, label);		\
+		____UNSAFE_GET_USER_INC(to, from, u_or_s##32, label);	\
 		break;							\
 	default:							\
-		dbg_sec_uaccess("%d: bad UNSAFE_GET_USER_INC size %u\n",\
+		dbg_sec_uaccess("%d: bad unsafe_get_user() size %u\n",	\
 				__LINE__, size);			\
 		return -EFAULT;						\
 	}								\
 })
 
+#define UNSAFE_GET_USER_UNSIGNED_INC(to, from, size, label)		\
+	__UNSAFE_GET_USER_INC(to, from, size, label, u)
+
+#define UNSAFE_GET_USER_SIGNED_INC(to, from, size, label)		\
+	__UNSAFE_GET_USER_INC(to, from, size, label, s)
+
+#define UNSAFE_GET_USER_INC(to, from, size, label)				\
+	_Generic(to,								\
+		 u8:	UNSAFE_GET_USER_UNSIGNED_INC(to, from, size, label),	\
+		 u16:	UNSAFE_GET_USER_UNSIGNED_INC(to, from, size, label),	\
+		 u32:	UNSAFE_GET_USER_UNSIGNED_INC(to, from, size, label),	\
+		 s8:	UNSAFE_GET_USER_SIGNED_INC(to, from, size, label),	\
+		 s16:	UNSAFE_GET_USER_SIGNED_INC(to, from, size, label),	\
+		 s32:	UNSAFE_GET_USER_SIGNED_INC(to, from, size, label))
+
 static __always_inline int __read_fre(struct sframe_section *sec,
 				      struct sframe_fde *fde,
 				      unsigned long fre_addr,
@@ -164,7 +179,8 @@ static __always_inline int __read_fre(struct sframe_section *sec,
 	unsigned char fde_type = SFRAME_FUNC_FDE_TYPE(fde->info);
 	unsigned char fre_type = SFRAME_FUNC_FRE_TYPE(fde->info);
 	unsigned char offset_count, offset_size;
-	s32 ip_off, cfa_off, ra_off, fp_off;
+	u32 ip_off;
+	s32 cfa_off, ra_off, fp_off;
 	unsigned long cur = fre_addr;
 	unsigned char addr_size;
 	u8 info;
@@ -248,9 +264,9 @@ static __always_inline int __find_fre(struct sframe_section *sec,
 	unsigned long fre_addr;
 	bool which = false;
 	unsigned int i;
-	s32 ip_off;
+	u32 ip_off;
 
-	ip_off = (s32)(ip - sec->sframe_start) - fde->start_addr;
+	ip_off = ip - (sec->sframe_start + fde->start_addr);
 
 	if (fde_type == SFRAME_FDE_TYPE_PCMASK)
 		ip_off %= fde->rep_size;
-- 
2.45.2