Skip to content

Commit

Permalink
Simplify configuration options (vbpf#775)
Browse files Browse the repository at this point in the history
Convert prepare_cfg to accepting options struct

Signed-off-by: Alan Jowett <[email protected]>
  • Loading branch information
Alan-Jowett authored Nov 7, 2024
1 parent 1e5de90 commit bd55b33
Show file tree
Hide file tree
Showing 15 changed files with 163 additions and 170 deletions.
10 changes: 4 additions & 6 deletions src/asm_cfg.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -359,16 +359,14 @@ std::map<std::string, int> collect_stats(const cfg_t& cfg) {
return res;
}

// ISSUE: 774 - Rationalize the list of bools being passed to prepare_cfg.
cfg_t prepare_cfg(const InstructionSeq& prog, const program_info& info, const bool simplify,
const bool check_for_termination, const bool must_have_exit) {
cfg_t prepare_cfg(const InstructionSeq& prog, const program_info& info, const prepare_cfg_options& options) {
// Convert the instruction sequence to a deterministic control-flow graph.
cfg_t det_cfg = instruction_seq_to_cfg(prog, must_have_exit);
cfg_t det_cfg = instruction_seq_to_cfg(prog, options.must_have_exit);

// Detect loops using Weak Topological Ordering (WTO) and insert counters at loop entry points. WTO provides a
// hierarchical decomposition of the CFG that identifies all strongly connected components (cycles) and their entry
// points. These entry points serve as natural locations for loop counters that help verify program termination.
if (check_for_termination) {
if (options.check_for_termination) {
const wto_t wto(det_cfg);
wto.for_each_loop_head(
[&](const label_t& label) { det_cfg.get_node(label).insert_front(IncrementLoopCounter{label}); });
Expand All @@ -386,7 +384,7 @@ cfg_t prepare_cfg(const InstructionSeq& prog, const program_info& info, const bo
// An abstract interpreter will keep values at every basic block,
// so the fewer basic blocks we have, the less information it has to
// keep track of.
if (simplify) {
if (options.simplify) {
cfg.simplify();
}

Expand Down
14 changes: 5 additions & 9 deletions src/asm_files.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ static std::tuple<ELFIO::Elf64_Addr, unsigned char> get_value(const ELFIO::const

// parse_maps_sections processes all maps sections in the provided ELF file by calling the platform-specific maps'
// parser. The section index of each maps section is inserted into section_indices.
static size_t parse_map_sections(const ebpf_verifier_options_t* options, const ebpf_platform_t* platform,
static size_t parse_map_sections(const ebpf_verifier_options_t& options, const ebpf_platform_t* platform,
const ELFIO::elfio& reader, vector<EbpfMapDescriptor>& map_descriptors,
std::set<ELFIO::Elf_Half>& section_indices,
const ELFIO::const_symbol_section_accessor& symbols) {
Expand Down Expand Up @@ -116,16 +116,15 @@ static size_t parse_map_sections(const ebpf_verifier_options_t* options, const e
if (s->get_size() % map_record_size != 0) {
throw std::runtime_error("bad maps section size");
}
platform->parse_maps_section(map_descriptors, s->get_data(), map_record_size, map_count, platform,
*options);
platform->parse_maps_section(map_descriptors, s->get_data(), map_record_size, map_count, platform, options);
}
section_indices.insert(s->get_index());
}
platform->resolve_inner_map_references(map_descriptors);
return map_record_size;
}

vector<raw_program> read_elf(const string& path, const string& desired_section, const ebpf_verifier_options_t* options,
vector<raw_program> read_elf(const string& path, const string& desired_section, const ebpf_verifier_options_t& options,
const ebpf_platform_t* platform) {
if (std::ifstream stream{path, std::ios::in | std::ios::binary}) {
return read_elf(stream, path, desired_section, options, platform);
Expand Down Expand Up @@ -280,10 +279,7 @@ std::map<std::string, size_t> parse_map_section(const libbtf::btf_type_data& btf
}

vector<raw_program> read_elf(std::istream& input_stream, const std::string& path, const std::string& desired_section,
const ebpf_verifier_options_t* options, const ebpf_platform_t* platform) {
if (options == nullptr) {
options = &ebpf_verifier_default_options;
}
const ebpf_verifier_options_t& options, const ebpf_platform_t* platform) {
ELFIO::elfio reader;
if (!reader.load(input_stream)) {
throw std::runtime_error("Can't process ELF file " + path);
Expand Down Expand Up @@ -311,7 +307,7 @@ vector<raw_program> read_elf(std::istream& input_stream, const std::string& path
if (btf) {
// Parse the BTF type data.
btf_data = vector_of<std::byte>(*btf);
if (options->dump_btf_types_json) {
if (options.dump_btf_types_json) {
std::stringstream output;
std::cout << "Dumping BTF data for" << path << std::endl;
// Dump the BTF data to cout for debugging purposes.
Expand Down
4 changes: 2 additions & 2 deletions src/asm_files.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@

std::vector<raw_program> read_raw(std::string path, program_info info);
std::vector<raw_program> read_elf(const std::string& path, const std::string& desired_section,
const ebpf_verifier_options_t* options, const ebpf_platform_t* platform);
const ebpf_verifier_options_t& options, const ebpf_platform_t* platform);
std::vector<raw_program> read_elf(std::istream& input_stream, const std::string& path,
const std::string& desired_section, const ebpf_verifier_options_t* options,
const std::string& desired_section, const ebpf_verifier_options_t& options,
const ebpf_platform_t* platform);

void write_binary_file(std::string path, const char* data, size_t size);
Expand Down
17 changes: 0 additions & 17 deletions src/config.cpp

This file was deleted.

41 changes: 28 additions & 13 deletions src/config.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,41 @@
// SPDX-License-Identifier: MIT
#pragma once

#include "crab/cfg.hpp"

struct ebpf_verifier_options_t {
bool check_termination;
bool assume_assertions;
bool print_invariants;
bool print_failures;
bool simplify;
// Options that control how the control flow graph is built.
prepare_cfg_options cfg_opts;

// True to assume prior failed assertions are true and continue verification.
bool assume_assertions = false;

// False to use actual map fd's, true to use mock fd's.
bool mock_map_fds;
bool mock_map_fds = true;

// True to do additional checks for some things that would fail at runtime.
bool strict;
bool strict = false;

// True to allow division by zero and assume BPF ISA defined semantics.
bool allow_division_by_zero = true;

// Setup the entry constraints for a BPF program.
bool setup_constraints = true;

// True if the ELF file is built on a big endian system.
bool big_endian = false;

// Print the invariants for each basic block.
bool print_invariants = false;

// Print failures that occur during verification.
bool print_failures = false;

bool print_line_info;
bool allow_division_by_zero;
bool setup_constraints;
bool big_endian;
// When printing the control flow graph, print the line number of each instruction.
bool print_line_info = false;

bool dump_btf_types_json;
// Print the BTF types in JSON format.
bool dump_btf_types_json = false;
};

struct ebpf_verifier_stats_t {
Expand All @@ -29,5 +45,4 @@ struct ebpf_verifier_stats_t {
int max_loop_count;
};

extern const ebpf_verifier_options_t ebpf_verifier_default_options;
extern thread_local ebpf_verifier_options_t thread_local_options;
12 changes: 10 additions & 2 deletions src/crab/cfg.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -613,8 +613,16 @@ std::vector<std::string> stats_headers();

std::map<std::string, int> collect_stats(const cfg_t&);

cfg_t prepare_cfg(const InstructionSeq& prog, const program_info& info, bool simplify, bool check_for_termination,
bool must_have_exit = true);
struct prepare_cfg_options {
/// When true, simplifies the control flow graph by merging basic blocks.
bool simplify = true;
/// When true, verifies that the program terminates.
bool check_for_termination = false;
/// When true, ensures the program has a valid exit block.
bool must_have_exit = true;
};

cfg_t prepare_cfg(const InstructionSeq& prog, const program_info& info, const prepare_cfg_options& options);

void explicate_assertions(cfg_t& cfg, const program_info& info);
std::vector<Assert> get_assertions(Instruction ins, const program_info& info, const std::optional<label_t>& label);
Expand Down
2 changes: 1 addition & 1 deletion src/crab/ebpf_domain.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -825,7 +825,7 @@ ebpf_domain_t ebpf_domain_t::calculate_constant_limits() {
inv += r.shared_offset >= 0;
inv += r.packet_offset <= variable_t::packet_size();
inv += r.packet_offset >= 0;
if (thread_local_options.check_termination) {
if (thread_local_options.cfg_opts.check_for_termination) {
for (const variable_t counter : variable_t::get_loop_counters()) {
inv += counter <= std::numeric_limits<int32_t>::max();
inv += counter >= 0;
Expand Down
2 changes: 1 addition & 1 deletion src/crab/fwd_analyzer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ class interleaved_fwd_fixpoint_iterator_t final {
std::pair<invariant_table_t, invariant_table_t> run_forward_analyzer(const cfg_t& cfg, ebpf_domain_t entry_inv) {
// Go over the CFG in weak topological order (accounting for loops).
interleaved_fwd_fixpoint_iterator_t analyzer(cfg);
if (thread_local_options.check_termination) {
if (thread_local_options.cfg_opts.check_for_termination) {
// Initialize loop counters for potential loop headers.
// This enables enforcement of upper bounds on loop iterations
// during program verification.
Expand Down
27 changes: 10 additions & 17 deletions src/crab_verifier.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ static checks_db generate_report(const cfg_t& cfg, const crab::invariant_table_t
}
}

if (thread_local_options.check_termination) {
if (thread_local_options.cfg_opts.check_for_termination) {
// Gather the upper bound of loop counts from post-invariants.
for (const auto& [label, inv] : post_invariants) {
if (inv.is_bottom()) {
Expand Down Expand Up @@ -163,12 +163,12 @@ static checks_db get_analysis_report(std::ostream& s, const cfg_t& cfg, const cr
}

static checks_db get_ebpf_report(std::ostream& s, const cfg_t& cfg, program_info info,
const ebpf_verifier_options_t* options,
const ebpf_verifier_options_t& options,
const std::optional<InstructionSeq>& prog = std::nullopt) {
global_program_info = std::move(info);
crab::domains::clear_global_state();
crab::variable_t::clear_thread_local_state();
thread_local_options = *options;
thread_local_options = options;

try {
// Get dictionaries of pre-invariants and post-invariants for each basic block.
Expand All @@ -185,10 +185,7 @@ static checks_db get_ebpf_report(std::ostream& s, const cfg_t& cfg, program_info

/// Returned value is true if the program passes verification.
bool run_ebpf_analysis(std::ostream& s, const cfg_t& cfg, const program_info& info,
const ebpf_verifier_options_t* options, ebpf_verifier_stats_t* stats) {
if (options == nullptr) {
options = &ebpf_verifier_default_options;
}
const ebpf_verifier_options_t& options, ebpf_verifier_stats_t* stats) {
const checks_db report = get_ebpf_report(s, cfg, info, options);
if (stats) {
stats->total_unreachable = report.total_unreachable;
Expand Down Expand Up @@ -221,7 +218,7 @@ std::tuple<string_invariant, bool> ebpf_analyze_program_for_test(std::ostream& o
throw std::runtime_error("Entry invariant is inconsistent");
}
try {
const cfg_t cfg = prepare_cfg(prog, info, options.simplify, options.check_termination, false);
const cfg_t cfg = prepare_cfg(prog, info, options.cfg_opts);
auto [pre_invariants, post_invariants] = run_forward_analyzer(cfg, std::move(entry_inv));
const checks_db report = get_analysis_report(std::cerr, cfg, pre_invariants, post_invariants);
print_report(os, report, prog, false);
Expand All @@ -237,23 +234,19 @@ std::tuple<string_invariant, bool> ebpf_analyze_program_for_test(std::ostream& o

/// Returned value is true if the program passes verification.
bool ebpf_verify_program(std::ostream& os, const InstructionSeq& prog, const program_info& info,
const ebpf_verifier_options_t* options, ebpf_verifier_stats_t* stats) {
if (options == nullptr) {
options = &ebpf_verifier_default_options;
}

const ebpf_verifier_options_t& options, ebpf_verifier_stats_t* stats) {
// Convert the instruction sequence to a control-flow graph
// in a "passive", non-deterministic form.
const cfg_t cfg = prepare_cfg(prog, info, options->simplify, options->check_termination);
const cfg_t cfg = prepare_cfg(prog, info, options.cfg_opts);

std::optional<InstructionSeq> prog_opt = std::nullopt;
if (options->print_failures) {
if (options.print_failures) {
prog_opt = prog;
}

const checks_db report = get_ebpf_report(os, cfg, info, options, prog_opt);
if (options->print_failures) {
print_report(os, report, prog, options->print_line_info);
if (options.print_failures) {
print_report(os, report, prog, options.print_line_info);
}
if (stats) {
stats->total_unreachable = report.total_unreachable;
Expand Down
6 changes: 3 additions & 3 deletions src/crab_verifier.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@
#include "spec_type_descriptors.hpp"
#include "string_constraints.hpp"

bool run_ebpf_analysis(std::ostream& s, const cfg_t& cfg, const program_info& info, const ebpf_verifier_options_t* options,
ebpf_verifier_stats_t* stats);
bool run_ebpf_analysis(std::ostream& s, const cfg_t& cfg, const program_info& info,
const ebpf_verifier_options_t& options, ebpf_verifier_stats_t* stats);

bool ebpf_verify_program(std::ostream& os, const InstructionSeq& prog, const program_info& info,
const ebpf_verifier_options_t* options, ebpf_verifier_stats_t* stats);
const ebpf_verifier_options_t& options, ebpf_verifier_stats_t* stats);

using string_invariant_map = std::map<label_t, string_invariant>;

Expand Down
20 changes: 9 additions & 11 deletions src/main/check.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ int main(int argc, char** argv) {
// Always call ebpf_verifier_clear_thread_local_state on scope exit.
at_scope_exit<ebpf_verifier_clear_thread_local_state> clear_thread_local_state;

ebpf_verifier_options_t ebpf_verifier_options = ebpf_verifier_default_options;
ebpf_verifier_options_t ebpf_verifier_options;

crab::CrabEnableWarningMsg(false);

Expand All @@ -97,7 +97,7 @@ int main(int argc, char** argv) {
->capture_default_str()
->check(CLI::IsMember({"stats", "linux", "zoneCrab", "cfg"}));

app.add_flag("--termination,!--no-verify-termination", ebpf_verifier_options.check_termination,
app.add_flag("--termination,!--no-verify-termination", ebpf_verifier_options.cfg_opts.check_for_termination,
"Verify termination. Default: ignore")
->group("Features");

Expand All @@ -124,7 +124,7 @@ int main(int argc, char** argv) {
->expected(0, _conformance_groups.size())
->check(CLI::IsMember(_get_conformance_group_names()));

app.add_flag("--simplify,!--no-simplify", ebpf_verifier_options.simplify,
app.add_flag("--simplify,!--no-simplify", ebpf_verifier_options.cfg_opts.simplify,
"Simplify the CFG before analysis by merging chains of instructions into a single basic block. "
"Default: enabled")
->group("Verbosity");
Expand Down Expand Up @@ -193,7 +193,7 @@ int main(int argc, char** argv) {
// Read a set of raw program sections from an ELF file.
vector<raw_program> raw_progs;
try {
raw_progs = read_elf(filename, desired_section, &ebpf_verifier_options, &platform);
raw_progs = read_elf(filename, desired_section, ebpf_verifier_options, &platform);
} catch (std::runtime_error& e) {
std::cerr << "error: " << e.what() << std::endl;
return 1;
Expand All @@ -208,7 +208,7 @@ int main(int argc, char** argv) {
if (!desired_section.empty() && raw_progs.empty()) {
// We could not find the desired program, so get the full list
// of possibilities.
raw_progs = read_elf(filename, string(), &ebpf_verifier_options, &platform);
raw_progs = read_elf(filename, string(), ebpf_verifier_options, &platform);
}
for (const raw_program& raw_prog : raw_progs) {
std::cout << "section=" << raw_prog.section_name << " function=" << raw_prog.function_name << std::endl;
Expand All @@ -235,9 +235,9 @@ int main(int argc, char** argv) {
if (domain == "zoneCrab") {
ebpf_verifier_stats_t verifier_stats;
const auto [res, seconds] = timed_execution([&] {
return ebpf_verify_program(std::cout, prog, raw_prog.info, &ebpf_verifier_options, &verifier_stats);
return ebpf_verify_program(std::cout, prog, raw_prog.info, ebpf_verifier_options, &verifier_stats);
});
if (res && ebpf_verifier_options.check_termination &&
if (res && ebpf_verifier_options.cfg_opts.check_for_termination &&
(ebpf_verifier_options.print_failures || ebpf_verifier_options.print_invariants)) {
std::cout << "Program terminates within " << verifier_stats.max_loop_count << " loop iterations\n";
}
Expand All @@ -250,8 +250,7 @@ int main(int argc, char** argv) {
return !res;
} else if (domain == "stats") {
// Convert the instruction sequence to a control-flow graph.
const cfg_t cfg =
prepare_cfg(prog, raw_prog.info, ebpf_verifier_options.simplify, ebpf_verifier_options.check_termination);
const cfg_t cfg = prepare_cfg(prog, raw_prog.info, ebpf_verifier_options.cfg_opts);

// Just print eBPF program stats.
auto stats = collect_stats(cfg);
Expand All @@ -265,8 +264,7 @@ int main(int argc, char** argv) {
std::cout << "\n";
} else if (domain == "cfg") {
// Convert the instruction sequence to a control-flow graph.
const cfg_t cfg =
prepare_cfg(prog, raw_prog.info, ebpf_verifier_options.simplify, ebpf_verifier_options.check_termination);
const cfg_t cfg = prepare_cfg(prog, raw_prog.info, ebpf_verifier_options.cfg_opts);
std::cout << cfg;
std::cout << "\n";
} else {
Expand Down
Loading

0 comments on commit bd55b33

Please sign in to comment.