Fix miscompilation for maliciously-crafted use of locals

This commit is contained in:
Jef
2019-01-15 14:00:43 +01:00
parent 45b2a5dae2
commit 62fe065e85
2 changed files with 106 additions and 59 deletions

View File

@@ -263,15 +263,32 @@ impl Value {
} }
} }
/// A value on the logical stack. The logical stack is the value stack as it
/// is visible to the WebAssembly, whereas the physical stack is the stack as
/// it exists on the machine (i.e. as offsets in memory relative to `rsp`).
#[derive(Debug, Copy, Clone, PartialEq, Eq)] #[derive(Debug, Copy, Clone, PartialEq, Eq)]
enum StackValue { enum StackValue {
/// A local (arguments are also locals). Note that when setting a local
/// any values of this local stored on the stack are rendered invalid.
/// See `manifest_local` for how we manage this.
Local(u32), Local(u32),
/// A temporary, stored in a register
Temp(GPR), Temp(GPR),
/// An immediate value, created with `i32.const`/`i64.const` etc.
Immediate(i64), Immediate(i64),
/// This value is on the physical stack and so should be accessed
/// with the `pop` instruction.
Pop, Pop,
} }
impl StackValue { impl StackValue {
/// Returns either the location that this value can be accessed at
/// if possible. If this value is `Pop`, you can only access it by
/// popping the physical stack and so this function returns `None`.
///
/// Of course, we could calculate the location of the value on the
/// physical stack, but that would be unncessary computation for
/// our usecases.
fn location(&self, locals: &Locals) -> Option<ValueLocation> { fn location(&self, locals: &Locals) -> Option<ValueLocation> {
match *self { match *self {
StackValue::Local(loc) => Some(locals.get(loc)), StackValue::Local(loc) => Some(locals.get(loc)),
@@ -282,13 +299,20 @@ impl StackValue {
} }
} }
/// A store for the local values for our function, including arguments.
#[derive(Debug, Default, Clone)] #[derive(Debug, Default, Clone)]
struct Locals { pub struct Locals {
// TODO: Store all places that the value can be read, so we can optimise // TODO: Store all places that the value can be read, so we can optimise
// passing (register) arguments along into a noop after saving their // passing (register) arguments along into a noop after saving their
// values. // values.
/// All arguments in registers, check `ARGS_IN_GPRS` for the list of
/// registers that this can contain. If we need to move the argument
/// out of a register (for example, because we're calling a function)
/// we note that down here, so we don't have to move it back afterwards.
register_arguments: ArrayVec<[ValueLocation; ARGS_IN_GPRS.len()]>, register_arguments: ArrayVec<[ValueLocation; ARGS_IN_GPRS.len()]>,
/// The number of arguments stored on the stack.
num_stack_args: u32, num_stack_args: u32,
/// The number of local stack slots, i.e. the amount of stack space reserved for locals.
num_local_stack_slots: u32, num_local_stack_slots: u32,
} }
@@ -340,10 +364,10 @@ pub struct BlockState {
return_register: Option<GPR>, return_register: Option<GPR>,
regs: Registers, regs: Registers,
/// This is the _current_ locals, since we can shuffle them about during function calls. /// This is the _current_ locals, since we can shuffle them about during function calls.
/// We will restore this to be the same state as the `Locals` in `Context<T>` at the end pub locals: Locals,
/// of a block. /// In non-linear control flow (ifs and loops) we have to set the locals to the state that
locals: Locals, /// the next block that we enter will expect them in.
end_locals: Option<Locals>, pub end_locals: Option<Locals>,
} }
type Stack = Vec<StackValue>; type Stack = Vec<StackValue>;
@@ -366,7 +390,7 @@ pub struct Context<'a> {
asm: &'a mut Assembler, asm: &'a mut Assembler,
func_starts: &'a Vec<(Option<AssemblyOffset>, DynamicLabel)>, func_starts: &'a Vec<(Option<AssemblyOffset>, DynamicLabel)>,
/// Each push and pop on the value stack increments or decrements this value by 1 respectively. /// Each push and pop on the value stack increments or decrements this value by 1 respectively.
block_state: BlockState, pub block_state: BlockState,
has_memory: bool, has_memory: bool,
} }
@@ -1064,13 +1088,7 @@ impl Context<'_> {
// We use `put` instead of `pop` since with `BrIf` it's possible // We use `put` instead of `pop` since with `BrIf` it's possible
// that the block will continue after returning. // that the block will continue after returning.
pub fn return_from_block(&mut self, arity: u32, is_function_end: bool) { pub fn return_from_block(&mut self, arity: u32) {
// This should just be an optimisation, passing `false` should always result
// in correct code.
if !is_function_end {
self.restore_locals();
}
if arity == 0 { if arity == 0 {
return; return;
} }
@@ -1092,7 +1110,7 @@ impl Context<'_> {
} }
} }
pub fn start_block(&mut self, is_loop: bool) -> BlockState { pub fn start_block(&mut self) -> BlockState {
use std::mem; use std::mem;
// OPTIMISATION: We cannot use the parent's stack values (it is disallowed by the spec) // OPTIMISATION: We cannot use the parent's stack values (it is disallowed by the spec)
@@ -1105,10 +1123,6 @@ impl Context<'_> {
let mut current_state = self.block_state.clone(); let mut current_state = self.block_state.clone();
current_state.stack = out_stack; current_state.stack = out_stack;
if is_loop {
self.block_state.end_locals = Some(current_state.locals.clone());
}
self.block_state.return_register = None; self.block_state.return_register = None;
current_state current_state
} }
@@ -1140,6 +1154,8 @@ impl Context<'_> {
); );
} }
self.restore_locals();
let return_reg = self.block_state.return_register; let return_reg = self.block_state.return_register;
let locals = mem::replace(&mut self.block_state.locals, Default::default()); let locals = mem::replace(&mut self.block_state.locals, Default::default());
self.block_state = parent_block_state; self.block_state = parent_block_state;
@@ -1153,33 +1169,32 @@ impl Context<'_> {
} }
} }
fn restore_locals(&mut self) { pub fn restore_locals(&mut self) {
if let Some(end_registers) = self if let Some(end_locals) = self.block_state.end_locals.clone() {
.block_state self.restore_locals_to(&end_locals);
.end_locals }
.as_ref() }
.map(|l| l.register_arguments.clone())
{
for (src, dst) in self
.block_state
.locals
.register_arguments
.clone()
.iter()
.zip(&end_registers)
{
self.copy_value(*src, *dst);
}
for (src, dst) in self pub fn restore_locals_to(&mut self, locals: &Locals) {
.block_state for (src, dst) in self
.locals .block_state
.register_arguments .locals
.iter_mut() .register_arguments
.zip(&end_registers) .clone()
{ .iter()
*src = *dst; .zip(&locals.register_arguments)
} {
self.copy_value(*src, *dst);
}
for (src, dst) in self
.block_state
.locals
.register_arguments
.iter_mut()
.zip(&locals.register_arguments)
{
*src = *dst;
} }
} }

View File

@@ -40,6 +40,13 @@ impl ControlFrameKind {
} }
} }
fn is_loop(&self) -> bool {
match *self {
ControlFrameKind::Loop { .. } => true,
_ => false,
}
}
fn branch_target(&self) -> Label { fn branch_target(&self) -> Label {
match *self { match *self {
ControlFrameKind::Block { end_label } => end_label, ControlFrameKind::Block { end_label } => end_label,
@@ -101,13 +108,30 @@ pub fn translate(
control_frames: &mut Vec<ControlFrame>, control_frames: &mut Vec<ControlFrame>,
idx: usize, idx: usize,
) { ) {
control_frames let control_frame = control_frames.get_mut(idx).expect("wrong depth");
.last_mut()
.expect("Control stack is empty!")
.mark_unreachable();
let control_frame = control_frames.get(idx).expect("wrong depth"); if control_frame.kind.is_loop() {
ctx.return_from_block(control_frame.arity(), idx == 0); ctx.restore_locals_to(&control_frame.block_state.locals);
} else {
// We can't do any execution after the function end so we just skip this logic
// if we're breaking out of the whole function.
if idx != 0 {
// Workaround for borrowck limitations
let should_set = if let Some(locals) = control_frame.block_state.end_locals.as_ref()
{
ctx.restore_locals_to(locals);
false
} else {
true
};
if should_set {
control_frame.block_state.end_locals = Some(ctx.block_state.locals.clone());
}
}
ctx.return_from_block(control_frame.arity());
}
ctx.br(control_frame.kind.branch_target()); ctx.br(control_frame.kind.branch_target());
} }
@@ -141,7 +165,7 @@ pub fn translate(
// Upon entering the function implicit frame for function body is pushed. It has the same // Upon entering the function implicit frame for function body is pushed. It has the same
// result type as the function itself. Branching to it is equivalent to returning from the function. // result type as the function itself. Branching to it is equivalent to returning from the function.
let epilogue_label = ctx.create_label(); let epilogue_label = ctx.create_label();
let function_block_state = ctx.start_block(false); let function_block_state = ctx.start_block();
control_frames.push(ControlFrame::new( control_frames.push(ControlFrame::new(
ControlFrameKind::Block { ControlFrameKind::Block {
end_label: epilogue_label, end_label: epilogue_label,
@@ -180,7 +204,7 @@ pub fn translate(
} }
Operator::Block { ty } => { Operator::Block { ty } => {
let label = ctx.create_label(); let label = ctx.create_label();
let state = ctx.start_block(false); let state = ctx.start_block();
control_frames.push(ControlFrame::new( control_frames.push(ControlFrame::new(
ControlFrameKind::Block { end_label: label }, ControlFrameKind::Block { end_label: label },
state, state,
@@ -188,23 +212,31 @@ pub fn translate(
)); ));
} }
Operator::Return => { Operator::Return => {
control_frames
.last_mut()
.expect("Control stack is empty!")
.mark_unreachable();
break_from_control_frame_with_id(ctx, &mut control_frames, 0); break_from_control_frame_with_id(ctx, &mut control_frames, 0);
} }
Operator::Br { relative_depth } => { Operator::Br { relative_depth } => {
control_frames
.last_mut()
.expect("Control stack is empty!")
.mark_unreachable();
let idx = control_frames.len() - 1 - relative_depth as usize; let idx = control_frames.len() - 1 - relative_depth as usize;
break_from_control_frame_with_id(ctx, &mut control_frames, idx); break_from_control_frame_with_id(ctx, &mut control_frames, idx);
} }
Operator::BrIf { relative_depth } => { Operator::BrIf { relative_depth } => {
let idx = control_frames.len() - 1 - relative_depth as usize; let idx = control_frames.len() - 1 - relative_depth as usize;
let control_frame = control_frames.get(idx).expect("wrong depth");
let if_not = ctx.create_label(); let if_not = ctx.create_label();
ctx.jump_if_false(if_not); ctx.jump_if_false(if_not);
ctx.return_from_block(control_frame.arity(), idx == 0); break_from_control_frame_with_id(ctx, &mut control_frames, idx);
ctx.br(control_frame.kind.branch_target());
ctx.define_label(if_not); ctx.define_label(if_not);
} }
@@ -213,7 +245,7 @@ pub fn translate(
let if_not = ctx.create_label(); let if_not = ctx.create_label();
ctx.jump_if_false(if_not); ctx.jump_if_false(if_not);
let state = ctx.start_block(false); let state = ctx.start_block();
control_frames.push(ControlFrame::new( control_frames.push(ControlFrame::new(
ControlFrameKind::IfTrue { end_label, if_not }, ControlFrameKind::IfTrue { end_label, if_not },
@@ -225,7 +257,7 @@ pub fn translate(
let header = ctx.create_label(); let header = ctx.create_label();
ctx.define_label(header); ctx.define_label(header);
let state = ctx.start_block(true); let state = ctx.start_block();
control_frames.push(ControlFrame::new( control_frames.push(ControlFrame::new(
ControlFrameKind::Loop { header }, ControlFrameKind::Loop { header },
@@ -241,7 +273,7 @@ pub fn translate(
block_state, block_state,
.. ..
}) => { }) => {
ctx.return_from_block(arity(ty), false); ctx.return_from_block(arity(ty));
ctx.reset_block(block_state.clone()); ctx.reset_block(block_state.clone());
// Finalize `then` block by jumping to the `end_label`. // Finalize `then` block by jumping to the `end_label`.
@@ -280,7 +312,7 @@ pub fn translate(
// Don't bother generating this code if we're in unreachable code // Don't bother generating this code if we're in unreachable code
if !control_frame.unreachable { if !control_frame.unreachable {
ctx.return_from_block(arity, control_frames.is_empty()); ctx.return_from_block(arity);
} }
let block_end = control_frame.kind.block_end(); let block_end = control_frame.kind.block_end();