arm64/scs: Deal with 64-bit relative offsets in FDE frames

In some cases, the compiler may decide to emit DWARF FDE frames with
64-bit signed fields for the code offset and range fields. This may
happen when using the large code model, for instance, which permits
an executable to be spread out over more than 4 GiB of address space.

Whether this is the case can be inferred from the augmentation data in
the CIE frame, so decode this data before processing the FDE frames.

Signed-off-by: Ard Biesheuvel <ardb@kernel.org>
Reviewed-by: Sami Tolvanen <samitolvanen@google.com>
Tested-by: Sami Tolvanen <samitolvanen@google.com>
Link: https://lore.kernel.org/r/20241106185513.3096442-7-ardb+git@google.com
Signed-off-by: Catalin Marinas <catalin.marinas@arm.com>
This commit is contained in:
Ard Biesheuvel 2024-11-06 19:55:16 +01:00 committed by Catalin Marinas
parent ccf54058f5
commit 60de7a647f

View File

@ -50,6 +50,10 @@ bool dynamic_scs_is_enabled;
#define DW_CFA_GNU_negative_offset_extended 0x2f
#define DW_CFA_hi_user 0x3f
#define DW_EH_PE_sdata4 0x0b
#define DW_EH_PE_sdata8 0x0c
#define DW_EH_PE_pcrel 0x10
enum {
PACIASP = 0xd503233f,
AUTIASP = 0xd50323bf,
@ -125,6 +129,7 @@ struct eh_frame {
u8 data_alignment_factor;
u8 return_address_register;
u8 augmentation_data_size;
u8 fde_pointer_format;
};
struct { // FDE
@ -132,11 +137,18 @@ struct eh_frame {
s32 range;
u8 opcodes[];
};
struct { // FDE
s64 initial_loc64;
s64 range64;
u8 opcodes64[];
};
};
};
static int scs_handle_fde_frame(const struct eh_frame *frame,
int code_alignment_factor,
bool use_sdata8,
bool dry_run)
{
int size = frame->size - offsetof(struct eh_frame, opcodes) + 4;
@ -144,6 +156,12 @@ static int scs_handle_fde_frame(const struct eh_frame *frame,
const u8 *opcode = frame->opcodes;
int l;
if (use_sdata8) {
loc = (u64)&frame->initial_loc64 + frame->initial_loc64;
opcode = frame->opcodes64;
size -= 8;
}
// assume single byte uleb128_t for augmentation data size
if (*opcode & BIT(7))
return EDYNSCS_INVALID_FDE_AUGM_DATA_SIZE;
@ -210,6 +228,7 @@ static int scs_handle_fde_frame(const struct eh_frame *frame,
int scs_patch(const u8 eh_frame[], int size)
{
int code_alignment_factor = 1;
bool fde_use_sdata8 = false;
const u8 *p = eh_frame;
while (size > 4) {
@ -245,13 +264,24 @@ int scs_patch(const u8 eh_frame[], int size)
return EDYNSCS_INVALID_CIE_HEADER;
code_alignment_factor = frame->code_alignment_factor;
switch (frame->fde_pointer_format) {
case DW_EH_PE_pcrel | DW_EH_PE_sdata4:
fde_use_sdata8 = false;
break;
case DW_EH_PE_pcrel | DW_EH_PE_sdata8:
fde_use_sdata8 = true;
break;
default:
return EDYNSCS_INVALID_CIE_SDATA_SIZE;
}
} else {
ret = scs_handle_fde_frame(frame, code_alignment_factor,
true);
fde_use_sdata8, true);
if (ret)
return ret;
scs_handle_fde_frame(frame, code_alignment_factor,
false);
fde_use_sdata8, false);
}
p += sizeof(frame->size) + frame->size;