Skip to content

Commit 75fdf33

Browse files
author
Alan Jowett
committed
Implement ebpf_check_constraints_at_label
Signed-off-by: Alan Jowett <alan.jowett@microsoft.com>
1 parent 16e06cf commit 75fdf33

File tree

9 files changed

+282
-1
lines changed

9 files changed

+282
-1
lines changed

src/asm_parse.hpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,21 @@
33
#pragma once
44

55
#include <string>
6+
#include <vector>
67

78
#include "asm_syntax.hpp"
9+
#include "crab/interval.hpp"
10+
#include "crab/linear_constraint.hpp"
811

912
Instruction parse_instruction(const std::string& line, const std::map<std::string, label_t>& label_name_to_label);
13+
14+
/***
15+
* Parse a set of string form linear constraints into a vector of crab::linear_constraint_t
16+
*
17+
* @param[in] constraints: A set of string form linear constraints.
18+
* @param[out] numeric_ranges: A vector of numeric ranges that are extracted from the constraints.
19+
*
20+
* @return A vector of crab::linear_constraint_t
21+
*/
22+
std::vector<crab::linear_constraint_t> parse_linear_constraints(const std::set<std::string>& constraints,
23+
std::vector<crab::interval_t>& numeric_ranges);

src/asm_syntax.hpp

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,29 @@ struct label_t {
2323
explicit label_t(const int index, const int to = -1, std::string stack_frame_prefix = {}) noexcept
2424
: from(index), to(to), stack_frame_prefix(std::move(stack_frame_prefix)) {}
2525

26+
explicit label_t(std::string_view string_label) {
27+
auto pos = string_label.find(STACK_FRAME_DELIMITER);
28+
if (pos != std::string_view::npos) {
29+
stack_frame_prefix = std::string(string_label.substr(0, pos));
30+
string_label = string_label.substr(pos + 1);
31+
}
32+
33+
pos = string_label.find(':');
34+
try {
35+
if (pos != std::string_view::npos) {
36+
from = std::stoi(std::string(string_label.substr(0, pos)));
37+
to = std::stoi(std::string(string_label.substr(pos + 1)));
38+
} else {
39+
from = std::stoi(std::string(string_label));
40+
to = -1;
41+
}
42+
} catch (const std::invalid_argument& e) {
43+
throw std::invalid_argument("Invalid label format: " + std::string(string_label));
44+
} catch (const std::out_of_range& e) {
45+
throw std::out_of_range("Label value out of range: " + std::string(string_label));
46+
}
47+
}
48+
2649
static label_t make_jump(const label_t& src_label, const label_t& target_label) {
2750
return label_t{src_label.from, target_label.from, target_label.stack_frame_prefix};
2851
}

src/config.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,5 @@ const ebpf_verifier_options_t ebpf_verifier_default_options = {
1414
.allow_division_by_zero = true,
1515
.setup_constraints = true,
1616
.big_endian = false,
17+
.store_pre_invariants = false, // Enable this to permit usage of the ebpf_check_constraints_at_label and ebpf_get_invariants_at_label functions.
1718
};

src/config.hpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ struct ebpf_verifier_options_t {
2121
bool big_endian;
2222

2323
bool dump_btf_types_json;
24+
25+
// Store pre-invariants for use in ebpf_check_constraints_at_label and ebpf_get_invariants_at_label.
26+
bool store_pre_invariants;
2427
};
2528

2629
struct ebpf_verifier_stats_t {

src/crab_verifier.cpp

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#include "crab_utils/lazy_allocator.hpp"
1818

1919
#include "asm_syntax.hpp"
20+
#include "asm_parse.hpp"
2021
#include "crab_verifier.hpp"
2122
#include "string_constraints.hpp"
2223

@@ -161,6 +162,14 @@ static checks_db get_analysis_report(std::ostream& s, cfg_t& cfg, const crab::in
161162
return db;
162163
}
163164

165+
static thread_local std::optional<crab::invariant_table_t> saved_pre_invariants = std::nullopt;
166+
167+
static void save_invariants_if_needed(const crab::invariant_table_t& pre_invariants) {
168+
if (thread_local_options.store_pre_invariants) {
169+
saved_pre_invariants = pre_invariants;
170+
}
171+
}
172+
164173
static checks_db get_ebpf_report(std::ostream& s, cfg_t& cfg, program_info info, const ebpf_verifier_options_t* options,
165174
const std::optional<InstructionSeq>& prog = std::nullopt) {
166175
global_program_info = std::move(info);
@@ -172,6 +181,7 @@ static checks_db get_ebpf_report(std::ostream& s, cfg_t& cfg, program_info info,
172181
// Get dictionaries of pre-invariants and post-invariants for each basic block.
173182
ebpf_domain_t entry_dom = ebpf_domain_t::setup_entry(true);
174183
auto [pre_invariants, post_invariants] = run_forward_analyzer(cfg, std::move(entry_dom));
184+
save_invariants_if_needed(pre_invariants);
175185
return get_analysis_report(s, cfg, pre_invariants, post_invariants, prog);
176186
} catch (std::runtime_error& e) {
177187
// Convert verifier runtime_error exceptions to failure.
@@ -221,6 +231,7 @@ std::tuple<string_invariant, bool> ebpf_analyze_program_for_test(std::ostream& o
221231
try {
222232
cfg_t cfg = prepare_cfg(prog, info, options.simplify, false);
223233
auto [pre_invariants, post_invariants] = run_forward_analyzer(cfg, std::move(entry_inv));
234+
save_invariants_if_needed(pre_invariants);
224235
const checks_db report = get_analysis_report(std::cerr, cfg, pre_invariants, post_invariants);
225236
print_report(os, report, prog, false);
226237

@@ -267,4 +278,65 @@ void ebpf_verifier_clear_thread_local_state() {
267278
global_program_info.clear();
268279
crab::domains::clear_thread_local_state();
269280
crab::domains::SplitDBM::clear_thread_local_state();
281+
saved_pre_invariants = std::nullopt;
282+
}
283+
284+
bool ebpf_check_constraints_at_label(std::ostream& os, const std::string& label_string,
285+
const std::set<std::string>& constraints) try {
286+
label_t label = label_t(label_string);
287+
if (!saved_pre_invariants.has_value()) {
288+
os << "No pre-invariants available\n";
289+
return false;
290+
}
291+
if (saved_pre_invariants.value().find(label) == saved_pre_invariants.value().end()) {
292+
os << "No pre-invariants available for label " << label << "\n";
293+
return false;
294+
}
295+
ebpf_domain_t from_inv(saved_pre_invariants.value().at(label));
296+
auto concrete_domain = ebpf_domain_t::from_constraints(constraints, false);
297+
298+
if (concrete_domain.is_bottom()) {
299+
os << "The provided constraints are unsatisfiable (concrete domain is bottom)\n";
300+
os << "Concrete constraints are self-contradictory\n";
301+
os << concrete_domain << "\n";
302+
return false;
303+
}
304+
305+
if (from_inv.is_bottom()) {
306+
os << "The abstract state is unreachable\n";
307+
os << from_inv << "\n";
308+
return false;
309+
}
310+
311+
if ((from_inv & concrete_domain).is_bottom()) {
312+
os << "Concrete state does not match invariant\n";
313+
314+
// Print the concrete state
315+
os << "--- Concrete state ---\n";
316+
os << concrete_domain << "\n";
317+
318+
os << "--- Abstract state ---\n";
319+
os << from_inv << "\n";
320+
321+
return false;
322+
}
323+
324+
return true;
325+
} catch (std::exception& e) {
326+
os << "Error occurred while checking constraints: " << e.what() << "\n";
327+
return false;
328+
}
329+
330+
std::set<std::string> ebpf_get_invariants_at_label(const std::string& label)
331+
{
332+
// If the label is malformed, throw an exception so the caller can handle it.
333+
label_t l = label_t(label);
334+
335+
if (!saved_pre_invariants.has_value()) {
336+
return {};
337+
}
338+
if (saved_pre_invariants.value().find(l) == saved_pre_invariants.value().end()) {
339+
return {};
340+
}
341+
return saved_pre_invariants.value().at(l).to_set().value();
270342
}

src/crab_verifier.hpp

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,41 @@ int create_map_crab(const EbpfMapType& map_type, uint32_t key_size, uint32_t val
2626
EbpfMapDescriptor* find_map_descriptor(int map_fd);
2727

2828
void ebpf_verifier_clear_thread_local_state();
29+
30+
/**
31+
* @brief Given a label and a set of concrete constraints, check if the concrete constraints match the abstract
32+
* verifier constraints at the label. Requires the `store_pre_invariants` option to be set.
33+
*
34+
* Abstract constraints are computed by the verifier and stored if the `store_pre_invariants` option is set.
35+
* These constraints represent the program state at a specific point in the control flow graph,
36+
* as determined by the static analysis performed by the verifier.
37+
*
38+
* If the 'store_pre_invariants' option is not set, this function will always return false along with an error message.
39+
* This is because the verifier did not store the abstract constraints at each label.
40+
*
41+
* For invalid labels, this function will return false along with an error message.
42+
*
43+
* @param[in,out] os Print output to this stream.
44+
* @param[in] label The location in the CFG to check against.
45+
* @param[in] constraints The concrete state to check.
46+
* @return true If the state is valid.
47+
* @return false If the state is invalid.
48+
* @throw std::invalid_argument If the label format is invalid
49+
* @throw std::out_of_range If the label value causes numeric overflow
50+
*/
51+
bool ebpf_check_constraints_at_label(std::ostream& os, const std::string& label,
52+
const std::set<std::string>& constraints);
53+
/**
54+
* @brief Get the invariants at a given label. Requires the `store_pre_invariants` option to be set.
55+
*
56+
* If the 'store_pre_invariants' option is not set, this function will return an empty set
57+
* as no invariants were stored during verification.
58+
*
59+
* @param[in] label The label in the CFG where invariants should be retrieved
60+
* @return The set of invariants at the given label.
61+
* Each invariant represents a constraint on the program state at this point.
62+
* Returns an empty set if no invariants are available.
63+
* @throw std::invalid_argument If the label format is invalid
64+
* @throw std::out_of_range If the label value causes numeric overflow
65+
*/
66+
std::set<std::string> ebpf_get_invariants_at_label(const std::string& label);

src/test/ebpf_yaml.cpp

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ struct RawTestCase {
9999
vector<std::tuple<string, vector<string>>> raw_blocks;
100100
vector<string> post;
101101
std::set<string> messages;
102+
std::map<std::string, std::set<std::string>> invariants_to_check;
102103
};
103104

104105
static vector<string> parse_block(const YAML::Node& block_node) {
@@ -126,6 +127,21 @@ static std::set<string> as_set_empty_default(const YAML::Node& optional_node) {
126127
return vector_to_set(optional_node.as<vector<string>>());
127128
}
128129

130+
static std::map<std::string, std::set<std::string>> parse_invariants_to_check(const YAML::Node& case_node) {
131+
if (!case_node["invariants-to-check"].IsDefined() || case_node["invariants-to-check"].IsNull()) {
132+
return {};
133+
}
134+
std::map<std::string, std::set<std::string>> res;
135+
try {
136+
for (const auto& node : case_node["invariants-to-check"]) {
137+
res.emplace(node.first.as<string>(), vector_to_set(node.second.as<vector<string>>()));
138+
}
139+
} catch (const YAML::Exception& e) {
140+
throw std::runtime_error("Error parsing 'invariants-to-check': " + std::string(e.what()));
141+
}
142+
return res;
143+
}
144+
129145
static RawTestCase parse_case(const YAML::Node& case_node) {
130146
return RawTestCase{
131147
.test_case = case_node["test-case"].as<string>(),
@@ -134,6 +150,7 @@ static RawTestCase parse_case(const YAML::Node& case_node) {
134150
.raw_blocks = parse_code(case_node["code"]),
135151
.post = case_node["post"].as<vector<string>>(),
136152
.messages = as_set_empty_default(case_node["messages"]),
153+
.invariants_to_check = parse_invariants_to_check(case_node),
137154
};
138155
}
139156

@@ -210,7 +227,9 @@ static TestCase read_case(const RawTestCase& raw_case) {
210227
.assumed_pre_invariant = read_invariant(raw_case.pre),
211228
.instruction_seq = raw_cfg_to_instruction_seq(raw_case.raw_blocks),
212229
.expected_post_invariant = read_invariant(raw_case.post),
213-
.expected_messages = raw_case.messages};
230+
.expected_messages = raw_case.messages,
231+
.invariants_to_check = raw_case.invariants_to_check,
232+
};
214233
}
215234

216235
static vector<TestCase> read_suite(const string& path) {
@@ -251,6 +270,10 @@ std::optional<Failure> run_yaml_test_case(TestCase test_case, bool debug) {
251270
test_case.options.simplify = false;
252271
}
253272

273+
if (!test_case.invariants_to_check.empty()) {
274+
test_case.options.store_pre_invariants = true;
275+
}
276+
254277
ebpf_context_descriptor_t context_descriptor{64, 0, 4, -1};
255278
EbpfProgramType program_type = make_program_type(test_case.name, &context_descriptor);
256279

@@ -261,6 +284,21 @@ std::optional<Failure> run_yaml_test_case(TestCase test_case, bool debug) {
261284
ss, test_case.instruction_seq, test_case.assumed_pre_invariant, info, test_case.options);
262285
std::set<string> actual_messages = extract_messages(ss.str());
263286

287+
for (auto& [label, expected_invariant] : test_case.invariants_to_check) {
288+
ss = {};
289+
try {
290+
if (!ebpf_check_constraints_at_label(ss, label, expected_invariant)) {
291+
// If debug is enabled, print the output of ebpf_check_constraints_at_label.
292+
if (debug) {
293+
std::cout << ss.str();
294+
}
295+
actual_messages.insert(label + ": Concrete invariants do not match abstract invariants");
296+
}
297+
} catch (const std::exception& e) {
298+
actual_messages.insert(label + ": Exception occurred during invariant check");
299+
}
300+
}
301+
264302
if (actual_last_invariant == test_case.expected_post_invariant && actual_messages == test_case.expected_messages) {
265303
return {};
266304
}

src/test/ebpf_yaml.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ struct TestCase {
1414
InstructionSeq instruction_seq;
1515
string_invariant expected_post_invariant;
1616
std::set<std::string> expected_messages;
17+
std::map<std::string, std::set<std::string>> invariants_to_check; ///< Map from label to expected invariants, used for validating program state at specific labels during test execution.
1718
};
1819

1920
void foreach_suite(const std::string& path, const std::function<void(const TestCase&)>& f);

0 commit comments

Comments
 (0)