Skip to main content

wasm_smith/core/
terminate.rs

1use super::*;
2use anyhow::{Result, bail};
3
4impl Module {
5    /// Ensure that all of this Wasm module's functions will terminate when
6    /// executed.
7    ///
8    /// This adds a new mutable, exported global to the module to keep track of
9    /// how much "fuel" is left. Fuel is decremented at the head of each loop
10    /// and function. When fuel reaches zero, a trap is raised.
11    ///
12    /// The index of the fuel global is returned, so that you may control how
13    /// much fuel the module is given.
14    ///
15    /// # Errors
16    ///
17    /// Returns an error if any function body was generated with
18    /// possibly-invalid bytes rather than being generated by wasm-smith. In
19    /// such a situation this pass does not parse the input bytes and inject
20    /// instructions, instead it returns an error.
21    pub fn ensure_termination(&mut self, default_fuel: u32) -> Result<u32> {
22        let fuel_global = self.globals.len() as u32;
23        self.globals.push(GlobalType {
24            val_type: ValType::I32,
25            mutable: true,
26            shared: false,
27        });
28        self.defined_globals
29            .push((fuel_global, ConstExpr::i32_const(default_fuel as i32)));
30
31        let defined_funcs = &self.funcs[self.funcs.len() - self.code.len()..];
32        for ((_, ty), code) in defined_funcs.iter().zip(self.code.iter_mut()) {
33            let params = ty.params.len();
34
35            let mut temp_i32_local = None;
36            let mut temp_i32_local = |locals: &mut Vec<_>| {
37                if let Some(l) = temp_i32_local {
38                    return l;
39                }
40                let l = u32::try_from(locals.len() + params).unwrap();
41                locals.push(ValType::I32);
42                temp_i32_local = Some(l);
43                l
44            };
45
46            let mut temp_i64_local = None;
47            let mut temp_i64_local = |locals: &mut Vec<_>| {
48                if let Some(l) = temp_i64_local {
49                    return l;
50                }
51                let l = u32::try_from(locals.len() + params).unwrap();
52                locals.push(ValType::I64);
53                temp_i64_local = Some(l);
54                l
55            };
56
57            let check_fuel_1 = |insts: &mut Vec<Instruction>| {
58                // if fuel == 0 { trap }
59                insts.push(Instruction::GlobalGet(fuel_global));
60                insts.push(Instruction::I32Eqz);
61                insts.push(Instruction::If(BlockType::Empty));
62                insts.push(Instruction::Unreachable);
63                insts.push(Instruction::End);
64
65                // fuel -= 1
66                insts.push(Instruction::GlobalGet(fuel_global));
67                insts.push(Instruction::I32Const(1));
68                insts.push(Instruction::I32Sub);
69                insts.push(Instruction::GlobalSet(fuel_global));
70            };
71
72            let check_fuel_n = |insts: &mut Vec<Instruction>, local: u32| {
73                // if local >= fuel { trap }
74                insts.push(Instruction::LocalGet(local));
75                insts.push(Instruction::GlobalGet(fuel_global));
76                insts.push(Instruction::I32GeU);
77                insts.push(Instruction::If(BlockType::Empty));
78                insts.push(Instruction::Unreachable);
79                insts.push(Instruction::End);
80                // fuel -= local
81                insts.push(Instruction::GlobalGet(fuel_global));
82                insts.push(Instruction::LocalGet(local));
83                insts.push(Instruction::I32Sub);
84                insts.push(Instruction::GlobalSet(fuel_global));
85            };
86
87            let check_fuel_32_or_64 = |locals: &mut Vec<ValType>,
88                                       temp_i32_local: &mut dyn FnMut(&mut Vec<ValType>) -> u32,
89                                       temp_i64_local: &mut dyn FnMut(&mut Vec<ValType>) -> u32,
90                                       new_insts: &mut Vec<Instruction>,
91                                       inst: Instruction,
92                                       is_64: bool| {
93                if is_64 {
94                    let local64 = temp_i64_local(locals);
95                    let local32 = temp_i32_local(locals);
96                    new_insts.push(Instruction::LocalTee(local64));
97                    new_insts.push(Instruction::I32WrapI64);
98                    new_insts.push(Instruction::LocalSet(local32));
99                    check_fuel_n(new_insts, local32);
100                    new_insts.push(Instruction::LocalGet(local64));
101                    new_insts.push(inst);
102                } else {
103                    let local = temp_i32_local(locals);
104                    new_insts.push(Instruction::LocalTee(local));
105                    check_fuel_n(new_insts, local);
106                    new_insts.push(inst);
107                }
108            };
109
110            let instrs = match &mut code.instructions {
111                Instructions::Generated(list) => list,
112                Instructions::Arbitrary(_) => {
113                    bail!(
114                        "failed to ensure that a function generated due to it \
115                         containing arbitrary instructions"
116                    )
117                }
118            };
119            let mut new_insts = Vec::with_capacity(instrs.len() * 2);
120
121            // Check fuel at the start of functions to deal with infinite
122            // recursion.
123            check_fuel_1(&mut new_insts);
124
125            for inst in mem::replace(instrs, vec![]) {
126                match &inst {
127                    // Check fuel at loop heads to deal with infinite loops.
128                    Instruction::Loop(_) => {
129                        new_insts.push(inst);
130                        check_fuel_1(&mut new_insts);
131                    }
132
133                    // Check fuel on instructions that imply loops and decrement
134                    // fuel accordingly and always have `len: i32` on top of the
135                    // stack.
136                    Instruction::ArrayCopy { .. }
137                    | Instruction::ArrayFill(_)
138                    | Instruction::ArrayInitData { .. }
139                    | Instruction::ArrayInitElem { .. }
140                    | Instruction::ArrayNew(_)
141                    | Instruction::ArrayNewDefault(_)
142                    | Instruction::ArrayNewData { .. }
143                    | Instruction::ArrayNewElem { .. } => {
144                        let local = temp_i32_local(&mut code.locals);
145                        new_insts.push(Instruction::LocalTee(local));
146                        check_fuel_n(&mut new_insts, local);
147                        new_insts.push(inst);
148                    }
149
150                    // Check fuel on `table.init`, whose `len` operand is always
151                    // `i32`, even for `table64`.
152                    Instruction::TableInit { .. } => {
153                        let local = temp_i32_local(&mut code.locals);
154                        new_insts.push(Instruction::LocalTee(local));
155                        check_fuel_n(&mut new_insts, local);
156                        new_insts.push(inst);
157                    }
158
159                    // Check fuel on `table.fill`, whose `len` operand has the
160                    // table's index type.
161                    Instruction::TableFill(table) => {
162                        let table = usize::try_from(*table).unwrap();
163                        let is_64 = self.tables[table].table64;
164                        check_fuel_32_or_64(
165                            &mut code.locals,
166                            &mut temp_i32_local,
167                            &mut temp_i64_local,
168                            &mut new_insts,
169                            inst,
170                            is_64,
171                        );
172                    }
173
174                    // Check fuel on `table.copy`, whose `len` operand has the
175                    // smaller of the source and destination tables' index types.
176                    Instruction::TableCopy {
177                        dst_table,
178                        src_table,
179                    } => {
180                        let dst_table = usize::try_from(*dst_table).unwrap();
181                        let src_table = usize::try_from(*src_table).unwrap();
182                        check_fuel_32_or_64(
183                            &mut code.locals,
184                            &mut temp_i32_local,
185                            &mut temp_i64_local,
186                            &mut new_insts,
187                            inst,
188                            self.tables[dst_table].table64 && self.tables[src_table].table64,
189                        );
190                    }
191
192                    // Check fuel on `memory.init`, whose `len` operand is
193                    // always `i32`, even for `memory64`.
194                    Instruction::MemoryInit { .. } => {
195                        let local = temp_i32_local(&mut code.locals);
196                        new_insts.push(Instruction::LocalTee(local));
197                        check_fuel_n(&mut new_insts, local);
198                        new_insts.push(inst);
199                    }
200
201                    // Check fuel on `memory.fill`, whose `len` operand has the
202                    // memory's index type.
203                    Instruction::MemoryFill(mem) => {
204                        let mem = usize::try_from(*mem).unwrap();
205                        check_fuel_32_or_64(
206                            &mut code.locals,
207                            &mut temp_i32_local,
208                            &mut temp_i64_local,
209                            &mut new_insts,
210                            inst,
211                            self.memories[mem].memory64,
212                        );
213                    }
214
215                    // Check fuel on `memory.copy`, whose `len` operand has the
216                    // smaller of the source and destination memories' index
217                    // types.
218                    Instruction::MemoryCopy { dst_mem, src_mem } => {
219                        let dst_mem = usize::try_from(*dst_mem).unwrap();
220                        let src_mem = usize::try_from(*src_mem).unwrap();
221                        check_fuel_32_or_64(
222                            &mut code.locals,
223                            &mut temp_i32_local,
224                            &mut temp_i64_local,
225                            &mut new_insts,
226                            inst,
227                            self.memories[dst_mem].memory64 && self.memories[src_mem].memory64,
228                        );
229                    }
230
231                    // Otherwise, just keep the instruction.
232                    _ => new_insts.push(inst),
233                }
234            }
235
236            *instrs = new_insts;
237        }
238
239        Ok(fuel_global)
240    }
241}