2022-10-27 17:59:08 +02:00
|
|
|
// SPDX-License-Identifier: GPL-2.0-only
|
|
|
|
/*
|
|
|
|
* Copyright (C) 2022 - Google LLC
|
|
|
|
* Author: Ard Biesheuvel <ardb@google.com>
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <linux/errno.h>
|
|
|
|
#include <linux/init.h>
|
|
|
|
#include <linux/linkage.h>
|
|
|
|
#include <linux/types.h>
|
|
|
|
|
|
|
|
#include <asm/scs.h>
|
|
|
|
|
2024-02-14 13:29:04 +01:00
|
|
|
#include "pi.h"
|
|
|
|
|
|
|
|
bool dynamic_scs_is_enabled;
|
|
|
|
|
2022-10-27 17:59:08 +02:00
|
|
|
//
|
|
|
|
// This minimal DWARF CFI parser is partially based on the code in
|
|
|
|
// arch/arc/kernel/unwind.c, and on the document below:
|
|
|
|
// https://refspecs.linuxbase.org/LSB_4.0.0/LSB-Core-generic/LSB-Core-generic/ehframechpt.html
|
|
|
|
//
|
|
|
|
|
|
|
|
#define DW_CFA_nop 0x00
|
|
|
|
#define DW_CFA_set_loc 0x01
|
|
|
|
#define DW_CFA_advance_loc1 0x02
|
|
|
|
#define DW_CFA_advance_loc2 0x03
|
|
|
|
#define DW_CFA_advance_loc4 0x04
|
|
|
|
#define DW_CFA_offset_extended 0x05
|
|
|
|
#define DW_CFA_restore_extended 0x06
|
|
|
|
#define DW_CFA_undefined 0x07
|
|
|
|
#define DW_CFA_same_value 0x08
|
|
|
|
#define DW_CFA_register 0x09
|
|
|
|
#define DW_CFA_remember_state 0x0a
|
|
|
|
#define DW_CFA_restore_state 0x0b
|
|
|
|
#define DW_CFA_def_cfa 0x0c
|
|
|
|
#define DW_CFA_def_cfa_register 0x0d
|
|
|
|
#define DW_CFA_def_cfa_offset 0x0e
|
|
|
|
#define DW_CFA_def_cfa_expression 0x0f
|
|
|
|
#define DW_CFA_expression 0x10
|
|
|
|
#define DW_CFA_offset_extended_sf 0x11
|
|
|
|
#define DW_CFA_def_cfa_sf 0x12
|
|
|
|
#define DW_CFA_def_cfa_offset_sf 0x13
|
|
|
|
#define DW_CFA_val_offset 0x14
|
|
|
|
#define DW_CFA_val_offset_sf 0x15
|
|
|
|
#define DW_CFA_val_expression 0x16
|
|
|
|
#define DW_CFA_lo_user 0x1c
|
|
|
|
#define DW_CFA_negate_ra_state 0x2d
|
|
|
|
#define DW_CFA_GNU_args_size 0x2e
|
|
|
|
#define DW_CFA_GNU_negative_offset_extended 0x2f
|
|
|
|
#define DW_CFA_hi_user 0x3f
|
|
|
|
|
2024-11-06 19:55:16 +01:00
|
|
|
#define DW_EH_PE_sdata4 0x0b
|
|
|
|
#define DW_EH_PE_sdata8 0x0c
|
|
|
|
#define DW_EH_PE_pcrel 0x10
|
|
|
|
|
2022-10-27 17:59:08 +02:00
|
|
|
enum {
|
|
|
|
PACIASP = 0xd503233f,
|
|
|
|
AUTIASP = 0xd50323bf,
|
|
|
|
SCS_PUSH = 0xf800865e,
|
|
|
|
SCS_POP = 0xf85f8e5e,
|
|
|
|
};
|
|
|
|
|
|
|
|
static void __always_inline scs_patch_loc(u64 loc)
|
|
|
|
{
|
|
|
|
u32 insn = le32_to_cpup((void *)loc);
|
|
|
|
|
|
|
|
switch (insn) {
|
|
|
|
case PACIASP:
|
|
|
|
*(u32 *)loc = cpu_to_le32(SCS_PUSH);
|
|
|
|
break;
|
|
|
|
case AUTIASP:
|
|
|
|
*(u32 *)loc = cpu_to_le32(SCS_POP);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
/*
|
|
|
|
* While the DW_CFA_negate_ra_state directive is guaranteed to
|
|
|
|
* appear right after a PACIASP/AUTIASP instruction, it may
|
|
|
|
* also appear after a DW_CFA_restore_state directive that
|
|
|
|
* restores a state that is only partially accurate, and is
|
|
|
|
* followed by DW_CFA_negate_ra_state directive to toggle the
|
|
|
|
* PAC bit again. So we permit other instructions here, and ignore
|
|
|
|
* them.
|
|
|
|
*/
|
|
|
|
return;
|
|
|
|
}
|
2024-02-14 13:28:55 +01:00
|
|
|
if (IS_ENABLED(CONFIG_ARM64_WORKAROUND_CLEAN_CACHE))
|
|
|
|
asm("dc civac, %0" :: "r"(loc));
|
|
|
|
else
|
|
|
|
asm(ALTERNATIVE("dc cvau, %0", "nop", ARM64_HAS_CACHE_IDC)
|
|
|
|
:: "r"(loc));
|
2022-10-27 17:59:08 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Skip one uleb128/sleb128 encoded quantity from the opcode stream. All bytes
|
|
|
|
* except the last one have bit #7 set.
|
|
|
|
*/
|
|
|
|
static int __always_inline skip_xleb128(const u8 **opcode, int size)
|
|
|
|
{
|
|
|
|
u8 c;
|
|
|
|
|
|
|
|
do {
|
|
|
|
c = *(*opcode)++;
|
|
|
|
size--;
|
|
|
|
} while (c & BIT(7));
|
|
|
|
|
|
|
|
return size;
|
|
|
|
}
|
|
|
|
|
|
|
|
struct eh_frame {
|
|
|
|
/*
|
|
|
|
* The size of this frame if 0 < size < U32_MAX, 0 terminates the list.
|
|
|
|
*/
|
|
|
|
u32 size;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* The first frame is a Common Information Entry (CIE) frame, followed
|
|
|
|
* by one or more Frame Description Entry (FDE) frames. In the former
|
|
|
|
* case, this field is 0, otherwise it is the negated offset relative
|
|
|
|
* to the associated CIE frame.
|
|
|
|
*/
|
|
|
|
u32 cie_id_or_pointer;
|
|
|
|
|
|
|
|
union {
|
|
|
|
struct { // CIE
|
|
|
|
u8 version;
|
arm64/scs: Fix handling of DWARF augmentation data in CIE/FDE frames
The dynamic SCS patching code pretends to parse the DWARF augmentation
data in the CIE (header) frame, and handle accordingly when processing
the individual FDE frames based on this CIE frame. However, the boolean
variable is defined inside the loop, and so the parsed value is ignored.
The same applies to the code alignment field, which is also read from
the header but then discarded.
This was never spotted before because Clang is the only compiler that
supports dynamic SCS patching (which is essentially an Android feature),
and the unwind tables it produces are highly uniform, and match the
de facto defaults.
So instead of testing for the 'z' flag in the augmentation data field,
require a fixed augmentation data string of 'zR', and simplify the rest
of the code accordingly.
Also introduce some error codes to specify why the patching failed, and
log it to the kernel console on failure when this happens when loading a
module. (Doing so for vmlinux is infeasible, as the patching is done
extremely early in the boot.)
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-6-ardb+git@google.com
Signed-off-by: Catalin Marinas <catalin.marinas@arm.com>
2024-11-06 19:55:15 +01:00
|
|
|
u8 augmentation_string[3];
|
|
|
|
u8 code_alignment_factor;
|
|
|
|
u8 data_alignment_factor;
|
|
|
|
u8 return_address_register;
|
|
|
|
u8 augmentation_data_size;
|
2024-11-06 19:55:16 +01:00
|
|
|
u8 fde_pointer_format;
|
2022-10-27 17:59:08 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
struct { // FDE
|
|
|
|
s32 initial_loc;
|
|
|
|
s32 range;
|
|
|
|
u8 opcodes[];
|
|
|
|
};
|
2024-11-06 19:55:16 +01:00
|
|
|
|
|
|
|
struct { // FDE
|
|
|
|
s64 initial_loc64;
|
|
|
|
s64 range64;
|
|
|
|
u8 opcodes64[];
|
|
|
|
};
|
2022-10-27 17:59:08 +02:00
|
|
|
};
|
|
|
|
};
|
|
|
|
|
2024-02-14 13:28:55 +01:00
|
|
|
static int scs_handle_fde_frame(const struct eh_frame *frame,
|
|
|
|
int code_alignment_factor,
|
2024-11-06 19:55:16 +01:00
|
|
|
bool use_sdata8,
|
2024-02-14 13:28:55 +01:00
|
|
|
bool dry_run)
|
2022-10-27 17:59:08 +02:00
|
|
|
{
|
|
|
|
int size = frame->size - offsetof(struct eh_frame, opcodes) + 4;
|
|
|
|
u64 loc = (u64)offset_to_ptr(&frame->initial_loc);
|
|
|
|
const u8 *opcode = frame->opcodes;
|
arm64/scs: Fix handling of DWARF augmentation data in CIE/FDE frames
The dynamic SCS patching code pretends to parse the DWARF augmentation
data in the CIE (header) frame, and handle accordingly when processing
the individual FDE frames based on this CIE frame. However, the boolean
variable is defined inside the loop, and so the parsed value is ignored.
The same applies to the code alignment field, which is also read from
the header but then discarded.
This was never spotted before because Clang is the only compiler that
supports dynamic SCS patching (which is essentially an Android feature),
and the unwind tables it produces are highly uniform, and match the
de facto defaults.
So instead of testing for the 'z' flag in the augmentation data field,
require a fixed augmentation data string of 'zR', and simplify the rest
of the code accordingly.
Also introduce some error codes to specify why the patching failed, and
log it to the kernel console on failure when this happens when loading a
module. (Doing so for vmlinux is infeasible, as the patching is done
extremely early in the boot.)
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-6-ardb+git@google.com
Signed-off-by: Catalin Marinas <catalin.marinas@arm.com>
2024-11-06 19:55:15 +01:00
|
|
|
int l;
|
2022-10-27 17:59:08 +02:00
|
|
|
|
2024-11-06 19:55:16 +01:00
|
|
|
if (use_sdata8) {
|
|
|
|
loc = (u64)&frame->initial_loc64 + frame->initial_loc64;
|
|
|
|
opcode = frame->opcodes64;
|
|
|
|
size -= 8;
|
|
|
|
}
|
|
|
|
|
arm64/scs: Fix handling of DWARF augmentation data in CIE/FDE frames
The dynamic SCS patching code pretends to parse the DWARF augmentation
data in the CIE (header) frame, and handle accordingly when processing
the individual FDE frames based on this CIE frame. However, the boolean
variable is defined inside the loop, and so the parsed value is ignored.
The same applies to the code alignment field, which is also read from
the header but then discarded.
This was never spotted before because Clang is the only compiler that
supports dynamic SCS patching (which is essentially an Android feature),
and the unwind tables it produces are highly uniform, and match the
de facto defaults.
So instead of testing for the 'z' flag in the augmentation data field,
require a fixed augmentation data string of 'zR', and simplify the rest
of the code accordingly.
Also introduce some error codes to specify why the patching failed, and
log it to the kernel console on failure when this happens when loading a
module. (Doing so for vmlinux is infeasible, as the patching is done
extremely early in the boot.)
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-6-ardb+git@google.com
Signed-off-by: Catalin Marinas <catalin.marinas@arm.com>
2024-11-06 19:55:15 +01:00
|
|
|
// assume single byte uleb128_t for augmentation data size
|
|
|
|
if (*opcode & BIT(7))
|
|
|
|
return EDYNSCS_INVALID_FDE_AUGM_DATA_SIZE;
|
2022-10-27 17:59:08 +02:00
|
|
|
|
arm64/scs: Fix handling of DWARF augmentation data in CIE/FDE frames
The dynamic SCS patching code pretends to parse the DWARF augmentation
data in the CIE (header) frame, and handle accordingly when processing
the individual FDE frames based on this CIE frame. However, the boolean
variable is defined inside the loop, and so the parsed value is ignored.
The same applies to the code alignment field, which is also read from
the header but then discarded.
This was never spotted before because Clang is the only compiler that
supports dynamic SCS patching (which is essentially an Android feature),
and the unwind tables it produces are highly uniform, and match the
de facto defaults.
So instead of testing for the 'z' flag in the augmentation data field,
require a fixed augmentation data string of 'zR', and simplify the rest
of the code accordingly.
Also introduce some error codes to specify why the patching failed, and
log it to the kernel console on failure when this happens when loading a
module. (Doing so for vmlinux is infeasible, as the patching is done
extremely early in the boot.)
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-6-ardb+git@google.com
Signed-off-by: Catalin Marinas <catalin.marinas@arm.com>
2024-11-06 19:55:15 +01:00
|
|
|
l = *opcode++;
|
|
|
|
opcode += l;
|
|
|
|
size -= l + 1;
|
2022-10-27 17:59:08 +02:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Starting from 'loc', apply the CFA opcodes that advance the location
|
|
|
|
* pointer, and identify the locations of the PAC instructions.
|
|
|
|
*/
|
|
|
|
while (size-- > 0) {
|
|
|
|
switch (*opcode++) {
|
|
|
|
case DW_CFA_nop:
|
|
|
|
case DW_CFA_remember_state:
|
|
|
|
case DW_CFA_restore_state:
|
|
|
|
break;
|
|
|
|
|
|
|
|
case DW_CFA_advance_loc1:
|
|
|
|
loc += *opcode++ * code_alignment_factor;
|
|
|
|
size--;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case DW_CFA_advance_loc2:
|
|
|
|
loc += *opcode++ * code_alignment_factor;
|
|
|
|
loc += (*opcode++ << 8) * code_alignment_factor;
|
|
|
|
size -= 2;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case DW_CFA_def_cfa:
|
|
|
|
case DW_CFA_offset_extended:
|
|
|
|
size = skip_xleb128(&opcode, size);
|
|
|
|
fallthrough;
|
|
|
|
case DW_CFA_def_cfa_offset:
|
|
|
|
case DW_CFA_def_cfa_offset_sf:
|
|
|
|
case DW_CFA_def_cfa_register:
|
|
|
|
case DW_CFA_same_value:
|
|
|
|
case DW_CFA_restore_extended:
|
|
|
|
case 0x80 ... 0xbf:
|
|
|
|
size = skip_xleb128(&opcode, size);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case DW_CFA_negate_ra_state:
|
2022-12-13 15:28:49 +01:00
|
|
|
if (!dry_run)
|
|
|
|
scs_patch_loc(loc - 4);
|
2022-10-27 17:59:08 +02:00
|
|
|
break;
|
|
|
|
|
|
|
|
case 0x40 ... 0x7f:
|
|
|
|
// advance loc
|
|
|
|
loc += (opcode[-1] & 0x3f) * code_alignment_factor;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0xc0 ... 0xff:
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
arm64/scs: Fix handling of DWARF augmentation data in CIE/FDE frames
The dynamic SCS patching code pretends to parse the DWARF augmentation
data in the CIE (header) frame, and handle accordingly when processing
the individual FDE frames based on this CIE frame. However, the boolean
variable is defined inside the loop, and so the parsed value is ignored.
The same applies to the code alignment field, which is also read from
the header but then discarded.
This was never spotted before because Clang is the only compiler that
supports dynamic SCS patching (which is essentially an Android feature),
and the unwind tables it produces are highly uniform, and match the
de facto defaults.
So instead of testing for the 'z' flag in the augmentation data field,
require a fixed augmentation data string of 'zR', and simplify the rest
of the code accordingly.
Also introduce some error codes to specify why the patching failed, and
log it to the kernel console on failure when this happens when loading a
module. (Doing so for vmlinux is infeasible, as the patching is done
extremely early in the boot.)
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-6-ardb+git@google.com
Signed-off-by: Catalin Marinas <catalin.marinas@arm.com>
2024-11-06 19:55:15 +01:00
|
|
|
return EDYNSCS_INVALID_CFA_OPCODE;
|
2022-10-27 17:59:08 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2024-02-14 13:28:55 +01:00
|
|
|
int scs_patch(const u8 eh_frame[], int size)
|
2022-10-27 17:59:08 +02:00
|
|
|
{
|
arm64/scs: Fix handling of DWARF augmentation data in CIE/FDE frames
The dynamic SCS patching code pretends to parse the DWARF augmentation
data in the CIE (header) frame, and handle accordingly when processing
the individual FDE frames based on this CIE frame. However, the boolean
variable is defined inside the loop, and so the parsed value is ignored.
The same applies to the code alignment field, which is also read from
the header but then discarded.
This was never spotted before because Clang is the only compiler that
supports dynamic SCS patching (which is essentially an Android feature),
and the unwind tables it produces are highly uniform, and match the
de facto defaults.
So instead of testing for the 'z' flag in the augmentation data field,
require a fixed augmentation data string of 'zR', and simplify the rest
of the code accordingly.
Also introduce some error codes to specify why the patching failed, and
log it to the kernel console on failure when this happens when loading a
module. (Doing so for vmlinux is infeasible, as the patching is done
extremely early in the boot.)
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-6-ardb+git@google.com
Signed-off-by: Catalin Marinas <catalin.marinas@arm.com>
2024-11-06 19:55:15 +01:00
|
|
|
int code_alignment_factor = 1;
|
2024-11-06 19:55:16 +01:00
|
|
|
bool fde_use_sdata8 = false;
|
2022-10-27 17:59:08 +02:00
|
|
|
const u8 *p = eh_frame;
|
|
|
|
|
|
|
|
while (size > 4) {
|
|
|
|
const struct eh_frame *frame = (const void *)p;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
if (frame->size == 0 ||
|
|
|
|
frame->size == U32_MAX ||
|
|
|
|
frame->size > size)
|
|
|
|
break;
|
|
|
|
|
|
|
|
if (frame->cie_id_or_pointer == 0) {
|
arm64/scs: Fix handling of DWARF augmentation data in CIE/FDE frames
The dynamic SCS patching code pretends to parse the DWARF augmentation
data in the CIE (header) frame, and handle accordingly when processing
the individual FDE frames based on this CIE frame. However, the boolean
variable is defined inside the loop, and so the parsed value is ignored.
The same applies to the code alignment field, which is also read from
the header but then discarded.
This was never spotted before because Clang is the only compiler that
supports dynamic SCS patching (which is essentially an Android feature),
and the unwind tables it produces are highly uniform, and match the
de facto defaults.
So instead of testing for the 'z' flag in the augmentation data field,
require a fixed augmentation data string of 'zR', and simplify the rest
of the code accordingly.
Also introduce some error codes to specify why the patching failed, and
log it to the kernel console on failure when this happens when loading a
module. (Doing so for vmlinux is infeasible, as the patching is done
extremely early in the boot.)
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-6-ardb+git@google.com
Signed-off-by: Catalin Marinas <catalin.marinas@arm.com>
2024-11-06 19:55:15 +01:00
|
|
|
/*
|
|
|
|
* Require presence of augmentation data (z) with a
|
|
|
|
* specifier for the size of the FDE initial_loc and
|
|
|
|
* range fields (R), and nothing else.
|
|
|
|
*/
|
|
|
|
if (strcmp(frame->augmentation_string, "zR"))
|
|
|
|
return EDYNSCS_INVALID_CIE_HEADER;
|
2022-10-27 17:59:08 +02:00
|
|
|
|
|
|
|
/*
|
|
|
|
* The code alignment factor is a uleb128 encoded field
|
|
|
|
* but given that the only sensible values are 1 or 4,
|
arm64/scs: Fix handling of DWARF augmentation data in CIE/FDE frames
The dynamic SCS patching code pretends to parse the DWARF augmentation
data in the CIE (header) frame, and handle accordingly when processing
the individual FDE frames based on this CIE frame. However, the boolean
variable is defined inside the loop, and so the parsed value is ignored.
The same applies to the code alignment field, which is also read from
the header but then discarded.
This was never spotted before because Clang is the only compiler that
supports dynamic SCS patching (which is essentially an Android feature),
and the unwind tables it produces are highly uniform, and match the
de facto defaults.
So instead of testing for the 'z' flag in the augmentation data field,
require a fixed augmentation data string of 'zR', and simplify the rest
of the code accordingly.
Also introduce some error codes to specify why the patching failed, and
log it to the kernel console on failure when this happens when loading a
module. (Doing so for vmlinux is infeasible, as the patching is done
extremely early in the boot.)
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-6-ardb+git@google.com
Signed-off-by: Catalin Marinas <catalin.marinas@arm.com>
2024-11-06 19:55:15 +01:00
|
|
|
* there is no point in decoding the whole thing. Also
|
|
|
|
* sanity check the size of the data alignment factor
|
|
|
|
* field, and the values of the return address register
|
|
|
|
* and augmentation data size fields.
|
2022-10-27 17:59:08 +02:00
|
|
|
*/
|
arm64/scs: Fix handling of DWARF augmentation data in CIE/FDE frames
The dynamic SCS patching code pretends to parse the DWARF augmentation
data in the CIE (header) frame, and handle accordingly when processing
the individual FDE frames based on this CIE frame. However, the boolean
variable is defined inside the loop, and so the parsed value is ignored.
The same applies to the code alignment field, which is also read from
the header but then discarded.
This was never spotted before because Clang is the only compiler that
supports dynamic SCS patching (which is essentially an Android feature),
and the unwind tables it produces are highly uniform, and match the
de facto defaults.
So instead of testing for the 'z' flag in the augmentation data field,
require a fixed augmentation data string of 'zR', and simplify the rest
of the code accordingly.
Also introduce some error codes to specify why the patching failed, and
log it to the kernel console on failure when this happens when loading a
module. (Doing so for vmlinux is infeasible, as the patching is done
extremely early in the boot.)
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-6-ardb+git@google.com
Signed-off-by: Catalin Marinas <catalin.marinas@arm.com>
2024-11-06 19:55:15 +01:00
|
|
|
if ((frame->code_alignment_factor & BIT(7)) ||
|
|
|
|
(frame->data_alignment_factor & BIT(7)) ||
|
|
|
|
frame->return_address_register != 30 ||
|
|
|
|
frame->augmentation_data_size != 1)
|
|
|
|
return EDYNSCS_INVALID_CIE_HEADER;
|
|
|
|
|
|
|
|
code_alignment_factor = frame->code_alignment_factor;
|
2024-11-06 19:55:16 +01:00
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
2022-10-27 17:59:08 +02:00
|
|
|
} else {
|
arm64/scs: Fix handling of DWARF augmentation data in CIE/FDE frames
The dynamic SCS patching code pretends to parse the DWARF augmentation
data in the CIE (header) frame, and handle accordingly when processing
the individual FDE frames based on this CIE frame. However, the boolean
variable is defined inside the loop, and so the parsed value is ignored.
The same applies to the code alignment field, which is also read from
the header but then discarded.
This was never spotted before because Clang is the only compiler that
supports dynamic SCS patching (which is essentially an Android feature),
and the unwind tables it produces are highly uniform, and match the
de facto defaults.
So instead of testing for the 'z' flag in the augmentation data field,
require a fixed augmentation data string of 'zR', and simplify the rest
of the code accordingly.
Also introduce some error codes to specify why the patching failed, and
log it to the kernel console on failure when this happens when loading a
module. (Doing so for vmlinux is infeasible, as the patching is done
extremely early in the boot.)
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-6-ardb+git@google.com
Signed-off-by: Catalin Marinas <catalin.marinas@arm.com>
2024-11-06 19:55:15 +01:00
|
|
|
ret = scs_handle_fde_frame(frame, code_alignment_factor,
|
2024-11-06 19:55:16 +01:00
|
|
|
fde_use_sdata8, true);
|
2022-10-27 17:59:08 +02:00
|
|
|
if (ret)
|
|
|
|
return ret;
|
arm64/scs: Fix handling of DWARF augmentation data in CIE/FDE frames
The dynamic SCS patching code pretends to parse the DWARF augmentation
data in the CIE (header) frame, and handle accordingly when processing
the individual FDE frames based on this CIE frame. However, the boolean
variable is defined inside the loop, and so the parsed value is ignored.
The same applies to the code alignment field, which is also read from
the header but then discarded.
This was never spotted before because Clang is the only compiler that
supports dynamic SCS patching (which is essentially an Android feature),
and the unwind tables it produces are highly uniform, and match the
de facto defaults.
So instead of testing for the 'z' flag in the augmentation data field,
require a fixed augmentation data string of 'zR', and simplify the rest
of the code accordingly.
Also introduce some error codes to specify why the patching failed, and
log it to the kernel console on failure when this happens when loading a
module. (Doing so for vmlinux is infeasible, as the patching is done
extremely early in the boot.)
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-6-ardb+git@google.com
Signed-off-by: Catalin Marinas <catalin.marinas@arm.com>
2024-11-06 19:55:15 +01:00
|
|
|
scs_handle_fde_frame(frame, code_alignment_factor,
|
2024-11-06 19:55:16 +01:00
|
|
|
fde_use_sdata8, false);
|
2022-10-27 17:59:08 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
p += sizeof(frame->size) + frame->size;
|
|
|
|
size -= sizeof(frame->size) + frame->size;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|