Skip to content

Commit

Permalink
Merge pull request #1771 from multiversx/view-detect-write-op
Browse files Browse the repository at this point in the history
  • Loading branch information
BiancaIalangi authored Oct 9, 2024
2 parents a52e46a + 80959ed commit 4d3f6d6
Show file tree
Hide file tree
Showing 4 changed files with 387 additions and 51 deletions.
21 changes: 16 additions & 5 deletions framework/meta-lib/src/contract/sc_config/wasm_build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -130,13 +130,21 @@ impl ContractVariant {
fn extract_wasm_info(&self, build_args: &BuildArgs, output_path: &str) -> WasmInfo {
let output_wasm_path = format!("{output_path}/{}", self.wasm_output_name(build_args));

let abi = ContractAbiJson::from(&self.abi);
let mut view_endpoints: Vec<&str> = Vec::new();
for endpoint in &abi.endpoints {
if let crate::abi_json::EndpointMutabilityAbiJson::Readonly = endpoint.mutability {
view_endpoints.push(&endpoint.name);
}
}

if !build_args.extract_imports {
return WasmInfo::extract_wasm_info(
&output_wasm_path,
build_args.extract_imports,
&self.settings.check_ei,
)
.expect("error occured while extracting imports from .wasm ");
view_endpoints,
);
}

let output_imports_json_path = format!(
Expand All @@ -146,9 +154,12 @@ impl ContractVariant {
);
print_extract_imports(&output_imports_json_path);

let wasm_data =
WasmInfo::extract_wasm_info(&output_wasm_path, true, &self.settings.check_ei)
.expect("error occured while extracting imports from .wasm ");
let wasm_data = WasmInfo::extract_wasm_info(
&output_wasm_path,
true,
&self.settings.check_ei,
view_endpoints,
);

write_imports_output(
output_imports_json_path.as_str(),
Expand Down
10 changes: 10 additions & 0 deletions framework/meta-lib/src/tools/report_creator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,13 @@ pub struct ReportCreator {
}

impl ReportCreator {}

impl Default for ReportCreator {
fn default() -> Self {
ReportCreator {
path: String::new(),
has_allocator: false,
has_panic: PanicReport::None,
}
}
}
190 changes: 150 additions & 40 deletions framework/meta-lib/src/tools/wasm_extractor.rs
Original file line number Diff line number Diff line change
@@ -1,84 +1,185 @@
use colored::Colorize;
use std::fs;
use std::{
collections::{HashMap, HashSet},
fs,
};
use wasmparser::{
BinaryReaderError, DataSectionReader, FunctionBody, ImportSectionReader, Operator, Parser,
Payload,
BinaryReaderError, DataSectionReader, ExportSectionReader, FunctionBody, ImportSectionReader,
Operator, Parser, Payload,
};

use crate::ei::EIVersion;

use super::{panic_report::PanicReport, report_creator::ReportCreator};
use super::report_creator::ReportCreator;

type CallGraph = HashMap<usize, HashSet<usize>>;

const ERROR_FAIL_ALLOCATOR: &[u8; 27] = b"memory allocation forbidden";
const WRITE_OP: &[&str] = &[
"mBufferStorageStore",
"storageStore",
"int64storageStore",
"bigIntStorageStoreUnsigned",
"smallIntStorageStoreUnsigned",
"smallIntStorageStoreSigned",
];

#[derive(Default)]
pub struct WasmInfo {
pub imports: Vec<String>,
pub ei_check: bool,
pub memory_grow_flag: bool,
pub has_format: bool,
pub report: ReportCreator,
pub call_graph: CallGraph,
pub write_index_functions: HashSet<usize>,
pub view_endpoints: HashMap<String, usize>,
}

impl WasmInfo {
pub fn extract_wasm_info(
output_wasm_path: &str,
extract_imports_enabled: bool,
check_ei: &Option<EIVersion>,
) -> Result<WasmInfo, BinaryReaderError> {
view_endpoints: Vec<&str>,
) -> WasmInfo {
let wasm_data = fs::read(output_wasm_path)
.expect("error occured while extracting information from .wasm: file not found");

populate_wasm_info(
let wasm_info = populate_wasm_info(
output_wasm_path.to_string(),
wasm_data,
extract_imports_enabled,
check_ei,
)
view_endpoints,
);

wasm_info.expect("error occured while extracting information from .wasm file")
}

fn create_call_graph(&mut self, body: FunctionBody) {
let mut instructions_reader = body
.get_operators_reader()
.expect("Failed to get operators reader");

let mut call_functions = HashSet::new();
while let Ok(op) = instructions_reader.read() {
if let Operator::Call { function_index } = op {
let function_usize: usize = function_index.try_into().unwrap();
call_functions.insert(function_usize);
}
}

self.call_graph
.insert(self.call_graph.len(), call_functions);
}

pub fn process_imports(
&mut self,
import_section: ImportSectionReader,
import_extraction_enabled: bool,
) {
for (index, import) in import_section.into_iter().flatten().enumerate() {
if import_extraction_enabled {
self.imports.push(import.name.to_string());
}
self.call_graph.insert(index, HashSet::new());
if WRITE_OP.contains(&import.name) {
self.write_index_functions.insert(index);
}
}

self.imports.sort();
}

pub fn detect_write_operations_in_views(&mut self) {
let mut visited: HashSet<usize> = HashSet::new();
for index in self.view_endpoints.values() {
mark_write(
*index,
&self.call_graph,
&mut self.write_index_functions,
&mut visited,
);
}

for (name, index) in &self.view_endpoints {
if self.write_index_functions.contains(index) {
println!(
"{} {}",
"Write storage operation in VIEW endpoint:"
.to_string()
.red()
.bold(),
name.red().bold()
);
}
}
}

fn parse_export_section(
&mut self,
export_section: ExportSectionReader,
view_endpoints: &[&str],
) {
for export in export_section {
let export = export.expect("Failed to read export section");
if let wasmparser::ExternalKind::Func = export.kind {
if view_endpoints.contains(&export.name) {
self.view_endpoints
.insert(export.name.to_owned(), export.index.try_into().unwrap());
}
}
}
}
}

pub(crate) fn populate_wasm_info(
path: String,
wasm_data: Vec<u8>,
extract_imports_enabled: bool,
import_extraction_enabled: bool,
check_ei: &Option<EIVersion>,
view_endpoints: Vec<&str>,
) -> Result<WasmInfo, BinaryReaderError> {
let mut imports = Vec::new();
let mut allocator_trigger = false;
let mut ei_check = false;
let mut memory_grow_flag = false;
let mut has_panic: PanicReport = PanicReport::default();
let mut wasm_info = WasmInfo::default();

let parser = Parser::new(0);
for payload in parser.parse_all(&wasm_data) {
match payload? {
Payload::ImportSection(import_section) => {
imports = extract_imports(import_section, extract_imports_enabled);
ei_check |= is_ei_valid(imports.clone(), check_ei);
wasm_info.process_imports(import_section, import_extraction_enabled);
wasm_info.ei_check |= is_ei_valid(&wasm_info.imports, check_ei);
},
Payload::DataSection(data_section) => {
allocator_trigger |= is_fail_allocator_triggered(data_section.clone());
has_panic.max_severity(data_section);
wasm_info.report.has_allocator |= is_fail_allocator_triggered(data_section.clone());
wasm_info.report.has_panic.max_severity(data_section);
},
Payload::CodeSectionEntry(code_section) => {
memory_grow_flag |= is_mem_grow(code_section);
wasm_info.memory_grow_flag |= is_mem_grow(&code_section);
wasm_info.create_call_graph(code_section);
},
Payload::ExportSection(export_section) => {
wasm_info.parse_export_section(export_section, &view_endpoints);
},
_ => (),
}
}

wasm_info.detect_write_operations_in_views();

let report = ReportCreator {
path,
has_allocator: allocator_trigger,
has_panic,
has_allocator: wasm_info.report.has_allocator,
has_panic: wasm_info.report.has_panic,
};

Ok(WasmInfo {
imports,
ei_check,
memory_grow_flag,
has_format: true,
imports: wasm_info.imports,
ei_check: wasm_info.ei_check,
memory_grow_flag: wasm_info.memory_grow_flag,
call_graph: wasm_info.call_graph,
report,
write_index_functions: wasm_info.write_index_functions,
view_endpoints: wasm_info.view_endpoints,
})
}

Expand All @@ -103,25 +204,34 @@ fn is_fail_allocator_triggered(data_section: DataSectionReader) -> bool {
false
}

pub fn extract_imports(
import_section: ImportSectionReader,
import_extraction_enabled: bool,
) -> Vec<String> {
if !import_extraction_enabled {
return Vec::new();
}

let mut import_names = Vec::new();
for import in import_section.into_iter().flatten() {
import_names.push(import.name.to_string());
fn mark_write(
func: usize,
call_graph: &CallGraph,
write_functions: &mut HashSet<usize>,
visited: &mut HashSet<usize>,
) {
// Return early to prevent cycles.
if visited.contains(&func) {
return;
}

import_names.sort();
visited.insert(func);

import_names
if let Some(callees) = call_graph.get(&func) {
for &callee in callees {
if write_functions.contains(&callee) {
write_functions.insert(func);
} else {
mark_write(callee, call_graph, write_functions, visited);
if write_functions.contains(&callee) {
write_functions.insert(func);
}
}
}
}
}

fn is_ei_valid(imports: Vec<String>, check_ei: &Option<EIVersion>) -> bool {
fn is_ei_valid(imports: &[String], check_ei: &Option<EIVersion>) -> bool {
if let Some(ei) = check_ei {
let mut num_errors = 0;
for import in imports {
Expand All @@ -138,7 +248,7 @@ fn is_ei_valid(imports: Vec<String>, check_ei: &Option<EIVersion>) -> bool {
false
}

fn is_mem_grow(code_section: FunctionBody) -> bool {
fn is_mem_grow(code_section: &FunctionBody) -> bool {
let mut instructions_reader = code_section
.get_operators_reader()
.expect("Failed to get operators reader");
Expand Down
Loading

0 comments on commit 4d3f6d6

Please sign in to comment.