rtvm_interpreter/interpreter/
analysis.rs1use rtvm_primitives::{eof::EofDecodeError, HashSet};
2
3use crate::{
4 instructions::utility::{read_i16, read_u16},
5 opcode,
6 primitives::{
7 bitvec::prelude::{bitvec, BitVec, Lsb0},
8 eof::TypesSection,
9 legacy::JumpTable,
10 Bytecode, Bytes, Eof, LegacyAnalyzedBytecode,
11 },
12 OPCODE_INFO_JUMPTABLE, STACK_LIMIT,
13};
14use std::{sync::Arc, vec, vec::Vec};
15
16const EOF_NON_RETURNING_FUNCTION: u8 = 0x80;
17
18#[inline]
24pub fn to_analysed(bytecode: Bytecode) -> Bytecode {
25 let (bytes, len) = match bytecode {
26 Bytecode::LegacyRaw(bytecode) => {
27 let len = bytecode.len();
28 let mut padded_bytecode = Vec::with_capacity(len + 33);
29 padded_bytecode.extend_from_slice(&bytecode);
30 padded_bytecode.resize(len + 33, 0);
31 (Bytes::from(padded_bytecode), len)
32 }
33 n => return n,
34 };
35 let jump_table = analyze(bytes.as_ref());
36
37 Bytecode::LegacyAnalyzed(LegacyAnalyzedBytecode::new(bytes, len, jump_table))
38}
39
40fn analyze(code: &[u8]) -> JumpTable {
42 let mut jumps: BitVec<u8> = bitvec![u8, Lsb0; 0; code.len()];
43
44 let range = code.as_ptr_range();
45 let start = range.start;
46 let mut iterator = start;
47 let end = range.end;
48 while iterator < end {
49 let opcode = unsafe { *iterator };
50 if opcode::JUMPDEST == opcode {
51 unsafe { jumps.set_unchecked(iterator.offset_from(start) as usize, true) }
53 iterator = unsafe { iterator.offset(1) };
54 } else {
55 let push_offset = opcode.wrapping_sub(opcode::PUSH1);
56 if push_offset < 32 {
57 iterator = unsafe { iterator.offset((push_offset + 2) as isize) };
59 } else {
60 iterator = unsafe { iterator.offset(1) };
62 }
63 }
64 }
65
66 JumpTable(Arc::new(jumps))
67}
68
69pub fn validate_raw_eof(bytecode: Bytes) -> Result<Eof, EofError> {
70 let eof = Eof::decode(bytecode)?;
71 validate_eof(&eof)?;
72 Ok(eof)
73}
74
75pub fn validate_eof(eof: &Eof) -> Result<(), EofError> {
77 let mut queue = vec![eof.clone()];
79
80 while let Some(eof) = queue.pop() {
81 validate_eof_codes(&eof)?;
83 for container in eof.body.container_section {
85 queue.push(Eof::decode(container)?);
86 }
87 }
88
89 Ok(())
91}
92
93pub fn validate_eof_codes(eof: &Eof) -> Result<(), EofValidationError> {
95 let mut queued_codes = vec![false; eof.body.code_section.len()];
96 if eof.body.code_section.len() != eof.body.types_section.len() {
97 return Err(EofValidationError::InvalidTypesSection);
98 }
99
100 if eof.body.code_section.is_empty() {
101 return Err(EofValidationError::NoCodeSections);
103 }
104 queued_codes[0] = true;
106
107 let first_types = &eof.body.types_section[0];
110 if first_types.inputs != 0 || first_types.outputs != EOF_NON_RETURNING_FUNCTION {
111 return Err(EofValidationError::InvalidTypesSection);
112 }
113
114 let mut queue = vec![0];
116 while let Some(index) = queue.pop() {
117 let code = &eof.body.code_section[index];
118 let accessed_codes = validate_eof_code(
119 code,
120 eof.header.data_size as usize,
121 index,
122 eof.body.container_section.len(),
123 &eof.body.types_section,
124 )?;
125
126 accessed_codes.into_iter().for_each(|i| {
128 if !queued_codes[i] {
129 queued_codes[i] = true;
130 queue.push(i);
131 }
132 });
133 }
134 if queued_codes.into_iter().any(|x| !x) {
136 return Err(EofValidationError::CodeSectionNotAccessed);
137 }
138
139 Ok(())
140}
141
142#[derive(Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
144pub enum EofError {
145 Decode(EofDecodeError),
146 Validation(EofValidationError),
147}
148
149impl From<EofDecodeError> for EofError {
150 fn from(err: EofDecodeError) -> Self {
151 EofError::Decode(err)
152 }
153}
154
155impl From<EofValidationError> for EofError {
156 fn from(err: EofValidationError) -> Self {
157 EofError::Validation(err)
158 }
159}
160
161#[derive(Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
162pub enum EofValidationError {
163 FalsePossitive,
164 UnknownOpcode,
166 OpcodeDisabled,
168 InstructionNotForwardAccessed,
172 MissingImmediateBytes,
174 MissingRJUMPVImmediateBytes,
176 JumpToImmediateBytes,
178 BackwardJumpToImmediateBytes,
180 RJUMPVZeroMaxIndex,
182 JumpZeroOffset,
184 EOFCREATEInvalidIndex,
186 CodeSectionOutOfBounds,
188 CALLFNonReturningFunction,
190 StackOverflow,
192 JUMPFEnoughOutputs,
194 JUMPFStackHigherThanOutputs,
196 DataLoadOutOfBounds,
198 RETFBiggestStackNumMoreThenOutputs,
200 StackUnderflow,
202 TypesStackUnderflow,
204 JumpUnderflow,
206 JumpOverflow,
208 BackwardJumpBiggestNumMismatch,
210 BackwardJumpSmallestNumMismatch,
212 LastInstructionNotTerminating,
214 CodeSectionNotAccessed,
216 InvalidTypesSection,
218 InvalidFirstTypesSection,
221 MaxStackMismatch,
223 NoCodeSections,
225}
226
227pub fn validate_eof_code(
244 code: &[u8],
245 data_size: usize,
246 this_types_index: usize,
247 num_of_containers: usize,
248 types: &[TypesSection],
249) -> Result<HashSet<usize>, EofValidationError> {
250 let mut accessed_codes = HashSet::<usize>::new();
251 let this_types = &types[this_types_index];
252
253 #[derive(Debug, Copy, Clone)]
254 struct InstructionInfo {
255 is_immediate: bool,
257 is_jumpdest: bool,
260 smallest: i32,
262 biggest: i32,
264 }
265
266 impl InstructionInfo {
267 #[inline]
268 fn mark_as_immediate(&mut self) -> Result<(), EofValidationError> {
269 if self.is_jumpdest {
270 return Err(EofValidationError::JumpToImmediateBytes);
272 }
273 self.is_immediate = true;
274 Ok(())
275 }
276 }
277
278 impl Default for InstructionInfo {
279 fn default() -> Self {
280 Self {
281 is_immediate: false,
282 is_jumpdest: false,
283 smallest: i32::MAX,
284 biggest: i32::MIN,
285 }
286 }
287 }
288
289 let mut jumps = vec![InstructionInfo::default(); code.len()];
291 let mut is_after_termination = false;
292
293 let mut next_smallest = this_types.inputs as i32;
294 let mut next_biggest = this_types.inputs as i32;
295
296 let mut i = 0;
297 while i < code.len() {
299 let op = code[i];
300 let opcode = &OPCODE_INFO_JUMPTABLE[op as usize];
301
302 let Some(opcode) = opcode else {
303 return Err(EofValidationError::UnknownOpcode);
305 };
306
307 if !opcode.is_eof {
308 return Err(EofValidationError::OpcodeDisabled);
310 }
311
312 let this_instruction = &mut jumps[i];
313
314 if !is_after_termination {
316 this_instruction.smallest = core::cmp::min(this_instruction.smallest, next_smallest);
317 this_instruction.biggest = core::cmp::max(this_instruction.biggest, next_biggest);
318 }
319
320 let this_instruction = *this_instruction;
321
322 if is_after_termination && !this_instruction.is_jumpdest {
324 return Err(EofValidationError::InstructionNotForwardAccessed);
326 }
327 is_after_termination = opcode.is_terminating_opcode;
328
329 if opcode.immediate_size != 0 {
331 if i + opcode.immediate_size as usize >= code.len() {
333 return Err(EofValidationError::MissingImmediateBytes);
335 }
336
337 for imm in 1..opcode.immediate_size as usize + 1 {
339 jumps[i + imm].mark_as_immediate()?;
341 }
342 }
343 let mut stack_io_diff = opcode.io_diff() as i32;
345 let mut stack_requirement = opcode.inputs as i32;
347 let mut rjumpv_additional_immediates = 0;
349 let mut absolute_jumpdest = vec![];
351 match op {
352 opcode::RJUMP | opcode::RJUMPI => {
353 let offset = unsafe { read_i16(code.as_ptr().add(i + 1)) } as isize;
354 absolute_jumpdest = vec![offset + 3 + i as isize];
355 }
357 opcode::RJUMPV => {
358 let max_index = code[i + 1] as usize;
360 let len = max_index + 1;
361 rjumpv_additional_immediates = len * 2;
363
364 if i + 1 + rjumpv_additional_immediates >= code.len() {
366 return Err(EofValidationError::MissingRJUMPVImmediateBytes);
368 }
369
370 for imm in 0..rjumpv_additional_immediates {
372 jumps[i + 2 + imm].mark_as_immediate()?;
374 }
375
376 let mut jumps = Vec::with_capacity(len);
377 for vtablei in 0..len {
378 let offset =
379 unsafe { read_i16(code.as_ptr().add(i + 2 + 2 * vtablei)) } as isize;
380 jumps.push(offset + i as isize + 2 + rjumpv_additional_immediates as isize);
381 }
382 absolute_jumpdest = jumps
383 }
384 opcode::CALLF => {
385 let section_i = unsafe { read_u16(code.as_ptr().add(i + 1)) } as usize;
386 let Some(target_types) = types.get(section_i) else {
387 return Err(EofValidationError::CodeSectionOutOfBounds);
389 };
390
391 if target_types.outputs == EOF_NON_RETURNING_FUNCTION {
392 return Err(EofValidationError::CALLFNonReturningFunction);
394 }
395 stack_requirement = target_types.inputs as i32;
397 stack_io_diff = target_types.io_diff();
399 accessed_codes.insert(section_i);
401
402 if this_instruction.biggest - stack_requirement + target_types.max_stack_size as i32
405 > STACK_LIMIT as i32
406 {
407 return Err(EofValidationError::StackOverflow);
409 }
410 }
411 opcode::JUMPF => {
412 let target_index = unsafe { read_u16(code.as_ptr().add(i + 1)) } as usize;
413 let Some(target_types) = types.get(target_index) else {
415 return Err(EofValidationError::CodeSectionOutOfBounds);
417 };
418
419 if this_instruction.biggest - target_types.inputs as i32
422 + target_types.max_stack_size as i32
423 > STACK_LIMIT as i32
424 {
425 return Err(EofValidationError::StackOverflow);
427 }
428 accessed_codes.insert(target_index);
429
430 if target_types.outputs == EOF_NON_RETURNING_FUNCTION {
431 stack_requirement = target_types.inputs as i32;
433 } else {
434 if this_types.outputs < target_types.outputs {
436 return Err(EofValidationError::JUMPFEnoughOutputs);
437 }
438
439 stack_requirement = this_types.outputs as i32 + target_types.inputs as i32
440 - target_types.outputs as i32;
441
442 if this_instruction.biggest > stack_requirement {
444 return Err(EofValidationError::JUMPFStackHigherThanOutputs);
445 }
446
447 if this_instruction.biggest + stack_requirement > STACK_LIMIT as i32 {
449 return Err(EofValidationError::StackOverflow);
450 }
451 }
452 }
453 opcode::EOFCREATE => {
454 let index = code[i + 1] as usize;
455 if index >= num_of_containers {
456 return Err(EofValidationError::EOFCREATEInvalidIndex);
458 }
459 }
460 opcode::DATALOADN => {
461 let index = unsafe { read_u16(code.as_ptr().add(i + 1)) } as isize;
462 if data_size < 32 || index > data_size as isize - 32 {
463 return Err(EofValidationError::DataLoadOutOfBounds);
465 }
466 }
467 opcode::RETF => {
468 stack_requirement = this_types.outputs as i32;
469 if this_instruction.biggest > stack_requirement {
470 return Err(EofValidationError::RETFBiggestStackNumMoreThenOutputs);
471 }
472 }
473 opcode::DUPN => {
474 stack_requirement = code[i + 1] as i32 + 1;
475 }
476 opcode::SWAPN => {
477 stack_requirement = code[i + 1] as i32 + 2;
478 }
479 opcode::EXCHANGE => {
480 let imm = code[i + 1];
481 let n = (imm >> 4) + 1;
482 let m = (imm & 0x0F) + 1;
483 stack_requirement = n as i32 + m as i32 + 1;
484 }
485 _ => {}
486 }
487 if stack_requirement > this_instruction.smallest {
489 return Err(EofValidationError::StackUnderflow);
491 }
492
493 next_smallest = this_instruction.smallest + stack_io_diff;
494 next_biggest = this_instruction.biggest + stack_io_diff;
495
496 for absolute_jump in absolute_jumpdest {
498 if absolute_jump < 0 {
499 return Err(EofValidationError::JumpUnderflow);
501 }
502 if absolute_jump >= code.len() as isize {
503 return Err(EofValidationError::JumpOverflow);
505 }
506 let absolute_jump = absolute_jump as usize;
508
509 let target_jump = &mut jumps[absolute_jump];
510 if target_jump.is_immediate {
511 return Err(EofValidationError::BackwardJumpToImmediateBytes);
513 }
514
515 target_jump.is_jumpdest = true;
517
518 if absolute_jump <= i {
519 if target_jump.biggest != next_biggest {
521 return Err(EofValidationError::BackwardJumpBiggestNumMismatch);
523 }
524 if target_jump.smallest != next_smallest {
525 return Err(EofValidationError::BackwardJumpSmallestNumMismatch);
527 }
528 } else {
529 target_jump.smallest = core::cmp::min(target_jump.smallest, next_smallest);
532 target_jump.biggest = core::cmp::max(target_jump.biggest, next_biggest);
533 }
534 }
535
536 i += 1 + opcode.immediate_size as usize + rjumpv_additional_immediates;
538 }
539
540 if !is_after_termination {
542 return Err(EofValidationError::LastInstructionNotTerminating);
544 }
545 let mut max_stack_requirement = 0;
547 for opcode in jumps {
548 max_stack_requirement = core::cmp::max(opcode.biggest, max_stack_requirement);
549 }
550
551 if max_stack_requirement != types[this_types_index].max_stack_size as i32 {
552 return Err(EofValidationError::MaxStackMismatch);
554 }
555
556 Ok(accessed_codes)
557}
558
559#[cfg(test)]
560mod test {
561 use super::*;
562 use rtvm_primitives::hex;
563
564 #[test]
565 fn test1() {
566 let err =
568 validate_raw_eof(hex!("ef0001010004020001000704000000008000016000e200fffc00").into());
569 assert!(err.is_err(), "{err:#?}");
570 }
571
572 #[test]
573 fn test2() {
574 let err =
576 validate_raw_eof(hex!("ef000101000c02000300040004000204000000008000020002000100010001e30001005fe500025fe4").into());
577 assert!(err.is_ok(), "{err:#?}");
578 }
579
580 #[test]
581 fn test3() {
582 let err =
584 validate_raw_eof(hex!("ef000101000c02000300040008000304000000008000020002000503010003e30001005f5f5f5f5fe500025050e4").into());
585 assert_eq!(
586 err,
587 Err(EofError::Validation(
588 EofValidationError::JUMPFStackHigherThanOutputs
589 ))
590 );
591 }
592}