Skip to content

Commit ccf5405

Browse files
ardbiesheuvelctmarinas
authored andcommitted
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>
1 parent 8e929cb commit ccf5405

File tree

3 files changed

+50
-30
lines changed

3 files changed

+50
-30
lines changed

arch/arm64/include/asm/scs.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,13 @@ static inline void dynamic_scs_init(void)
4646
static inline void dynamic_scs_init(void) {}
4747
#endif
4848

49+
enum {
50+
EDYNSCS_INVALID_CIE_HEADER = 1,
51+
EDYNSCS_INVALID_CIE_SDATA_SIZE = 2,
52+
EDYNSCS_INVALID_FDE_AUGM_DATA_SIZE = 3,
53+
EDYNSCS_INVALID_CFA_OPCODE = 4,
54+
};
55+
4956
int __pi_scs_patch(const u8 eh_frame[], int size);
5057
asmlinkage void __pi_scs_patch_vmlinux(void);
5158

arch/arm64/kernel/module.c

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -462,14 +462,20 @@ int module_finalize(const Elf_Ehdr *hdr,
462462
struct module *me)
463463
{
464464
const Elf_Shdr *s;
465+
int ret;
466+
465467
s = find_section(hdr, sechdrs, ".altinstructions");
466468
if (s)
467469
apply_alternatives_module((void *)s->sh_addr, s->sh_size);
468470

469471
if (scs_is_dynamic()) {
470472
s = find_section(hdr, sechdrs, ".init.eh_frame");
471-
if (s)
472-
__pi_scs_patch((void *)s->sh_addr, s->sh_size);
473+
if (s) {
474+
ret = __pi_scs_patch((void *)s->sh_addr, s->sh_size);
475+
if (ret)
476+
pr_err("module %s: error occurred during dynamic SCS patching (%d)\n",
477+
me->name, ret);
478+
}
473479
}
474480

475481
return module_init_ftrace_plt(hdr, sechdrs, me);

arch/arm64/kernel/pi/patch-scs.c

Lines changed: 35 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,11 @@ struct eh_frame {
120120
union {
121121
struct { // CIE
122122
u8 version;
123-
u8 augmentation_string[];
123+
u8 augmentation_string[3];
124+
u8 code_alignment_factor;
125+
u8 data_alignment_factor;
126+
u8 return_address_register;
127+
u8 augmentation_data_size;
124128
};
125129

126130
struct { // FDE
@@ -132,25 +136,21 @@ struct eh_frame {
132136
};
133137

134138
static int scs_handle_fde_frame(const struct eh_frame *frame,
135-
bool fde_has_augmentation_data,
136139
int code_alignment_factor,
137140
bool dry_run)
138141
{
139142
int size = frame->size - offsetof(struct eh_frame, opcodes) + 4;
140143
u64 loc = (u64)offset_to_ptr(&frame->initial_loc);
141144
const u8 *opcode = frame->opcodes;
145+
int l;
142146

143-
if (fde_has_augmentation_data) {
144-
int l;
147+
// assume single byte uleb128_t for augmentation data size
148+
if (*opcode & BIT(7))
149+
return EDYNSCS_INVALID_FDE_AUGM_DATA_SIZE;
145150

146-
// assume single byte uleb128_t
147-
if (WARN_ON(*opcode & BIT(7)))
148-
return -ENOEXEC;
149-
150-
l = *opcode++;
151-
opcode += l;
152-
size -= l + 1;
153-
}
151+
l = *opcode++;
152+
opcode += l;
153+
size -= l + 1;
154154

155155
/*
156156
* Starting from 'loc', apply the CFA opcodes that advance the location
@@ -201,20 +201,19 @@ static int scs_handle_fde_frame(const struct eh_frame *frame,
201201
break;
202202

203203
default:
204-
return -ENOEXEC;
204+
return EDYNSCS_INVALID_CFA_OPCODE;
205205
}
206206
}
207207
return 0;
208208
}
209209

210210
int scs_patch(const u8 eh_frame[], int size)
211211
{
212+
int code_alignment_factor = 1;
212213
const u8 *p = eh_frame;
213214

214215
while (size > 4) {
215216
const struct eh_frame *frame = (const void *)p;
216-
bool fde_has_augmentation_data = true;
217-
int code_alignment_factor = 1;
218217
int ret;
219218

220219
if (frame->size == 0 ||
@@ -223,28 +222,36 @@ int scs_patch(const u8 eh_frame[], int size)
223222
break;
224223

225224
if (frame->cie_id_or_pointer == 0) {
226-
const u8 *p = frame->augmentation_string;
227-
228-
/* a 'z' in the augmentation string must come first */
229-
fde_has_augmentation_data = *p == 'z';
225+
/*
226+
* Require presence of augmentation data (z) with a
227+
* specifier for the size of the FDE initial_loc and
228+
* range fields (R), and nothing else.
229+
*/
230+
if (strcmp(frame->augmentation_string, "zR"))
231+
return EDYNSCS_INVALID_CIE_HEADER;
230232

231233
/*
232234
* The code alignment factor is a uleb128 encoded field
233235
* but given that the only sensible values are 1 or 4,
234-
* there is no point in decoding the whole thing.
236+
* there is no point in decoding the whole thing. Also
237+
* sanity check the size of the data alignment factor
238+
* field, and the values of the return address register
239+
* and augmentation data size fields.
235240
*/
236-
p += strlen(p) + 1;
237-
if (!WARN_ON(*p & BIT(7)))
238-
code_alignment_factor = *p;
241+
if ((frame->code_alignment_factor & BIT(7)) ||
242+
(frame->data_alignment_factor & BIT(7)) ||
243+
frame->return_address_register != 30 ||
244+
frame->augmentation_data_size != 1)
245+
return EDYNSCS_INVALID_CIE_HEADER;
246+
247+
code_alignment_factor = frame->code_alignment_factor;
239248
} else {
240-
ret = scs_handle_fde_frame(frame,
241-
fde_has_augmentation_data,
242-
code_alignment_factor,
249+
ret = scs_handle_fde_frame(frame, code_alignment_factor,
243250
true);
244251
if (ret)
245252
return ret;
246-
scs_handle_fde_frame(frame, fde_has_augmentation_data,
247-
code_alignment_factor, false);
253+
scs_handle_fde_frame(frame, code_alignment_factor,
254+
false);
248255
}
249256

250257
p += sizeof(frame->size) + frame->size;

0 commit comments

Comments
 (0)