Skip to content

Commit 2a50190

Browse files
authored
x64: Fix possible overflow in Amode::offset (#12949)
* x64: Fix possible overflow in `Amode::offset` This commit fixes an issue in the x64 backend of Cranelift where the `Amode::offset` method contained unchecked arithmetic meaning that it could possibly overflow. This in turn could lead to a miscompile of loading/storing 128-bit integers where this method is used to generate an `Amode` that is 8 bytes beyond the based address to load the upper bits. This miscompile isn't reachable from WebAssembly but is nonetheless still a good bugfix to have for Cranelift. The fix here is to switch the `Amode::offset` method to being fallible, returning `None` on overflow. This then propagates up into ISLE where the `amode_offset` helper now has a separate case for when the addition fails, using `lea` to generate a register with an address in it. This then subsequently also needed fixing for various `Atomic128*` operations where instead of storing just a single `SyntheticAmode` they now store two, one for the address of the low bits and one for the address of the high bits. * Fix tests Notably package up all the arguments into a boxed structure for the atomic128 ops to avoid making `Inst` too large. * Fix clippy
1 parent e9e1665 commit 2a50190

9 files changed

Lines changed: 303 additions & 169 deletions

File tree

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

Lines changed: 48 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -236,20 +236,15 @@
236236
;;
237237
;; For `AtomicRmwOp::Xchg`, use `Atomic128XchgSeq` instead.
238238
;;
239+
;; This requires that `mem_high` addresses 8 bytes beyond `mem_low`.
240+
;;
239241
;; This instruction sequence has fixed register uses as follows:
240-
;; - %rax (low), %rdx (high) (written) the old value at `mem`
242+
;; - %rax (low), %rdx (high) (written) the old value at `mem_low`
241243
;; - %rbx (low), %rcx (high) (written) used as temp registers to hold
242244
;; the replacement value
243245
;; - %rflags is written. Do not assume anything about it after the
244246
;; instruction.
245-
(Atomic128RmwSeq (op Atomic128RmwSeqOp)
246-
(mem BoxSyntheticAmode)
247-
(operand_low Gpr)
248-
(operand_high Gpr)
249-
(temp_low WritableGpr)
250-
(temp_high WritableGpr)
251-
(dst_old_low WritableGpr)
252-
(dst_old_high WritableGpr))
247+
(Atomic128RmwSeq (args BoxAtomic128RmwSeqArgs))
253248

254249
;; A synthetic instruction, based on a loop around a native `lock
255250
;; cmpxchg16b` instruction.
@@ -258,16 +253,14 @@
258253
;; replacement value is the same every time, this instruction doesn't
259254
;; require any temporary registers.
260255
;;
256+
;; This requires that `mem_high` addresses 8 bytes beyond `mem_low`.
257+
;;
261258
;; This instruction sequence has fixed register uses as follows:
262-
;; - %rax (low), %rdx (high) (written) the old value at `mem`
259+
;; - %rax (low), %rdx (high) (written) the old value at `mem_low`
263260
;; - %rbx (low), %rcx (high) (read) the replacement value
264261
;; - %rflags is written. Do not assume anything about it after the
265262
;; instruction.
266-
(Atomic128XchgSeq (mem SyntheticAmode)
267-
(operand_low Gpr)
268-
(operand_high Gpr)
269-
(dst_old_low WritableGpr)
270-
(dst_old_high WritableGpr))
263+
(Atomic128XchgSeq (args BoxAtomic128XchgSeqArgs))
271264

272265
;; =========================================
273266
;; Meta-instructions generating no code.
@@ -338,11 +331,8 @@
338331
(type BoxCallIndInfo extern (enum))
339332
(type BoxReturnCallInfo extern (enum))
340333
(type BoxReturnCallIndInfo extern (enum))
341-
(type BoxSyntheticAmode extern (enum))
342-
343-
(decl pure box_synthetic_amode (SyntheticAmode) BoxSyntheticAmode)
344-
(extern constructor box_synthetic_amode box_synthetic_amode)
345-
(convert SyntheticAmode BoxSyntheticAmode box_synthetic_amode)
334+
(type BoxAtomic128RmwSeqArgs extern (enum))
335+
(type BoxAtomic128XchgSeqArgs extern (enum))
346336

347337
;; Get the `OperandSize` for a given `Type`, rounding smaller types up to 32 bits.
348338
(decl operand_size_of_type_32_64 (Type) OperandSize)
@@ -352,6 +342,16 @@
352342
(decl raw_operand_size_of_type (Type) OperandSize)
353343
(extern constructor raw_operand_size_of_type raw_operand_size_of_type)
354344

345+
(decl atomic128_rmw_seq_args
346+
(Atomic128RmwSeqOp SyntheticAmode SyntheticAmode Gpr Gpr WritableGpr WritableGpr WritableGpr WritableGpr)
347+
BoxAtomic128RmwSeqArgs)
348+
(extern constructor atomic128_rmw_seq_args atomic128_rmw_seq_args)
349+
350+
(decl atomic128_xchg_seq_args
351+
(SyntheticAmode SyntheticAmode Gpr Gpr WritableGpr WritableGpr)
352+
BoxAtomic128XchgSeqArgs)
353+
(extern constructor atomic128_xchg_seq_args atomic128_xchg_seq_args)
354+
355355
;; Get the bit width of an `OperandSize`.
356356
(decl operand_size_bits (OperandSize) u16)
357357
(rule (operand_size_bits (OperandSize.Size8)) 8)
@@ -588,8 +588,19 @@
588588

589589
;; Offsetting an Amode. Used when we need to do consecutive
590590
;; loads/stores to adjacent addresses.
591-
(decl amode_offset (SyntheticAmode i32) SyntheticAmode)
592-
(extern constructor amode_offset amode_offset)
591+
;;
592+
;; Note that the input `Amode` might have a static offset that's too large to
593+
;; add the specified `i32` to so in the case of overflow a new `Amode` is
594+
;; generated entirely using `lea`
595+
(decl amode_offset (SyntheticAmode MemFlags i32) SyntheticAmode)
596+
(rule 1 (amode_offset base _flags offset)
597+
(if-let new (amode_try_offset base offset))
598+
new)
599+
(rule 0 (amode_offset base flags offset)
600+
(Amode.ImmReg offset (x64_lea $I64 base) flags))
601+
602+
(decl pure partial amode_try_offset (SyntheticAmode i32) SyntheticAmode)
603+
(extern constructor amode_try_offset amode_try_offset)
593604

594605
;; Return a zero offset as an `Offset32`.
595606
(spec (zero_offset) (provide (= result #x00000000)))
@@ -3693,32 +3704,38 @@
36933704
(_ Unit (emit (MInst.AtomicRmwSeq ty op mem input tmp dst))))
36943705
dst))
36953706

3696-
(decl x64_atomic_128_rmw_seq (AtomicRmwOp SyntheticAmode ValueRegs) ValueRegs)
3697-
(rule (x64_atomic_128_rmw_seq op mem input)
3707+
(decl x64_atomic_128_rmw_seq (AtomicRmwOp SyntheticAmode MemFlags ValueRegs) ValueRegs)
3708+
(rule (x64_atomic_128_rmw_seq op mem_low flags input)
36983709
(let ((dst_low WritableGpr (temp_writable_gpr))
36993710
(dst_high WritableGpr (temp_writable_gpr))
37003711
(tmp_low WritableGpr (temp_writable_gpr))
37013712
(tmp_high WritableGpr (temp_writable_gpr))
37023713
(input_low Gpr (value_regs_get_gpr input 0))
37033714
(input_high Gpr (value_regs_get_gpr input 1))
3704-
(_ Unit (emit (MInst.Atomic128RmwSeq (atomic_128_rmw_seq_op op) mem input_low input_high tmp_low tmp_high dst_low dst_high))))
3715+
(mem_high SyntheticAmode (amode_offset mem_low flags 8))
3716+
(args BoxAtomic128RmwSeqArgs (atomic128_rmw_seq_args (atomic_128_rmw_seq_op op) mem_low mem_high input_low input_high tmp_low tmp_high dst_low dst_high))
3717+
(_ Unit (emit (MInst.Atomic128RmwSeq args))))
37053718
(value_regs dst_low dst_high)))
37063719

3707-
(rule 1 (x64_atomic_128_rmw_seq (AtomicRmwOp.Xchg) mem input)
3720+
(rule 1 (x64_atomic_128_rmw_seq (AtomicRmwOp.Xchg) mem_low flags input)
37083721
(let ((dst_low WritableGpr (temp_writable_gpr))
37093722
(dst_high WritableGpr (temp_writable_gpr))
37103723
(input_low Gpr (value_regs_get_gpr input 0))
37113724
(input_high Gpr (value_regs_get_gpr input 1))
3712-
(_ Unit (emit (MInst.Atomic128XchgSeq mem input_low input_high dst_low dst_high))))
3725+
(mem_high SyntheticAmode (amode_offset mem_low flags 8))
3726+
(args BoxAtomic128XchgSeqArgs (atomic128_xchg_seq_args mem_low mem_high input_low input_high dst_low dst_high))
3727+
(_ Unit (emit (MInst.Atomic128XchgSeq args))))
37133728
(value_regs dst_low dst_high)))
37143729

3715-
(decl x64_atomic_128_store_seq (SyntheticAmode ValueRegs) SideEffectNoResult)
3716-
(rule (x64_atomic_128_store_seq mem input)
3730+
(decl x64_atomic_128_store_seq (SyntheticAmode MemFlags ValueRegs) SideEffectNoResult)
3731+
(rule (x64_atomic_128_store_seq mem_low flags input)
37173732
(let ((dst_low WritableGpr (temp_writable_gpr))
37183733
(dst_high WritableGpr (temp_writable_gpr))
37193734
(input_low Gpr (value_regs_get_gpr input 0))
3720-
(input_high Gpr (value_regs_get_gpr input 1)))
3721-
(SideEffectNoResult.Inst (MInst.Atomic128XchgSeq mem input_low input_high dst_low dst_high))))
3735+
(input_high Gpr (value_regs_get_gpr input 1))
3736+
(mem_high SyntheticAmode (amode_offset mem_low flags 8))
3737+
(args BoxAtomic128XchgSeqArgs (atomic128_xchg_seq_args mem_low mem_high input_low input_high dst_low dst_high)))
3738+
(SideEffectNoResult.Inst (MInst.Atomic128XchgSeq args))))
37223739

37233740

37243741
(type AtomicRmwSeqOp

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

Lines changed: 41 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -439,14 +439,14 @@ impl Amode {
439439
}
440440

441441
/// Offset the amode by a fixed offset.
442-
pub(crate) fn offset(&self, offset: i32) -> Self {
442+
pub(crate) fn offset(&self, offset: i32) -> Option<Self> {
443443
let mut ret = self.clone();
444-
match &mut ret {
445-
&mut Amode::ImmReg { ref mut simm32, .. } => *simm32 += offset,
446-
&mut Amode::ImmRegRegShift { ref mut simm32, .. } => *simm32 += offset,
444+
let simm32 = match &mut ret {
445+
Amode::ImmReg { simm32, .. } | Amode::ImmRegRegShift { simm32, .. } => simm32,
447446
_ => panic!("Cannot offset amode: {self:?}"),
448-
}
449-
ret
447+
};
448+
*simm32 = simm32.checked_add(offset)?;
449+
Some(ret)
450450
}
451451

452452
pub(crate) fn aligned(&self) -> bool {
@@ -575,18 +575,18 @@ impl SyntheticAmode {
575575
}
576576

577577
/// Offset the synthetic amode by a fixed offset.
578-
pub(crate) fn offset(&self, offset: i32) -> Self {
578+
pub(crate) fn offset(&self, offset: i32) -> Option<Self> {
579579
let mut ret = self.clone();
580580
match &mut ret {
581-
SyntheticAmode::Real(amode) => *amode = amode.offset(offset),
582-
SyntheticAmode::SlotOffset { simm32 } => *simm32 += offset,
581+
SyntheticAmode::Real(amode) => *amode = amode.offset(offset)?,
582+
SyntheticAmode::SlotOffset { simm32 } => *simm32 = simm32.checked_add(offset)?,
583583
// `amode_offset` is used only in i128.load/store which
584584
// takes a synthetic amode from `to_amode`; `to_amode` can
585585
// only produce Real or SlotOffset amodes, never
586586
// IncomingArg or ConstantOffset.
587587
_ => panic!("Cannot offset SyntheticAmode: {self:?}"),
588588
}
589-
ret
589+
Some(ret)
590590
}
591591
}
592592

@@ -1061,3 +1061,34 @@ impl OperandSize {
10611061
self.to_bytes() * 8
10621062
}
10631063
}
1064+
1065+
pub use crate::isa::x64::lower::isle::generated_code::Atomic128RmwSeqOp;
1066+
1067+
/// "Package" of the arguments for the instruction `Atomic128RmwSeq` to avoid
1068+
/// making the `Inst` enum massive.
1069+
#[derive(Debug, Clone)]
1070+
#[expect(missing_docs, reason = "self-describing fields")]
1071+
pub struct Atomic128RmwSeqArgs {
1072+
pub op: Atomic128RmwSeqOp,
1073+
pub mem_low: SyntheticAmode,
1074+
pub mem_high: SyntheticAmode,
1075+
pub operand_low: Gpr,
1076+
pub operand_high: Gpr,
1077+
pub temp_low: WritableGpr,
1078+
pub temp_high: WritableGpr,
1079+
pub dst_old_low: WritableGpr,
1080+
pub dst_old_high: WritableGpr,
1081+
}
1082+
1083+
/// "Package" of the arguments for the instruction `Atomic128XchgSeq` to avoid
1084+
/// making the `Inst` enum massive.
1085+
#[derive(Debug, Clone)]
1086+
#[expect(missing_docs, reason = "self-describing fields")]
1087+
pub struct Atomic128XchgSeqArgs {
1088+
pub mem_low: SyntheticAmode,
1089+
pub mem_high: SyntheticAmode,
1090+
pub operand_low: Gpr,
1091+
pub operand_high: Gpr,
1092+
pub dst_old_low: WritableGpr,
1093+
pub dst_old_high: WritableGpr,
1094+
}

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

Lines changed: 31 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1544,16 +1544,18 @@ pub(crate) fn emit(
15441544
one_way_jmp(sink, CC::NZ, again_label);
15451545
}
15461546

1547-
Inst::Atomic128RmwSeq {
1548-
op,
1549-
mem,
1550-
operand_low,
1551-
operand_high,
1552-
temp_low,
1553-
temp_high,
1554-
dst_old_low,
1555-
dst_old_high,
1556-
} => {
1547+
Inst::Atomic128RmwSeq { args } => {
1548+
let Atomic128RmwSeqArgs {
1549+
op,
1550+
mem_low,
1551+
mem_high,
1552+
operand_low,
1553+
operand_high,
1554+
temp_low,
1555+
temp_high,
1556+
dst_old_low,
1557+
dst_old_high,
1558+
} = &**args;
15571559
let operand_low = *operand_low;
15581560
let operand_high = *operand_high;
15591561
let temp_low = *temp_low;
@@ -1564,13 +1566,14 @@ pub(crate) fn emit(
15641566
debug_assert_eq!(temp_high.to_reg(), regs::rcx());
15651567
debug_assert_eq!(dst_old_low.to_reg(), regs::rax());
15661568
debug_assert_eq!(dst_old_high.to_reg(), regs::rdx());
1567-
let mem = mem.finalize(state.frame_layout(), sink).clone();
1569+
let mem_low = mem_low.finalize(state.frame_layout(), sink).clone();
1570+
let mem_high = mem_high.finalize(state.frame_layout(), sink).clone();
15681571

15691572
let again_label = sink.get_label();
15701573

15711574
// Load the initial value.
1572-
asm::inst::movq_rm::new(dst_old_low, mem.clone()).emit(sink, info, state);
1573-
asm::inst::movq_rm::new(dst_old_high, mem.offset(8)).emit(sink, info, state);
1575+
asm::inst::movq_rm::new(dst_old_low, mem_low.clone()).emit(sink, info, state);
1576+
asm::inst::movq_rm::new(dst_old_high, mem_high).emit(sink, info, state);
15741577

15751578
// again:
15761579
sink.bind_label(again_label, state.ctrl_plane_mut());
@@ -1656,21 +1659,23 @@ pub(crate) fn emit(
16561659
PairedGpr::from(dst_old_high),
16571660
temp_low.to_reg(),
16581661
temp_high.to_reg(),
1659-
mem,
1662+
mem_low,
16601663
)
16611664
.emit(sink, info, state);
16621665

16631666
// jnz again
16641667
one_way_jmp(sink, CC::NZ, again_label);
16651668
}
16661669

1667-
Inst::Atomic128XchgSeq {
1668-
mem,
1669-
operand_low,
1670-
operand_high,
1671-
dst_old_low,
1672-
dst_old_high,
1673-
} => {
1670+
Inst::Atomic128XchgSeq { args } => {
1671+
let Atomic128XchgSeqArgs {
1672+
mem_low,
1673+
mem_high,
1674+
operand_low,
1675+
operand_high,
1676+
dst_old_low,
1677+
dst_old_high,
1678+
} = &**args;
16741679
let operand_low = *operand_low;
16751680
let operand_high = *operand_high;
16761681
let dst_old_low = *dst_old_low;
@@ -1679,13 +1684,14 @@ pub(crate) fn emit(
16791684
debug_assert_eq!(operand_high, regs::rcx());
16801685
debug_assert_eq!(dst_old_low.to_reg(), regs::rax());
16811686
debug_assert_eq!(dst_old_high.to_reg(), regs::rdx());
1682-
let mem = mem.finalize(state.frame_layout(), sink).clone();
1687+
let mem_low = mem_low.finalize(state.frame_layout(), sink).clone();
1688+
let mem_high = mem_high.finalize(state.frame_layout(), sink).clone();
16831689

16841690
let again_label = sink.get_label();
16851691

16861692
// Load the initial value.
1687-
asm::inst::movq_rm::new(dst_old_low, mem.clone()).emit(sink, info, state);
1688-
asm::inst::movq_rm::new(dst_old_high, mem.offset(8)).emit(sink, info, state);
1693+
asm::inst::movq_rm::new(dst_old_low, mem_low.clone()).emit(sink, info, state);
1694+
asm::inst::movq_rm::new(dst_old_high, mem_high).emit(sink, info, state);
16891695

16901696
// again:
16911697
sink.bind_label(again_label, state.ctrl_plane_mut());
@@ -1696,7 +1702,7 @@ pub(crate) fn emit(
16961702
PairedGpr::from(dst_old_high),
16971703
operand_low,
16981704
operand_high,
1699-
mem,
1705+
mem_low,
17001706
)
17011707
.emit(sink, info, state);
17021708

0 commit comments

Comments
 (0)