1use crate::stdlib::prelude::*;
2
3use num_traits::ToPrimitive;
4
5use super::{
6 errors::{runner_errors::RunnerError, vm_errors::VirtualMachineError},
7 runners::cairo_runner::{CairoRunner, RunnerMode},
8};
9use crate::types::relocatable::MaybeRelocatable;
10use crate::Felt252;
11
12pub fn verify_secure_runner(
24 runner: &CairoRunner,
25 verify_builtins: bool,
26 program_segment_size: Option<usize>,
27) -> Result<(), VirtualMachineError> {
28 let builtins_segment_info = match verify_builtins {
29 true => runner.get_builtin_segments_info()?,
30 false => Vec::new(),
31 };
32 for (index, stop_ptr) in builtins_segment_info {
34 let current_size = runner
35 .vm
36 .segments
37 .memory
38 .data
39 .get(index)
40 .map(|segment| segment.len());
41 if current_size >= Some(stop_ptr + 1) {
43 return Err(VirtualMachineError::OutOfBoundsBuiltinSegmentAccess);
44 }
45 }
46 let program_segment_index = runner
48 .program_base
49 .and_then(|rel| rel.segment_index.to_usize())
50 .ok_or(RunnerError::NoProgBase)?;
51 let program_segment_size =
52 program_segment_size.unwrap_or(runner.program.shared_program_data.data.len());
53 let program_length = runner
54 .vm
55 .segments
56 .memory
57 .data
58 .get(program_segment_index)
59 .map(|segment| segment.len());
60 if program_length >= Some(program_segment_size + 1) {
62 return Err(VirtualMachineError::OutOfBoundsProgramSegmentAccess);
63 }
64 if !runner.vm.segments.memory.temp_data.is_empty() {
68 for value in runner.vm.segments.memory.data.iter().flatten() {
69 match value.get_value() {
70 Some(MaybeRelocatable::RelocatableValue(addr)) if addr.segment_index < 0 => {
71 return Err(VirtualMachineError::InvalidMemoryValueTemporaryAddress(
72 Box::new(addr),
73 ))
74 }
75 _ => {}
76 }
77 }
78 }
79 for builtin in runner.vm.builtin_runners.iter() {
80 builtin.run_security_checks(&runner.vm)?;
81 }
82
83 let initial_fp = runner.get_initial_fp().ok_or_else(|| {
85 VirtualMachineError::Other(anyhow::anyhow!(
86 "Failed to retrieve the initial_fp: it is None. \
87 The initial_fp field should be initialized after running the entry point."
88 ))
89 })?;
90 let ret_fp_addr = (initial_fp - 2).map_err(VirtualMachineError::Math)?;
91 let ret_fp = runner.vm.get_maybe(&ret_fp_addr).ok_or_else(|| {
92 VirtualMachineError::Other(anyhow::anyhow!(
93 "Ret FP address is not in memory: {ret_fp_addr}"
94 ))
95 })?;
96 let final_fp = runner.vm.get_fp();
97 match ret_fp {
98 MaybeRelocatable::RelocatableValue(value) => {
99 if runner.runner_mode == RunnerMode::ProofModeCanonical && value != final_fp {
100 return Err(VirtualMachineError::Other(anyhow::anyhow!(
101 "Return FP is not equal to final FP: ret_f={ret_fp}, final_fp={final_fp}"
102 )));
103 }
104 if runner.runner_mode == RunnerMode::ExecutionMode && value.offset != final_fp.offset {
105 return Err(VirtualMachineError::Other(anyhow::anyhow!(
106 "Return FP offset is not equal to final FP offset: ret_f={ret_fp}, final_fp={final_fp}"
107 )));
108 }
109 }
110 MaybeRelocatable::Int(value) => {
111 if Felt252::from(final_fp.offset) != value {
112 return Err(VirtualMachineError::Other(anyhow::anyhow!(
113 "Return FP felt value is not equal to final FP offset: ret_fp={ret_fp}, final_fp={final_fp}"
114 )));
115 }
116 }
117 }
118 Ok(())
119}
120
121#[cfg(test)]
122mod test {
123 use super::*;
124 use crate::hint_processor::builtin_hint_processor::builtin_hint_processor_definition::BuiltinHintProcessor;
125
126 use crate::types::builtin_name::BuiltinName;
127 use crate::types::relocatable::Relocatable;
128
129 use crate::Felt252;
130 use crate::{relocatable, types::program::Program, utils::test_utils::*};
131 use assert_matches::assert_matches;
132
133 #[cfg(target_arch = "wasm32")]
134 use wasm_bindgen_test::*;
135
136 #[test]
137 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
138 fn verify_secure_runner_without_program_base() {
139 let program = program!();
140
141 let runner = cairo_runner!(program);
142
143 assert_matches!(
144 verify_secure_runner(&runner, true, None),
145 Err(VirtualMachineError::RunnerError(RunnerError::NoProgBase))
146 );
147 }
148
149 #[test]
150 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
151 fn verify_secure_runner_empty_memory() {
152 let program = program!(main = Some(0),);
153 let mut runner = cairo_runner!(program);
154 runner.initialize(false).unwrap();
155 let mut hint_processor = BuiltinHintProcessor::new_empty();
157 runner.end_run(false, false, &mut hint_processor).unwrap();
158 runner.vm.run_context.fp = 0;
161 assert_matches!(verify_secure_runner(&runner, true, None), Ok(()));
162 }
163
164 #[test]
165 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
166 fn verify_secure_runner_program_access_out_of_bounds() {
167 let program = program!(main = Some(0),);
168 let mut runner = cairo_runner!(program);
169
170 runner.initialize(false).unwrap();
171
172 runner.vm.segments = segments![((0, 0), 100)];
173 runner.vm.segments.segment_used_sizes = Some(vec![1]);
174
175 assert_matches!(
176 verify_secure_runner(&runner, true, None),
177 Err(VirtualMachineError::OutOfBoundsProgramSegmentAccess)
178 );
179 }
180
181 #[test]
182 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
183 fn verify_secure_runner_program_with_program_size() {
184 let program = program!(main = Some(0),);
185 let mut runner = cairo_runner!(program);
186
187 runner.initialize(false).unwrap();
188 runner.vm.segments = segments![((0, 0), 100), ((1, 0), 0)];
190 runner.vm.segments.segment_used_sizes = Some(vec![1]);
191 runner.vm.run_context.fp = 0;
194 assert_matches!(verify_secure_runner(&runner, true, Some(1)), Ok(()));
195 }
196
197 #[test]
198 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
199 fn verify_secure_runner_builtin_access_out_of_bounds() {
200 let program = program!(main = Some(0), builtins = vec![BuiltinName::range_check],);
201 let mut runner = cairo_runner!(program);
202
203 runner.initialize(false).unwrap();
204 runner.vm.builtin_runners[0].set_stop_ptr(0);
205 runner.vm.segments.memory = memory![((2, 0), 1)];
206 runner.vm.segments.segment_used_sizes = Some(vec![0, 0, 0, 0]);
207
208 assert_matches!(
209 verify_secure_runner(&runner, true, None),
210 Err(VirtualMachineError::OutOfBoundsBuiltinSegmentAccess)
211 );
212 }
213
214 #[test]
215 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
216 fn verify_secure_runner_builtin_access_correct() {
217 let program = program!(main = Some(0), builtins = vec![BuiltinName::range_check],);
218 let mut runner = cairo_runner!(program);
219
220 runner.initialize(false).unwrap();
221 let mut hint_processor = BuiltinHintProcessor::new_empty();
222 runner.end_run(false, false, &mut hint_processor).unwrap();
223 runner.vm.builtin_runners[0].set_stop_ptr(1);
224 runner.vm.segments.memory = memory![((2, 0), 1), ((1, 1), (3, 0))];
226 runner.vm.run_context.fp = 0;
229 runner.vm.segments.segment_used_sizes = Some(vec![0, 0, 1, 0]);
230
231 assert_matches!(verify_secure_runner(&runner, true, None), Ok(()));
232 }
233
234 #[test]
235 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
236 fn verify_secure_runner_success() {
237 let program = program!(
238 data = vec![
239 Felt252::ZERO.into(),
240 Felt252::ZERO.into(),
241 Felt252::ZERO.into(),
242 Felt252::ZERO.into(),
243 ],
244 main = Some(0),
245 );
246
247 let mut runner = cairo_runner!(program);
248
249 runner.initialize(false).unwrap();
250 runner.vm.segments.memory = memory![
252 ((0, 0), (1, 0)),
253 ((0, 1), (2, 1)),
254 ((0, 2), (3, 2)),
255 ((0, 3), (4, 3)),
256 ((1, 0), 0)
257 ];
258 runner.vm.segments.segment_used_sizes = Some(vec![5, 1, 2, 3, 4]);
259 runner.vm.run_context.fp = 0;
262
263 assert_matches!(verify_secure_runner(&runner, true, None), Ok(()));
264 }
265
266 #[test]
267 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
268 fn verify_secure_runner_temporary_memory_properly_relocated() {
269 let program = program!(
270 data = vec![
271 Felt252::ZERO.into(),
272 Felt252::ZERO.into(),
273 Felt252::ZERO.into(),
274 Felt252::ZERO.into(),
275 ],
276 main = Some(0),
277 );
278
279 let mut runner = cairo_runner!(program);
280
281 runner.initialize(false).unwrap();
283 runner.vm.segments.memory = memory![
284 ((0, 1), (1, 0)),
285 ((0, 2), (2, 1)),
286 ((0, 3), (3, 2)),
287 ((-1, 0), (1, 2)),
288 ((1, 0), 0)
289 ];
290 runner.vm.segments.segment_used_sizes = Some(vec![5, 1, 2, 3, 4]);
291 runner.vm.run_context.fp = 0;
294
295 assert_matches!(verify_secure_runner(&runner, true, None), Ok(()));
296 }
297
298 #[test]
299 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
300 fn verify_secure_runner_temporary_memory_not_fully_relocated() {
301 let program = program!(
302 data = vec![
303 Felt252::ZERO.into(),
304 Felt252::ZERO.into(),
305 Felt252::ZERO.into(),
306 Felt252::ZERO.into(),
307 ],
308 main = Some(0),
309 );
310
311 let mut runner = cairo_runner!(program);
312
313 runner.initialize(false).unwrap();
314 runner.vm.segments.memory = memory![
316 ((0, 0), (1, 0)),
317 ((0, 1), (2, 1)),
318 ((0, 2), (-3, 2)),
319 ((0, 3), (4, 3)),
320 ((-1, 0), (1, 2)),
321 ((1, 0), 0)
322 ];
323 runner.vm.segments.segment_used_sizes = Some(vec![5, 1, 2, 3, 4]);
324
325 assert_matches!(
326 verify_secure_runner(&runner, true, None),
327 Err(VirtualMachineError::InvalidMemoryValueTemporaryAddress(
328 bx
329 )) if *bx == relocatable!(-3, 2)
330 );
331 }
332
333 #[test]
334 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
335 fn verify_secure_runner_missing_initial_fp_error() {
336 let program = program!(main = Some(0),);
337 let mut runner = cairo_runner!(program);
338 runner.program_base = Some(runner.vm.add_memory_segment());
340
341 assert_matches!(
342 verify_secure_runner(&runner, true, None),
343 Err(VirtualMachineError::Other(ref err)) if err.to_string().contains("Failed to retrieve the initial_fp: it is None")
344 );
345 }
346
347 #[test]
348 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
349 fn verify_secure_runner_ret_fp_address_not_in_memory() {
350 let program = program!(main = Some(0),);
351 let mut runner = cairo_runner!(program);
352 runner.initialize(false).unwrap();
353 runner.vm.segments.memory = crate::vm::vm_memory::memory::Memory::new();
355 assert_matches!(
356 verify_secure_runner(&runner, true, None),
357 Err(VirtualMachineError::Other(ref err))
358 if err.to_string().contains("Ret FP address is not in memory")
359 );
360 }
361
362 #[test]
363 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
364 fn verify_secure_runner_return_fp_not_equal_final_fp_proof_mode() {
365 let program = program!(main = Some(0),);
366 let mut runner = cairo_runner!(program);
367 runner.initialize(false).unwrap();
368
369 runner.runner_mode = RunnerMode::ProofModeCanonical;
372
373 assert_matches!(
374 verify_secure_runner(&runner, true, None),
375 Err(VirtualMachineError::Other(ref err))
376 if err.to_string().contains("Return FP is not equal to final FP")
377 );
378 }
379
380 #[test]
381 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
382 fn verify_secure_runner_return_fp_offset_not_equal_final_fp_offset_execution_mode() {
383 let program = program!(main = Some(0),);
384 let mut runner = cairo_runner!(program);
385 runner.initialize(false).unwrap();
386
387 assert_matches!(
389 verify_secure_runner(&runner, true, None),
390 Err(VirtualMachineError::Other(ref err))
391 if err.to_string().contains("Return FP offset is not equal to final FP offset")
392 );
393 }
394
395 #[test]
396 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
397 fn verify_secure_runner_return_fp_felt_not_equal_final_fp_offse() {
398 let program = program!(main = Some(0),);
399 let mut runner = cairo_runner!(program);
400 runner.initialize(false).unwrap();
401 runner.vm.segments.memory = memory![((1, 0), 0)];
403
404 assert_matches!(
406 verify_secure_runner(&runner, true, None),
407 Err(VirtualMachineError::Other(ref err))
408 if err.to_string().contains("Return FP felt value is not equal to final FP offset")
409 );
410 }
411}