Skip to content

Commit bfb8db4

Browse files
alexcrichtoneduardomourar
authored andcommitted
aarch64: Fix return_call's interaction with pointer authentication (bytecodealliance#6810)
* aarch64: Fix `return_call`'s interaction with pointer authentication This commit fixes an issue where a `return_call` would not decrypt the return address when pointer authentication is enabled. The return address would be encrypted upon entry into the function but would never get restored later on. This addresses the issue by changing the encryption keys in use from the A/B key plus SP to instead using A/B plus the zero key. The reason for this is that during a normal function call before returning the SP value is guaranteed to be the same as it was upon entry. For tail calls though SP may be different due to differing numbers of stack arguments. This means that the modifier of SP can't be used for the tail convention. New `APIKey` definitions were added and that now additionally represents the A/B key plus the modifier. Non-`tail` calling conventions still use the same keys as before, it's just the `tail` convention that's different. Closes bytecodealliance#6799 * Fix tests * Fix test expectations * Allow `sign_return_address` at all times in filetests
1 parent 255778a commit bfb8db4

14 files changed

Lines changed: 380 additions & 129 deletions

File tree

cranelift/codegen/src/isa/aarch64/abi.rs

Lines changed: 63 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -396,27 +396,21 @@ impl ABIMachineSpec for AArch64MachineDeps {
396396
fn gen_ret(
397397
setup_frame: bool,
398398
isa_flags: &aarch64_settings::Flags,
399+
call_conv: isa::CallConv,
399400
rets: Vec<RetPair>,
400401
stack_bytes_to_pop: u32,
401402
) -> Inst {
402-
if isa_flags.sign_return_address() && (setup_frame || isa_flags.sign_return_address_all()) {
403-
let key = if isa_flags.sign_return_address_with_bkey() {
404-
APIKey::B
405-
} else {
406-
APIKey::A
407-
};
408-
409-
Inst::AuthenticatedRet {
403+
match select_api_key(isa_flags, call_conv, setup_frame) {
404+
Some(key) => Inst::AuthenticatedRet {
410405
key,
411406
is_hint: !isa_flags.has_pauth(),
412407
rets,
413408
stack_bytes_to_pop,
414-
}
415-
} else {
416-
Inst::Ret {
409+
},
410+
None => Inst::Ret {
417411
rets,
418412
stack_bytes_to_pop,
419-
}
413+
},
420414
}
421415
}
422416

@@ -558,36 +552,32 @@ impl ABIMachineSpec for AArch64MachineDeps {
558552
) -> SmallInstVec<Inst> {
559553
let mut insts = SmallVec::new();
560554

561-
if isa_flags.sign_return_address() && (setup_frame || isa_flags.sign_return_address_all()) {
562-
let key = if isa_flags.sign_return_address_with_bkey() {
563-
APIKey::B
564-
} else {
565-
APIKey::A
566-
};
567-
568-
insts.push(Inst::Pacisp { key });
569-
570-
if flags.unwind_info() {
571-
insts.push(Inst::Unwind {
572-
inst: UnwindInst::Aarch64SetPointerAuth {
573-
return_addresses: true,
574-
},
575-
});
576-
}
577-
} else {
578-
if isa_flags.use_bti() {
579-
insts.push(Inst::Bti {
580-
targets: BranchTargetType::C,
581-
});
555+
match select_api_key(isa_flags, call_conv, setup_frame) {
556+
Some(key) => {
557+
insts.push(Inst::Paci { key });
558+
if flags.unwind_info() {
559+
insts.push(Inst::Unwind {
560+
inst: UnwindInst::Aarch64SetPointerAuth {
561+
return_addresses: true,
562+
},
563+
});
564+
}
582565
}
566+
None => {
567+
if isa_flags.use_bti() {
568+
insts.push(Inst::Bti {
569+
targets: BranchTargetType::C,
570+
});
571+
}
583572

584-
if flags.unwind_info() && call_conv.extends_apple_aarch64() {
585-
// The macOS unwinder seems to require this.
586-
insts.push(Inst::Unwind {
587-
inst: UnwindInst::Aarch64SetPointerAuth {
588-
return_addresses: false,
589-
},
590-
});
573+
if flags.unwind_info() && call_conv.extends_apple_aarch64() {
574+
// The macOS unwinder seems to require this.
575+
insts.push(Inst::Unwind {
576+
inst: UnwindInst::Aarch64SetPointerAuth {
577+
return_addresses: false,
578+
},
579+
});
580+
}
591581
}
592582
}
593583

@@ -1161,8 +1151,39 @@ impl ABIMachineSpec for AArch64MachineDeps {
11611151
}
11621152
}
11631153

1154+
fn select_api_key(
1155+
isa_flags: &aarch64_settings::Flags,
1156+
call_conv: isa::CallConv,
1157+
setup_frame: bool,
1158+
) -> Option<APIKey> {
1159+
if isa_flags.sign_return_address() && (setup_frame || isa_flags.sign_return_address_all()) {
1160+
// The `tail` calling convention uses a zero modifier rather than SP
1161+
// because tail calls may happen with a different stack pointer than
1162+
// when the function was entered, meaning that it won't be the same when
1163+
// the return address is decrypted.
1164+
Some(if isa_flags.sign_return_address_with_bkey() {
1165+
match call_conv {
1166+
isa::CallConv::Tail => APIKey::BZ,
1167+
_ => APIKey::BSP,
1168+
}
1169+
} else {
1170+
match call_conv {
1171+
isa::CallConv::Tail => APIKey::AZ,
1172+
_ => APIKey::ASP,
1173+
}
1174+
})
1175+
} else {
1176+
None
1177+
}
1178+
}
1179+
11641180
impl AArch64CallSite {
1165-
pub fn emit_return_call(mut self, ctx: &mut Lower<Inst>, args: isle::ValueSlice) {
1181+
pub fn emit_return_call(
1182+
mut self,
1183+
ctx: &mut Lower<Inst>,
1184+
args: isle::ValueSlice,
1185+
isa_flags: &aarch64_settings::Flags,
1186+
) {
11661187
let (new_stack_arg_size, old_stack_arg_size) =
11671188
self.emit_temporary_tail_call_frame(ctx, args);
11681189

@@ -1174,6 +1195,7 @@ impl AArch64CallSite {
11741195
opcode,
11751196
old_stack_arg_size,
11761197
new_stack_arg_size,
1198+
key: select_api_key(isa_flags, isa::CallConv::Tail, true),
11771199
});
11781200

11791201
match dest {

cranelift/codegen/src/isa/aarch64/inst.isle

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -902,7 +902,7 @@
902902
;; Pointer authentication code for instruction address with modifier in SP;
903903
;; equivalent to a no-op if Pointer authentication (FEAT_PAuth) is not
904904
;; supported.
905-
(Pacisp
905+
(Paci
906906
(key APIKey))
907907

908908
;; Strip pointer authentication code from instruction address in LR;
@@ -1645,8 +1645,14 @@
16451645
;; Keys for instruction address PACs
16461646
(type APIKey
16471647
(enum
1648-
(A)
1649-
(B)
1648+
;; API key A with the modifier of SP
1649+
(ASP)
1650+
;; API key B with the modifier of SP
1651+
(BSP)
1652+
;; API key A with the modifier of zero
1653+
(AZ)
1654+
;; API key B with the modifier of zero
1655+
(BZ)
16501656
))
16511657

16521658
;; Branch target types

cranelift/codegen/src/isa/aarch64/inst/args.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -739,3 +739,17 @@ impl VectorSize {
739739
}
740740
}
741741
}
742+
743+
impl APIKey {
744+
/// Returns the encoding of the `auti{key}` instruction used to decrypt the
745+
/// `lr` register.
746+
pub fn enc_auti_hint(&self) -> u32 {
747+
let (crm, op2) = match self {
748+
APIKey::AZ => (0b0011, 0b100),
749+
APIKey::ASP => (0b0011, 0b101),
750+
APIKey::BZ => (0b0011, 0b110),
751+
APIKey::BSP => (0b0011, 0b111),
752+
};
753+
0xd503201f | (crm << 8) | (op2 << 5)
754+
}
755+
}

cranelift/codegen/src/isa/aarch64/inst/emit.rs

Lines changed: 45 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -3139,35 +3139,38 @@ impl MachInstEmit for Inst {
31393139
stack_bytes_to_pop,
31403140
..
31413141
} => {
3142-
let key = match key {
3143-
APIKey::A => 0b0,
3144-
APIKey::B => 0b1,
3142+
if stack_bytes_to_pop != 0 {
3143+
// The requirement that `stack_bytes_to_pop` fit in an
3144+
// `Imm12` isn't fundamental, but lifting it is left for
3145+
// future PRs.
3146+
let imm12 = Imm12::maybe_from_u64(u64::from(stack_bytes_to_pop))
3147+
.expect("stack bytes to pop must fit in Imm12");
3148+
Inst::AluRRImm12 {
3149+
alu_op: ALUOp::Add,
3150+
size: OperandSize::Size64,
3151+
rd: writable_stack_reg(),
3152+
rn: stack_reg(),
3153+
imm12,
3154+
}
3155+
.emit(&[], sink, emit_info, state);
3156+
}
3157+
3158+
let (op2, is_hint) = match key {
3159+
APIKey::AZ => (0b100, true),
3160+
APIKey::ASP => (0b101, is_hint),
3161+
APIKey::BZ => (0b110, true),
3162+
APIKey::BSP => (0b111, is_hint),
31453163
};
31463164

31473165
if is_hint {
3148-
sink.put4(0xd50323bf | key << 6); // autiasp / autibsp
3166+
sink.put4(key.enc_auti_hint());
31493167
Inst::Ret {
31503168
rets: vec![],
3151-
stack_bytes_to_pop,
3169+
stack_bytes_to_pop: 0,
31523170
}
31533171
.emit(&[], sink, emit_info, state);
31543172
} else {
3155-
if stack_bytes_to_pop != 0 {
3156-
// The requirement that `stack_bytes_to_pop` fit in an
3157-
// `Imm12` isn't fundamental, but lifting it is left for
3158-
// future PRs.
3159-
let imm12 = Imm12::maybe_from_u64(u64::from(stack_bytes_to_pop))
3160-
.expect("stack bytes to pop must fit in Imm12");
3161-
Inst::AluRRImm12 {
3162-
alu_op: ALUOp::Add,
3163-
size: OperandSize::Size64,
3164-
rd: writable_stack_reg(),
3165-
rn: stack_reg(),
3166-
imm12,
3167-
}
3168-
.emit(&[], sink, emit_info, state);
3169-
}
3170-
sink.put4(0xd65f0bff | key << 10); // retaa / retab
3173+
sink.put4(0xd65f0bff | (op2 << 9)); // reta{key}
31713174
}
31723175
}
31733176
&Inst::Call { ref info } => {
@@ -3208,15 +3211,7 @@ impl MachInstEmit for Inst {
32083211
ref callee,
32093212
ref info,
32103213
} => {
3211-
emit_return_call_common_sequence(
3212-
&mut allocs,
3213-
sink,
3214-
emit_info,
3215-
state,
3216-
info.new_stack_arg_size,
3217-
info.old_stack_arg_size,
3218-
&info.uses,
3219-
);
3214+
emit_return_call_common_sequence(&mut allocs, sink, emit_info, state, info);
32203215

32213216
// Note: this is not `Inst::Jump { .. }.emit(..)` because we
32223217
// have different metadata in this case: we don't have a label
@@ -3233,15 +3228,7 @@ impl MachInstEmit for Inst {
32333228
&Inst::ReturnCallInd { callee, ref info } => {
32343229
let callee = allocs.next(callee);
32353230

3236-
emit_return_call_common_sequence(
3237-
&mut allocs,
3238-
sink,
3239-
emit_info,
3240-
state,
3241-
info.new_stack_arg_size,
3242-
info.old_stack_arg_size,
3243-
&info.uses,
3244-
);
3231+
emit_return_call_common_sequence(&mut allocs, sink, emit_info, state, info);
32453232

32463233
Inst::IndirectBr {
32473234
rn: callee,
@@ -3556,13 +3543,15 @@ impl MachInstEmit for Inst {
35563543
add.emit(&[], sink, emit_info, state);
35573544
}
35583545
}
3559-
&Inst::Pacisp { key } => {
3560-
let key = match key {
3561-
APIKey::A => 0b0,
3562-
APIKey::B => 0b1,
3546+
&Inst::Paci { key } => {
3547+
let (crm, op2) = match key {
3548+
APIKey::AZ => (0b0011, 0b000),
3549+
APIKey::ASP => (0b0011, 0b001),
3550+
APIKey::BZ => (0b0011, 0b010),
3551+
APIKey::BSP => (0b0011, 0b011),
35633552
};
35643553

3565-
sink.put4(0xd503233f | key << 6);
3554+
sink.put4(0xd503211f | (crm << 8) | (op2 << 5));
35663555
}
35673556
&Inst::Xpaclri => sink.put4(0xd50320ff),
35683557
&Inst::Bti { targets } => {
@@ -3768,18 +3757,16 @@ fn emit_return_call_common_sequence(
37683757
sink: &mut MachBuffer<Inst>,
37693758
emit_info: &EmitInfo,
37703759
state: &mut EmitState,
3771-
new_stack_arg_size: u32,
3772-
old_stack_arg_size: u32,
3773-
uses: &CallArgList,
3760+
info: &ReturnCallInfo,
37743761
) {
3775-
for u in uses {
3762+
for u in info.uses.iter() {
37763763
let _ = allocs.next(u.vreg);
37773764
}
37783765

37793766
// We are emitting a dynamic number of instructions and might need an
37803767
// island. We emit four instructions regardless of how many stack arguments
37813768
// we have, and then two instructions per word of stack argument space.
3782-
let new_stack_words = new_stack_arg_size / 8;
3769+
let new_stack_words = info.new_stack_arg_size / 8;
37833770
let insts = 4 + 2 * new_stack_words;
37843771
let size_of_inst = 4;
37853772
let space_needed = insts * size_of_inst;
@@ -3820,7 +3807,7 @@ fn emit_return_call_common_sequence(
38203807
// actual jump happens outside this helper function.
38213808

38223809
assert_eq!(
3823-
new_stack_arg_size % 8,
3810+
info.new_stack_arg_size % 8,
38243811
0,
38253812
"size of new stack arguments must be 8-byte aligned"
38263813
);
@@ -3830,7 +3817,8 @@ fn emit_return_call_common_sequence(
38303817
// arguments as well as accounting for the two words we pushed onto the
38313818
// stack upon entry to this function (the return address and old frame
38323819
// pointer).
3833-
let fp_to_callee_sp = i64::from(old_stack_arg_size) - i64::from(new_stack_arg_size) + 16;
3820+
let fp_to_callee_sp =
3821+
i64::from(info.old_stack_arg_size) - i64::from(info.new_stack_arg_size) + 16;
38343822

38353823
let tmp1 = regs::writable_spilltmp_reg();
38363824
let tmp2 = regs::writable_tmp2_reg();
@@ -3910,10 +3898,14 @@ fn emit_return_call_common_sequence(
39103898
}
39113899
.emit(&[], sink, emit_info, state);
39123900

3913-
state.virtual_sp_offset -= i64::from(new_stack_arg_size);
3901+
state.virtual_sp_offset -= i64::from(info.new_stack_arg_size);
39143902
trace!(
39153903
"return_call[_ind] adjusts virtual sp offset by {} -> {}",
3916-
new_stack_arg_size,
3904+
info.new_stack_arg_size,
39173905
state.virtual_sp_offset
39183906
);
3907+
3908+
if let Some(key) = info.key {
3909+
sink.put4(key.enc_auti_hint());
3910+
}
39193911
}

0 commit comments

Comments
 (0)