Skip to content

Commit

Permalink
CLI Tool Verifier Option (#138)
Browse files Browse the repository at this point in the history
* Adds an option to verify programs before execution or disassembly in the CLI tool.

* Improve symbol verification by looking up instruction offsets.
  • Loading branch information
Lichtso committed Feb 9, 2021
1 parent 527ab75 commit fcde4bd
Show file tree
Hide file tree
Showing 2 changed files with 134 additions and 61 deletions.
2 changes: 1 addition & 1 deletion cli/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

193 changes: 133 additions & 60 deletions cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,18 @@ use clap::{App, Arg};
use rustc_demangle::demangle;
use solana_rbpf::{
assembler::assemble,
disassembler::{HLInsn, to_insn_vec},
disassembler::{to_insn_vec, HLInsn},
ebpf,
memory_region::{MemoryMapping, MemoryRegion},
user_error::UserError,
verifier::check,
vm::{Config, EbpfVm, Executable, SyscallObject, SyscallRegistry},
};
use std::{
fs::File, io::Read, path::Path,
collections::{HashMap, BTreeMap},
collections::{BTreeMap, HashMap},
fs::File,
io::Read,
path::Path,
};
use test_utils::{Result, TestInstructionMeter};

Expand Down Expand Up @@ -75,25 +78,38 @@ impl AnalysisResult {
};
let (syscalls, bpf_functions) = executable.get_symbols();
for (pc, bpf_function) in bpf_functions {
result.destinations.insert(pc, Label {
name: demangle(&bpf_function.0).to_string(),
length: 0, // bpf_function.1,
kind: LabelKind::Function,
sources: Vec::new(),
});
result.destinations.insert(
pc,
Label {
name: demangle(&bpf_function.0).to_string(),
length: 0, // bpf_function.1,
kind: LabelKind::Function,
sources: Vec::new(),
},
);
}
let entrypoint_pc = executable.get_entrypoint_instruction_offset().unwrap();
result.destinations.entry(entrypoint_pc).or_insert(Label {
name: "entrypoint".to_string(),
length: 0,
kind: LabelKind::Function,
sources: Vec::new(),
});
for insn in result.instructions.iter() {
match insn.opc {
ebpf::CALL_IMM => {
if let Some(target_pc) = executable.lookup_bpf_function(insn.imm as u32) {
// result.sources.insert(insn.ptr, vec![*target_pc]);
if !result.destinations.contains_key(target_pc) {
result.destinations.insert(*target_pc, Label {
name: format!("function_{}", target_pc),
length: 0,
kind: LabelKind::Function,
sources: Vec::new(),
});
result.destinations.insert(
*target_pc,
Label {
name: format!("function_{}", target_pc),
length: 0,
kind: LabelKind::Function,
sources: Vec::new(),
},
);
}
}
}
Expand Down Expand Up @@ -131,43 +147,57 @@ impl AnalysisResult {
| ebpf::JSGE_REG
| ebpf::JSLT_REG
| ebpf::JSLE_REG => {
result.sources.insert(insn.ptr, vec![insn.ptr + 1, target_pc]);
result.destinations.insert(insn.ptr + 1, Label {
name: format!("lbb_{}", insn.ptr + 1),
length: 0,
kind: LabelKind::BasicBlock,
sources: Vec::new(),
});
result
.sources
.insert(insn.ptr, vec![insn.ptr + 1, target_pc]);
result.destinations.insert(
insn.ptr + 1,
Label {
name: format!("lbb_{}", insn.ptr + 1),
length: 0,
kind: LabelKind::BasicBlock,
sources: Vec::new(),
},
);
}
_ => continue
}
if !result.destinations.contains_key(&target_pc) {
result.destinations.insert(target_pc, Label {
name: format!("lbb_{}", target_pc),
length: 0,
kind: LabelKind::BasicBlock,
sources: Vec::new(),
});
_ => continue,
}
result.destinations.entry(target_pc).or_insert(Label {
name: format!("lbb_{}", target_pc),
length: 0,
kind: LabelKind::BasicBlock,
sources: Vec::new(),
});
}
for (source, destinations) in &result.sources {
for destination in destinations {
result.destinations.get_mut(destination).unwrap().sources.push(*source);
result
.destinations
.get_mut(destination)
.unwrap()
.sources
.push(*source);
}
}
let mut destination_iter = result.destinations.iter_mut().peekable();
let mut source_iter = result.sources.iter().peekable();
while let Some((begin, label)) = destination_iter.next() {
if *begin >= result.instructions.last().unwrap().ptr {
println!("WARNING: Invalid symbol {:?} at {} beyond last instruction", label.name, begin);
label.length = 0;
continue;
match result
.instructions
.binary_search_by(|insn| insn.ptr.cmp(begin))
{
Ok(_) => {}
Err(_index) => {
println!("WARNING: Invalid symbol {:?}, pc={}", label.name, begin);
label.length = 0;
continue;
}
}
if label.length > 0 {
continue;
}
while let Some(next_source) = source_iter.peek() {
if *next_source.0 + 1 <= *begin {
if *next_source.0 < *begin {
source_iter.next();
} else {
break;
Expand All @@ -185,12 +215,10 @@ impl AnalysisResult {
} else {
*next_destination.0
}
} else if let Some(next_source) = source_iter.next() {
*next_source.0 + 1
} else {
if let Some(next_source) = source_iter.next() {
*next_source.0 + 1
} else {
result.instructions.last().unwrap().ptr
}
result.instructions.last().unwrap().ptr
};
label.length = end - begin;
}
Expand All @@ -210,9 +238,10 @@ impl AnalysisResult {
let target_pc = (insn.ptr as isize + insn.off as isize + 1) as usize;
insn.desc = format!(
"{} {}",
insn.name, resolve_label!(result.destinations, target_pc)
insn.name,
resolve_label!(result.destinations, target_pc)
);
},
}
ebpf::JEQ_IMM
| ebpf::JGT_IMM
| ebpf::JGE_IMM
Expand All @@ -227,7 +256,10 @@ impl AnalysisResult {
let target_pc = (insn.ptr as isize + insn.off as isize + 1) as usize;
insn.desc = format!(
"{} r{}, {:#x}, {}",
insn.name, insn.dst, insn.imm, resolve_label!(result.destinations, target_pc)
insn.name,
insn.dst,
insn.imm,
resolve_label!(result.destinations, target_pc)
);
}
ebpf::JEQ_REG
Expand All @@ -244,7 +276,10 @@ impl AnalysisResult {
let target_pc = (insn.ptr as isize + insn.off as isize + 1) as usize;
insn.desc = format!(
"{} r{}, r{}, {}",
insn.name, insn.dst, insn.src, resolve_label!(result.destinations, target_pc)
insn.name,
insn.dst,
insn.src,
resolve_label!(result.destinations, target_pc)
);
}
_ => {}
Expand All @@ -256,7 +291,7 @@ impl AnalysisResult {
pub fn print_label_at(&self, ptr: usize) -> bool {
if let Some(label) = self.destinations.get(&ptr) {
if label.kind == LabelKind::Function {
println!("");
println!();
}
println!("{}:", label.name);
true
Expand Down Expand Up @@ -337,26 +372,48 @@ fn main() {
.short('p')
.long("prof"),
)
.arg(
Arg::new("verify")
.about("Run the verifier before execution or disassembly")
.short('v')
.long("veri"),
)
.get_matches();

let mut config = Config::default();
config.enable_instruction_tracing = matches.is_present("trace") || matches.is_present("profile");
let mut executable = match matches.value_of("assembler") {
let config = Config {
enable_instruction_tracing: matches.is_present("trace") || matches.is_present("profile"),
..Config::default()
};
let verifier: Option<for<'r> fn(&'r [u8]) -> std::result::Result<_, _>> =
if matches.is_present("verify") {
Some(check)
} else {
None
};
let executable = match matches.value_of("assembler") {
Some(asm_file_name) => {
let mut file = File::open(&Path::new(asm_file_name)).unwrap();
let mut source = Vec::new();
file.read_to_end(&mut source).unwrap();
let program = assemble(std::str::from_utf8(source.as_slice()).unwrap()).unwrap();
Executable::<UserError, TestInstructionMeter>::from_text_bytes(&program, None, config)
Executable::<UserError, TestInstructionMeter>::from_text_bytes(
&program, verifier, config,
)
}
None => {
let mut file = File::open(&Path::new(matches.value_of("elf").unwrap())).unwrap();
let mut elf = Vec::new();
file.read_to_end(&mut elf).unwrap();
Executable::<UserError, TestInstructionMeter>::from_elf(&elf, None, config)
Executable::<UserError, TestInstructionMeter>::from_elf(&elf, verifier, config)
}
}
.unwrap();
};
let mut executable = match executable {
Ok(executable) => executable,
Err(err) => {
println!("Executable constructor failed: {:?}", err);
return;
}
};

let (syscalls, _bpf_functions) = executable.get_symbols();
let mut syscall_registry = SyscallRegistry::default();
Expand Down Expand Up @@ -437,25 +494,41 @@ fn main() {
}
let trace = &vm.get_tracer().log;
for (index, traced_instruction) in trace.iter().enumerate() {
if let Some(destination_counter) = destination_counters.get_mut(&(traced_instruction[11] as usize)) {
if let Some(destination_counter) =
destination_counters.get_mut(&(traced_instruction[11] as usize))
{
*destination_counter += 1;
}
if let Some(source_counter) = source_counters.get_mut(&(traced_instruction[11] as usize)) {
if let Some(source_counter) =
source_counters.get_mut(&(traced_instruction[11] as usize))
{
let next_traced_instruction = trace[index + 1];
let destinations = analysis_result.sources.get(&(traced_instruction[11] as usize)).unwrap();
if let Some(destination_index) = destinations.iter().position(|&ptr| ptr == next_traced_instruction[11] as usize) {
let destinations = analysis_result
.sources
.get(&(traced_instruction[11] as usize))
.unwrap();
if let Some(destination_index) = destinations
.iter()
.position(|&ptr| ptr == next_traced_instruction[11] as usize)
{
source_counter[destination_index] += 1;
}
}
}
println!("Profile:");
for insn in analysis_result.instructions.iter() {
if analysis_result.print_label_at(insn.ptr) {
println!(" # Basic block executed: {}", destination_counters[&insn.ptr]);
println!(
" # Basic block executed: {}",
destination_counters[&insn.ptr]
);
}
println!(" {}", insn.desc);
if let Some(source_counter) = source_counters.get(&insn.ptr) {
println!(" # Branch: {} fall through, {} jump", source_counter[0], source_counter[1]);
println!(
" # Branch: {} fall through, {} jump",
source_counter[0], source_counter[1]
);
}
}
}
Expand Down

0 comments on commit fcde4bd

Please sign in to comment.